From e62cc338e60e29646a17667e19777bcbbed27a0a Mon Sep 17 00:00:00 2001 From: Douglas RAILLARD Date: Mon, 11 Feb 2019 12:28:26 +0000 Subject: [PATCH 1/3] bisector: Add -odelete-artifact Allow explicitely deleting the artifact folder/archive of exekall. --- tools/bisector/bisector/bisector.py | 40 ++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/tools/bisector/bisector/bisector.py b/tools/bisector/bisector/bisector.py index 113a7281c..3d0e2e015 100755 --- a/tools/bisector/bisector/bisector.py +++ b/tools/bisector/bisector/bisector.py @@ -1766,6 +1766,7 @@ class ExekallLISATestStep(ShellStep): use_systemd_run = False, compress_artifact = True, upload_artifact = False, + delete_artifact = False, prune_db = True, ) @@ -1773,6 +1774,7 @@ class ExekallLISATestStep(ShellStep): __init__ = dict( compress_artifact = BoolParam('compress the exekall artifact directory in an archive'), upload_artifact = BoolParam('upload the exekall artifact directory to Artifactorial as the execution goes, and delete the local archive.'), + delete_artifact = BoolParam('delete the exekall artifact directory to Artifactorial as the execution goes.'), prune_db = BoolParam("Prune exekall's ValueDB so that only roots values are preserved. That allows smaller reports that are faster to load"), # Some options are not supported **filter_keys(StepBase.options['__init__'], remove={'trials'}), @@ -1803,6 +1805,7 @@ class ExekallLISATestStep(ShellStep): def __init__(self, compress_artifact = Default, upload_artifact = Default, + delete_artifact = Default, prune_db = Default, **kwargs ): @@ -1811,11 +1814,15 @@ class ExekallLISATestStep(ShellStep): self.upload_artifact = upload_artifact # upload_artifact implies compress_artifact, in order to have an - # archive instead of a folder + # archive instead of a folder. + # It also implies deleting the local artifact folder, since it has been + # uploaded. if self.upload_artifact: compress_artifact = True + delete_artifact = True self.compress_artifact = compress_artifact + self.delete_artifact = delete_artifact self.prune_db = prune_db def run(self, i_stack, service_hub): @@ -1906,25 +1913,34 @@ class ExekallLISATestStep(ShellStep): except Exception as e: warn('Failed to compress exekall artifact: {e}'.format(e=e)) + artifact_local_path = artifact_path + delete_artifact = self.delete_artifact + # If an upload service is available, upload the traces as we go if self.upload_artifact: upload_service = service_hub.upload if upload_service: - artifact_local_path = artifact_path try: artifact_path = upload_service.upload(artifact_path) except Exception as e: - error('Could not upload exekall artifact: ' + str(e)) - else: - try: - os.remove(artifact_local_path) - except Exception as e: - error('Could not delete local artifact {path}: {e}'.format( - e = e, - path = artifact_local_path, - )) + error('Could not upload exekall artifact, will not delete the folder: ' + str(e)) + # Avoid deleting the artifact if something went wrong, so + # we still have the local copy to salvage using + # bisector report + delete_artifact = False else: - error('No upload service available, could not upload exekall artifact.') + error('No upload service available, could not upload exekall artifact. The artifacts will not be deleted.') + delete_artifact = False + + if delete_artifact: + info('Deleting exekall artifact: {}'.format(artifact_local_path)) + try: + os.remove(artifact_local_path) + except Exception as e: + error('Could not delete local artifact {path}: {e}'.format( + e = e, + path = artifact_local_path, + )) return LISATestStepResult( step = self, -- GitLab From a3b965d90326167f8e65d5c663694b0a252674f1 Mon Sep 17 00:00:00 2001 From: Douglas RAILLARD Date: Fri, 8 Feb 2019 19:01:17 +0000 Subject: [PATCH 2/3] exekall: Add support for multiple iterations Allow running multiple iterations using -n. The objects that are shared between all iterations are controlled by --share. This command will run the tests 10 times, and connect to the target only once, and therefore run the RTapp calibration only once as well: $ exekall run lisa/tests/ --conf target_conf.yml -n10 --share '*TestEnv' --- tools/exekall/exekall/engine.py | 40 +++++++++++++++++++++++++++++++-- tools/exekall/exekall/main.py | 38 ++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/tools/exekall/exekall/engine.py b/tools/exekall/exekall/engine.py index 0dd45f0d5..3cc614fba 100644 --- a/tools/exekall/exekall/engine.py +++ b/tools/exekall/exekall/engine.py @@ -1097,6 +1097,43 @@ class ComputableExpression(ExpressionBase): self.data = data if data is not None else ExprData() super().__init__(op=op, param_map=param_map) + def _find_shared_op_set(self, predicate, shared): + shared_op_set = set() + + # propagate shared-ness to all parents if we are shared + # otherwise, we call the clone predicate + if not shared: + shared = not predicate(self) + + if shared: + shared_op_set.add(self.op) + + shared_op_set.update(utils.flatten_seq( + param_expr._find_shared_op_set(predicate, shared=shared) + for param_expr in self.param_map.values() + )) + return shared_op_set + + def clone_by_predicate(self, predicate): + shared_op_set = self._find_shared_op_set(predicate, False) + return self._clone(shared_op_set) + + def _clone(self, shared_op_set): + op = self.op + if op in shared_op_set: + return self + + param_map = { + param: param_expr._clone(shared_op_set) + for param, param_expr in self.param_map.items() + } + + # create a new clone, with different UUID and ExprData + return self.__class__( + op=op, + param_map=param_map, + ) + @classmethod def from_expr(cls, expr, **kwargs): param_map = { @@ -1112,8 +1149,7 @@ class ComputableExpression(ExpressionBase): @classmethod def from_expr_list(cls, expr_list): # Apply Common Subexpression Elimination to ExpressionBase before they - # are run, and then get a bound reference of "execute" that can be - # readily iterated over to get the results. + # are run return cls.cse( cls.from_expr(expr) for expr in expr_list diff --git a/tools/exekall/exekall/main.py b/tools/exekall/exekall/main.py index 604aa8c8a..d7f96ddc0 100755 --- a/tools/exekall/exekall/main.py +++ b/tools/exekall/exekall/main.py @@ -319,6 +319,14 @@ PATTERNS run_parser.add_argument('--adaptor', help="""Adaptor to use from the customization module, if there is more than one to choose from.""") + run_parser.add_argument('-n', type=int, + default=1, + help="""Run the tests for a number of iterations.""") + + run_parser.add_argument('--share', action='append', + default=[], + help="""Class name pattern to share between multiple iterations.""") + merge_parser = subparsers.add_parser('merge', description=""" @@ -561,6 +569,9 @@ def do_run(args, parser, run_parser, argv): verbose = args.verbose + iteration_nr = args.n + shared_pattern_set = set(args.share) + adaptor = adaptor_cls(args) only_list = args.list @@ -773,6 +784,30 @@ def do_run(args, parser, run_parser, argv): if only_list: return 0 + # Get a list of ComputableExpression in order to execute them + expr_list = engine.ComputableExpression.from_expr_list(expr_list) + + if iteration_nr > 1: + shared_op_set = { + # We don't allow matching on root operators, since that would be + # pointless. Sharing root operators basically means doing the work + # once, and then reusing everything at every iteration. + op for op in (op_set - root_op_set) + if utils.match_base_cls(op.value_type, shared_pattern_set) + } + predicate = lambda expr: expr.op not in shared_op_set + + expr_list = utils.flatten_seq( + # Apply CSE within each iteration + engine.ComputableExpression.cse( + expr.clone_by_predicate(predicate) + for expr in expr_list + ) + for i in range(iteration_nr) + ) + + + exec_ret_code = exec_expr_list( expr_list=expr_list, adaptor=adaptor, @@ -806,9 +841,6 @@ def exec_expr_list(expr_list, adaptor, artifact_dir, testsession_uuid, out('\nArtifacts dir: {}\n'.format(artifact_dir)) - # Get a list of ComputableExpression in order to execute them - expr_list = engine.ComputableExpression.from_expr_list(expr_list) - for expr in expr_list: expr_short_id = expr.get_id( hidden_callable_set=hidden_callable_set, -- GitLab From 43aff40c6b26b7c41a934d14de4dfe90b8d0f658 Mon Sep 17 00:00:00 2001 From: Douglas RAILLARD Date: Mon, 11 Feb 2019 13:50:14 +0000 Subject: [PATCH 3/3] exekall: Display iteration number --- tools/exekall/exekall/main.py | 269 ++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 130 deletions(-) diff --git a/tools/exekall/exekall/main.py b/tools/exekall/exekall/main.py index d7f96ddc0..5c9389678 100755 --- a/tools/exekall/exekall/main.py +++ b/tools/exekall/exekall/main.py @@ -797,19 +797,22 @@ def do_run(args, parser, run_parser, argv): } predicate = lambda expr: expr.op not in shared_op_set - expr_list = utils.flatten_seq( + iteration_expr_list = [ # Apply CSE within each iteration engine.ComputableExpression.cse( expr.clone_by_predicate(predicate) for expr in expr_list ) for i in range(iteration_nr) - ) + ] + else: + iteration_expr_list = [expr_list] + exec_ret_code = exec_expr_list( - expr_list=expr_list, + iteration_expr_list=iteration_expr_list, adaptor=adaptor, artifact_dir=artifact_dir, testsession_uuid=testsession_uuid, @@ -830,7 +833,7 @@ def do_run(args, parser, run_parser, argv): return exec_ret_code -def exec_expr_list(expr_list, adaptor, artifact_dir, testsession_uuid, +def exec_expr_list(iteration_expr_list, adaptor, artifact_dir, testsession_uuid, hidden_callable_set, only_template_scripts, adaptor_cls, verbose): if not only_template_scripts: @@ -841,7 +844,7 @@ def exec_expr_list(expr_list, adaptor, artifact_dir, testsession_uuid, out('\nArtifacts dir: {}\n'.format(artifact_dir)) - for expr in expr_list: + for expr in utils.flatten_seq(iteration_expr_list): expr_short_id = expr.get_id( hidden_callable_set=hidden_callable_set, with_tags=False, @@ -891,7 +894,7 @@ def exec_expr_list(expr_list, adaptor, artifact_dir, testsession_uuid, ) as f: f.write( expr.get_script( - prefix = 'testcase', + prefix = 'expr', db_path = os.path.join('..', DB_FILENAME), db_relative_to = '__file__', )[1]+'\n', @@ -903,145 +906,150 @@ def exec_expr_list(expr_list, adaptor, artifact_dir, testsession_uuid, # Preserve the execution order, so the summary is displayed in the same # order result_map = collections.OrderedDict() - for expr in expr_list: - exec_start_msg = 'Executing: {short_id}\n\nID: {full_id}\nArtifacts: {folder}\nUUID: {uuid_}'.format( - short_id=expr.get_id( - hidden_callable_set=hidden_callable_set, - full_qual=False, - qual=False, - ), - - full_id=expr.get_id( - hidden_callable_set=hidden_callable_set if not verbose else None, - full_qual=True, - ), - folder=expr.data['expr_artifact_dir'], - uuid_=expr.uuid - ).replace('\n', '\n# ') - - delim = '#' * (len(exec_start_msg.splitlines()[0]) + 2) - out(delim + '\n# ' + exec_start_msg + '\n' + delim) - - result_list = list() - result_map[expr] = result_list - - def pre_line(): - out('-' * 40) - # Make sure that all the output of the expression is flushed to ensure - # there won't be any buffered stderr output being displayed after the - # "official" end of the Expression's execution. - def flush_std_streams(): - sys.stdout.flush() - sys.stderr.flush() - - def get_uuid_str(expr_val): - return 'UUID={}'.format(expr_val.uuid) - - computed_expr_val_set = set() - reused_expr_val_set = set() - def log_expr_val(expr_val, reused): - # Consider that PrebuiltOperator reuse values instead of actually - # computing them. - if isinstance(expr_val.expr.op, engine.PrebuiltOperator): - reused = True - - if reused: - msg = 'Reusing already computed {id} {uuid}' - reused_expr_val_set.add(expr_val) - else: - msg = 'Computed {id} {uuid}' - computed_expr_val_set.add(expr_val) - - op = expr_val.expr.op - if ( - op.callable_ not in hidden_callable_set - and not issubclass(op.value_type, engine.ForcedParamType) - ): - info(msg.format( - id=expr_val.get_id( + for i, expr_list in enumerate(iteration_expr_list): + i += 1 + info('Iteration #{}\n'.format(i)) + + for expr in expr_list: + exec_start_msg = 'Executing: {short_id}\n\nID: {full_id}\nArtifacts: {folder}\nUUID: {uuid_}'.format( + short_id=expr.get_id( + hidden_callable_set=hidden_callable_set, full_qual=False, + qual=False, + ), + + full_id=expr.get_id( + hidden_callable_set=hidden_callable_set if not verbose else None, + full_qual=True, + ), + folder=expr.data['expr_artifact_dir'], + uuid_=expr.uuid + ).replace('\n', '\n# ') + + delim = '#' * (len(exec_start_msg.splitlines()[0]) + 2) + out(delim + '\n# ' + exec_start_msg + '\n' + delim) + + result_list = list() + result_map[expr] = result_list + + def pre_line(): + out('-' * 40) + # Make sure that all the output of the expression is flushed to ensure + # there won't be any buffered stderr output being displayed after the + # "official" end of the Expression's execution. + def flush_std_streams(): + sys.stdout.flush() + sys.stderr.flush() + + def get_uuid_str(expr_val): + return 'UUID={}'.format(expr_val.uuid) + + computed_expr_val_set = set() + reused_expr_val_set = set() + def log_expr_val(expr_val, reused): + # Consider that PrebuiltOperator reuse values instead of actually + # computing them. + if isinstance(expr_val.expr.op, engine.PrebuiltOperator): + reused = True + + if reused: + msg = 'Reusing already computed {id} {uuid}' + reused_expr_val_set.add(expr_val) + else: + msg = 'Computed {id} {uuid}' + computed_expr_val_set.add(expr_val) + + op = expr_val.expr.op + if ( + op.callable_ not in hidden_callable_set + and not issubclass(op.value_type, engine.ForcedParamType) + ): + info(msg.format( + id=expr_val.get_id( + full_qual=False, + with_tags=True, + hidden_callable_set=hidden_callable_set, + ), + uuid=get_uuid_str(expr_val), + )) + + # This returns an iterator + executor = expr.execute(log_expr_val) + + out('') + for result in utils.iterate_cb(executor, pre_line, flush_std_streams): + for excep_val in result.get_excep(): + excep = excep_val.excep + tb = utils.format_exception(excep) + error('{e_name}: {e}\nID: {id}\n{tb}'.format( + id=excep_val.get_id(), + e_name=utils.get_name(type(excep)), + e=excep, + tb=tb, + ), + ) + + prefix = 'Finished {uuid} '.format(uuid=get_uuid_str(result)) + out('{prefix}{id}'.format( + id=result.get_id( + full_qual=False, + qual=False, + mark_excep=True, with_tags=True, hidden_callable_set=hidden_callable_set, - ), - uuid=get_uuid_str(expr_val), + ).strip().replace('\n', '\n'+len(prefix)*' '), + prefix=prefix, )) - # This returns an iterator - executor = expr.execute(log_expr_val) - - out('') - for result in utils.iterate_cb(executor, pre_line, flush_std_streams): - for excep_val in result.get_excep(): - excep = excep_val.excep - tb = utils.format_exception(excep) - error('{e_name}: {e}\nID: {id}\n{tb}'.format( - id=excep_val.get_id(), - e_name=utils.get_name(type(excep)), - e=excep, - tb=tb, - ), - ) + out(adaptor.result_str(result)) + result_list.append(result) - prefix = 'Finished {uuid} '.format(uuid=get_uuid_str(result)) - out('{prefix}{id}'.format( - id=result.get_id( - full_qual=False, - qual=False, - mark_excep=True, - with_tags=True, - hidden_callable_set=hidden_callable_set, - ).strip().replace('\n', '\n'+len(prefix)*' '), - prefix=prefix, - )) - out(adaptor.result_str(result)) - result_list.append(result) + out('') + expr_artifact_dir = expr.data['expr_artifact_dir'] + # Finalize the computation + adaptor.finalize_expr(expr) - out('') - expr_artifact_dir = expr.data['expr_artifact_dir'] + # Dump the reproducer script + with (expr_artifact_dir/'TESTCASE.py').open('wt', encoding='utf-8') as f: + f.write( + expr.get_script( + prefix = 'testcase', + db_path = os.path.join('..', '..', DB_FILENAME), + db_relative_to = '__file__', + )[1]+'\n', + ) - # Finalize the computation - adaptor.finalize_expr(expr) + def format_uuid(expr_val_list): + uuid_list = sorted({ + expr_val.uuid + for expr_val in expr_val_list + }) + return '\n'.join(uuid_list) - # Dump the reproducer script - with (expr_artifact_dir/'TESTCASE.py').open('wt', encoding='utf-8') as f: - f.write( - expr.get_script( - prefix = 'testcase', - db_path = os.path.join('..', '..', DB_FILENAME), - db_relative_to = '__file__', - )[1]+'\n', - ) + def write_uuid(path, *args): + with path.open('wt') as f: + f.write(format_uuid(*args) + '\n') - def format_uuid(expr_val_list): - uuid_list = sorted({ + write_uuid(expr_artifact_dir/'VALUES_UUID', result_list) + write_uuid(expr_artifact_dir/'REUSED_VALUES_UUID', reused_expr_val_set) + write_uuid(expr_artifact_dir/'COMPUTED_VALUES_UUID', computed_expr_val_set) + + # From there, use a relative path for symlinks + expr_artifact_dir = pathlib.Path('..', expr_artifact_dir.relative_to(artifact_dir)) + computed_uuid_set = { expr_val.uuid - for expr_val in expr_val_list - }) - return '\n'.join(uuid_list) - - def write_uuid(path, *args): - with path.open('wt') as f: - f.write(format_uuid(*args) + '\n') - - write_uuid(expr_artifact_dir/'VALUES_UUID', result_list) - write_uuid(expr_artifact_dir/'REUSED_VALUES_UUID', reused_expr_val_set) - write_uuid(expr_artifact_dir/'COMPUTED_VALUES_UUID', computed_expr_val_set) - - # From there, use a relative path for symlinks - expr_artifact_dir = pathlib.Path('..', expr_artifact_dir.relative_to(artifact_dir)) - computed_uuid_set = { - expr_val.uuid - for expr_val in computed_expr_val_set - } - computed_uuid_set.add(expr.uuid) - for uuid_ in computed_uuid_set: - (artifact_dir/'BY_UUID'/uuid_).symlink_to(expr_artifact_dir) + for expr_val in computed_expr_val_set + } + computed_uuid_set.add(expr.uuid) + for uuid_ in computed_uuid_set: + (artifact_dir/'BY_UUID'/uuid_).symlink_to(expr_artifact_dir) db = engine.ValueDB( engine.FrozenExprValSeq.from_expr_list( - expr_list, hidden_callable_set=hidden_callable_set + utils.flatten_seq(iteration_expr_list), + hidden_callable_set=hidden_callable_set, ), adaptor_cls=adaptor_cls, ) @@ -1062,7 +1070,8 @@ def exec_expr_list(expr_list, adaptor, artifact_dir, testsession_uuid, # Output the merged script with all subscripts script_path = artifact_dir/'ALL_SCRIPTS.py' result_name_map, all_scripts = engine.Expression.get_all_script( - expr_list, prefix='testcase', + utils.flatten_seq(iteration_expr_list), + prefix='expr', db_path=db_path.relative_to(artifact_dir), db_relative_to='__file__', db=db, -- GitLab