From 0ae35fb068067f8431540fa92cf1c3b45b7dae34 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 11:14:46 +0000 Subject: [PATCH 01/16] workload: Formatting --- libs/wlgen/wlgen/workload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index 6e65ca9cb..fd3ba20d6 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -49,7 +49,7 @@ class Workload(object): # NOTE: for the time being we support just a single CPU self.cpus = None - # The cgroup on which the workload will be executed + # The cgroup on which the workload will be executed # NOTE: requires cgroups to be properly configured and associated # tools deployed on the target self.cgroup = None -- GitLab From 6ee4d8e520fe57b59cb16db923945f4ed3395a62 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 11:15:03 +0000 Subject: [PATCH 02/16] workload: Remove taskset instance variables `taskset` isn't used, remove it. `taskset_cmd` is used but overridden, just make it a local variable. --- libs/wlgen/wlgen/workload.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index fd3ba20d6..0922891e1 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -55,11 +55,6 @@ class Workload(object): self.cgroup = None self.cgroup_cmd = '' - # taskset configuration to constraint workload execution on a specified - # set of CPUs - self.taskset = None - self.taskset_cmd = '' - # The command to execute a workload (defined by a derived class) self.command = None @@ -214,11 +209,11 @@ class Workload(object): # Prepend eventually required taskset command if cpus or self.cpus: cpus_mask = self.getCpusMask(cpus if cpus else self.cpus) - self.taskset_cmd = '{}/taskset 0x{:X}'\ + taskset_cmd = '{}/taskset 0x{:X}'\ .format(self.target.executables_directory, cpus_mask) _command = '{} {}'\ - .format(self.taskset_cmd, _command) + .format(taskset_cmd, _command) if self.cgroup and hasattr(self.target, 'cgroups'): # Get a reference to the CGroup to use -- GitLab From 629e77ed374c58fc1ae4b3033d9a610d75c33b72 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 11:20:39 +0000 Subject: [PATCH 03/16] workload: Remove unused cgroup_cmd instance variable --- libs/wlgen/wlgen/workload.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index 0922891e1..7a9d1688e 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -53,7 +53,6 @@ class Workload(object): # NOTE: requires cgroups to be properly configured and associated # tools deployed on the target self.cgroup = None - self.cgroup_cmd = '' # The command to execute a workload (defined by a derived class) self.command = None -- GitLab From 9f757767b2e60f9528134ac3fbfa614e2f1cd800 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 11:27:32 +0000 Subject: [PATCH 04/16] workload: Remove getTasks method This is a method of Trace now --- libs/wlgen/wlgen/workload.py | 47 ------------------------------------ 1 file changed, 47 deletions(-) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index 7a9d1688e..ec439e37d 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -270,53 +270,6 @@ class Workload(object): def getOutput(self, step='executor'): return self.output[step] - def getTasks(self, dataframe=None, task_names=None, - name_key='comm', pid_key='pid'): - # """ Helper function to get PIDs of specified tasks - # - # This method requires a Pandas dataset in input to be used to - # fiter our the PIDs of all the specified tasks. - # In a dataset is not provided, previouslt filtered PIDs are - # returned. If a list of task names is not provided, the workload - # defined task names is used instead. - # The specified dataframe must provide at least two columns - # reporting the task name and the task PID. The default values of - # this colums could be specified using the provided parameters. - # - # :param task_names: The list of tasks to get the PID of (by default - # the workload defined tasks) - # :param dataframe: A Pandas datafram containing at least 'pid' and - # 'task name' columns - # If None, the previously filtered PIDs are - # returned - # :param name_key: The name of the dataframe columns containing - # task names - # :param pid_key: The name of the dataframe columns containing - # task PIDs - # """ - if dataframe is None: - return self.tasks - if task_names is None: - task_names = self.tasks.keys() - self._log.debug('Lookup dataset for tasks...') - for task_name in task_names: - results = dataframe[dataframe[name_key] == task_name]\ - [[name_key,pid_key]] - if len(results)==0: - self._log.error(' task %16s NOT found', task_name) - continue - (name, pid) = results.head(1).values[0] - if name != task_name: - self._log.error(' task %16s NOT found', task_name) - continue - if task_name not in self.tasks: - self.tasks[task_name] = {} - pids = list(results[pid_key].unique()) - self.tasks[task_name]['pid'] = pids - self._log.info(' task %16s found, pid: %s', - task_name, self.tasks[task_name]['pid']) - return self.tasks - def listAll(self, kill=False): # Show all the instances for the current executor tasks = self.target.run('ps | grep {0:s}'.format(self.executor)) -- GitLab From c2842a8a44a463a462bc4d2389692839a9ec6b9f Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 13:32:26 +0000 Subject: [PATCH 05/16] workload: Remove rt-app specific `calibration` --- libs/wlgen/wlgen/perf_bench.py | 2 +- libs/wlgen/wlgen/rta.py | 2 +- libs/wlgen/wlgen/workload.py | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libs/wlgen/wlgen/perf_bench.py b/libs/wlgen/wlgen/perf_bench.py index df87bfa67..cca303c4e 100644 --- a/libs/wlgen/wlgen/perf_bench.py +++ b/libs/wlgen/wlgen/perf_bench.py @@ -35,7 +35,7 @@ class PerfMessaging(Workload): # TODO: Assume perf is pre-installed on target #target.setup('perf') - super(PerfMessaging, self).__init__(target, name, None) + super(PerfMessaging, self).__init__(target, name) # perf "sched" executor self.wtype = 'perf_bench_messaging' diff --git a/libs/wlgen/wlgen/rta.py b/libs/wlgen/wlgen/rta.py index ceaa8b6fc..0f70874ba 100644 --- a/libs/wlgen/wlgen/rta.py +++ b/libs/wlgen/wlgen/rta.py @@ -58,7 +58,7 @@ class RTA(Workload): # TODO: Assume rt-app is pre-installed on target # self.target.setup('rt-app') - super(RTA, self).__init__(target, name, calibration) + super(RTA, self).__init__(target, name) # rt-app executor self.wtype = 'rtapp' diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index ec439e37d..0ae3d0b04 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -27,8 +27,7 @@ class Workload(object): def __init__(self, target, - name, - calibration=None): + name): # Target device confguration self.target = target @@ -42,9 +41,6 @@ class Workload(object): # The dictionary of tasks descriptors generated by this workload self.tasks = {} - # CPU load calibration values, measured on each core - self.calibration = calibration - # The cpus on which the workload will be executed # NOTE: for the time being we support just a single CPU self.cpus = None -- GitLab From c9af5a7b9e64d54c3f9e344a9c077a403518da92 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 13 Jan 2017 16:18:07 +0000 Subject: [PATCH 06/16] workload: Error if cgroup specified but no cgroups module Instead of failing silently --- libs/wlgen/wlgen/workload.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index 0ae3d0b04..e960a17ce 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -210,9 +210,13 @@ class Workload(object): _command = '{} {}'\ .format(taskset_cmd, _command) - if self.cgroup and hasattr(self.target, 'cgroups'): - # Get a reference to the CGroup to use - _command = self.target.cgroups.run_into_cmd(self.cgroup, _command) + if self.cgroup: + if hasattr(self.target, 'cgroups'): + _command = self.target.cgroups.run_into_cmd(self.cgroup, + _command) + else: + raise ValueError('To run workload in a cgroup, add "cgroups" ' + 'devlib module to target/test configuration') # Start FTrace (if required) if ftrace: -- GitLab From 156fe82c4c19c156d286b27a4fbe32f918bcd08a Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 13 Jan 2017 16:19:23 +0000 Subject: [PATCH 07/16] workload: Remove outdated comment Multiple values for `cpu` works fine. --- libs/wlgen/wlgen/workload.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index e960a17ce..8383ecc08 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -42,7 +42,6 @@ class Workload(object): self.tasks = {} # The cpus on which the workload will be executed - # NOTE: for the time being we support just a single CPU self.cpus = None # The cgroup on which the workload will be executed -- GitLab From 0758a503aec9bf31f43aca6c7adcdef73848800d Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 31 Jan 2017 19:24:43 +0000 Subject: [PATCH 08/16] workload: Create run_dir in conf method --- libs/wlgen/wlgen/workload.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index 8383ecc08..7ea4128e7 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -126,6 +126,7 @@ class Workload(object): # Initialize run folder if self.run_dir is None: self.run_dir = self.target.working_directory + self.target.execute('mkdir -p {}'.format(self.run_dir)) # Configure a profile workload if kind == 'profile': -- GitLab From d822d25ebe3fd9bfe161a80bc8d1461e85737583 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 16:00:13 +0000 Subject: [PATCH 09/16] perf_bench: Increase default loop count for PerfMessaging 10 loops is very fast which makes it likely that the calculated completion time is rounded down to 0.0, resulting in a division by zero. --- libs/wlgen/wlgen/perf_bench.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wlgen/wlgen/perf_bench.py b/libs/wlgen/wlgen/perf_bench.py index cca303c4e..a6d9ffc35 100644 --- a/libs/wlgen/wlgen/perf_bench.py +++ b/libs/wlgen/wlgen/perf_bench.py @@ -46,7 +46,7 @@ class PerfMessaging(Workload): def conf(self, group = 1, - loop = 10, + loop = 500, pipe = '', thread = '', cpus=None, -- GitLab From 45af331575fcd933d342af6dc02357160c997c4c Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 16:01:14 +0000 Subject: [PATCH 10/16] perf_bench: Set up logging for PerfPipe --- libs/wlgen/wlgen/perf_bench.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/wlgen/wlgen/perf_bench.py b/libs/wlgen/wlgen/perf_bench.py index a6d9ffc35..bb88f581e 100644 --- a/libs/wlgen/wlgen/perf_bench.py +++ b/libs/wlgen/wlgen/perf_bench.py @@ -120,7 +120,10 @@ class PerfPipe(Workload): # TODO: Assume perf is pre-installed on target #target.setup('perf') - super(PerfPipe, self).__init__(target, name, None) + # Setup logging + self.logger = logging.getLogger('perf_bench') + + super(PerfPipe, self).__init__(target, name) # perf "sched" executor self.wtype = 'perf_bench_pipe' -- GitLab From 6a5df67db8b4ef96f55b683576f5ada006a4b2e6 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 3 Feb 2017 14:35:52 +0000 Subject: [PATCH 11/16] perf_bench: Pass run_dir to superclass in conf --- libs/wlgen/wlgen/perf_bench.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libs/wlgen/wlgen/perf_bench.py b/libs/wlgen/wlgen/perf_bench.py index bb88f581e..e0cdcce78 100644 --- a/libs/wlgen/wlgen/perf_bench.py +++ b/libs/wlgen/wlgen/perf_bench.py @@ -51,17 +51,21 @@ class PerfMessaging(Workload): thread = '', cpus=None, cgroup=None, - exc_id=0): + exc_id=0, + run_dir=None): if pipe is not '': pipe = '--pipe' if thread is not '': thread = '--thread' - super(PerfMessaging, self).conf('custom', - {'group': str(group), 'loop': str(loop), 'pipe': pipe, 'thread': thread}, - 0, cpus, cgroup, exc_id) - + super(PerfMessaging, self).conf( + 'custom', + params={'group': str(group), + 'loop': str(loop), + 'pipe': pipe, + 'thread': thread}, + duration=0, cpus=cpus, exc_id=exc_id, run_dir=run_dir) self.command = '{0:s}/perf bench sched messaging {1:s} {2:s} --group {3:s} --loop {4:s}'\ .format(self.target.executables_directory, -- GitLab From ec2602210df17844e4a57301602983ecca5f7201 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 15:22:13 +0000 Subject: [PATCH 12/16] rta: Rename _TaskBase -> RTATask This is just to make the class public so we can document it with sphinx --- libs/wlgen/wlgen/rta.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/wlgen/wlgen/rta.py b/libs/wlgen/wlgen/rta.py index 0f70874ba..a30839f3a 100644 --- a/libs/wlgen/wlgen/rta.py +++ b/libs/wlgen/wlgen/rta.py @@ -536,7 +536,7 @@ class RTA(Workload): self.test_label = '{0:s}_{1:02d}'.format(self.name, self.exc_id) return self.test_label -class _TaskBase(object): +class RTATask(object): def __init__(self): self._task = {} @@ -549,7 +549,7 @@ class _TaskBase(object): return self -class Ramp(_TaskBase): +class Ramp(RTATask): def __init__(self, start_pct=0, end_pct=100, delta_pct=10, time_s=1, period_ms=100, delay_s=0, loops=1, sched=None, cpus=None): @@ -641,7 +641,7 @@ class Step(Ramp): super(Step, self).__init__(start_pct, end_pct, delta_pct, time_s, period_ms, delay_s, loops, sched, cpus) -class Pulse(_TaskBase): +class Pulse(RTATask): def __init__(self, start_pct=100, end_pct=0, time_s=1, period_ms=100, delay_s=0, loops=1, sched=None, cpus=None): -- GitLab From 78f26862ea5facaf2b4784ff708b8ab062746a42 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 3 Feb 2017 12:12:05 +0000 Subject: [PATCH 13/16] rta: Don't put single quotes around glob patterns This only works by accident over ADB and SSH. On a LocalConnection it results in trying to look for files containing quote characters. --- libs/wlgen/wlgen/rta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/wlgen/wlgen/rta.py b/libs/wlgen/wlgen/rta.py index a30839f3a..d4d81e8cd 100644 --- a/libs/wlgen/wlgen/rta.py +++ b/libs/wlgen/wlgen/rta.py @@ -144,7 +144,7 @@ class RTA(Workload): return self._log.debug('Pulling logfiles to [%s]...', destdir) for task in self.tasks.keys(): - logfile = "'{0:s}/*{1:s}*.log'"\ + logfile = "{0:s}/*{1:s}*.log"\ .format(self.run_dir, task) self.target.pull(logfile, destdir) self._log.debug('Pulling JSON to [%s]...', destdir) -- GitLab From 7c57a58ca9e6dd898e597df37a3eb9a9080f0e9b Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Wed, 8 Feb 2017 11:47:00 +0000 Subject: [PATCH 14/16] rta: Use self.target.path instead of explicit '/'s --- libs/wlgen/wlgen/rta.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/wlgen/wlgen/rta.py b/libs/wlgen/wlgen/rta.py index d4d81e8cd..60c116b44 100644 --- a/libs/wlgen/wlgen/rta.py +++ b/libs/wlgen/wlgen/rta.py @@ -144,12 +144,13 @@ class RTA(Workload): return self._log.debug('Pulling logfiles to [%s]...', destdir) for task in self.tasks.keys(): - logfile = "{0:s}/*{1:s}*.log"\ - .format(self.run_dir, task) + logfile = self.target.path.join(self.run_dir, + '*{}*.log'.format(task)) self.target.pull(logfile, destdir) self._log.debug('Pulling JSON to [%s]...', destdir) - self.target.pull('{}/{}'.format(self.run_dir, self.json), destdir) - logfile = '{}/output.log'.format(destdir) + self.target.pull(self.target.path.join(self.run_dir, self.json), + destdir) + logfile = self.target.path.join(destdir, 'output.log') self._log.debug('Saving output on [%s]...', logfile) with open(logfile, 'w') as ofile: for line in self.output['executor'].split('\n'): -- GitLab From 351f346d28a5107aa570d19a144fd72c5a86cd4b Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Tue, 3 Jan 2017 12:08:14 +0000 Subject: [PATCH 15/16] wlgen: Add some Sphinx-compatible docstrings --- libs/wlgen/wlgen/rta.py | 265 +++++++++++++++++++---------------- libs/wlgen/wlgen/workload.py | 40 +++++- 2 files changed, 179 insertions(+), 126 deletions(-) diff --git a/libs/wlgen/wlgen/rta.py b/libs/wlgen/wlgen/rta.py index 60c116b44..4531bbc76 100644 --- a/libs/wlgen/wlgen/rta.py +++ b/libs/wlgen/wlgen/rta.py @@ -43,11 +43,20 @@ class Phase(_Phase): pass class RTA(Workload): + """ + Class for creating RT-App workloads + """ def __init__(self, target, name, calibration=None): + """ + :param target: Devlib target to run workload on. + :param name: Human-readable name for the workload + :param calibration: CPU calibration specification. Can be obtained from + :meth:`calibrate`. + """ # Setup logging self._log = logging.getLogger('RTApp') @@ -77,6 +86,12 @@ class RTA(Workload): @staticmethod def calibrate(target): + """ + Calibrate RT-App on each CPU in the system + + :param target: Devlib target to run calibration on + :returns: Dict mapping CPU numbers to RT-App calibration values + """ pload_regexp = re.compile(r'pLoad = ([0-9]+)ns') pload = {} @@ -484,32 +499,41 @@ class RTA(Workload): workloads. The classes supported so far are detailed hereafter. Custom workloads - ---------------- - When 'kind' is 'custom' the tasks generated by this workload are the - ones defined in a provided rt-app JSON configuration file. - In this case the 'params' parameter must be used to specify the - complete path of the rt-app JSON configuration file to use. - + When 'kind' is 'custom' the tasks generated by this workload are the + ones defined in a provided rt-app JSON configuration file. + In this case the 'params' parameter must be used to specify the + complete path of the rt-app JSON configuration file to use. Profile based workloads - ----------------------- - When 'kind' is 'profile' the tasks generated by this workload have a - profile which is defined by a sequence of phases and they are defined - according to the following grammar: + When ``kind`` is "profile", ``params`` is a dictionary mapping task + names to task specifications. The easiest way to create these task + specifications using :meth:`RTATask.get`. + + For example, the following configures an RTA workload with a single + task, named 't1', using the default parameters for a Periodic RTATask: + + :: + + wl = RTA(...) + wl.conf(kind='profile', params={'t1': Periodic().get()}) - params := {task, ...} - task := NAME : {SCLASS, PRIO, [phase, ...]} - phase := (PTIME, PRIOD, DCYCLE) + :param kind: Either 'custom' or 'profile' - see above. + :param params: RT-App parameters - see above. + :param duration: Maximum duration of the workload in seconds. Any + remaining tasks are killed by rt-app when this time has + elapsed. + :param cpus: CPUs to restrict this workload to, using ``taskset``. - where the terminals are: - NAME : string, the task name (max 16 chars) - SCLASS : string, the scheduling class (OTHER, FIFO, RR) - PRIO : int, the priority of the task - PTIME : float, length of the current phase in [s] - PERIOD : float, task activation interval in [ms] - DCYCLE : int, task running interval in [0..100]% within each period + :param sched: Global RT-App scheduler configuration. Dict with fields: + policy + The default scheduler policy. Choose from 'OTHER', 'FIFO', 'RR', + and 'DEADLINE'. + + :param run_dir: Target dir to store output and config files in. + + .. TODO: document or remove loadref """ if not sched: @@ -538,11 +562,24 @@ class RTA(Workload): return self.test_label class RTATask(object): + """ + Base class for conveniently constructing params to :meth:`RTA.conf` + + This class represents an RT-App task which may contain multiple phases. It + implements ``__add__`` so that using ``+`` on two tasks concatenates their + phases. For example ``Ramp() + Periodic()`` would yield an ``RTATask`` that + executes the default phases for ``Ramp`` followed by the default phases for + ``Periodic``. + """ def __init__(self): self._task = {} def get(self): + """ + Return a dict that can be passed as an element of the ``params`` field + to :meth:`RTA.conf`. + """ return self._task def __add__(self, next_phases): @@ -551,34 +588,31 @@ class RTATask(object): class Ramp(RTATask): + """ + Configure a ramp load. + + This class defines a task which load is a ramp with a configured number + of steps according to the input parameters. + + :param start_pct: the initial load percentage. + :param end_pct: the final load percentage. + :param delta_pct: the load increase/decrease at each step, in percentage + points. + :param time_s: the duration in seconds of each load step. + :param period_ms: the period used to define the load in [ms] + :param delay_s: the delay in seconds before ramp start + :param loops: number of time to repeat the ramp, with the specified delay in + between + + :param sched: the scheduler configuration for this task + :type sched: dict + + :param cpus: the list of CPUs on which task can run + :type cpus: list + """ def __init__(self, start_pct=0, end_pct=100, delta_pct=10, time_s=1, period_ms=100, delay_s=0, loops=1, sched=None, cpus=None): - """ - Configure a ramp load. - - This class defines a task which load is a ramp with a configured number - of steps according to the input parameters. - - Args: - start_pct (int, [0-100]): the initial load [%], (default 0[%]) - end_pct (int, [0-100]): the final load [%], (default 100[%]) - delta_pct (int, [0-100]): the load increase/decrease [%], - default: 10[%] - increase if start_prc < end_prc - decrease if start_prc > end_prc - time_s (float): the duration in [s] of each load step - default: 1.0[s] - period_ms (float): the period used to define the load in [ms] - default: 100.0[ms] - delay_s (float): the delay in [s] before ramp start - default: 0[s] - loops (int): number of time to repeat the ramp, with the - specified delay in between - default: 0 - sched (dict): the scheduler configuration for this task - cpus (list): the list of CPUs on which task can run - """ super(Ramp, self).__init__() self._task['cpus'] = cpus @@ -612,73 +646,67 @@ class Ramp(RTATask): self._task['phases'] = phases class Step(Ramp): + """ + Configure a step load. + + This class defines a task which load is a step with a configured initial and + final load. Using the ``loops`` param, this can be used to create a workload + that alternates between two load values. + + :param start_pct: the initial load percentage. + :param end_pct: the final load percentage. + :param time_s: the duration in seconds of each load step. + :param period_ms: the period used to define the load in [ms] + :param delay_s: the delay in seconds before ramp start + :param loops: number of time to repeat the step, with the specified delay in + between. + + :param sched: the scheduler configuration for this task + :type sched: dict + + :param cpus: the list of CPUs on which task can run + :type cpus: list + """ def __init__(self, start_pct=0, end_pct=100, time_s=1, period_ms=100, delay_s=0, loops=1, sched=None, cpus=None): - """ - Configure a step load. - - This class defines a task which load is a step with a configured - initial and final load. - - Args: - start_pct (int, [0-100]): the initial load [%] - default 0[%]) - end_pct (int, [0-100]): the final load [%] - default 100[%] - time_s (float): the duration in [s] of the start and end load - default: 1.0[s] - period_ms (float): the period used to define the load in [ms] - default 100.0[ms] - delay_s (float): the delay in [s] before ramp start - default 0[s] - loops (int): number of time to repeat the ramp, with the - specified delay in between - default: 0 - sched (dict): the scheduler configuration for this task - cpus (list): the list of CPUs on which task can run - """ delta_pct = abs(end_pct - start_pct) super(Step, self).__init__(start_pct, end_pct, delta_pct, time_s, period_ms, delay_s, loops, sched, cpus) class Pulse(RTATask): + """ + Configure a pulse load. + + This class defines a task which load is a pulse with a configured + initial and final load. + + The main difference with the 'step' class is that a pulse workload is + by definition a 'step down', i.e. the workload switch from an finial + load to a final one which is always lower than the initial one. + Moreover, a pulse load does not generate a sleep phase in case of 0[%] + load, i.e. the task ends as soon as the non null initial load has + completed. + + :param start_pct: the initial load percentage. + :param end_pct: the final load percentage. Must be lower than ``start_pct`` + value. If end_pct is 0, the task end after the ``start_pct`` + period has completed. + :param time_s: the duration in seconds of each load step. + :param period_ms: the period used to define the load in [ms] + :param delay_s: the delay in seconds before ramp start + :param loops: number of time to repeat the pulse, with the specified delay + in between. + + :param sched: the scheduler configuration for this task + :type sched: dict + + :param cpus: the list of CPUs on which task can run + :type cpus: list + """ def __init__(self, start_pct=100, end_pct=0, time_s=1, period_ms=100, delay_s=0, loops=1, sched=None, cpus=None): - """ - Configure a pulse load. - - This class defines a task which load is a pulse with a configured - initial and final load. - - The main difference with the 'step' class is that a pulse workload is - by definition a 'step down', i.e. the workload switch from an finial - load to a final one which is always lower than the initial one. - Moreover, a pulse load does not generate a sleep phase in case of 0[%] - load, i.e. the task ends as soon as the non null initial load has - completed. - - Args: - start_pct (int, [0-100]): the initial load [%] - default: 0[%] - end_pct (int, [0-100]): the final load [%] - default: 100[%] - NOTE: must be lower than start_pct value - time_s (float): the duration in [s] of the start and end load - default: 1.0[s] - NOTE: if end_pct is 0, the task end after the - start_pct period completed - period_ms (float): the period used to define the load in [ms] - default: 100.0[ms] - delay_s (float): the delay in [s] before ramp start - default: 0[s] - loops (int): number of time to repeat the ramp, with the - specified delay in between - default: 0 - sched (dict): the scheduler configuration for this task - cpus (list): the list of CPUs on which task can run - """ super(Pulse, self).__init__() if end_pct >= start_pct: @@ -710,30 +738,25 @@ class Pulse(RTATask): class Periodic(Pulse): + """ + Configure a periodic load. This is the simplest type of RTA task. - def __init__(self, duty_cycle_pct=50, duration_s=1, period_ms=100, - delay_s=0, sched=None, cpus=None): - """ - Configure a periodic load. + This class defines a task which load is periodic with a configured + period and duty-cycle. - This class defines a task which load is periodic with a configured - period and duty-cycle. + :param duty_cycle_pct: the load percentage. + :param duration_s: the total duration in seconds of the task. + :param period_ms: the period used to define the load in milliseconds. + :param delay_s: the delay in seconds before starting the workload. - This class is a specialization of the 'pulse' class since a periodic - load is generated as a sequence of pulse loads. + :param sched: the scheduler configuration for this task + :type sched: dict - Args: - cuty_cycle_pct (int, [0-100]): the pulses load [%] - default: 50[%] - duration_s (float): the duration in [s] of the entire workload - default: 1.0[s] - period_ms (float): the period used to define the load in [ms] - default: 100.0[ms] - delay_s (float): the delay in [s] before ramp start - default: 0[s] - sched (dict): the scheduler configuration for this task + :param cpus: the list of CPUs on which task can run + :type cpus: list + """ - """ + def __init__(self, duty_cycle_pct=50, duration_s=1, period_ms=100, + delay_s=0, sched=None, cpus=None): super(Periodic, self).__init__(duty_cycle_pct, 0, duration_s, period_ms, delay_s, 1, sched, cpus) - diff --git a/libs/wlgen/wlgen/workload.py b/libs/wlgen/wlgen/workload.py index 7ea4128e7..6155ee5a8 100644 --- a/libs/wlgen/wlgen/workload.py +++ b/libs/wlgen/wlgen/workload.py @@ -24,6 +24,20 @@ from time import sleep import logging class Workload(object): + """ + Base class for workload specifications + + To use this class, you'll need to instantiate it, then call :meth:`conf` on + the instance. + + :param target: Devlib target to run workload on. May be None, in which case + an RT-App configuration file can be generated but the + workload cannot be run, and calibration features will be + missing. + :param name: Human-readable name for the workload + :param calibration: CPU calibration specification. Can be obtained from + :meth:`RTA.calibration`. + """ def __init__(self, target, @@ -91,6 +105,21 @@ class Workload(object): self.steps[step](kwords) def setCallback(self, step, func): + """ + Add a callback to be called during an execution stage. + + Intended for use by subclasses. Only one callback can exist for each + stage. Available callback stages are: + + "postrun" + Called after the workload has finished executing, unless it's being + run in the background. Receives a ``params`` dictionary with + ``params["destdir"]`` set to the host directory to store workload + output in. + + :param step: Name of the step at which to call the callback + :param func: Callback function + """ self._log.debug('Setup step [%s] callback to [%s] function', step, func.__name__) self.steps[step] = func @@ -110,6 +139,7 @@ class Workload(object): sched={'policy': 'OTHER'}, run_dir=None, exc_id=0): + """Configure workload. See documentation for subclasses""" self.cpus = cpus self.sched = sched @@ -164,8 +194,9 @@ class Workload(object): :type cgroup: str :param cpus: the CPUs on which to run the workload. - NOTE: if specified it overrides the CPUs specified at - configuration time + + .. note:: if specified it overrides the CPUs specified at + configuration time :type cpus: list(int) :param background: run the workload in background. In this case the @@ -174,8 +205,8 @@ class Workload(object): collection :type background: bool - :param out_dir: output directory where to store the collected trace (if - any) + :param out_dir: output directory where to store the collected trace or + other workload report (if any) :type out_dir: str :param as_root: run the workload as root on the target @@ -284,4 +315,3 @@ class Workload(object): return self._log.info('Killing all [%s] instances:', self.executor) self.listAll(True) - -- GitLab From b19a03387bfecd141ef3c43f21242f919b8084d5 Mon Sep 17 00:00:00 2001 From: Brendan Jackman Date: Fri, 23 Dec 2016 14:12:34 +0000 Subject: [PATCH 16/16] test_wlgen: Add smoke tests for wlgen classes This adds basic tests for some wlgen classes. The functionality tested here is basically what is contained in the current example notebooks, i.e. it just tests that the API is not completely broken. Some of the infrastructure added here can probably be used to self-test other parts of LISA using a local target. To make the tests really trivial I have just used hard-coded values for the expected JSON phase output, the rationale being that this avoids bugs in the test code. If we do more hacking on the wlgen classes then we should be able to add more interesting tests here. --- tests/lisa/test_wlgen.py | 355 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 tests/lisa/test_wlgen.py diff --git a/tests/lisa/test_wlgen.py b/tests/lisa/test_wlgen.py new file mode 100644 index 000000000..4cd056618 --- /dev/null +++ b/tests/lisa/test_wlgen.py @@ -0,0 +1,355 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2016, ARM Limited and contributors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import json +import os +import shutil +from unittest import TestCase + +from devlib import LocalLinuxTarget, Platform + +from wlgen import RTA, Periodic, Ramp +from wlgen import PerfMessaging + +dummy_calibration = {} + +class TestTarget(LocalLinuxTarget): + """ + Devlib target for self-testing LISA + + Uses LocalLinuxTarget configured to disallow using root. + Adds facility to record the commands that were executed for asserting LISA + behaviour. + """ + def __init__(self): + self.execute_calls = [] + super(TestTarget, self).__init__(platform=Platform(), + load_default_modules=False, + connection_settings={'unrooted': True}) + + def execute(self, *args, **kwargs): + self.execute_calls.append((args, kwargs)) + return super(TestTarget, self).execute(*args, **kwargs) + + @property + def executed_commands(self): + return [args[0] if args else kwargs['command'] + for args, kwargs in self.execute_calls] + + def clear_execute_calls(self): + self.execute_calls = [] + +class LisaSelfBase(TestCase): + """ + Base class for LISA self-tests + + Creates and sets up a TestTarget. + + Provides directory paths to use for output files. Deletes those paths if + they already exist, to try and provide a clean test environment. This + doesn't create those paths, tests should create them if necessary. + """ + + tools = [] + """Tools to install on the 'target' before each test""" + + @property + def target_run_dir(self): + """Unique directory to use for creating files on the 'target'""" + return os.path.join(self.target.working_directory, + 'lisa_target_{}'.format(self.__class__.__name__)) + + @property + def host_out_dir(self): + """Unique directory to use for creating files on the host""" + return os.path.join( + os.getenv('LISA_HOME'), 'results', + 'lisa_selftest_out_{}'.format(self.__class__.__name__)) + + def setUp(self): + self.target = TestTarget() + + tools_path = os.path.join(os.getenv('LISA_HOME'), + 'tools', self.target.abi) + self.target.setup([os.path.join(tools_path, tool) + for tool in self.tools]) + + if self.target.directory_exists(self.target_run_dir): + self.target.remove(self.target_run_dir) + + if os.path.isdir(self.host_out_dir): + shutil.rmtree(self.host_out_dir) + + self.target.clear_execute_calls() + +class RTABase(LisaSelfBase): + """ + Common functionality for testing RTA + + Doesn't have "Test" in the name so that nosetests doesn't try to run it + directly + """ + + tools = ['rt-app'] + + def get_expected_command(self, rta_wload): + """Return the rt-app command we should execute when `run` is called""" + rta_path = os.path.join(self.target.executables_directory, 'rt-app') + json_path = os.path.join(rta_wload.run_dir, rta_wload.json) + return '{} {} 2>&1'.format(rta_path, json_path) + + def setUp(self): + super(RTABase, self).setUp() + + # Can't calibrate rt-app because: + # - Need to set performance governor + # - Need to use SCHED_FIFO + high priority + # We probably don't have permissions so use a dummy calibration. + self.calibration = {c: 100 + for c in range(len(self.target.cpuinfo.cpu_names))} + + os.makedirs(self.host_out_dir) + + def assert_output_file_exists(self, path): + """Assert that a file was created in host_out_dir""" + path = os.path.join(self.host_out_dir, path) + self.assertTrue(os.path.isfile(path), + 'No output file {} from rt-app'.format(path)) + +class TestRTAProfile(RTABase): + def test_profile_periodic_smoke(self): + """ + Smoketest Periodic rt-app workload + + Creates a workload using Periodic, tests that the JSON has the expected + content, then tests that it can be run. + """ + rtapp = RTA(self.target, name='test', calibration=self.calibration) + + rtapp.conf( + kind = 'profile', + params = { + 'task_p20': Periodic( + period_ms = 100, + duty_cycle_pct = 20, + duration_s = 5, + ).get(), + }, + run_dir=self.target_run_dir + ) + + with open(rtapp.json) as f: + conf = json.load(f) + + [phase] = conf['tasks']['task_p20']['phases'].values() + self.assertDictEqual(phase, { + 'loop': 50, + 'run': 20000, + 'timer': { + 'period': 100000, + 'ref': 'task_p20' + } + }) + rtapp.run(out_dir=self.host_out_dir) + + rtapp_cmds = [c for c in self.target.executed_commands if 'rt-app' in c] + self.assertListEqual(rtapp_cmds, [self.get_expected_command(rtapp)]) + + self.assert_output_file_exists('output.log') + self.assert_output_file_exists('rt-app-task_p20-0.log') + self.assert_output_file_exists('test_00.json') + +class TestRTAComposition(RTABase): + def test_composition(self): + """ + Test RTA task composition with __add__ + + Creates a composed workload by +-ing RTATask objects, tests that the + JSON has the expected content, then tests running the workload + """ + rtapp = RTA(self.target, name='test', calibration=self.calibration) + + light = Periodic(duty_cycle_pct=10, duration_s=1.0, period_ms=10) + + start_pct = 10 + end_pct = 90 + delta_pct = 20 + num_ramp_phases = ((end_pct - start_pct) / delta_pct) + 1 + ramp = Ramp(start_pct=start_pct, end_pct=end_pct, delta_pct=delta_pct, + time_s=1, period_ms=50) + + heavy = Periodic(duty_cycle_pct=90, duration_s=0.1, period_ms=100) + + lrh_task = light + ramp + heavy + + rtapp.conf( + kind = 'profile', + params = { + 'task_ramp': lrh_task.get() + }, + run_dir=self.target_run_dir + ) + + with open(rtapp.json) as f: + conf = json.load(f, object_pairs_hook=OrderedDict) + + phases = conf['tasks']['task_ramp']['phases'].values() + + exp_phases = [ + # Light phase: + { + "loop": 100, + "run": 1000, + "timer": { + "period": 10000, + "ref": "task_ramp" + } + }, + # Ramp phases: + { + "loop": 20, + "run": 5000, + "timer": { + "period": 50000, + "ref": "task_ramp" + } + }, + { + "loop": 20, + "run": 15000, + "timer": { + "period": 50000, + "ref": "task_ramp" + } + }, + { + "loop": 20, + "run": 25000, + "timer": { + "period": 50000, + "ref": "task_ramp" + } + }, + { + "loop": 20, + "run": 35000, + "timer": { + "period": 50000, + "ref": "task_ramp" + } + }, + { + "loop": 20, + "run": 45000, + "timer": { + "period": 50000, + "ref": "task_ramp" + } + }, + # Heavy phase: + { + "loop": 1, + "run": 90000, + "timer": { + "period": 100000, + "ref": "task_ramp" + } + }] + + self.assertListEqual(phases, exp_phases) + + rtapp.run(out_dir=self.host_out_dir) + + rtapp_cmds = [c for c in self.target.executed_commands if 'rt-app' in c] + self.assertListEqual(rtapp_cmds, [self.get_expected_command(rtapp)]) + + self.assert_output_file_exists('output.log') + self.assert_output_file_exists('rt-app-task_ramp-0.log') + self.assert_output_file_exists('test_00.json') + + +class TestRTACustom(RTABase): + def test_custom_smoke(self): + """ + Test RTA custom workload + + Creates an rt-app workload using 'custom' and checks that the json + roughly matches the file we provided. If we have root, attempts to run + the workload. + """ + + json_path = os.path.join(os.getenv('LISA_HOME'), + 'assets', 'mp3-short.json') + rtapp = RTA(self.target, name='test', calibration=self.calibration) + + # Configure this RTApp instance to: + rtapp.conf(kind='custom', params=json_path, duration=5, + run_dir=self.target_run_dir) + + with open(rtapp.json) as f: + conf = json.load(f) + + # Convert to str because unicode + tasks = set([str(k) for k in conf['tasks'].keys()]) + self.assertSetEqual( + tasks, + set(['AudioTick', 'AudioOut', 'AudioTrack', + 'mp3.decoder', 'OMXCall'])) + + # Would like to try running the workload but mp3-short.json has nonzero + # 'priority' fields, and we probably don't have permission for that + # unless we're root. + if self.target.is_rooted: + rtapp.run(out_dir=self.host_out_dir) + + rtapp_cmds = [c for c in self.target.executed_commands + if 'rt-app' in c] + self.assertListEqual(rtapp_cmds, [self.get_expected_command(rtapp)]) + + self.assert_output_file_exists('output.log') + self.assert_output_file_exists('test_00.json') + +class TestHackBench(LisaSelfBase): + tools = ['perf'] + + def test_hackbench_smoke(self): + """ + Test PerfMessaging hackbench workload + + Runs a 'hackbench' workload and tests that the expected output was + produced. + """ + perf = PerfMessaging(self.target, 'hackbench') + perf.conf(group=1, loop=100, pipe=True, thread=True, + run_dir=self.target_run_dir) + + os.makedirs(self.host_out_dir) + perf.run(out_dir=self.host_out_dir) + + try: + with open(os.path.join('.', 'performance.json'), 'r') as fh: + perf_json = json.load(fh) + except IOError: + raise AssertionError( + "PerfMessaging didn't create performance report file") + + for field in ['ctime', 'performance']: + msg = 'PerfMessaging performance report missing {} field'\ + .format(field) + self.assertIn(field, perf_json, msg) + -- GitLab