diff --git a/cm/CHANGES.md b/cm/CHANGES.md index cd843fef2..57b892232 100644 --- a/cm/CHANGES.md +++ b/cm/CHANGES.md @@ -1,6 +1,9 @@ -## V3.2.4 +## V3.2.5 - CMX: improved logging - CMX: improved error handling (show module path and line number) + - CMX: fixed bug when detecting unknown control flag + - CMX: do not change output to json if -j or --json + just print json in the end ... ## V3.2.3 - added --new_branch to `cm pull repo` and `cm checkout repo` diff --git a/cm/cmind/__init__.py b/cm/cmind/__init__.py index 8319cb86c..f7f78e1f5 100644 --- a/cm/cmind/__init__.py +++ b/cm/cmind/__init__.py @@ -2,7 +2,7 @@ # # Written by Grigori Fursin -__version__ = "3.2.4" +__version__ = "3.2.5" from cmind.core import access from cmind.core import x diff --git a/cm/cmind/core.py b/cm/cmind/core.py index f563fbfa1..8e3577ce1 100644 --- a/cm/cmind/core.py +++ b/cm/cmind/core.py @@ -616,7 +616,7 @@ def access(self, i, out = None): if automation=='': return {'return':4, 'error':'automation was not specified'} else: - return {'return':4, 'error':'automation {} not found'.format(automation)} + return {'return':4, 'error':'automation "{}" not found'.format(automation)} # If no automation was found or we force common automation if use_common_automation or len(automation_lst)==0: @@ -842,19 +842,30 @@ def x(self, i, out = None): 'h', 'help', 'version', 'out', 'j', 'json', 'save_to_json_file', 'save_to_yaml_file', 'common', 'ignore_inheritance', 'log', 'logfile', 'raise', 'repro', - 'f']] + 'f', 'time', 'profile']] + delayed_error = '' + if len(unknown_control_flags)>0: unknown_control_flags_str = ','.join(unknown_control_flags) - print (f'Unknown control flag(s): {unknown_control_flags_str}') - print ('') + delayed_error = f'Unknown control flag(s): {unknown_control_flags_str}' + # Force print help control['h'] = True if control.pop('f', ''): i['f'] = True + output_json = (control.get('j', False) or control.get('json', False)) + + self_time = control.get('time', False) + if not x_was_called and self_time: + import time + self_time1 = time.time() + + self_profile = control.get('profile', False) + # Check repro use_log = str(control_flags.pop('log', '')).strip().lower() log_file = control_flags.pop('logfile', '') @@ -928,18 +939,48 @@ def x(self, i, out = None): self.log(f"x input: {spaces} ({i})", "debug") # Call access helper + if not x_was_called and self_profile: + # https://docs.python.org/3/library/profile.html#module-cProfile + import cProfile, pstats, io + from pstats import SortKey + profile = cProfile.Profile() + profile.enable() + r = self._x(i, control) - + + if delayed_error != '' and r['return'] == 0: + r['return'] = 1 + r['error'] = delayed_error + if not self.logger == None: self.log(f"x output: {r}", "debug") self.state['recursion'] = recursion if not x_was_called: + if self_profile: + profile.disable() + s = io.StringIO() + sortby = SortKey.CUMULATIVE + ps = pstats.Stats(profile, stream=s).sort_stats(sortby) + ps.print_stats(32) + print ('') + print ('CMX profile:') + print ('') + print (s.getvalue()) + # Very first call (not recursive) # Check if output to json and save file - if self.output == 'json': + if self_time: + self_time = time.time() - self_time1 + r['self_time'] = self_time + + if self.output == 'con': + print ('') + print ('CMX elapsed time: {:.3f} sec.'.format(self_time)) + + if output_json: utils.dump_safe_json(r) # Restore directory of call @@ -975,9 +1016,10 @@ def _x(self, i, control): if output == True: output = 'con' - # Check and force json console output - if control.get('j', False) or control.get('json', False): - output = 'json' +# Changed in v3.2.5 +# # Check and force json console output +# if control.get('j', False) or control.get('json', False): +# output = 'json' # Set self.output to the output of the very first access # to print error in the end if needed @@ -1011,6 +1053,17 @@ def _x(self, i, control): elif action == 'init' and automation == '': automation = 'core' + # Can add popular shortcuts + elif action == 'ff': + task = '' + if automation != '' and (' ' in automation or ',' in automation): + task = automation + if ' ' in automation: task = automation.replace(' ',',') + i['task'] = task + automation = 'flex.flow' + action = 'run' + i['automation'] = automation + i['action'] = action # Print basic help if action == '' extra_help = True if action == 'help' and automation == '' else False @@ -1038,6 +1091,8 @@ def _x(self, i, control): print (' -log={info (default) | debug | warning | error} - log level') print (' -logfile={path to log file} - record log to file instead of console') print (' -raise - raise Python error when automation action fails') + print (' -time - print elapsed time for a given automation') + print (' -profile - profile a given automation') print (' -repro - record various info to the cmx-repro directory to replay CMX command') print ('') print ('Check https://github.com/mlcommons/ck/tree/master/cm/docs/cmx for more details.') @@ -1211,7 +1266,7 @@ def _x(self, i, control): return {'return':4, 'error':'automation meta not found in {}'.format(automation_path)} # Load artifact class - r=utils.load_yaml_and_json(automation_path_meta) + r = utils.load_yaml_and_json(automation_path_meta) if r['return']>0: return r automation_meta = r['meta'] @@ -1250,7 +1305,7 @@ def _x(self, i, control): if automation=='': return {'return':4, 'error':'automation was not specified'} else: - return {'return':4, 'error':f'automation {automation} not found'} + return {'return':4, 'error':f'automation "{automation}" not found'} # If no automation was found or we force common automation loaded_common_automation = False