• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# SPDX-License-Identifier: Apache-2.0
2#
3# Copyright (C) 2015, ARM Limited and contributors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18from bart.common.Analyzer import Analyzer
19import collections
20from collections import namedtuple
21import datetime
22import gzip
23import json
24import os
25import re
26import time
27import trappy
28from devlib import TargetError
29
30# Configure logging
31import logging
32
33# Add JSON parsing support
34from conf import JsonConf
35
36import wlgen
37
38from devlib import TargetError
39
40Experiment = namedtuple('Experiment', ['wload_name', 'wload',
41                                       'conf', 'iteration', 'out_dir'])
42
43class Executor():
44    """
45    Abstraction for running sets of experiments and gathering data from targets
46
47    An executor can be configured to run a set of workloads (wloads) in each
48    different target configuration of a specified set (confs). These wloads and
49    confs can be specified by the "experiments_conf" input dictionary. Each
50    (workload, conf, iteration) tuple is called an "experiment".
51
52    After the workloads have been run, the Executor object's `experiments`
53    attribute is a list of Experiment objects. The `out_dir` attribute of these
54    objects can be used to find the results of the experiment. This output
55    directory follows this format:
56
57        results/<test_id>/<wltype>:<conf>:<wload>/<run_id>
58
59    where:
60
61        test_id
62            Is the "tid" defined by the experiments_conf, or a timestamp based
63            folder in case "tid" is not specified.
64        wltype
65            Is the class of workload executed, e.g. rtapp or sched_perf.
66        conf
67            Is the "tag" of one of the specified **confs**.
68        wload
69            Is the identifier of one of the specified **wloads**.
70        run_id
71            Is the progressive execution number from 1 up to the specified
72            **iterations**.
73
74    :param experiments_conf: Dict with experiment configuration. Keys are:
75
76        **confs**
77          Mandatory. Platform configurations to be tested. List of dicts,
78          each with keys:
79
80            tag
81              String to identify this configuration. Required, may be empty.
82            flags
83              List of strings describing features required for this
84              conf. Available flags are:
85
86              "ftrace"
87                  Enable collecting ftrace during the experiment.
88              "freeze_userspace"
89                  Use the cgroups freezer to freeze as many userspace tasks as
90                  possible during the experiment execution, in order to reduce
91                  system noise. Some tasks cannot be frozen, such as those
92                  required to maintain a connection to LISA.
93
94            sched_features
95              Optional list of features to be written to
96              /sys/kernel/debug/sched_features. Prepend "NO\_" to a feature to
97              actively disable it. Requires ``CONFIG_SCHED_DEBUG`` in target
98              kernel.
99            cpufreq
100              Parameters to configure cpufreq via Devlib's cpufreq
101              module. Dictionary with fields:
102
103              .. TODO link to devlib cpufreq module docs (which don't exist)
104
105              governor
106                cpufreq governor to set (for all CPUs) before execution. The
107                previous governor is not restored when execution is finished.
108              governor_tunables
109                Dictionary of governor-specific tunables, expanded and passed as
110                kwargs to the cpufreq module's ``set_governor_tunables`` method.
111              freq
112                Requires "governor" to be "userspace". Dictionary mapping CPU
113                numbers to frequencies. Exact frequencies should be available on
114                those CPUs. It is not necessary to provide a frequency for every
115                CPU - the frequency for unspecified CPUs is not affected. Note
116                that cpufreq will transparrently set the frequencies of any
117                other CPUs sharing a clock domain.
118
119            cgroups
120              Optional cgroups configuration. To use this, ensure the 'cgroups'
121              devlib module is enabled in your test_conf Contains fields:
122
123              .. TODO reference test_conf
124              .. TODO link to devlib cgroup module's docs (which don't exist)
125
126              conf
127                Dict specifying the cgroup controllers, cgroups, and cgroup
128                parameters to setup. If a controller listed here is not
129                enabled in the target kernel, a message is logged and the
130                configuration is **ignored**. Of the form:
131
132                ::
133
134                  "<controller>" : {
135                      "<group1>" : { "<group_param" : <value> }
136                      "<group2>" : { "<group_param" : <value> }
137                  }
138
139                These cgroups can then be used in the "cgroup" field of workload
140                specifications.
141
142              default
143                The default cgroup to run workloads in, if no "cgroup" is
144                specified.
145
146              For example, to create a cpuset cgroup named "/big" which
147              restricts constituent tasks to CPUs 1 and 2:
148
149              ::
150
151                "cgroups" : {
152                    "conf" : {
153                        "cpuset" : {
154                            "/big" : {"cpus" : "1-2"},
155                        }
156                    },
157                    "default" : "/",
158                }
159
160          **wloads**
161            .. TODO document wloads field.
162
163            Mandatory. Workloads to run on each platform configuration
164
165          **iterations**
166            Number of iterations for each workload/conf combination. Default
167            is 1.
168    :type experiments_conf: dict
169
170    :ivar experiments: After calling `meth`:run:, the list of
171                       :class:`Experiment` s that were run
172
173    :ivar iterations: The number of iterations run for each wload/conf pair
174                       (i.e. ``experiments_conf['iterations']``.
175
176    """
177
178    critical_tasks = {
179        'linux': ['init', 'systemd', 'sh', 'ssh', 'rsyslogd', 'jbd2'],
180        'android': [
181            'sh', 'adbd',
182            'usb', 'transport',
183            # We don't actually need this task but on Google Pixel it apparently
184            # cannot be frozen, so the cgroup state gets stuck in FREEZING if we
185            # try to freeze it.
186            'thermal-engine'
187        ]
188    }
189    """
190    Dictionary mapping OS name to list of task names that we can't afford to
191    freeze when using freeeze_userspace.
192    """
193
194    def __init__(self, test_env, experiments_conf):
195        # Initialize globals
196        self._default_cgroup = None
197        self._cgroup = None
198
199        # Setup logging
200        self._log = logging.getLogger('Executor')
201
202        # Setup test configuration
203        if isinstance(experiments_conf, dict):
204            self._log.info('Loading custom (inline) test configuration')
205            self._experiments_conf = experiments_conf
206        elif isinstance(experiments_conf, str):
207            self._log.info('Loading custom (file) test configuration')
208            json_conf = JsonConf(experiments_conf)
209            self._experiments_conf = json_conf.load()
210        else:
211            raise ValueError(
212                'experiments_conf must be either a dictionary or a filepath')
213
214        # Check for mandatory configurations
215        if not self._experiments_conf.get('confs', None):
216            raise ValueError('Configuration error: '
217                             'missing "conf" definitions')
218        if not self._experiments_conf.get('wloads', None):
219            raise ValueError('Configuration error: '
220                             'missing "wloads" definitions')
221
222        self.te = test_env
223        self.target = self.te.target
224
225        self.iterations = self._experiments_conf.get('iterations', 1)
226        # Compute total number of experiments
227        self._exp_count = self.iterations \
228                * len(self._experiments_conf['wloads']) \
229                * len(self._experiments_conf['confs'])
230
231        self._print_section('Experiments configuration')
232
233        self._log.info('Configured to run:')
234
235        self._log.info('   %3d target configurations:',
236                       len(self._experiments_conf['confs']))
237        target_confs = [conf['tag'] for conf in self._experiments_conf['confs']]
238        target_confs = ', '.join(target_confs)
239        self._log.info('      %s', target_confs)
240
241        self._log.info('   %3d workloads (%d iterations each)',
242                       len(self._experiments_conf['wloads']),
243                       self.iterations)
244        wload_confs = ', '.join(self._experiments_conf['wloads'])
245        self._log.info('      %s', wload_confs)
246
247        self._log.info('Total: %d experiments', self._exp_count)
248
249        self._log.info('Results will be collected under:')
250        self._log.info('      %s', self.te.res_dir)
251
252        if any(wl['type'] == 'rt-app'
253               for wl in self._experiments_conf['wloads'].values()):
254            self._log.info('rt-app workloads found, installing tool on target')
255            self.te.install_tools(['rt-app'])
256
257    def run(self):
258        self._print_section('Experiments execution')
259
260        self.experiments = []
261
262        # Run all the configured experiments
263        exp_idx = 0
264        for tc in self._experiments_conf['confs']:
265            # TARGET: configuration
266            if not self._target_configure(tc):
267                continue
268            for wl_idx in self._experiments_conf['wloads']:
269                # TEST: configuration
270                wload, test_dir = self._wload_init(tc, wl_idx)
271                for itr_idx in range(1, self.iterations + 1):
272                    exp = Experiment(
273                        wload_name=wl_idx,
274                        wload=wload,
275                        conf=tc,
276                        iteration=itr_idx,
277                        out_dir=os.path.join(test_dir, str(itr_idx)))
278                    self.experiments.append(exp)
279
280                    # WORKLOAD: execution
281                    self._wload_run(exp_idx, exp)
282                    exp_idx += 1
283            self._target_cleanup(tc)
284
285        self._print_section('Experiments execution completed')
286        self._log.info('Results available in:')
287        self._log.info('      %s', self.te.res_dir)
288
289
290################################################################################
291# Target Configuration
292################################################################################
293
294    def _cgroups_init(self, tc):
295        self._default_cgroup = None
296        if 'cgroups' not in tc:
297            return True
298        if 'cgroups' not in self.target.modules:
299            raise RuntimeError('CGroups module not available. Please ensure '
300                               '"cgroups" is listed in your target/test modules')
301        self._log.info('Initialize CGroups support...')
302        errors = False
303        for kind in tc['cgroups']['conf']:
304            self._log.info('Setup [%s] CGroup controller...', kind)
305            controller = self.target.cgroups.controller(kind)
306            if not controller:
307                self._log.warning('CGroups controller [%s] NOT available',
308                                  kind)
309                errors = True
310        return not errors
311
312    def _setup_kernel(self, tc):
313        # Deploy kernel on the device
314        self.te.install_kernel(tc, reboot=True)
315        # Setup the rootfs for the experiments
316        self._setup_rootfs(tc)
317
318    def _setup_sched_features(self, tc):
319        if 'sched_features' not in tc:
320            self._log.debug('Scheduler features configuration not provided')
321            return
322        feats = tc['sched_features'].split(",")
323        for feat in feats:
324            self._log.info('Set scheduler feature: %s', feat)
325            self.target.execute('echo {} > /sys/kernel/debug/sched_features'.format(feat),
326                                as_root=True)
327
328    def _setup_rootfs(self, tc):
329        # Initialize CGroups if required
330        self._cgroups_init(tc)
331        # Setup target folder for experiments execution
332        self.te.run_dir = os.path.join(
333                self.target.working_directory, TGT_RUN_DIR)
334        # Create run folder as tmpfs
335        self._log.debug('Setup RT-App run folder [%s]...', self.te.run_dir)
336        self.target.execute('[ -d {0} ] || mkdir {0}'\
337                .format(self.te.run_dir))
338        self.target.execute(
339                'grep schedtest /proc/mounts || '\
340                '  mount -t tmpfs -o size=1024m {} {}'\
341                .format('schedtest', self.te.run_dir),
342                as_root=True)
343        # tmpfs mounts have an SELinux context with "tmpfs" as the type (while
344        # other files we create have "shell_data_file"). That prevents non-root
345        # users from creating files in tmpfs mounts. For now, just put SELinux
346        # in permissive mode to get around that.
347        try:
348            # First, save the old SELinux mode
349            self._old_selinux_mode = self.target.execute('getenforce')
350            self._log.warning('Setting target SELinux in permissive mode')
351            self.target.execute('setenforce 0', as_root=True)
352        except TargetError:
353            # Probably the target doesn't have SELinux, or there are no
354            # contexts set up. No problem.
355            self._log.warning("Couldn't set SELinux in permissive mode. "
356                                "This is probably fine.")
357            self._old_selinux_mode = None
358
359    def _setup_cpufreq(self, tc):
360        if 'cpufreq' not in tc:
361            self._log.warning('cpufreq governor not specified, '
362                              'using currently configured governor')
363            return
364
365        cpufreq = tc['cpufreq']
366        self._log.info('Configuring all CPUs to use [%s] cpufreq governor',
367                       cpufreq['governor'])
368
369        self.target.cpufreq.set_all_governors(cpufreq['governor'])
370
371        if 'freqs' in cpufreq:
372            if cpufreq['governor'] != 'userspace':
373                raise ValueError('Must use userspace governor to set CPU freqs')
374            self._log.info(r'%14s - CPU frequencies: %s',
375                    'CPUFreq', str(cpufreq['freqs']))
376            for cpu, freq in cpufreq['freqs'].iteritems():
377                self.target.cpufreq.set_frequency(cpu, freq)
378
379        if 'params' in cpufreq:
380            self._log.info('governor params: %s', str(cpufreq['params']))
381            for cpu in self.target.list_online_cpus():
382                self.target.cpufreq.set_governor_tunables(
383                        cpu,
384                        cpufreq['governor'],
385                        **cpufreq['params'])
386
387    def _setup_cgroups(self, tc):
388        if 'cgroups' not in tc:
389            return True
390        # Setup default CGroup to run tasks into
391        if 'default' in tc['cgroups']:
392            self._default_cgroup = tc['cgroups']['default']
393        # Configure each required controller
394        if 'conf' not in tc['cgroups']:
395            return True
396        errors = False
397        for kind in tc['cgroups']['conf']:
398            controller = self.target.cgroups.controller(kind)
399            if not controller:
400                self._log.warning('Configuration error: '
401                                  '[%s] contoller NOT supported',
402                                  kind)
403                errors = True
404                continue
405            self._setup_controller(tc, controller)
406        return not errors
407
408    def _setup_controller(self, tc, controller):
409        kind = controller.kind
410        # Configure each required groups for that controller
411        errors = False
412        for name in tc['cgroups']['conf'][controller.kind]:
413            if name[0] != '/':
414                raise ValueError('Wrong CGroup name [{}]. '
415                                 'CGroups names must start by "/".'
416                                 .format(name))
417            group = controller.cgroup(name)
418            if not group:
419                self._log.warning('Configuration error: '
420                                  '[%s/%s] cgroup NOT available',
421                                  kind, name)
422                errors = True
423                continue
424            self._setup_group(tc, group)
425        return not errors
426
427    def _setup_group(self, tc, group):
428        kind = group.controller.kind
429        name = group.name
430        # Configure each required attribute
431        group.set(**tc['cgroups']['conf'][kind][name])
432
433    def _setup_files(self, tc):
434        if 'files' not in tc:
435            self._log.debug('\'files\' Configuration block not provided')
436            return True
437        for name, value in tc['files'].iteritems():
438            check = False
439            if name.startswith('!/'):
440                check = True
441                name = name[1:]
442            self._log.info('File Write(check=%s): \'%s\' -> \'%s\'',
443                         check, value, name)
444            try:
445                self.target.write_value(name, value, True)
446            except TargetError:
447                self._log.info('File Write Failed: \'%s\' -> \'%s\'',
448                         value, name)
449                if check:
450                    raise
451        return False
452
453    def _target_configure(self, tc):
454        self._print_header(
455                'configuring target for [{}] experiments'\
456                .format(tc['tag']))
457        self._setup_kernel(tc)
458        self._setup_sched_features(tc)
459        self._setup_cpufreq(tc)
460        self._setup_files(tc)
461        return self._setup_cgroups(tc)
462
463    def _target_conf_flag(self, tc, flag):
464        if 'flags' not in tc:
465            has_flag = False
466        else:
467            has_flag = flag in tc['flags']
468        self._log.debug('Check if target configuration [%s] has flag [%s]: %s',
469                        tc['tag'], flag, has_flag)
470        return has_flag
471
472    def _target_cleanup(self, tc):
473        if self._old_selinux_mode is not None:
474            self._log.info('Restoring target SELinux mode: %s',
475                           self._old_selinux_mode)
476            self.target.execute('setenforce ' + self._old_selinux_mode,
477                                as_root=True)
478
479################################################################################
480# Workload Setup and Execution
481################################################################################
482
483    def _wload_cpus(self, wl_idx, wlspec):
484        if not 'cpus' in wlspec['conf']:
485            return None
486        cpus = wlspec['conf']['cpus']
487
488        if type(cpus) == list:
489            return cpus
490        if type(cpus) == int:
491            return [cpus]
492
493        # SMP target (or not bL module loaded)
494        if not hasattr(self.target, 'bl'):
495            if 'first' in cpus:
496                return [ self.target.list_online_cpus()[0] ]
497            if 'last' in cpus:
498                return [ self.target.list_online_cpus()[-1] ]
499            return self.target.list_online_cpus()
500
501        # big.LITTLE target
502        if cpus.startswith('littles'):
503            if 'first' in cpus:
504                return [ self.target.bl.littles_online[0] ]
505            if 'last' in cpus:
506                return [ self.target.bl.littles_online[-1] ]
507            return self.target.bl.littles_online
508        if cpus.startswith('bigs'):
509            if 'first' in cpus:
510                return [ self.target.bl.bigs_online[0] ]
511            if 'last' in cpus:
512                return [ self.target.bl.bigs_online[-1] ]
513            return self.target.bl.bigs_online
514        raise ValueError('unsupported [{}] "cpus" value for [{}] '
515                         'workload specification'
516                         .format(cpus, wl_idx))
517
518    def _wload_task_idxs(self, wl_idx, tasks):
519        if type(tasks) == int:
520            return range(tasks)
521        if tasks == 'cpus':
522            return range(len(self.target.core_names))
523        if tasks == 'little':
524            return range(len([t
525                for t in self.target.core_names
526                if t == self.target.little_core]))
527        if tasks == 'big':
528            return range(len([t
529                for t in self.target.core_names
530                if t == self.target.big_core]))
531        raise ValueError('unsupported "tasks" value for [{}] RT-App '
532                         'workload specification'
533                         .format(wl_idx))
534
535    def _wload_rtapp(self, wl_idx, wlspec, cpus):
536        conf = wlspec['conf']
537        self._log.debug('Configuring [%s] rt-app...', conf['class'])
538
539        # Setup a default "empty" task name prefix
540        if 'prefix' not in conf:
541            conf['prefix'] = 'task_'
542
543        # Setup a default loadref CPU
544        loadref = None
545        if 'loadref' in wlspec:
546            loadref = wlspec['loadref']
547
548        if conf['class'] == 'profile':
549            params = {}
550            # Load each task specification
551            for task_name, task in conf['params'].items():
552                if task['kind'] not in wlgen.__dict__:
553                    self._log.error('RTA task of kind [%s] not supported',
554                                    task['kind'])
555                    raise ValueError('unsupported "kind" value for task [{}] '
556                                     'in RT-App workload specification'
557                                     .format(task))
558                task_ctor = getattr(wlgen, task['kind'])
559                num_tasks = task.get('tasks', 1)
560                task_idxs = self._wload_task_idxs(wl_idx, num_tasks)
561                for idx in task_idxs:
562                    idx_name = "_{}".format(idx) if len(task_idxs) > 1 else ""
563                    task_name_idx = conf['prefix'] + task_name + idx_name
564                    params[task_name_idx] = task_ctor(**task['params']).get()
565
566            rtapp = wlgen.RTA(self.target,
567                        wl_idx, calibration = self.te.calibration())
568            rtapp.conf(kind='profile', params=params, loadref=loadref,
569                       cpus=cpus, run_dir=self.te.run_dir,
570                       duration=conf.get('duration'))
571            return rtapp
572
573        if conf['class'] == 'periodic':
574            task_idxs = self._wload_task_idxs(wl_idx, conf['tasks'])
575            params = {}
576            for idx in task_idxs:
577                task = conf['prefix'] + str(idx)
578                params[task] = wlgen.Periodic(**conf['params']).get()
579            rtapp = wlgen.RTA(self.target,
580                        wl_idx, calibration = self.te.calibration())
581            rtapp.conf(kind='profile', params=params, loadref=loadref,
582                       cpus=cpus, run_dir=self.te.run_dir,
583                       duration=conf.get('duration'))
584            return rtapp
585
586        if conf['class'] == 'custom':
587            rtapp = wlgen.RTA(self.target,
588                              wl_idx, calibration = self.te.calibration())
589            rtapp.conf(kind='custom',
590                    params=conf['json'],
591                    duration=conf.get('duration'),
592                    loadref=loadref,
593                    cpus=cpus, run_dir=self.te.run_dir)
594            return rtapp
595
596        raise ValueError('unsupported \'class\' value for [{}] '
597                         'RT-App workload specification'
598                         .format(wl_idx))
599
600    def _wload_perf_bench(self, wl_idx, wlspec, cpus):
601        conf = wlspec['conf']
602        self._log.debug('Configuring perf_message...')
603
604        if conf['class'] == 'messaging':
605            perf_bench = wlgen.PerfMessaging(self.target, wl_idx)
606            perf_bench.conf(**conf['params'])
607            return perf_bench
608
609        if conf['class'] == 'pipe':
610            perf_bench = wlgen.PerfPipe(self.target, wl_idx)
611            perf_bench.conf(**conf['params'])
612            return perf_bench
613
614        raise ValueError('unsupported "class" value for [{}] '
615                         'perf bench workload specification'
616                         .format(wl_idx))
617
618    def _wload_conf(self, wl_idx, wlspec):
619
620        # CPUS: setup execution on CPUs if required by configuration
621        cpus = self._wload_cpus(wl_idx, wlspec)
622
623        # CGroup: setup CGroups if requried by configuration
624        self._cgroup = self._default_cgroup
625        if 'cgroup' in wlspec:
626            if 'cgroups' not in self.target.modules:
627                raise RuntimeError('Target not supporting CGroups or CGroups '
628                                   'not configured for the current test configuration')
629            self._cgroup = wlspec['cgroup']
630
631        if wlspec['type'] == 'rt-app':
632            return self._wload_rtapp(wl_idx, wlspec, cpus)
633        if wlspec['type'] == 'perf_bench':
634            return self._wload_perf_bench(wl_idx, wlspec, cpus)
635
636
637        raise ValueError('unsupported "type" value for [{}] '
638                         'workload specification'
639                         .format(wl_idx))
640
641    def _wload_init(self, tc, wl_idx):
642        tc_idx = tc['tag']
643
644        # Configure the test workload
645        wlspec = self._experiments_conf['wloads'][wl_idx]
646        wload = self._wload_conf(wl_idx, wlspec)
647
648        # Keep track of platform configuration
649        test_dir = '{}/{}:{}:{}'\
650            .format(self.te.res_dir, wload.wtype, tc_idx, wl_idx)
651        os.makedirs(test_dir)
652        self.te.platform_dump(test_dir)
653
654        # Keep track of kernel configuration and version
655        config = self.target.config
656        with gzip.open(os.path.join(test_dir, 'kernel.config'), 'wb') as fh:
657            fh.write(config.text)
658        output = self.target.execute('{} uname -a'\
659                .format(self.target.busybox))
660        with open(os.path.join(test_dir, 'kernel.version'), 'w') as fh:
661            fh.write(output)
662
663        return wload, test_dir
664
665    def _wload_run(self, exp_idx, experiment):
666        tc = experiment.conf
667        wload = experiment.wload
668        tc_idx = tc['tag']
669
670        self._print_title('Experiment {}/{}, [{}:{}] {}/{}'\
671                .format(exp_idx, self._exp_count,
672                        tc_idx, experiment.wload_name,
673                        experiment.iteration, self.iterations))
674
675        # Setup local results folder
676        self._log.debug('out_dir set to [%s]', experiment.out_dir)
677        os.system('mkdir -p ' + experiment.out_dir)
678
679        # Freeze all userspace tasks that we don't need for running tests
680        need_thaw = False
681        if self._target_conf_flag(tc, 'freeze_userspace'):
682            need_thaw = self._freeze_userspace()
683
684        # FTRACE: start (if a configuration has been provided)
685        if self.te.ftrace and self._target_conf_flag(tc, 'ftrace'):
686            self._log.warning('FTrace events collection enabled')
687            self.te.ftrace.start()
688
689        # ENERGY: start sampling
690        if self.te.emeter:
691            self.te.emeter.reset()
692
693        # WORKLOAD: Run the configured workload
694        wload.run(out_dir=experiment.out_dir, cgroup=self._cgroup)
695
696        # ENERGY: collect measurements
697        if self.te.emeter:
698            self.te.emeter.report(experiment.out_dir)
699
700        # FTRACE: stop and collect measurements
701        if self.te.ftrace and self._target_conf_flag(tc, 'ftrace'):
702            self.te.ftrace.stop()
703
704            trace_file = experiment.out_dir + '/trace.dat'
705            self.te.ftrace.get_trace(trace_file)
706            self._log.info('Collected FTrace binary trace:')
707            self._log.info('   %s',
708                           trace_file.replace(self.te.res_dir, '<res_dir>'))
709
710            stats_file = experiment.out_dir + '/trace_stat.json'
711            self.te.ftrace.get_stats(stats_file)
712            self._log.info('Collected FTrace function profiling:')
713            self._log.info('   %s',
714                           stats_file.replace(self.te.res_dir, '<res_dir>'))
715
716        # Unfreeze the tasks we froze
717        if need_thaw:
718            self._thaw_userspace()
719
720        self._print_footer()
721
722    def _freeze_userspace(self):
723        if 'cgroups' not in self.target.modules:
724            raise RuntimeError(
725                'Failed to freeze userspace. Ensure "cgroups" module is listed '
726                'among modules in target/test configuration')
727        controllers = [s.name for s in self.target.cgroups.list_subsystems()]
728        if 'freezer' not in controllers:
729            self._log.warning('No freezer cgroup controller on target. '
730                              'Not freezing userspace')
731            return False
732
733        exclude = self.critical_tasks[self.te.target.os]
734        self._log.info('Freezing all tasks except: %s', ','.join(exclude))
735        self.te.target.cgroups.freeze(exclude)
736        return True
737
738
739    def _thaw_userspace(self):
740        self._log.info('Un-freezing userspace tasks')
741        self.te.target.cgroups.freeze(thaw=True)
742
743################################################################################
744# Utility Functions
745################################################################################
746
747    def _print_section(self, message):
748        self._log.info('')
749        self._log.info(FMT_SECTION)
750        self._log.info(message)
751        self._log.info(FMT_SECTION)
752
753    def _print_header(self, message):
754        self._log.info('')
755        self._log.info(FMT_HEADER)
756        self._log.info(message)
757
758    def _print_title(self, message):
759        self._log.info(FMT_TITLE)
760        self._log.info(message)
761
762    def _print_footer(self, message=None):
763        if message:
764            self._log.info(message)
765        self._log.info(FMT_FOOTER)
766
767
768################################################################################
769# Globals
770################################################################################
771
772# Regular expression for comments
773JSON_COMMENTS_RE = re.compile(
774    '(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
775    re.DOTALL | re.MULTILINE
776)
777
778# Target specific paths
779TGT_RUN_DIR = 'run_dir'
780
781# Logging formatters
782FMT_SECTION = r'{:#<80}'.format('')
783FMT_HEADER  = r'{:=<80}'.format('')
784FMT_TITLE   = r'{:~<80}'.format('')
785FMT_FOOTER  = r'{:-<80}'.format('')
786
787# vim :set tabstop=4 shiftwidth=4 expandtab
788