|
1 |
| -# Copyright 2018 ARM Limited |
| 1 | +# Copyright 2018-2019 ARM Limited |
2 | 2 | #
|
3 | 3 | # Licensed under the Apache License, Version 2.0 (the "License");
|
4 | 4 | # you may not use this file except in compliance with the License.
|
|
11 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 | 12 | # See the License for the specific language governing permissions and
|
13 | 13 | # limitations under the License.
|
14 |
| -# |
15 | 14 |
|
| 15 | +# pylint: disable=missing-docstring |
16 | 16 |
|
| 17 | +import collections |
17 | 18 | import os
|
18 |
| -import re |
19 |
| -from past.builtins import basestring, zip |
| 19 | +import sys |
20 | 20 |
|
| 21 | +from devlib.utils.cli import Command |
21 | 22 | from devlib.host import PACKAGE_BIN_DIRECTORY
|
22 | 23 | from devlib.trace import TraceCollector
|
23 |
| -from devlib.utils.misc import ensure_file_directory_exists as _f |
24 |
| - |
25 |
| - |
26 |
| -PERF_COMMAND_TEMPLATE = '{} stat {} {} sleep 1000 > {} 2>&1 ' |
27 |
| - |
28 |
| -PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') |
29 | 24 |
|
30 |
| -DEFAULT_EVENTS = [ |
31 |
| - 'migrations', |
32 |
| - 'cs', |
33 |
| -] |
34 |
| - |
35 |
| - |
36 |
| -class PerfCollector(TraceCollector): |
37 |
| - """ |
38 |
| - Perf is a Linux profiling with performance counters. |
| 25 | +if sys.version_info >= (3, 0): |
| 26 | + from shlex import quote |
| 27 | +else: |
| 28 | + from pipes import quote |
39 | 29 |
|
40 |
| - Performance counters are CPU hardware registers that count hardware events |
41 |
| - such as instructions executed, cache-misses suffered, or branches |
42 |
| - mispredicted. They form a basis for profiling applications to trace dynamic |
43 |
| - control flow and identify hotspots. |
44 | 30 |
|
45 |
| - pref accepts options and events. If no option is given the default '-a' is |
46 |
| - used. For events, the default events are migrations and cs. They both can |
47 |
| - be specified in the config file. |
| 31 | +class PerfCommandDict(collections.OrderedDict): |
48 | 32 |
|
49 |
| - Events must be provided as a list that contains them and they will look like |
50 |
| - this :: |
| 33 | + def __init__(self, yaml_dict): |
| 34 | + super().__init__() |
| 35 | + self._stat_command_labels = set() |
| 36 | + if isinstance(yaml_dict, self.__class__): |
| 37 | + for key, val in yaml_dict.items(): |
| 38 | + self[key] = val |
| 39 | + return |
| 40 | + yaml_dict_copy = yaml_dict.copy() |
| 41 | + for label, parameters in yaml_dict_copy.items(): |
| 42 | + self[label] = Command(kwflags_join=',', |
| 43 | + kwflags_sep='=', |
| 44 | + end_of_options='--', |
| 45 | + **parameters) |
| 46 | + if 'stat'in parameters['command']: |
| 47 | + self._stat_command_labels.add(label) |
51 | 48 |
|
52 |
| - perf_events = ['migrations', 'cs'] |
| 49 | + def stat_commands(self): |
| 50 | + return {label: self[label] for label in self._stat_command_labels} |
53 | 51 |
|
54 |
| - Events can be obtained by typing the following in the command line on the |
55 |
| - device :: |
| 52 | + def as_strings(self): |
| 53 | + return {label: str(cmd) for label, cmd in self.items()} |
56 | 54 |
|
57 |
| - perf list |
58 | 55 |
|
59 |
| - Whereas options, they can be provided as a single string as following :: |
60 |
| -
|
61 |
| - perf_options = '-a -i' |
62 |
| -
|
63 |
| - Options can be obtained by running the following in the command line :: |
64 |
| -
|
65 |
| - man perf-stat |
| 56 | +class PerfCollector(TraceCollector): |
| 57 | + """Perf is a Linux profiling tool based on performance counters. |
| 58 | +
|
| 59 | + Performance counters are typically CPU hardware registers (found in the |
| 60 | + Performance Monitoring Unit) that count hardware events such as |
| 61 | + instructions executed, cache-misses suffered, or branches mispredicted. |
| 62 | + Because each ``event`` corresponds to a hardware counter, the maximum |
| 63 | + number of events that can be tracked is imposed by the available hardware. |
| 64 | +
|
| 65 | + By extension, performance counters, in the context of ``perf``, also refer |
| 66 | + to so-called "software counters" representing events that can be tracked by |
| 67 | + the OS kernel (e.g. context switches). As these are software events, the |
| 68 | + counters are kept in RAM and the hardware virtually imposes no limit on the |
| 69 | + number that can be used. |
| 70 | +
|
| 71 | + This collector calls ``perf`` ``commands`` to capture a run of a workload. |
| 72 | + The ``pre_commands`` and ``post_commands`` are provided to suit those |
| 73 | + ``perf`` commands that don't actually capture data (``list``, ``config``, |
| 74 | + ``report``, ...). |
| 75 | +
|
| 76 | + ``pre_commands``, ``commands`` and ``post_commands`` are instances of |
| 77 | + :class:`PerfCommandDict`. |
66 | 78 | """
|
67 |
| - |
68 |
| - def __init__(self, target, |
69 |
| - events=None, |
70 |
| - optionstring=None, |
71 |
| - labels=None, |
72 |
| - force_install=False): |
| 79 | + def __init__(self, target, force_install=False, pre_commands=None, |
| 80 | + commands=None, post_commands=None): |
| 81 | + # pylint: disable=too-many-arguments |
73 | 82 | super(PerfCollector, self).__init__(target)
|
74 |
| - self.events = events if events else DEFAULT_EVENTS |
75 |
| - self.force_install = force_install |
76 |
| - self.labels = labels |
77 |
| - |
78 |
| - # Validate parameters |
79 |
| - if isinstance(optionstring, list): |
80 |
| - self.optionstrings = optionstring |
81 |
| - else: |
82 |
| - self.optionstrings = [optionstring] |
83 |
| - if self.events and isinstance(self.events, basestring): |
84 |
| - self.events = [self.events] |
85 |
| - if not self.labels: |
86 |
| - self.labels = ['perf_{}'.format(i) for i in range(len(self.optionstrings))] |
87 |
| - if len(self.labels) != len(self.optionstrings): |
88 |
| - raise ValueError('The number of labels must match the number of optstrings provided for perf.') |
| 83 | + self.pre_commands = pre_commands or PerfCommandDict({}) |
| 84 | + self.commands = commands or PerfCommandDict({}) |
| 85 | + self.post_commands = post_commands or PerfCommandDict({}) |
89 | 86 |
|
90 | 87 | self.binary = self.target.get_installed('perf')
|
91 |
| - if self.force_install or not self.binary: |
92 |
| - self.binary = self._deploy_perf() |
| 88 | + if force_install or not self.binary: |
| 89 | + host_binary = os.path.join(PACKAGE_BIN_DIRECTORY, |
| 90 | + self.target.abi, 'perf') |
| 91 | + self.binary = self.target.install(host_binary) |
93 | 92 |
|
94 |
| - self.commands = self._build_commands() |
| 93 | + self.kill_sleep = False |
95 | 94 |
|
96 | 95 | def reset(self):
|
| 96 | + super(PerfCollector, self).reset() |
| 97 | + self.target.remove(self.working_directory()) |
97 | 98 | self.target.killall('perf', as_root=self.target.is_rooted)
|
98 |
| - for label in self.labels: |
99 |
| - filepath = self._get_target_outfile(label) |
100 |
| - self.target.remove(filepath) |
101 | 99 |
|
102 | 100 | def start(self):
|
103 |
| - for command in self.commands: |
104 |
| - self.target.kick_off(command) |
| 101 | + super(PerfCollector, self).start() |
| 102 | + for label, command in self.pre_commands.items(): |
| 103 | + self.execute(str(command), label) |
| 104 | + for label, command in self.commands.items(): |
| 105 | + self.kick_off(str(command), label) |
| 106 | + if 'sleep' in str(command): |
| 107 | + self.kill_sleep = True |
105 | 108 |
|
106 | 109 | def stop(self):
|
| 110 | + super(PerfCollector, self).stop() |
107 | 111 | self.target.killall('perf', signal='SIGINT',
|
108 | 112 | as_root=self.target.is_rooted)
|
109 |
| - # perf doesn't transmit the signal to its sleep call so handled here: |
110 |
| - self.target.killall('sleep', as_root=self.target.is_rooted) |
111 |
| - # NB: we hope that no other "important" sleep is on-going |
112 |
| - |
113 |
| - # pylint: disable=arguments-differ |
114 |
| - def get_trace(self, outdir): |
115 |
| - for label in self.labels: |
116 |
| - target_file = self._get_target_outfile(label) |
117 |
| - host_relpath = os.path.basename(target_file) |
118 |
| - host_file = _f(os.path.join(outdir, host_relpath)) |
119 |
| - self.target.pull(target_file, host_file) |
120 |
| - |
121 |
| - def _deploy_perf(self): |
122 |
| - host_executable = os.path.join(PACKAGE_BIN_DIRECTORY, |
123 |
| - self.target.abi, 'perf') |
124 |
| - return self.target.install(host_executable) |
125 |
| - |
126 |
| - def _build_commands(self): |
127 |
| - commands = [] |
128 |
| - for opts, label in zip(self.optionstrings, self.labels): |
129 |
| - commands.append(self._build_perf_command(opts, self.events, label)) |
130 |
| - return commands |
131 |
| - |
132 |
| - def _get_target_outfile(self, label): |
133 |
| - return self.target.get_workpath('{}.out'.format(label)) |
134 |
| - |
135 |
| - def _build_perf_command(self, options, events, label): |
136 |
| - event_string = ' '.join(['-e {}'.format(e) for e in events]) |
137 |
| - command = PERF_COMMAND_TEMPLATE.format(self.binary, |
138 |
| - options or '', |
139 |
| - event_string, |
140 |
| - self._get_target_outfile(label)) |
141 |
| - return command |
| 113 | + if self.kill_sleep: |
| 114 | + self.target.killall('sleep', as_root=self.target.is_rooted) |
| 115 | + for label, command in self.post_commands.items(): |
| 116 | + self.execute(str(command), label) |
| 117 | + |
| 118 | + def kick_off(self, command, label=None): |
| 119 | + directory = quote(self.working_directory(label or 'default')) |
| 120 | + return self.target.kick_off('mkdir -p {0} && cd {0} && {1} {2}' |
| 121 | + .format(directory, self.binary, command), |
| 122 | + as_root=self.target.is_rooted) |
| 123 | + |
| 124 | + def execute(self, command, label=None): |
| 125 | + directory = quote(self.working_directory(label or 'default')) |
| 126 | + return self.target.execute('mkdir -p {0} && cd {0} && {1} {2}' |
| 127 | + .format(directory, self.binary, command), |
| 128 | + as_root=self.target.is_rooted) |
| 129 | + |
| 130 | + def working_directory(self, label=None): |
| 131 | + wdir = self.target.path.join(self.target.working_directory, |
| 132 | + 'instrument', 'perf') |
| 133 | + return wdir if label is None else self.target.path.join(wdir, label) |
| 134 | + |
| 135 | + def get_traces(self, host_outdir): |
| 136 | + self.target.pull(self.working_directory(), host_outdir, |
| 137 | + as_root=self.target.is_rooted) |
| 138 | + |
| 139 | + def get_trace(self, outfile): |
| 140 | + raise NotImplementedError |
0 commit comments