1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-02-22 12:58:36 +00:00

Adding intialize and finalize methods to workloads that will only be invoked once per run

- added initialze and finalize methods to workloads, which were the only
  major extension types that did not have them
- Semanatics for initialize/finalize for *all* Extensions are changed so
  that now they will always run at most once per run. They will not be
  executed twice even if invoke via istances of different subclasses (if
  those subclasses defined their own verions, then their versions will
  be invoked once each, but the base version will only get invoked
  once).
This commit is contained in:
Sergei Trofimov 2015-06-11 17:39:17 +01:00
parent 557b792c77
commit b3a0933221
7 changed files with 153 additions and 21 deletions

View File

@ -527,6 +527,10 @@ class Runner(object):
self.logger.info('Initializing device') self.logger.info('Initializing device')
self.device.initialize(self.context) self.device.initialize(self.context)
self.logger.info('Initializing workloads')
for workload_spec in self.context.config.workload_specs:
workload_spec.workload.initialize(self.context)
props = self.device.get_properties(self.context) props = self.device.get_properties(self.context)
self.context.run_info.device_properties = props self.context.run_info.device_properties = props
self.result_manager.initialize(self.context) self.result_manager.initialize(self.context)

View File

@ -392,7 +392,8 @@ class ExtensionMeta(type):
('core_modules', str, ListCollection), ('core_modules', str, ListCollection),
] ]
virtual_methods = ['validate'] virtual_methods = ['validate', 'initialize', 'finalize']
global_virtuals = ['initialize', 'finalize']
def __new__(mcs, clsname, bases, attrs): def __new__(mcs, clsname, bases, attrs):
mcs._propagate_attributes(bases, attrs) mcs._propagate_attributes(bases, attrs)
@ -441,13 +442,13 @@ class ExtensionMeta(type):
super(cls, self).vmname() super(cls, self).vmname()
.. note:: current implementation imposes a restriction in that This also ensures that the methods that have beend identified as
parameters into the function *must* be passed as keyword "globally virtual" are executed exactly once per WA execution, even if
arguments. There *must not* be positional arguments on invoked through instances of different subclasses
virutal method invocation.
""" """
methods = {} methods = {}
called_globals = set()
for vmname in mcs.virtual_methods: for vmname in mcs.virtual_methods:
clsmethod = getattr(cls, vmname, None) clsmethod = getattr(cls, vmname, None)
if clsmethod: if clsmethod:
@ -455,11 +456,24 @@ class ExtensionMeta(type):
methods[vmname] = [bm for bm in basemethods if bm != clsmethod] methods[vmname] = [bm for bm in basemethods if bm != clsmethod]
methods[vmname].append(clsmethod) methods[vmname].append(clsmethod)
def wrapper(self, __name=vmname, **kwargs): def generate_method_wrapper(vname): # pylint: disable=unused-argument
for dm in methods[__name]: # this creates a closure with the method name so that it
dm(self, **kwargs) # does not need to be passed to the wrapper as an argument,
# leaving the wrapper to accept exactly the same set of
# arguments as the method it is wrapping.
name__ = vmname # pylint: disable=cell-var-from-loop
setattr(cls, vmname, wrapper) def wrapper(self, *args, **kwargs):
for dm in methods[name__]:
if name__ in mcs.global_virtuals:
if dm not in called_globals:
dm(self, *args, **kwargs)
called_globals.add(dm)
else:
dm(self, *args, **kwargs)
return wrapper
setattr(cls, vmname, generate_method_wrapper(vmname))
class Extension(object): class Extension(object):
@ -539,6 +553,12 @@ class Extension(object):
for param in self.parameters: for param in self.parameters:
param.validate(self) param.validate(self)
def initialize(self, *args, **kwargs):
pass
def finalize(self, *args, **kwargs):
pass
def check_artifacts(self, context, level): def check_artifacts(self, context, level):
""" """
Make sure that all mandatory artifacts have been generated. Make sure that all mandatory artifacts have been generated.

