• 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
171    critical_tasks = {
172        'linux': ['init', 'systemd', 'sh', 'ssh'],
173        'android': [
174            'sh', 'adbd',
175            'usb', 'transport',
176            # We don't actually need this task but on Google Pixel it apparently
177            # cannot be frozen, so the cgroup state gets stuck in FREEZING if we
178            # try to freeze it.
179            'thermal-engine'
180        ]
181    }
182    """
183    Dictionary mapping OS name to list of task names that we can't afford to
184    freeze when using freeeze_userspace.
185    """
186
187    def __init__(self, test_env, experiments_conf):
188        # Initialize globals
189        self._default_cgroup = None
190        self._cgroup = None
191
192        # Setup logging
193        self._log = logging.getLogger('Executor')
194
195        # Setup test configuration
196        if isinstance(experiments_conf, dict):
197            self._log.info('Loading custom (inline) test configuration')
198            self._experiments_conf = experiments_conf
199        elif isinstance(experiments_conf, str):
200            self._log.info('Loading custom (file) test configuration')
201            json_conf = JsonConf(experiments_conf)
202            self._experiments_conf = json_conf.load()
203        else:
204            raise ValueError(
205                'experiments_conf must be either a dictionary or a filepath')
206
207        # Check for mandatory configurations
208        if not self._experiments_conf.get('confs', None):
209            raise ValueError('Configuration error: '
210                             'missing "conf" definitions')
211        if not self._experiments_conf.get('wloads', None):
212            raise ValueError('Configuration error: '
213                             'missing "wloads" definitions')
214
215        self.te = test_env
216        self.target = self.te.target
217
218        self._iterations = self._experiments_conf.get('iterations', 1)
219        # Compute total number of experiments
220        self._exp_count = self._iterations \
221                * len(self._experiments_conf['wloads']) \
222                * len(self._experiments_conf['confs'])
223
224        self._print_section('Experiments configuration')
225
226        self._log.info('Configured to run:')
227
228        self._log.info('   %3d target configurations:',
229                       len(self._experiments_conf['confs']))
230        target_confs = [conf['tag'] for conf in self._experiments_conf['confs']]
231        target_confs = ', '.join(target_confs)
232        self._log.info('      %s', target_confs)
233
234        self._log.info('   %3d workloads (%d iterations each)',
235                       len(self._experiments_conf['wloads']),
236                       self._iterations)
237        wload_confs = ', '.join(self._experiments_conf['wloads'])
238        self._log.info('      %s', wload_confs)
239
240        self._log.info('Total: %d experiments', self._exp_count)
241
242        self._log.info('Results will be collected under:')
243        self._log.info('      %s', self.te.res_dir)
244
245        if any(wl['type'] == 'rt-app'
246               for wl in self._experiments_conf['wloads'].values()):
247            self._log.info('rt-app workloads found, installing tool on target')
248            self.te.install_tools(['rt-app'])
249
250    def run(self):
251        self._print_section('Experiments execution')
252
253        self.experiments = []
254
255        # Run all the configured experiments
256        exp_idx = 0
257        for tc in self._experiments_conf['confs']:
258            # TARGET: configuration
259            if not self._target_configure(tc):
260                continue
261            for wl_idx in self._experiments_conf['wloads']:
262                # TEST: configuration
263                wload, test_dir = self._wload_init(tc, wl_idx)
264                for itr_idx in range(1, self._iterations + 1):
265                    exp = Experiment(
266                        wload_name=wl_idx,
267                        wload=wload,
268                        conf=tc,
269                        iteration=itr_idx,
270                        out_dir=os.path.join(test_dir, str(itr_idx)))
271                    self.experiments.append(exp)
272
273                    # WORKLOAD: execution
274                    self._wload_run(exp_idx, exp)
275                    exp_idx += 1
276            self._target_cleanup(tc)
277
278        self._print_section('Experiments execution completed')
279        self._log.info('Results available in:')
280        self._log.info('      %s', self.te.res_dir)
281
282
283################################################################################
284# Target Configuration
285################################################################################
286
287    def _cgroups_init(self, tc):
288        self._default_cgroup = None
289        if 'cgroups' not in tc:
290            return True
291        if 'cgroups' not in self.target.modules:
292            raise RuntimeError('CGroups module not available. Please ensure '
293                               '"cgroups" is listed in your target/test modules')
294        self._log.info('Initialize CGroups support...')
295        errors = False
296        for kind in tc['cgroups']['conf']:
297            self._log.info('Setup [%s] CGroup controller...', kind)
298            controller = self.target.cgroups.controller(kind)
299            if not controller:
300                self._log.warning('CGroups controller [%s] NOT available',
301                                  kind)
302                errors = True
303        return not errors
304
305    def _setup_kernel(self, tc):
306        # Deploy kernel on the device
307        self.te.install_kernel(tc, reboot=True)
308        # Setup the rootfs for the experiments
309        self._setup_rootfs(tc)
310
311    def _setup_sched_features(self, tc):
312        if 'sched_features' not in tc:
313            self._log.debug('Scheduler features configuration not provided')
314            return
315        feats = tc['sched_features'].split(",")
316        for feat in feats:
317            self._log.info('Set scheduler feature: %s', feat)
318            self.target.execute('echo {} > /sys/kernel/debug/sched_features'.format(feat),
319                                as_root=True)
320
321    def _setup_rootfs(self, tc):
322        # Initialize CGroups if required
323        self._cgroups_init(tc)
324        # Setup target folder for experiments execution
325        self.te.run_dir = os.path.join(
326                self.target.working_directory, TGT_RUN_DIR)
327        # Create run folder as tmpfs
328        self._log.debug('Setup RT-App run folder [%s]...', self.te.run_dir)
329        self.target.execute('[ -d {0} ] || mkdir {0}'\
330                .format(self.te.run_dir))
331        self.target.execute(
332                'grep schedtest /proc/mounts || '\
333                '  mount -t tmpfs -o size=1024m {} {}'\
334                .format('schedtest', self.te.run_dir),
335                as_root=True)
336        # tmpfs mounts have an SELinux context with "tmpfs" as the type (while
337        # other files we create have "shell_data_file"). That prevents non-root
338        # users from creating files in tmpfs mounts. For now, just put SELinux
339        # in permissive mode to get around that.
340        try:
341            # First, save the old SELinux mode
342            self._old_selinux_mode = self.target.execute('getenforce')
343            self._log.warning('Setting target SELinux in permissive mode')
344            self.target.execute('setenforce 0', as_root=True)
345        except TargetError:
346            # Probably the target doesn't have SELinux, or there are no
347            # contexts set up. No problem.
348            self._log.warning("Couldn't set SELinux in permissive mode. "
349                                "This is probably fine.")
350            self._old_selinux_mode = None
351
352    def _setup_cpufreq(self, tc):
353        if 'cpufreq' not in tc:
354            self._log.warning('cpufreq governor not specified, '
355                              'using currently configured governor')
356            return
357
358        cpufreq = tc['cpufreq']
359        self._log.info('Configuring all CPUs to use [%s] cpufreq governor',
360                       cpufreq['governor'])
361
362        self.target.cpufreq.set_all_governors(cpufreq['governor'])
363
364        if 'freqs' in cpufreq:
365            if cpufreq['governor'] != 'userspace':
366                raise ValueError('Must use userspace governor to set CPU freqs')
367            self._log.info(r'%14s - CPU frequencies: %s',
368                    'CPUFreq', str(cpufreq['freqs']))
369            for cpu, freq in cpufreq['freqs'].iteritems():
370                self.target.cpufreq.set_frequency(cpu, freq)
371
372        if 'params' in cpufreq:
373            self._log.info('governor params: %s', str(cpufreq['params']))
374            for cpu in self.target.list_online_cpus():
375                self.target.cpufreq.set_governor_tunables(
376                        cpu,
377                        cpufreq['governor'],
378                        **cpufreq['params'])
379
380    def _setup_cgroups(self, tc):
381        if 'cgroups' not in tc:
382            return True
383        # Setup default CGroup to run tasks into
384        if 'default' in tc['cgroups']:
385            self._default_cgroup = tc['cgroups']['default']
386        # Configure each required controller
387        if 'conf' not in tc['cgroups']:
388            return True
389        errors = False
390        for kind in tc['cgroups']['conf']:
391            controller = self.target.cgroups.controller(kind)
392            if not controller:
393                self._log.warning('Configuration error: '
394                                  '[%s] contoller NOT supported',
395                                  kind)
396                errors = True
397                continue
398            self._setup_controller(tc, controller)
399        return not errors
400
401    def _setup_controller(self, tc, controller):
402        kind = controller.kind
403        # Configure each required groups for that controller
404        errors = False
405        for name in tc['cgroups']['conf'][controller.kind]:
406            if name[0] != '/':
407                raise ValueError('Wrong CGroup name [{}]. '
408                                 'CGroups names must start by "/".'
409                                 .format(name))
410            group = controller.cgroup(name)
411            if not group:
412                self._log.warning('Configuration error: '
413                                  '[%s/%s] cgroup NOT available',
414                                  kind, name)
415                errors = True
416                continue
417            self._setup_group(tc, group)
418        return not errors
419
420    def _setup_group(self, tc, group):
421        kind = group.controller.kind
422        name = group.name
423        # Configure each required attribute
424        group.set(**tc['cgroups']['conf'][kind][name])
425
426    def _setup_files(self, tc):
427        if 'files' not in tc:
428            self._log.debug('\'files\' Configuration block not provided')
429            return True
430        for name, value in tc['files'].iteritems():
431            check = False
432            if name.startswith('!/'):
433                check = True
434                name = name[1:]
435            self._log.info('File Write(check=%s): \'%s\' -> \'%s\'',
436                         check, value, name)
437            try:
438                self.target.write_value(name, value, True)
439            except TargetError:
440                self._log.info('File Write Failed: \'%s\' -> \'%s\'',
441                         value, name)
442                if check:
443                    raise
444        return False
445
446    def _target_configure(self, tc):
447        self._print_header(
448                'configuring target for [{}] experiments'\
449                .format(tc['tag']))
450        self._setup_kernel(tc)
451        self._setup_sched_features(tc)
452        self._setup_cpufreq(tc)
453        self._setup_files(tc)
454        return self._setup_cgroups(tc)
455
456    def _target_conf_flag(self, tc, flag):
457        if 'flags' not in tc:
458            has_flag = False
459        else:
460            has_flag = flag in tc['flags']
461        self._log.debug('Check if target configuration [%s] has flag [%s]: %s',
462                        tc['tag'], flag, has_flag)
463        return has_flag
464
465    def _target_cleanup(self, tc):
466        if self._old_selinux_mode is not None:
467            self._log.info('Restoring target SELinux mode: %s',
468                           self._old_selinux_mode)
469            self.target.execute('setenforce ' + self._old_selinux_mode,
470                                as_root=True)
471
472################################################################################
473# Workload Setup and Execution
474################################################################################
475
476    def _wload_cpus(self, wl_idx, wlspec):
477        if not 'cpus' in wlspec['conf']:
478            return None
479        cpus = wlspec['conf']['cpus']
480
481        if type(cpus) == list:
482            return cpus
483        if type(cpus) == int:
484            return [cpus]
485
486        # SMP target (or not bL module loaded)
487        if not hasattr(self.target, 'bl'):
488            if 'first' in cpus:
489                return [ self.target.list_online_cpus()[0] ]
490            if 'last' in cpus:
491                return [ self.target.list_online_cpus()[-1] ]
492            return self.target.list_online_cpus()
493
494        # big.LITTLE target
495        if cpus.startswith('littles'):
496            if 'first' in cpus:
497                return [ self.target.bl.littles_online[0] ]
498            if 'last' in cpus:
499                return [ self.target.bl.littles_online[-1] ]
500            return self.target.bl.littles_online
501        if cpus.startswith('bigs'):
502            if 'first' in cpus:
503                return [ self.target.bl.bigs_online[0] ]
504            if 'last' in cpus:
505                return [ self.target.bl.bigs_online[-1] ]
506            return self.target.bl.bigs_online
507        raise ValueError('unsupported [{}] "cpus" value for [{}] '
508                         'workload specification'
509                         .format(cpus, wl_idx))
510
511    def _wload_task_idxs(self, wl_idx, tasks):
512        if type(tasks) == int:
513            return range(tasks)
514        if tasks == 'cpus':
515            return range(len(self.target.core_names))
516        if tasks == 'little':
517            return range(len([t
518                for t in self.target.core_names
519                if t == self.target.little_core]))
520        if tasks == 'big':
521            return range(len([t
522                for t in self.target.core_names
523                if t == self.target.big_core]))
524        raise ValueError('unsupported "tasks" value for [{}] RT-App '
525                         'workload specification'
526                         .format(wl_idx))
527
528    def _wload_rtapp(self, wl_idx, wlspec, cpus):
529        conf = wlspec['conf']
530        self._log.debug('Configuring [%s] rt-app...', conf['class'])
531
532        # Setup a default "empty" task name prefix
533        if 'prefix' not in conf:
534            conf['prefix'] = 'task_'
535
536        # Setup a default loadref CPU
537        loadref = None
538        if 'loadref' in wlspec:
539            loadref = wlspec['loadref']
540
541        if conf['class'] == 'profile':
542            params = {}
543            # Load each task specification
544            for task_name, task in conf['params'].items():
545                if task['kind'] not in wlgen.__dict__:
546                    self._log.error('RTA task of kind [%s] not supported',
547                                    task['kind'])
548                    raise ValueError('unsupported "kind" value for task [{}] '
549                                     'in RT-App workload specification'
550                                     .format(task))
551                task_ctor = getattr(wlgen, task['kind'])
552                num_tasks = task.get('tasks', 1)
553                task_idxs = self._wload_task_idxs(wl_idx, num_tasks)
554                for idx in task_idxs:
555                    idx_name = "_{}".format(idx) if len(task_idxs) > 1 else ""
556                    task_name_idx = conf['prefix'] + task_name + idx_name
557                    params[task_name_idx] = task_ctor(**task['params']).get()
558
559            rtapp = wlgen.RTA(self.target,
560                        wl_idx, calibration = self.te.calibration())
561            rtapp.conf(kind='profile', params=params, loadref=loadref,
562                       cpus=cpus, run_dir=self.te.run_dir,
563                       duration=conf.get('duration'))
564            return rtapp
565
566        if conf['class'] == 'periodic':
567            task_idxs = self._wload_task_idxs(wl_idx, conf['tasks'])
568            params = {}
569            for idx in task_idxs:
570                task = conf['prefix'] + str(idx)
571                params[task] = wlgen.Periodic(**conf['params']).get()
572            rtapp = wlgen.RTA(self.target,
573                        wl_idx, calibration = self.te.calibration())
574            rtapp.conf(kind='profile', params=params, loadref=loadref,
575                       cpus=cpus, run_dir=self.te.run_dir,
576                       duration=conf.get('duration'))
577            return rtapp
578
579        if conf['class'] == 'custom':
580            rtapp = wlgen.RTA(self.target,
581                        wl_idx, calibration = self.te.calib)
582            rtapp.conf(kind='custom',
583                    params=conf['json'],
584                    duration=conf['duration'],
585                    loadref=loadref,
586                    cpus=cpus, run_dir=self.te.run_dir)
587            return rtapp
588
589        raise ValueError('unsupported \'class\' value for [{}] '
590                         'RT-App workload specification'
591                         .format(wl_idx))
592
593    def _wload_perf_bench(self, wl_idx, wlspec, cpus):
594        conf = wlspec['conf']
595        self._log.debug('Configuring perf_message...')
596
597        if conf['class'] == 'messaging':
598            perf_bench = wlgen.PerfMessaging(self.target, wl_idx)
599            perf_bench.conf(**conf['params'])
600            return perf_bench
601
602        if conf['class'] == 'pipe':
603            perf_bench = wlgen.PerfPipe(self.target, wl_idx)
604            perf_bench.conf(**conf['params'])
605            return perf_bench
606
607        raise ValueError('unsupported "class" value for [{}] '
608                         'perf bench workload specification'
609                         .format(wl_idx))
610
611    def _wload_conf(self, wl_idx, wlspec):
612
613        # CPUS: setup execution on CPUs if required by configuration
614        cpus = self._wload_cpus(wl_idx, wlspec)
615
616        # CGroup: setup CGroups if requried by configuration
617        self._cgroup = self._default_cgroup
618        if 'cgroup' in wlspec:
619            if 'cgroups' not in self.target.modules:
620                raise RuntimeError('Target not supporting CGroups or CGroups '
621                                   'not configured for the current test configuration')
622            self._cgroup = wlspec['cgroup']
623
624        if wlspec['type'] == 'rt-app':
625            return self._wload_rtapp(wl_idx, wlspec, cpus)
626        if wlspec['type'] == 'perf_bench':
627            return self._wload_perf_bench(wl_idx, wlspec, cpus)
628
629
630        raise ValueError('unsupported "type" value for [{}] '
631                         'workload specification'
632                         .format(wl_idx))
633
634    def _wload_init(self, tc, wl_idx):
635        tc_idx = tc['tag']
636
637        # Configure the test workload
638        wlspec = self._experiments_conf['wloads'][wl_idx]
639        wload = self._wload_conf(wl_idx, wlspec)
640
641        # Keep track of platform configuration
642        test_dir = '{}/{}:{}:{}'\
643            .format(self.te.res_dir, wload.wtype, tc_idx, wl_idx)
644        os.makedirs(test_dir)
645        self.te.platform_dump(test_dir)
646
647        # Keep track of kernel configuration and version
648        config = self.target.config
649        with gzip.open(os.path.join(test_dir, 'kernel.config'), 'wb') as fh:
650            fh.write(config.text)
651        output = self.target.execute('{} uname -a'\
652                .format(self.target.busybox))
653        with open(os.path.join(test_dir, 'kernel.version'), 'w') as fh:
654            fh.write(output)
655
656        return wload, test_dir
657
658    def _wload_run(self, exp_idx, experiment):
659        tc = experiment.conf
660        wload = experiment.wload
661        tc_idx = tc['tag']
662
663        self._print_title('Experiment {}/{}, [{}:{}] {}/{}'\
664                .format(exp_idx, self._exp_count,
665                        tc_idx, experiment.wload_name,
666                        experiment.iteration, self._iterations))
667
668        # Setup local results folder
669        self._log.debug('out_dir set to [%s]', experiment.out_dir)
670        os.system('mkdir -p ' + experiment.out_dir)
671
672        # Freeze all userspace tasks that we don't need for running tests
673        need_thaw = False
674        if self._target_conf_flag(tc, 'freeze_userspace'):
675            need_thaw = self._freeze_userspace()
676
677        # FTRACE: start (if a configuration has been provided)
678        if self.te.ftrace and self._target_conf_flag(tc, 'ftrace'):
679            self._log.warning('FTrace events collection enabled')
680            self.te.ftrace.start()
681
682        # ENERGY: start sampling
683        if self.te.emeter:
684            self.te.emeter.reset()
685
686        # WORKLOAD: Run the configured workload
687        wload.run(out_dir=experiment.out_dir, cgroup=self._cgroup)
688
689        # ENERGY: collect measurements
690        if self.te.emeter:
691            self.te.emeter.report(experiment.out_dir)
692
693        # FTRACE: stop and collect measurements
694        if self.te.ftrace and self._target_conf_flag(tc, 'ftrace'):
695            self.te.ftrace.stop()
696
697            trace_file = experiment.out_dir + '/trace.dat'
698            self.te.ftrace.get_trace(trace_file)
699            self._log.info('Collected FTrace binary trace:')
700            self._log.info('   %s',
701                           trace_file.replace(self.te.res_dir, '<res_dir>'))
702
703            stats_file = experiment.out_dir + '/trace_stat.json'
704            self.te.ftrace.get_stats(stats_file)
705            self._log.info('Collected FTrace function profiling:')
706            self._log.info('   %s',
707                           stats_file.replace(self.te.res_dir, '<res_dir>'))
708
709        # Unfreeze the tasks we froze
710        if need_thaw:
711            self._thaw_userspace()
712
713        self._print_footer()
714
715    def _freeze_userspace(self):
716        if 'cgroups' not in self.target.modules:
717            raise RuntimeError(
718                'Failed to freeze userspace. Ensure "cgroups" module is listed '
719                'among modules in target/test configuration')
720        controllers = [s.name for s in self.target.cgroups.list_subsystems()]
721        if 'freezer' not in controllers:
722            self._log.warning('No freezer cgroup controller on target. '
723                              'Not freezing userspace')
724            return False
725
726        exclude = self.critical_tasks[self.te.target.os]
727        self._log.info('Freezing all tasks except: %s', ','.join(exclude))
728        self.te.target.cgroups.freeze(exclude)
729        return True
730
731
732    def _thaw_userspace(self):
733        self._log.info('Un-freezing userspace tasks')
734        self.te.target.cgroups.freeze(thaw=True)
735
736################################################################################
737# Utility Functions
738################################################################################
739
740    def _print_section(self, message):
741        self._log.info('')
742        self._log.info(FMT_SECTION)
743        self._log.info(message)
744        self._log.info(FMT_SECTION)
745
746    def _print_header(self, message):
747        self._log.info('')
748        self._log.info(FMT_HEADER)
749        self._log.info(message)
750
751    def _print_title(self, message):
752        self._log.info(FMT_TITLE)
753        self._log.info(message)
754
755    def _print_footer(self, message=None):
756        if message:
757            self._log.info(message)
758        self._log.info(FMT_FOOTER)
759
760
761################################################################################
762# Globals
763################################################################################
764
765# Regular expression for comments
766JSON_COMMENTS_RE = re.compile(
767    '(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
768    re.DOTALL | re.MULTILINE
769)
770
771# Target specific paths
772TGT_RUN_DIR = 'run_dir'
773
774# Logging formatters
775FMT_SECTION = r'{:#<80}'.format('')
776FMT_HEADER  = r'{:=<80}'.format('')
777FMT_TITLE   = r'{:~<80}'.format('')
778FMT_FOOTER  = r'{:-<80}'.format('')
779
780# vim :set tabstop=4 shiftwidth=4 expandtab
781