Skip to content

Commit 30ac951

Browse files
committed
trace/perf: Introduce a new, generic collector
Introduce a perf collector that is more generic than the previous one and which is expected to be able to handle all potential calls to perf (irrespective of the subcommand, flags, options or arguments being used).
1 parent 1edd0e1 commit 30ac951

File tree

1 file changed

+100
-101
lines changed

1 file changed

+100
-101
lines changed

devlib/trace/perf.py

+100-101
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 ARM Limited
1+
# Copyright 2018-2019 ARM Limited
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -11,131 +11,130 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
#
1514

15+
# pylint: disable=missing-docstring
1616

17+
import collections
1718
import os
18-
import re
19-
from past.builtins import basestring, zip
19+
import sys
2020

21+
from devlib.utils.cli import Command
2122
from devlib.host import PACKAGE_BIN_DIRECTORY
2223
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*$')
2924

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
3929

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.
4430

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):
4832

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)
5148

52-
perf_events = ['migrations', 'cs']
49+
def stat_commands(self):
50+
return {label: self[label] for label in self._stat_command_labels}
5351

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()}
5654

57-
perf list
5855

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`.
6678
"""
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
7382
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({})
8986

9087
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)
9392

94-
self.commands = self._build_commands()
93+
self.kill_sleep = False
9594

9695
def reset(self):
96+
super(PerfCollector, self).reset()
97+
self.target.remove(self.working_directory())
9798
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)
10199

102100
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
105108

106109
def stop(self):
110+
super(PerfCollector, self).stop()
107111
self.target.killall('perf', signal='SIGINT',
108112
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

Comments
 (0)