|
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