From c02c6cbceb332ca306b3789797f3924760f16ea8 Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Thu, 18 Aug 2016 14:03:17 +0100 Subject: [PATCH] Runtime Parameters: WIP - replace me --- wlauto/core/configuration/configuration.py | 181 +++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/wlauto/core/configuration/configuration.py b/wlauto/core/configuration/configuration.py index d71a95b0..1dd3f8b4 100644 --- a/wlauto/core/configuration/configuration.py +++ b/wlauto/core/configuration/configuration.py @@ -13,8 +13,10 @@ # limitations under the License. import os +import re from copy import copy from collections import OrderedDict, defaultdict +from devlib import TargetError from wlauto.exceptions import ConfigError from wlauto.utils.misc import (get_article, merge_config_values) @@ -303,10 +305,170 @@ class ConfigurationPoint(object): __str__ = __repr__ +class RuntimeParameter(object): + + def __init__(self, name, + kind=None, + description=None, + merge=False): + + self.name = re.compile(name) + if kind is not None: + if not callable(kind): + raise ValueError('Kind must be callable.') + if kind in KIND_MAP: + kind = KIND_MAP[kind] + else: + kind = str + self.kind = kind + self.description = description + self.merge = merge + + def validate_kind(self, value, name): + try: + value = self.kind(value) + except (ValueError, TypeError): + typename = get_type_name(self.kind) + msg = 'Bad value "{}" for {}; must be {} {}' + article = get_article(typename) + raise ConfigError(msg.format(value, name, article, typename)) + + def match(self, name): + if self.name.match(name): + return True + return False + + def update_value(self, name, new_value, source, dest): + self.validate_kind(new_value, name) + + if name in dest: + old_value, sources = dest[name] + else: + old_value = None + sources = {} + sources[source] = new_value + + if self.merge: + new_value = merge_config_values(old_value, new_value) + + dest[name] = (new_value, sources) + + +class RuntimeParameterManager(object): + + runtime_parameters = [] + + def __init__(self, target_manager): + self.state = {} + self.target_manager = target_manager + + def get_initial_state(self): + """ + Should be used to load the starting state from the device. This state + should be updated if any changes are made to the device, and they are successful. + """ + pass + + def match(self, name): + for rtp in self.runtime_parameters: + if rtp.match(name): + return True + return False + + def update_value(self, name, value, source, dest): + for rtp in self.runtime_parameters: + if rtp.match(name): + rtp.update_value(name, value, source, dest) + break + else: + msg = 'Unknown runtime parameter "{}"' + raise ConfigError(msg.format(name)) + + def static_validation(self, params): + """ + Validate values that do not require a active device connection. + This method should also pop all runtime parameters meant for this manager + from params, even if they are not beign statically validated. + """ + pass + + def dynamic_validation(self, params): + """ + Validate values that require an active device connection + """ + pass + + def commit(self): + """ + All values have been validated, this will now actually set values + """ + pass + +################################ +### RuntimeParameterManagers ### +################################ + +class CpuFreqParameters(object): + + runtime_parameters = { + "cores": RuntimeParameter("(.+)_cores"), + "min_frequency": RuntimeParameter("(.+)_min_frequency", kind=int), + "max_frequency": RuntimeParameter("(.+)_max_frequency", kind=int), + "frequency": RuntimeParameter("(.+)_frequency", kind=int), + "governor": RuntimeParameter("(.+)_governor"), + "governor_tunables": RuntimeParameter("(.+)_governor_tunables"), + } + + def __init__(self, target): + super(CpuFreqParameters, self).__init__(target) + self.core_names = set(target.core_names) + + def match(self, name): + for param in self.runtime_parameters.itervalues(): + if param.match(name): + return True + return False + + def update_value(self, name, value, source): + for param in self.runtime_parameters.iteritems(): + core_name_match = param.name.match(name) + if not core_name_match: + continue + + core_name = core_name_match.groups()[0] + if core_name not in self.core_names: + msg = '"{}" in {} is not a valid core name, must be in: {}' + raise ConfigError(msg.format(core_name, name, ", ".join(self.core_names))) + + param.update_value(name, value, source) + break + else: + RuntimeError('"{}" does not belong to CpuFreqParameters'.format(name)) + + def _get_merged_value(self, core, param_name): + return self.runtime_parameters[param_name].merged_values["{}_{}".format(core, param_name)] + + def _cross_validate(self, core): + min_freq = self._get_merged_value(core, "min_frequency") + max_frequency = self._get_merged_value(core, "max_frequency") + if max_frequency < min_freq: + msg = "{core}_max_frequency must be larger than {core}_min_frequency" + raise ConfigError(msg.format(core=core)) + frequency = self._get_merged_value(core, "frequency") + if not min_freq < frequency < max_frequency: + msg = "{core}_frequency must be between {core}_min_frequency and {core}_max_frequency" + raise ConfigError(msg.format(core=core)) + #TODO: more checks + + def commit_to_device(self, target): + pass + # TODO: Write values to device is correct order ect + ##################### ### Configuration ### ##################### + # pylint: disable=too-many-nested-blocks, too-many-branches def merge_using_priority_specificity(generic_name, specific_name, plugin_cache): """ @@ -719,6 +881,22 @@ class JobSpec(Configuration): self.workload_parameters = workload_params + def merge_runtime_parameters(self, plugin_cache, target_manager): + + # Order global runtime parameters + runtime_parameters = OrderedDict() + global_runtime_params = plugin_cache.get_plugin_config("runtime_parameters") + for source in plugin_cache.sources: + runtime_parameters[source] = global_runtime_params[source] + + # Add runtime parameters from JobSpec + for source, values in self.to_merge['runtime_parameters'].iteritems(): + runtime_parameters[source] = values + + # Merge + self.runtime_parameters = target_manager.merge_runtime_parameters(runtime_parameters) + + def finalize(self): self.id = "-".join([source.config['id'] for source in self._sources[1:]]) # ignore first id, "global" @@ -820,8 +998,11 @@ class JobGenerator(object): # PHASE 2.2: Merge global, section and workload entry "workload_parameters" job_spec.merge_workload_parameters(self.plugin_cache) + target_manager.static_runtime_parameter_validation(job_spec.runtime_parameters) # TODO: PHASE 2.3: Validate device runtime/boot paramerers + job_spec.merge_runtime_parameters(self.plugin_cache, target_manager) + target_manager.validate_runtime_parameters(job_spec.runtime_parameters) # PHASE 2.4: Disable globally disabled instrumentation job_spec.set("instrumentation", self.disabled_instruments)