1010import sys
1111import timeit
1212from concurrent .futures import ThreadPoolExecutor
13+ from dataclasses import dataclass
14+ from typing import Dict , Any , Optional
1315
1416from knack .cli import CLI
1517from knack .commands import CLICommandsLoader
@@ -196,6 +198,15 @@ def _configure_style(self):
196198 format_styled_text .theme = theme
197199
198200
201+ @dataclass
202+ class ModuleLoadResult :
203+ module_name : str
204+ command_table : Dict [str , Any ]
205+ group_table : Dict [str , Any ]
206+ elapsed_time : float
207+ error : Optional [Exception ] = None
208+
209+
199210class MainCommandsLoader (CLICommandsLoader ):
200211
201212 # Format string for pretty-print the command module table
@@ -222,11 +233,11 @@ def load_command_table(self, args):
222233 import pkgutil
223234 import traceback
224235 from azure .cli .core .commands import (
225- _load_module_command_loader , _load_extension_command_loader , BLOCKED_MODS , ExtensionCommandSource )
236+ _load_extension_command_loader , ExtensionCommandSource )
226237 from azure .cli .core .extension import (
227238 get_extensions , get_extension_path , get_extension_modname )
228239 from azure .cli .core .breaking_change import (
229- import_core_breaking_changes , import_module_breaking_changes , import_extension_breaking_changes )
240+ import_core_breaking_changes , import_extension_breaking_changes )
230241
231242 def _update_command_table_from_modules (args , command_modules = None ):
232243 """Loads command tables from modules and merge into the main command table.
@@ -252,55 +263,11 @@ def _update_command_table_from_modules(args, command_modules=None):
252263 except ImportError as e :
253264 logger .warning (e )
254265
255- count = 0
256- cumulative_elapsed_time = 0
257- cumulative_group_count = 0
258- cumulative_command_count = 0
259- logger .debug ("Loading command modules:" )
260- logger .debug (self .header_mod )
261-
262- print (f"*** Starting SUT***" )
263- start_time_wrapper = timeit .default_timer ()
264-
265- def load_module_threaded (mod ):
266- try :
267- start_time = timeit .default_timer ()
268- module_command_table , module_group_table = _load_module_command_loader (self , args , mod )
269- import_module_breaking_changes (mod )
270- elapsed_time = timeit .default_timer () - start_time
271- return (mod , module_command_table , module_group_table , elapsed_time , None )
272- except Exception as ex : # pylint: disable=broad-except
273- return (mod , {}, {}, 0 , ex )
274-
275- # Use ThreadPoolExecutor to load modules in parallel
276- with ThreadPoolExecutor (max_workers = 4 ) as executor :
277- futures = [executor .submit (load_module_threaded , mod )
278- for mod in command_modules if mod not in BLOCKED_MODS ]
279-
280- for future in futures :
281- mod , module_command_table , module_group_table , elapsed_time , error = future .result ()
282- if error :
283- # Changing this error message requires updating CI script that checks for failed
284- # module loading.
285- from azure .cli .core import telemetry
286- logger .error ("Error loading command module '%s': %s" , mod , error )
287- telemetry .set_exception (exception = error , fault_type = 'module-load-error-' + mod ,
288- summary = 'Error loading module: {}' .format (mod ))
289- logger .debug (traceback .format_exc ())
290- else :
291- for cmd in module_command_table .values ():
292- cmd .command_source = mod
293- self .command_table .update (module_command_table )
294- self .command_group_table .update (module_group_table )
295-
296- logger .debug (self .item_format_string , mod , elapsed_time ,
297- len (module_group_table ), len (module_command_table ))
298- count += 1
299- cumulative_elapsed_time += elapsed_time
300- cumulative_group_count += len (module_group_table )
301- cumulative_command_count += len (module_command_table )
302- elapsed_time = timeit .default_timer () - start_time_wrapper
303- print (f"*** SUT operation *** took: { elapsed_time :.6f} seconds for { len (command_modules )} modules" )
266+ results = self ._load_modules_threaded (args , command_modules )
267+
268+ # @TODO: export to own method:
269+ count , cumulative_elapsed_time , cumulative_group_count , cumulative_command_count = \
270+ self ._process_results_with_timing (results , command_modules )
304271 # Summary line
305272 logger .debug (self .item_format_string ,
306273 "Total ({})" .format (count ), cumulative_elapsed_time ,
@@ -577,6 +544,74 @@ def load_arguments(self, command=None):
577544 self .extra_argument_registry .update (loader .extra_argument_registry )
578545 loader ._update_command_definitions () # pylint: disable=protected-access
579546
547+ def _load_modules_threaded (self , args , command_modules ):
548+ """Load command modules using ThreadPoolExecutor for parallel processing."""
549+ from azure .cli .core .commands import _load_module_command_loader , BLOCKED_MODS
550+ from azure .cli .core .breaking_change import import_module_breaking_changes
551+
552+ def load_single_module (mod ):
553+ try :
554+ start_time = timeit .default_timer ()
555+ module_command_table , module_group_table = _load_module_command_loader (self , args , mod )
556+ import_module_breaking_changes (mod )
557+ elapsed_time = timeit .default_timer () - start_time
558+ return ModuleLoadResult (mod , module_command_table , module_group_table , elapsed_time )
559+ except Exception as ex : # pylint: disable=broad-except
560+ return ModuleLoadResult (mod , {}, {}, 0 , ex )
561+
562+ with ThreadPoolExecutor (max_workers = 4 ) as executor :
563+ futures = [executor .submit (load_single_module , mod )
564+ for mod in command_modules if mod not in BLOCKED_MODS ]
565+ return [future .result () for future in futures ]
566+
567+ def _handle_module_load_error (self , result ):
568+ """Handle errors that occurred during module loading."""
569+ import traceback
570+ from azure .cli .core import telemetry
571+
572+ # Changing this error message requires updating CI script that checks for failed module loading.
573+ logger .error ("Error loading command module '%s': %s" , result .module_name , result .error )
574+ telemetry .set_exception (exception = result .error ,
575+ fault_type = 'module-load-error-' + result .module_name ,
576+ summary = 'Error loading module: {}' .format (result .module_name ))
577+ logger .debug (traceback .format_exc ())
578+
579+ def _process_successful_load (self , result ):
580+ """Process successfully loaded module results."""
581+ # Set command source for all commands in the module
582+ for cmd in result .command_table .values ():
583+ cmd .command_source = result .module_name
584+
585+ # Update main command and group tables
586+ self .command_table .update (result .command_table )
587+ self .command_group_table .update (result .group_table )
588+
589+ # Log the results
590+ logger .debug (self .item_format_string , result .module_name , result .elapsed_time ,
591+ len (result .group_table ), len (result .command_table ))
592+
593+ def _process_results_with_timing (self , results , command_modules ):
594+ """Process pre-loaded module results with timing and progress reporting."""
595+ logger .debug ("Loading command modules:" )
596+ logger .debug (self .header_mod )
597+
598+ count = 0
599+ cumulative_elapsed_time = 0
600+ cumulative_group_count = 0
601+ cumulative_command_count = 0
602+
603+ for result in results :
604+ if result .error :
605+ self ._handle_module_load_error (result )
606+ else :
607+ self ._process_successful_load (result )
608+ count += 1
609+ cumulative_elapsed_time += result .elapsed_time
610+ cumulative_group_count += len (result .group_table )
611+ cumulative_command_count += len (result .command_table )
612+
613+ return count , cumulative_elapsed_time , cumulative_group_count , cumulative_command_count
614+
580615
581616class CommandIndex :
582617
0 commit comments