View File

@ -288,9 +288,15 @@ def install(instrument):
attr = getattr(instrument, attr_name) attr = getattr(instrument, attr_name)
if not callable(attr): if not callable(attr):
raise ValueError('Attribute {} not callable in {}.'.format(attr_name, instrument)) raise ValueError('Attribute {} not callable in {}.'.format(attr_name, instrument))
arg_num = len(inspect.getargspec(attr).args) argspec = inspect.getargspec(attr)
if not arg_num == 2: arg_num = len(argspec.args)
raise ValueError('{} must take exactly 2 arguments; {} given.'.format(attr_name, arg_num)) # Instrument callbacks will be passed exactly two arguments: self
# (the instrument instance to which the callback is bound) and
# context. However, we also allow callbacks to capture the context
# in variable arguments (declared as "*args" in the definition).
if arg_num > 2 or (arg_num < 2 and argspec.varargs is None):
message = '{} must take exactly positional arguments; {} given.'
raise ValueError(message.format(attr_name, arg_num))
logger.debug('\tConnecting %s to %s', attr.__name__, SIGNAL_MAP[stripped_attr_name]) logger.debug('\tConnecting %s to %s', attr.__name__, SIGNAL_MAP[stripped_attr_name])
mc = ManagedCallback(instrument, attr) mc = ManagedCallback(instrument, attr)
@ -379,6 +385,12 @@ class Instrument(Extension):
self.is_enabled = True self.is_enabled = True
self.is_broken = False self.is_broken = False
def initialize(self, context): # pylint: disable=arguments-differ
pass
def finalize(self, context): # pylint: disable=arguments-differ
pass
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -140,7 +140,7 @@ class ResultProcessor(Extension):
""" """
def initialize(self, context): def initialize(self, context): # pylint: disable=arguments-differ
pass pass
def process_iteration_result(self, result, context): def process_iteration_result(self, result, context):
@ -155,7 +155,7 @@ class ResultProcessor(Extension):
def export_run_result(self, result, context): def export_run_result(self, result, context):
pass pass
def finalize(self, context): def finalize(self, context): # pylint: disable=arguments-differ
pass pass

View File

@ -53,18 +53,25 @@ class Workload(Extension):
def init_resources(self, context): def init_resources(self, context):
""" """
May be optionally overridden by concrete instances in order to discover and initialise This method may be used to perform early resource discovery and initialization. This is invoked
necessary resources. This method will be invoked at most once during the execution: during the initial loading stage and before the device is ready, so cannot be used for any
before running any workloads, and before invocation of ``validate()``, but after it is device-dependent initialization. This method is invoked before the workload instance is
clear that this workload will run (i.e. this method will not be invoked for workloads validated.
that have been discovered but have not been scheduled run in the agenda).
"""
pass
def initialize(self, context): # pylint: disable=arguments-differ
"""
This method should be used to perform once-per-run initialization of a workload instance, i.e.,
unlike ``setup()`` it will not be invoked on each iteration.
""" """
pass pass
def setup(self, context): def setup(self, context):
""" """
Perform the setup necessary to run the workload, such as copying the necessry files Perform the setup necessary to run the workload, such as copying the necessary files
to the device, configuring the environments, etc. to the device, configuring the environments, etc.
This is also the place to perform any on-device checks prior to attempting to execute This is also the place to perform any on-device checks prior to attempting to execute
@ -89,6 +96,9 @@ class Workload(Extension):
""" Perform any final clean up for the Workload. """ """ Perform any final clean up for the Workload. """
pass pass
def finalize(self, context): # pylint: disable=arguments-differ
pass
def __str__(self): def __str__(self):
return '<Workload {}>'.format(self.name) return '<Workload {}>'.format(self.name)

View File

