mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-10-31 07:04:17 +00:00 
			
		
		
		
	Fixed run command to the point of invoking the Executor
This commit is contained in:
		| @@ -117,7 +117,7 @@ class CreateWorkloadSubcommand(CreateSubcommand): | ||||
|                                          'should place the APK file into the workload\'s directory at the ' + | ||||
|                                          'same level as the __init__.py.') | ||||
|  | ||||
|     def execute(self, args):  # pylint: disable=R0201 | ||||
|     def execute(self, state, args):  # pylint: disable=R0201 | ||||
|         where = args.path or 'local' | ||||
|         check_name = not args.force | ||||
|  | ||||
|   | ||||
| @@ -39,7 +39,7 @@ class ListCommand(Command): | ||||
|         self.parser.add_argument('-p', '--platform', help='Only list results that are supported by ' | ||||
|                                                           'the specified platform') | ||||
|  | ||||
|     def execute(self, args): | ||||
|     def execute(self, state, args): | ||||
|         filters = {} | ||||
|         if args.name: | ||||
|             filters['name'] = args.name | ||||
|   | ||||
| @@ -78,7 +78,7 @@ class RecordCommand(Command): | ||||
|             args.suffix += "." | ||||
|  | ||||
|     # pylint: disable=W0201 | ||||
|     def execute(self, args): | ||||
|     def execute(self, state, args): | ||||
|         self.validate_args(args) | ||||
|         self.logger.info("Connecting to device...") | ||||
|  | ||||
|   | ||||
| @@ -20,11 +20,13 @@ import shutil | ||||
|  | ||||
| import wlauto | ||||
| from wlauto import Command, settings | ||||
| from wlauto.core.execution import Executor | ||||
| from wlauto.utils.log import add_log_file | ||||
| from wlauto.core.configuration import RunConfiguration | ||||
| from wlauto.core import pluginloader | ||||
| from wlauto.core.configuration.parsers import AgendaParser, ConfigParser, CommandLineArgsParser | ||||
| from wlauto.core.configuration import RunConfiguration | ||||
| from wlauto.core.configuration.parsers import AgendaParser, ConfigParser | ||||
| from wlauto.core.execution import Executor | ||||
| from wlauto.exceptions import NotFoundError, ConfigError | ||||
| from wlauto.utils.log import add_log_file | ||||
| from wlauto.utils.types import toggle_set | ||||
|  | ||||
|  | ||||
| class RunCommand(Command): | ||||
| @@ -32,103 +34,6 @@ class RunCommand(Command): | ||||
|     name = 'run' | ||||
|     description = 'Execute automated workloads on a remote device and process the resulting output.' | ||||
|  | ||||
|     def initialize(self, context): | ||||
|         self.parser.add_argument('agenda', metavar='AGENDA', | ||||
|                                  help=""" | ||||
|                                  Agenda for this workload automation run. This defines which | ||||
|                                  workloads will be executed, how many times, with which | ||||
|                                  tunables, etc.  See example agendas in {} for an example of | ||||
|                                  how this file should be structured. | ||||
|                                  """.format(os.path.dirname(wlauto.__file__))) | ||||
|         self.parser.add_argument('-d', '--output-directory', metavar='DIR', default=None, | ||||
|                                  help=""" | ||||
|                                  Specify a directory where the output will be generated. If | ||||
|                                  the directory already exists, the script will abort unless -f | ||||
|                                  option (see below) is used, in which case the contents of the | ||||
|                                  directory will be overwritten. If this option is not specified, | ||||
|                                  then {} will be used instead. | ||||
|                                  """.format("settings.default_output_directory"))  # TODO: Fix this! | ||||
|         self.parser.add_argument('-f', '--force', action='store_true', | ||||
|                                  help=""" | ||||
|                                  Overwrite output directory if it exists. By default, the script | ||||
|                                  will abort in this situation to prevent accidental data loss. | ||||
|                                  """) | ||||
|         self.parser.add_argument('-i', '--id', action='append', dest='only_run_ids', metavar='ID', | ||||
|                                  help=""" | ||||
|                                  Specify a workload spec ID from an agenda to run. If this is | ||||
|                                  specified, only that particular spec will be run, and other | ||||
|                                  workloads in the agenda will be ignored. This option may be | ||||
|                                  used to specify multiple IDs. | ||||
|                                  """) | ||||
|         self.parser.add_argument('--disable', action='append', dest='instruments_to_disable', | ||||
|                                  metavar='INSTRUMENT', help=""" | ||||
|                                  Specify an instrument to disable from the command line. This | ||||
|                                  equivalent to adding "~{metavar}" to the instrumentation list in | ||||
|                                  the agenda. This can be used to temporarily disable a troublesome | ||||
|                                  instrument for a particular run without introducing permanent | ||||
|                                  change to the config (which one might then forget to revert). | ||||
|                                  This option may be specified multiple times. | ||||
|                                  """) | ||||
|  | ||||
|     def execute(self, args): | ||||
|  | ||||
|         # STAGE 1: Gather configuratation | ||||
|  | ||||
|         env = EnvironmentVars() | ||||
|         args = CommandLineArgs(args) | ||||
|  | ||||
|         # STAGE 2.1a: Early WAConfiguration, required to find config files | ||||
|         if env.user_directory: | ||||
|             settings.set("user_directory", env.user_directory) | ||||
|         if env.plugin_paths: | ||||
|             settings.set("plugin_paths", env.plugin_paths) | ||||
|         # STAGE 1 continued | ||||
|  | ||||
|         # TODO: Check for config.py and convert to yaml, if it fails, warn user. | ||||
|         configs = [ConfigFile(os.path.join(settings.user_directory, 'config.yaml'))] | ||||
|         for c in args.config: | ||||
|             configs.append(ConfigFile(c)) | ||||
|         agenda = Agenda(args.agenda) | ||||
|         configs.append(Agenda.config) | ||||
|  | ||||
|         # STAGE 2: Sending configuration to the correct place & merging in | ||||
|         #          order of priority. | ||||
|         # | ||||
|         #          Priorities (lowest to highest): | ||||
|         #           - Enviroment Variables | ||||
|         #           - config.yaml from `user_directory` | ||||
|         #           - config files passed via command line | ||||
|         #             (the first specified will be the first to be applied) | ||||
|         #           - Agenda | ||||
|         #           - Command line configuration e.g. disabled instrumentation. | ||||
|  | ||||
|         # STAGE 2.1b: WAConfiguration | ||||
|         for config in configs: | ||||
|             for config_point in settings.configuration.keys(): | ||||
|                 if hasattr(config, config_point): | ||||
|                     settings.set(config_point, config.getattr(config_point)) | ||||
|  | ||||
|  | ||||
|     def _parse_config(self): | ||||
|         pass | ||||
|  | ||||
|     def _serialize_raw_config(self, env, args, agenda, configs): | ||||
|         pod = {} | ||||
|         pod['environment_variables'] = env.to_pod() | ||||
|         pod['commandline_arguments'] = args.to_pod() | ||||
|         pod['agenda'] = agenda.to_pod() | ||||
|         pod['config_files'] = [c.to_pod() for c in configs] | ||||
|         return pod | ||||
|  | ||||
|     def _serialize_final_config(self): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| class OldRunCommand(Command): | ||||
|  | ||||
|     name = 'old_run' | ||||
|     description = 'Execute automated workloads on a remote device and process the resulting output.' | ||||
|  | ||||
|     def initialize(self, context): | ||||
|         self.parser.add_argument('agenda', metavar='AGENDA', | ||||
|                                  help=""" | ||||
| @@ -158,6 +63,7 @@ class OldRunCommand(Command): | ||||
|                                  used to specify multiple IDs. | ||||
|                                  """) | ||||
|         self.parser.add_argument('--disable', action='append', dest='instruments_to_disable', | ||||
|                                  default=[], | ||||
|                                  metavar='INSTRUMENT', help=""" | ||||
|                                  Specify an instrument to disable from the command line. This | ||||
|                                  equivalent to adding "~{metavar}" to the instrumentation list in | ||||
| @@ -167,38 +73,32 @@ class OldRunCommand(Command): | ||||
|                                  This option may be specified multiple times. | ||||
|                                  """) | ||||
|  | ||||
|     def execute(self, args):  # NOQA | ||||
|     def execute(self, state, args): | ||||
|         output_directory = self.set_up_output_directory(args) | ||||
|         add_log_file(os.path.join(output_directory, "run.log")) | ||||
|         config = RunConfiguration(pluginloader) | ||||
|  | ||||
|         disabled_instruments = toggle_set(["~{}".format(i)  | ||||
|                                            for i in args.instruments_to_disable]) | ||||
|         state.jobs_config.disable_instruments(disabled_instruments) | ||||
|         state.jobs_config.only_run_ids(args.only_run_ids) | ||||
|  | ||||
|         parser = AgendaParser() | ||||
|         if os.path.isfile(args.agenda): | ||||
|             agenda = Agenda(args.agenda) | ||||
|             settings.agenda = args.agenda | ||||
|             shutil.copy(args.agenda, config.meta_directory) | ||||
|             parser.load_from_path(state, args.agenda) | ||||
|         else: | ||||
|             self.logger.debug('{} is not a file; assuming workload name.'.format(args.agenda)) | ||||
|             agenda = Agenda() | ||||
|             agenda.add_workload_entry(args.agenda) | ||||
|             try: | ||||
|                 pluginloader.get_workload(args.agenda) | ||||
|                 agenda = {'workloads': [{'name': args.agenda}]} | ||||
|                 parser.load(state, agenda, 'CMDLINE_ARGS') | ||||
|             except NotFoundError: | ||||
|                 msg = 'Agenda file "{}" does not exist, and there no workload '\ | ||||
|                       'with that name.\nYou can get a list of available '\ | ||||
|                       'by running "wa list workloads".' | ||||
|                 raise ConfigError(msg.format(args.agenda)) | ||||
|  | ||||
|         for filepath in settings.config_paths: | ||||
|             config.load_config(filepath) | ||||
|  | ||||
|         if args.instruments_to_disable: | ||||
|             if 'instrumentation' not in agenda.config: | ||||
|                 agenda.config['instrumentation'] = [] | ||||
|             for itd in args.instruments_to_disable: | ||||
|                 self.logger.debug('Updating agenda to disable {}'.format(itd)) | ||||
|                 agenda.config['instrumentation'].append('~{}'.format(itd)) | ||||
|  | ||||
|         basename = 'config_' | ||||
|         for file_number, path in enumerate(settings.config_paths, 1): | ||||
|             file_ext = os.path.splitext(path)[1] | ||||
|             shutil.copy(path, os.path.join(config.meta_directory, | ||||
|                                            basename + str(file_number) + file_ext)) | ||||
|  | ||||
|         executor = Executor(config) | ||||
|         executor.execute(agenda, selectors={'ids': args.only_run_ids}) | ||||
|         executor = Executor() | ||||
|         # TODO: fix executor | ||||
|         # executor.execute(state, selectors={'ids': args.only_run_ids}) | ||||
|  | ||||
|     def set_up_output_directory(self, args): | ||||
|         if args.output_directory: | ||||
|   | ||||
| @@ -40,7 +40,7 @@ class ShowCommand(Command): | ||||
|                                  help='''The name of the plugin for which information will | ||||
|                                          be shown.''') | ||||
|  | ||||
|     def execute(self, args): | ||||
|     def execute(self, state, args): | ||||
|         # pylint: disable=unpacking-non-sequence | ||||
|         plugin = pluginloader.get_plugin_class(args.name) | ||||
|         out = StringIO() | ||||
|   | ||||
| @@ -21,19 +21,22 @@ from wlauto.core.version import get_wa_version | ||||
|  | ||||
|  | ||||
| def init_argument_parser(parser): | ||||
|     parser.add_argument('-c', '--config', help='specify an additional config.py', action='append', default=[]) | ||||
|     parser.add_argument('-c', '--config', action='append', default=[], | ||||
|                         help='specify an additional config.py') | ||||
|     parser.add_argument('-v', '--verbose', action='count', | ||||
|                         help='The scripts will produce verbose output.') | ||||
|     parser.add_argument('--version', action='version', version='%(prog)s {}'.format(get_wa_version())) | ||||
|     parser.add_argument('--version', action='version',  | ||||
|                         version='%(prog)s {}'.format(get_wa_version())) | ||||
|     return parser | ||||
|  | ||||
|  | ||||
| class Command(Plugin): | ||||
|     """ | ||||
|     Defines a Workload Automation command. This will be executed from the command line as | ||||
|     ``wa <command> [args ...]``. This defines the name to be used when invoking wa, the | ||||
|     code that will actually be executed on invocation and the argument parser to be used | ||||
|     to parse the reset of the command line arguments. | ||||
|     Defines a Workload Automation command. This will be executed from the | ||||
|     command line as ``wa <command> [args ...]``. This defines the name to be | ||||
|     used when invoking wa, the code that will actually be executed on | ||||
|     invocation and the argument parser to be used to parse the reset of the | ||||
|     command line arguments. | ||||
|  | ||||
|     """ | ||||
|     kind = "command" | ||||
| @@ -57,16 +60,19 @@ class Command(Plugin): | ||||
|  | ||||
|     def initialize(self, context): | ||||
|         """ | ||||
|         Perform command-specific initialisation (e.g. adding command-specific options to the command's | ||||
|         parser). ``context`` is always ``None``. | ||||
|         Perform command-specific initialisation (e.g. adding command-specific | ||||
|         options to the command's parser). ``context`` is always ``None``. | ||||
|  | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def execute(self, args): | ||||
|     def execute(self, state, args): | ||||
|         """ | ||||
|         Execute this command. | ||||
|  | ||||
|         :state: An initialized ``WAState`` that contains the current state of | ||||
|                 WA exeuction up to that point (processed configuraition, loaded | ||||
|                 plugins, etc). | ||||
|         :args: An ``argparse.Namespace`` containing command line arguments (as returned by | ||||
|                ``argparse.ArgumentParser.parse_args()``. This would usually be the result of | ||||
|                invoking ``self.parser``. | ||||
|   | ||||
| @@ -516,8 +516,10 @@ class Configuration(object): | ||||
|  | ||||
|     def set(self, name, value, check_mandatory=True): | ||||
|         if name not in self.configuration: | ||||
|             raise ConfigError('Unknown {} configuration "{}"'.format(self.name, name)) | ||||
|         self.configuration[name].set_value(self, value, check_mandatory=check_mandatory) | ||||
|             raise ConfigError('Unknown {} configuration "{}"'.format(self.name,  | ||||
|                                                                      name)) | ||||
|         self.configuration[name].set_value(self, value,  | ||||
|                                            check_mandatory=check_mandatory) | ||||
|  | ||||
|     def update_config(self, values, check_mandatory=True): | ||||
|         for k, v in values.iteritems(): | ||||
| @@ -610,6 +612,9 @@ class WAConfiguration(Configuration): | ||||
|     def plugins_directory(self): | ||||
|         return os.path.join(self.user_directory, 'plugins') | ||||
|  | ||||
|     @property | ||||
|     def user_config_file(self): | ||||
|         return os.path.joion(self.user_directory, 'config.yaml') | ||||
|  | ||||
|     def __init__(self, environ): | ||||
|         super(WAConfiguration, self).__init__() | ||||
| @@ -618,7 +623,6 @@ class WAConfiguration(Configuration): | ||||
|             self.set('user_directory', user_directory) | ||||
|  | ||||
|  | ||||
|  | ||||
| # This is generic top-level configuration for WA runs. | ||||
| class RunConfiguration(Configuration): | ||||
|  | ||||
|   | ||||
| @@ -20,13 +20,151 @@ from wlauto.utils.serializer import read_pod, SerializerSyntaxError | ||||
| from wlauto.utils.types import toggle_set, counter | ||||
| from wlauto.core.configuration.configuration import JobSpec | ||||
|  | ||||
|  | ||||
| ############### | ||||
| ### Parsers ### | ||||
| ############### | ||||
|  | ||||
| class ConfigParser(object): | ||||
|  | ||||
|     def load_from_path(self, state, filepath): | ||||
|         self.load(_load_file(filepath, "Config"), filepath) | ||||
|  | ||||
|     def load(self, state, raw, source, wrap_exceptions=True):  # pylint: disable=too-many-branches | ||||
|         try: | ||||
|             if 'run_name' in raw: | ||||
|                 msg = '"run_name" can only be specified in the config '\ | ||||
|                       'section of an agenda' | ||||
|                 raise ConfigError(msg) | ||||
|  | ||||
|             if 'id' in raw: | ||||
|                 raise ConfigError('"id" cannot be set globally') | ||||
|  | ||||
|             merge_result_processors_instruments(raw) | ||||
|  | ||||
|             # Get WA core configuration | ||||
|             for cfg_point in state.settings.configuration.itervalues(): | ||||
|                 value = get_aliased_param(cfg_point, raw) | ||||
|                 if value is not None: | ||||
|                     state.settings.set(cfg_point.name, value) | ||||
|  | ||||
|             # Get run specific configuration | ||||
|             for cfg_point in state.run_config.configuration.itervalues(): | ||||
|                 value = get_aliased_param(cfg_point, raw) | ||||
|                 if value is not None: | ||||
|                     state.run_config.set(cfg_point.name, value) | ||||
|  | ||||
|             # Get global job spec configuration | ||||
|             for cfg_point in JobSpec.configuration.itervalues(): | ||||
|                 value = get_aliased_param(cfg_point, raw) | ||||
|                 if value is not None: | ||||
|                     state.jobs_config.set_global_value(cfg_point.name, value) | ||||
|  | ||||
|             for name, values in raw.iteritems(): | ||||
|                 # Assume that all leftover config is for a plug-in or a global | ||||
|                 # alias it is up to PluginCache to assert this assumption | ||||
|                 state.plugin_cache.add_configs(name, values, source) | ||||
|  | ||||
|         except ConfigError as e: | ||||
|             if wrap_exceptions: | ||||
|                 raise ConfigError('Error in "{}":\n{}'.format(source, str(e))) | ||||
|             else: | ||||
|                 raise e | ||||
|  | ||||
|  | ||||
| class AgendaParser(object): | ||||
|  | ||||
|     def load_from_path(self, state, filepath): | ||||
|         raw = _load_file(filepath, 'Agenda') | ||||
|         self.load(state, raw, filepath) | ||||
|  | ||||
|     def load(self, state, raw, source): | ||||
|         try: | ||||
|             if not isinstance(raw, dict): | ||||
|                 raise ConfigError('Invalid agenda, top level entry must be a dict') | ||||
|  | ||||
|             self._populate_and_validate_config(state, raw, source) | ||||
|             sections = self._pop_sections(raw) | ||||
|             global_workloads = self._pop_workloads(raw) | ||||
|  | ||||
|             if raw: | ||||
|                 msg = 'Invalid top level agenda entry(ies): "{}"' | ||||
|                 raise ConfigError(msg.format('", "'.join(raw.keys()))) | ||||
|  | ||||
|             sect_ids, wkl_ids = self._collect_ids(sections, global_workloads) | ||||
|             self._process_global_workloads(state, global_workloads, wkl_ids) | ||||
|             self._process_sections(state, sections, sect_ids, wkl_ids) | ||||
|  | ||||
|         except (ConfigError, SerializerSyntaxError) as e: | ||||
|             raise ConfigError('Error in "{}":\n\t{}'.format(source, str(e))) | ||||
|  | ||||
|     def _populate_and_validate_config(self, state, raw, source): | ||||
|         for name in ['config', 'global']: | ||||
|             entry = raw.pop(name, None) | ||||
|             if entry is None: | ||||
|                 continue | ||||
|  | ||||
|             if not isinstance(entry, dict): | ||||
|                 msg = 'Invalid entry "{}" - must be a dict' | ||||
|                 raise ConfigError(msg.format(name)) | ||||
|  | ||||
|             if 'run_name' in entry: | ||||
|                 state.run_config.set('run_name', entry.pop('run_name')) | ||||
|  | ||||
|             state.load_config(entry, source, wrap_exceptions=False) | ||||
|  | ||||
|     def _pop_sections(self, raw): | ||||
|         sections = raw.pop("sections", []) | ||||
|         if not isinstance(sections, list): | ||||
|             raise ConfigError('Invalid entry "sections" - must be a list') | ||||
|         return sections | ||||
|  | ||||
|     def _pop_workloads(self, raw): | ||||
|         workloads = raw.pop("workloads", []) | ||||
|         if not isinstance(workloads, list): | ||||
|             raise ConfigError('Invalid entry "workloads" - must be a list') | ||||
|         return workloads | ||||
|  | ||||
|     def _collect_ids(self, sections, global_workloads): | ||||
|         seen_section_ids = set() | ||||
|         seen_workload_ids = set() | ||||
|  | ||||
|         for workload in global_workloads: | ||||
|             workload = _get_workload_entry(workload) | ||||
|             _collect_valid_id(workload.get("id"), seen_workload_ids, "workload") | ||||
|  | ||||
|         for section in sections: | ||||
|             _collect_valid_id(section.get("id"), seen_section_ids, "section") | ||||
|             for workload in section["workloads"] if "workloads" in section else []: | ||||
|                 workload = _get_workload_entry(workload) | ||||
|                 _collect_valid_id(workload.get("id"), seen_workload_ids,  | ||||
|                                   "workload") | ||||
|  | ||||
|         return seen_section_ids, seen_workload_ids | ||||
|  | ||||
|     def _process_global_workloads(self, state, global_workloads, seen_wkl_ids): | ||||
|         for workload_entry in global_workloads: | ||||
|             workload = _process_workload_entry(workload_entry, seen_wkl_ids, | ||||
|                                                state.jobs_config) | ||||
|             state.jobs_config.add_workload(workload) | ||||
|  | ||||
|     def _process_sections(self, state, sections, seen_sect_ids, seen_wkl_ids): | ||||
|         for section in sections: | ||||
|             workloads = [] | ||||
|             for workload_entry in section.pop("workloads", []): | ||||
|                 workload = _process_workload_entry(workload_entry, seen_workload_ids, | ||||
|                                                    state.jobs_config) | ||||
|                 workloads.append(workload) | ||||
|  | ||||
|             section = _construct_valid_entry(section, seen_section_ids,  | ||||
|                                              "s", state.jobs_config) | ||||
|             state.jobs_config.add_section(section, workloads) | ||||
|  | ||||
|  | ||||
| ######################## | ||||
| ### Helper functions ### | ||||
| ######################## | ||||
|  | ||||
| DUPLICATE_ENTRY_ERROR = 'Only one of {} may be specified in a single entry' | ||||
|  | ||||
|  | ||||
| def get_aliased_param(cfg_point, d, default=None, pop=True): | ||||
|     """ | ||||
|     Given a ConfigurationPoint and a dict, this function will search the dict for | ||||
| @@ -62,55 +200,79 @@ def _load_file(filepath, error_name): | ||||
|  | ||||
|  | ||||
| def merge_result_processors_instruments(raw): | ||||
|     instruments = toggle_set(get_aliased_param(JobSpec.configuration['instrumentation'], | ||||
|                                                raw, default=[])) | ||||
|     instr_config = JobSpec.configuration['instrumentation'] | ||||
|     instruments = toggle_set(get_aliased_param(instr_config, raw, default=[])) | ||||
|     result_processors = toggle_set(raw.pop('result_processors', [])) | ||||
|     if instruments and result_processors: | ||||
|         conflicts = instruments.conflicts_with(result_processors) | ||||
|         if conflicts: | ||||
|             msg = '"instrumentation" and "result_processors" have conflicting entries: {}' | ||||
|             msg = '"instrumentation" and "result_processors" have '\ | ||||
|                   'conflicting entries: {}' | ||||
|             entires = ', '.join('"{}"'.format(c.strip("~")) for c in conflicts) | ||||
|             raise ConfigError(msg.format(entires)) | ||||
|     raw['instrumentation'] = instruments.merge_with(result_processors) | ||||
|  | ||||
|  | ||||
| def _construct_valid_entry(raw, seen_ids, counter_name, jobs_config): | ||||
|     entries = {} | ||||
| def _pop_aliased(d, names, entry_id): | ||||
|     name_count = sum(1 for n in names if n in d) | ||||
|     if name_count > 1: | ||||
|         names_list = ', '.join(names) | ||||
|         msg = 'Inivalid workload entry "{}": at moust one of ({}}) must be specified.' | ||||
|         raise ConfigError(msg.format(workload_entry['id'], names_list)) | ||||
|     for name in names: | ||||
|         if name in d: | ||||
|             return d.pop(name) | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def _construct_valid_entry(raw, seen_ids, prefix, jobs_config): | ||||
|     workload_entry = {} | ||||
|  | ||||
|     # Generate an automatic ID if the entry doesn't already have one | ||||
|     if "id" not in raw: | ||||
|     if 'id' not in raw: | ||||
|         while True: | ||||
|             new_id = "{}{}".format(counter_name, counter(name=counter_name)) | ||||
|             new_id = '{}{}'.format(prefix, counter(name=prefix)) | ||||
|             if new_id not in seen_ids: | ||||
|                 break | ||||
|         entries["id"] = new_id | ||||
|         workload_entry['id'] = new_id | ||||
|         seen_ids.add(new_id) | ||||
|     else: | ||||
|         entries["id"] = raw.pop("id") | ||||
|         workload_entry['id'] = raw.pop('id') | ||||
|  | ||||
|     # Process instrumentation | ||||
|     merge_result_processors_instruments(raw) | ||||
|  | ||||
|     # Validate all entries | ||||
|     # Validate all workload_entry | ||||
|     for name, cfg_point in JobSpec.configuration.iteritems(): | ||||
|         value = get_aliased_param(cfg_point, raw) | ||||
|         if value is not None: | ||||
|             value = cfg_point.kind(value) | ||||
|             cfg_point.validate_value(name, value) | ||||
|             entries[name] = value | ||||
|     entries["workload_parameters"] = raw.pop("workload_parameters", None) | ||||
|     entries["runtime_parameters"] = raw.pop("runtime_parameters", None) | ||||
|     entries["boot_parameters"] = raw.pop("boot_parameters", None) | ||||
|             workload_entry[name] = value | ||||
|  | ||||
|     if "instrumentation" in entries: | ||||
|         jobs_config.update_enabled_instruments(entries["instrumentation"]) | ||||
|     wk_id = workload_entry['id'] | ||||
|     param_names = ['workload_params', 'workload_parameters'] | ||||
|     if prefix == 'wk': | ||||
|         param_names +=  ['params', 'parameters'] | ||||
|     workload_entry["workload_parameters"] = _pop_aliased(raw, param_names, wk_id) | ||||
|  | ||||
|     # error if there are unknown entries | ||||
|     param_names = ['runtime_parameters', 'runtime_params'] | ||||
|     if prefix == 's': | ||||
|         param_names +=  ['params', 'parameters'] | ||||
|     workload_entry["runtime_parameters"] = _pop_aliased(raw, param_names, wk_id) | ||||
|  | ||||
|     param_names = ['boot_parameters', 'boot_params'] | ||||
|     workload_entry["boot_parameters"] = _pop_aliased(raw, param_names, wk_id) | ||||
|  | ||||
|     if "instrumentation" in workload_entry: | ||||
|         jobs_config.update_enabled_instruments(workload_entry["instrumentation"]) | ||||
|  | ||||
|     # error if there are unknown workload_entry | ||||
|     if raw: | ||||
|         msg = 'Invalid entry(ies) in "{}": "{}"' | ||||
|         raise ConfigError(msg.format(entries['id'], ', '.join(raw.keys()))) | ||||
|         raise ConfigError(msg.format(workload_entry['id'], ', '.join(raw.keys()))) | ||||
|  | ||||
|     return entries | ||||
|     return workload_entry | ||||
|  | ||||
|  | ||||
| def _collect_valid_id(entry_id, seen_ids, entry_type): | ||||
| @@ -128,15 +290,6 @@ def _collect_valid_id(entry_id, seen_ids, entry_type): | ||||
|     seen_ids.add(entry_id) | ||||
|  | ||||
|  | ||||
| def _resolve_params_alias(entry, param_alias): | ||||
|     possible_names = {"params", "{}_params".format(param_alias), "{}_parameters".format(param_alias)} | ||||
|     duplicate_entries = possible_names.intersection(set(entry.keys())) | ||||
|     if len(duplicate_entries) > 1: | ||||
|         raise ConfigError(DUPLICATE_ENTRY_ERROR.format(list(possible_names))) | ||||
|     for name in duplicate_entries: | ||||
|         entry["{}_parameters".format(param_alias)] = entry.pop(name) | ||||
|  | ||||
|  | ||||
| def _get_workload_entry(workload): | ||||
|     if isinstance(workload, basestring): | ||||
|         workload = {'name': workload} | ||||
| @@ -147,150 +300,7 @@ def _get_workload_entry(workload): | ||||
|  | ||||
| def _process_workload_entry(workload, seen_workload_ids, jobs_config): | ||||
|     workload = _get_workload_entry(workload) | ||||
|     _resolve_params_alias(workload, "workload") | ||||
|     workload = _construct_valid_entry(workload, seen_workload_ids, "wk", jobs_config) | ||||
|     workload = _construct_valid_entry(workload, seen_workload_ids,  | ||||
|                                       "wk", jobs_config) | ||||
|     return workload | ||||
|  | ||||
| ############### | ||||
| ### Parsers ### | ||||
| ############### | ||||
|  | ||||
|  | ||||
| class ConfigParser(object): | ||||
|  | ||||
|     def __init__(self, wa_config, run_config, jobs_config, plugin_cache): | ||||
|         self.wa_config = wa_config | ||||
|         self.run_config = run_config | ||||
|         self.jobs_config = jobs_config | ||||
|         self.plugin_cache = plugin_cache | ||||
|  | ||||
|     def load_from_path(self, filepath): | ||||
|         self.load(_load_file(filepath, "Config"), filepath) | ||||
|  | ||||
|     def load(self, raw, source, wrap_exceptions=True):  # pylint: disable=too-many-branches | ||||
|         try: | ||||
|             if 'run_name' in raw: | ||||
|                 msg = '"run_name" can only be specified in the config section of an agenda' | ||||
|                 raise ConfigError(msg) | ||||
|             if 'id' in raw: | ||||
|                 raise ConfigError('"id" cannot be set globally') | ||||
|  | ||||
|             merge_result_processors_instruments(raw) | ||||
|  | ||||
|             # Get WA core configuration | ||||
|             for cfg_point in self.wa_config.configuration.itervalues(): | ||||
|                 value = get_aliased_param(cfg_point, raw) | ||||
|                 if value is not None: | ||||
|                     self.wa_config.set(cfg_point.name, value) | ||||
|  | ||||
|             # Get run specific configuration | ||||
|             for cfg_point in self.run_config.configuration.itervalues(): | ||||
|                 value = get_aliased_param(cfg_point, raw) | ||||
|                 if value is not None: | ||||
|                     self.run_config.set(cfg_point.name, value) | ||||
|  | ||||
|             # Get global job spec configuration | ||||
|             for cfg_point in JobSpec.configuration.itervalues(): | ||||
|                 value = get_aliased_param(cfg_point, raw) | ||||
|                 if value is not None: | ||||
|                     self.jobs_config.set_global_value(cfg_point.name, value) | ||||
|  | ||||
|             for name, values in raw.iteritems(): | ||||
|                 # Assume that all leftover config is for a plug-in or a global | ||||
|                 # alias it is up to PluginCache to assert this assumption | ||||
|                 self.plugin_cache.add_configs(name, values, source) | ||||
|  | ||||
|         except ConfigError as e: | ||||
|             if wrap_exceptions: | ||||
|                 raise ConfigError('Error in "{}":\n{}'.format(source, str(e))) | ||||
|             else: | ||||
|                 raise e | ||||
|  | ||||
|  | ||||
| class AgendaParser(object): | ||||
|  | ||||
|     def __init__(self, wa_config, run_config, jobs_config, plugin_cache): | ||||
|         self.wa_config = wa_config | ||||
|         self.run_config = run_config | ||||
|         self.jobs_config = jobs_config | ||||
|         self.plugin_cache = plugin_cache | ||||
|  | ||||
|     def load_from_path(self, filepath): | ||||
|         raw = _load_file(filepath, 'Agenda') | ||||
|         self.load(raw, filepath) | ||||
|  | ||||
|     def load(self, raw, source):  # pylint: disable=too-many-branches, too-many-locals | ||||
|         try: | ||||
|             if not isinstance(raw, dict): | ||||
|                 raise ConfigError('Invalid agenda, top level entry must be a dict') | ||||
|  | ||||
|             # PHASE 1: Populate and validate configuration. | ||||
|             for name in ['config', 'global']: | ||||
|                 entry = raw.pop(name, {}) | ||||
|                 if not isinstance(entry, dict): | ||||
|                     raise ConfigError('Invalid entry "{}" - must be a dict'.format(name)) | ||||
|                 if 'run_name' in entry: | ||||
|                     self.run_config.set('run_name', entry.pop('run_name')) | ||||
|                 config_parser = ConfigParser(self.wa_config, self.run_config, | ||||
|                                              self.jobs_config, self.plugin_cache) | ||||
|                 config_parser.load(entry, source, wrap_exceptions=False) | ||||
|  | ||||
|             # PHASE 2: Getting "section" and "workload" entries. | ||||
|             sections = raw.pop("sections", []) | ||||
|             if not isinstance(sections, list): | ||||
|                 raise ConfigError('Invalid entry "sections" - must be a list') | ||||
|             global_workloads = raw.pop("workloads", []) | ||||
|             if not isinstance(global_workloads, list): | ||||
|                 raise ConfigError('Invalid entry "workloads" - must be a list') | ||||
|             if raw: | ||||
|                 msg = 'Invalid top level agenda entry(ies): "{}"' | ||||
|                 raise ConfigError(msg.format('", "'.join(raw.keys()))) | ||||
|  | ||||
|             # PHASE 3: Collecting existing workload and section IDs | ||||
|             seen_section_ids = set() | ||||
|             seen_workload_ids = set() | ||||
|  | ||||
|             for workload in global_workloads: | ||||
|                 workload = _get_workload_entry(workload) | ||||
|                 _collect_valid_id(workload.get("id"), seen_workload_ids, "workload") | ||||
|  | ||||
|             for section in sections: | ||||
|                 _collect_valid_id(section.get("id"), seen_section_ids, "section") | ||||
|                 for workload in section["workloads"] if "workloads" in section else []: | ||||
|                     workload = _get_workload_entry(workload) | ||||
|                     _collect_valid_id(workload.get("id"), seen_workload_ids, "workload") | ||||
|  | ||||
|             # PHASE 4: Assigning IDs and validating entries | ||||
|             # TODO: Error handling for workload errors vs section errors ect | ||||
|             for workload in global_workloads: | ||||
|                 self.jobs_config.add_workload(_process_workload_entry(workload, | ||||
|                                                                       seen_workload_ids, | ||||
|                                                                       self.jobs_config)) | ||||
|  | ||||
|             for section in sections: | ||||
|                 workloads = [] | ||||
|                 for workload in section.pop("workloads", []): | ||||
|                     workloads.append(_process_workload_entry(workload, | ||||
|                                                              seen_workload_ids, | ||||
|                                                              self.jobs_config)) | ||||
|  | ||||
|                 _resolve_params_alias(section, seen_section_ids) | ||||
|                 section = _construct_valid_entry(section, seen_section_ids, "s", self.jobs_config) | ||||
|                 self.jobs_config.add_section(section, workloads) | ||||
|  | ||||
|             return seen_workload_ids, seen_section_ids | ||||
|         except (ConfigError, SerializerSyntaxError) as e: | ||||
|             raise ConfigError('Error in "{}":\n\t{}'.format(source, str(e))) | ||||
|  | ||||
|  | ||||
| # Command line options are parsed in the "run" command. This is used to send | ||||
| # certain arguments to the correct configuration points and keep a record of | ||||
| # how WA was invoked | ||||
| class CommandLineArgsParser(object): | ||||
|  | ||||
|     def __init__(self, cmd_args, wa_config, jobs_config): | ||||
|         wa_config.set("verbosity", cmd_args.verbosity) | ||||
|         # TODO: Is this correct? Does there need to be a third output dir param | ||||
|         disabled_instruments = toggle_set(["~{}".format(i) for i in cmd_args.instruments_to_disable]) | ||||
|         jobs_config.disable_instruments(disabled_instruments) | ||||
|         jobs_config.only_run_ids(cmd_args.only_run_ids) | ||||
|   | ||||
| @@ -21,16 +21,15 @@ import os | ||||
| import subprocess | ||||
| import warnings | ||||
|  | ||||
| from wlauto.core.configuration import settings | ||||
| from wlauto.core import pluginloader | ||||
| from wlauto.core.command import init_argument_parser | ||||
| from wlauto.core.configuration import settings | ||||
| from wlauto.core.host import init_user_directory | ||||
| from wlauto.exceptions import WAError, ConfigError | ||||
| from wlauto.utils.misc import get_traceback | ||||
| from wlauto.utils.log import init_logging | ||||
| from wlauto.core.state import WAState | ||||
| from wlauto.exceptions import WAError, DevlibError, ConfigError | ||||
| from wlauto.utils.doc import format_body | ||||
|  | ||||
| from devlib import DevlibError | ||||
| from wlauto.utils.log import init_logging | ||||
| from wlauto.utils.misc import get_traceback | ||||
|  | ||||
| warnings.filterwarnings(action='ignore', category=UserWarning, module='zope') | ||||
|  | ||||
| @@ -41,11 +40,14 @@ logger = logging.getLogger('command_line') | ||||
| def load_commands(subparsers): | ||||
|     commands = {} | ||||
|     for command in pluginloader.list_commands(): | ||||
|         commands[command.name] = pluginloader.get_command(command.name, subparsers=subparsers) | ||||
|         commands[command.name] = pluginloader.get_command(command.name,  | ||||
|                                                           subparsers=subparsers) | ||||
|     return commands | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     state = WAState() | ||||
|  | ||||
|     if not os.path.exists(settings.user_directory): | ||||
|         init_user_directory() | ||||
|  | ||||
| @@ -59,19 +61,22 @@ def main(): | ||||
|                                          formatter_class=argparse.RawDescriptionHelpFormatter, | ||||
|                                          ) | ||||
|         init_argument_parser(parser) | ||||
|         commands = load_commands(parser.add_subparsers(dest='command'))  # each command will add its own subparser | ||||
|         # each command will add its own subparser | ||||
|         commands = load_commands(parser.add_subparsers(dest='command'))   | ||||
|  | ||||
|         args = parser.parse_args() | ||||
|  | ||||
|         settings.set("verbosity", args.verbose) | ||||
|  | ||||
|         for config in args.config: | ||||
|             if not os.path.exists(config): | ||||
|                 raise ConfigError("Config file {} not found".format(config)) | ||||
|         for config_file in args.config: | ||||
|             if not os.path.exists(config_file): | ||||
|                 raise ConfigError("Config file {} not found".format(config_file)) | ||||
|             state.load_config_file(config_file) | ||||
|  | ||||
|         init_logging(settings.verbosity) | ||||
|  | ||||
|         command = commands[args.command] | ||||
|         sys.exit(command.execute(args)) | ||||
|         sys.exit(command.execute(state, args)) | ||||
|  | ||||
|     except KeyboardInterrupt: | ||||
|         logging.info('Got CTRL-C. Aborting.') | ||||
|   | ||||
| @@ -36,29 +36,31 @@ following actors: | ||||
|             allow instrumentation to do its stuff. | ||||
|  | ||||
| """ | ||||
| import os | ||||
| import uuid | ||||
| import logging | ||||
| import subprocess | ||||
| import os | ||||
| import random | ||||
| import subprocess | ||||
| import uuid | ||||
| from collections import Counter, defaultdict, OrderedDict | ||||
| from contextlib import contextmanager | ||||
| from copy import copy | ||||
| from datetime import datetime | ||||
| from contextlib import contextmanager | ||||
| from collections import Counter, defaultdict, OrderedDict | ||||
| from itertools import izip_longest | ||||
|  | ||||
| import wlauto.core.signal as signal | ||||
| from wlauto.core import instrumentation | ||||
| from wlauto.core import pluginloader | ||||
| from wlauto.core.configuration import settings | ||||
| from wlauto.core.plugin import Artifact | ||||
| from wlauto.core import pluginloader | ||||
| from wlauto.core.resolver import ResourceResolver | ||||
| from wlauto.core.result import ResultManager, IterationResult, RunResult | ||||
| from wlauto.exceptions import (WAError, ConfigError, TimeoutError, InstrumentError, | ||||
|                                DeviceError, DeviceNotRespondingError) | ||||
| from wlauto.utils.misc import ensure_directory_exists as _d, get_traceback, format_duration | ||||
| from wlauto.utils.misc import (ensure_directory_exists as _d,  | ||||
|                                get_traceback, format_duration) | ||||
| from wlauto.utils.serializer import json | ||||
|  | ||||
|  | ||||
| # The maximum number of reboot attempts for an iteration. | ||||
| MAX_REBOOT_ATTEMPTS = 3 | ||||
|  | ||||
| @@ -95,6 +97,7 @@ class RunInfo(object): | ||||
|         return d | ||||
|     #TODO: pod | ||||
|  | ||||
|  | ||||
| class ExecutionContext(object): | ||||
|     """ | ||||
|     Provides a context for instrumentation. Keeps track of things like | ||||
| @@ -239,31 +242,32 @@ def _check_artifact_path(path, rootpath): | ||||
|  | ||||
| class Executor(object): | ||||
|     """ | ||||
|     The ``Executor``'s job is to set up the execution context and pass to a ``Runner`` | ||||
|     along with a loaded run specification. Once the ``Runner`` has done its thing, | ||||
|     the ``Executor`` performs some final reporint before returning. | ||||
|     The ``Executor``'s job is to set up the execution context and pass to a | ||||
|     ``Runner`` along with a loaded run specification. Once the ``Runner`` has | ||||
|     done its thing, the ``Executor`` performs some final reporint before | ||||
|     returning. | ||||
|  | ||||
|     The initial context set up involves combining configuration from various sources, | ||||
|     loading of requided workloads, loading and installation of instruments and result | ||||
|     processors, etc. Static validation of the combined configuration is also performed. | ||||
|     The initial context set up involves combining configuration from various | ||||
|     sources, loading of requided workloads, loading and installation of | ||||
|     instruments and result processors, etc. Static validation of the combined | ||||
|     configuration is also performed. | ||||
|  | ||||
|     """ | ||||
|     # pylint: disable=R0915 | ||||
|  | ||||
|     def __init__(self, config): | ||||
|     def __init__(self): | ||||
|         self.logger = logging.getLogger('Executor') | ||||
|         self.error_logged = False | ||||
|         self.warning_logged = False | ||||
|         self.config = config | ||||
|         pluginloader = None | ||||
|         self.device_manager = None | ||||
|         self.device = None | ||||
|         self.context = None | ||||
|  | ||||
|     def execute(self, agenda, selectors=None):  # NOQA | ||||
|     def execute(self, state, selectors=None):  # NOQA | ||||
|         """ | ||||
|         Execute the run specified by an agenda. Optionally, selectors may be used to only | ||||
|         selecute a subset of the specified agenda. | ||||
|         Execute the run specified by an agenda. Optionally, selectors may be | ||||
|         used to only selecute a subset of the specified agenda. | ||||
|  | ||||
|         Params:: | ||||
|  | ||||
| @@ -275,9 +279,10 @@ class Executor(object): | ||||
|         Currently, the following seectors are supported: | ||||
|  | ||||
|         ids | ||||
|             The value must be a sequence of workload specfication IDs to be executed. Note | ||||
|             that if sections are specified inthe agenda, the workload specifacation ID will | ||||
|             be a combination of the section and workload IDs. | ||||
|             The value must be a sequence of workload specfication IDs to be | ||||
|             executed. Note that if sections are specified inthe agenda, the | ||||
|             workload specifacation ID will be a combination of the section and | ||||
|             workload IDs. | ||||
|  | ||||
|         """ | ||||
|         signal.connect(self._error_signalled_callback, signal.ERROR_LOGGED) | ||||
|   | ||||
							
								
								
									
										28
									
								
								wlauto/core/state.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								wlauto/core/state.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| from wlauto.core.configuration.configuration import (RunConfiguration, | ||||
|                                                      JobGenerator, settings) | ||||
| from wlauto.core.configuration.parsers import ConfigParser | ||||
| from wlauto.core.configuration.plugin_cache import PluginCache | ||||
|  | ||||
|  | ||||
| class WAState(object): | ||||
|     """ | ||||
|     Represents run-time state of WA. Mostly used as a container for loaded  | ||||
|     configuration and discovered plugins. | ||||
|  | ||||
|     This exists outside of any command or run and is associated with the running  | ||||
|     instance of wA itself. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, settings=settings): | ||||
|         self.settings = settings | ||||
|         self.run_config = RunConfiguration() | ||||
|         self.plugin_cache = PluginCache() | ||||
|         self.jobs_config = JobGenerator(self.plugin_cache) | ||||
|  | ||||
|         self._config_parser = ConfigParser() | ||||
|  | ||||
|     def load_config_file(self, filepath): | ||||
|         self._config_parser.load_from_path(self, filepath) | ||||
|  | ||||
|     def load_config(self, values, source, wrap_exceptions=True): | ||||
|         self._config_parser.load(self, values, source) | ||||
		Reference in New Issue
	
	Block a user