@ -27,7 +27,7 @@ from wlauto.core.execution import BySpecRunner, ByIterationRunner
from wlauto.exceptions import DeviceError from wlauto.exceptions import DeviceError
from wlauto.core.configuration import WorkloadRunSpec, RebootPolicy from wlauto.core.configuration import WorkloadRunSpec, RebootPolicy
from wlauto.core.instrumentation import Instrument from wlauto.core.instrumentation import Instrument
from wlauto.core.device import Device from wlauto.core.device import Device, DeviceMeta
from wlauto.core import instrumentation, signal from wlauto.core import instrumentation, signal
from wlauto.core.workload import Workload from wlauto.core.workload import Workload
from wlauto.core.result import IterationResult from wlauto.core.result import IterationResult
@ -61,8 +61,37 @@ class Mock(object):
pass pass
class BadDeviceMeta(DeviceMeta):
@classmethod
def _implement_virtual(mcs, cls, bases):
"""
This version of _implement_virtual does not inforce "call global virutals only once"
policy, so that intialize() and finalize() my be invoked multiple times to test that
the errors they generated are handled correctly.
"""
# pylint: disable=cell-var-from-loop,unused-argument
methods = {}
for vmname in mcs.virtual_methods:
clsmethod = getattr(cls, vmname, None)
if clsmethod:
basemethods = [getattr(b, vmname) for b in bases if hasattr(b, vmname)]
methods[vmname] = [bm for bm in basemethods if bm != clsmethod]
methods[vmname].append(clsmethod)
def generate_method_wrapper(vname):
name__ = vmname
def wrapper(self, *args, **kwargs):
for dm in methods[name__]:
dm(self, *args, **kwargs)
return wrapper
setattr(cls, vmname, generate_method_wrapper(vmname))
class BadDevice(Device): class BadDevice(Device):
__metaclass__ = BadDeviceMeta
def __init__(self, when_to_fail, exception=DeviceError): def __init__(self, when_to_fail, exception=DeviceError):
#pylint: disable=super-init-not-called #pylint: disable=super-init-not-called
self.when_to_fail = when_to_fail self.when_to_fail = when_to_fail

View File

@ -220,6 +220,63 @@ class ExtensionMetaTest(TestCase):
assert_equal(acid.v2, 2) assert_equal(acid.v2, 2)
assert_equal(acid.vv2, 2) assert_equal(acid.vv2, 2)
def test_initialization(self):
class MyExt(Extension):
name = 'myext'
values = {'a': 0}
def __init__(self, *args, **kwargs):
super(MyExt, self).__init__(*args, **kwargs)
self.instance_init = 0
def initialize(self):
self.values['a'] += 1
class MyChildExt(MyExt):
name = 'mychildext'
def initialize(self):
self.instance_init += 1
ext = _instantiate(MyChildExt)
ext.initialize()
assert_equal(MyExt.values['a'], 1)
assert_equal(ext.instance_init, 1)
def test_initialization_happens_once(self):
class MyExt(Extension):
name = 'myext'
values = {'a': 0}
def __init__(self, *args, **kwargs):
super(MyExt, self).__init__(*args, **kwargs)
self.instance_init = 0
self.instance_validate = 0
def initialize(self):
self.values['a'] += 1
def validate(self):
self.instance_validate += 1
class MyChildExt(MyExt):
name = 'mychildext'
def initialize(self):
self.instance_init += 1
def validate(self):
self.instance_validate += 1
ext1 = _instantiate(MyExt)
ext2 = _instantiate(MyExt)
ext3 = _instantiate(MyChildExt)
ext1.initialize()
ext2.initialize()
ext3.initialize()
ext1.validate()
ext2.validate()
ext3.validate()
assert_equal(MyExt.values['a'], 1)
assert_equal(ext1.instance_init, 0)
assert_equal(ext3.instance_init, 1)
assert_equal(ext1.instance_validate, 1)
assert_equal(ext3.instance_validate, 2)
class ParametersTest(TestCase): class ParametersTest(TestCase):