1
0
mirror of https://github.com/ARM-software/devlib.git synced 2024-10-06 02:40:50 +01:00

Add simpleperf type to perf TraceCollector

* Added simperf type to trace collector
* Added record command to allow for perf/simpleperf
  recording and reporting
This commit is contained in:
Robert Freeman 2019-08-27 14:26:32 +01:00 committed by Marc Bonnici
parent 9e6cfde832
commit 5e69f06d77
5 changed files with 128 additions and 29 deletions

BIN
devlib/bin/arm/simpleperf Executable file

Binary file not shown.

BIN
devlib/bin/arm64/simpleperf Executable file

Binary file not shown.

BIN
devlib/bin/x86/simpleperf Executable file

Binary file not shown.

BIN
devlib/bin/x86_64/simpleperf Executable file

Binary file not shown.

View File

@ -13,9 +13,9 @@
# limitations under the License. # limitations under the License.
# #
import os import os
import re import re
import time
from past.builtins import basestring, zip from past.builtins import basestring, zip
from devlib.host import PACKAGE_BIN_DIRECTORY from devlib.host import PACKAGE_BIN_DIRECTORY
@ -23,19 +23,34 @@ from devlib.trace import TraceCollector
from devlib.utils.misc import ensure_file_directory_exists as _f from devlib.utils.misc import ensure_file_directory_exists as _f
PERF_COMMAND_TEMPLATE = '{} stat {} {} sleep 1000 > {} 2>&1 ' PERF_COMMAND_TEMPLATE = '{binary} {command} {options} {events} sleep 1000 > {outfile} 2>&1 '
PERF_REPORT_COMMAND_TEMPLATE= '{binary} report {options} -i {datafile} > {outfile} 2>&1 '
PERF_RECORD_COMMAND_TEMPLATE= '{binary} record {options} {events} -o {outfile}'
PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') PERF_DEFAULT_EVENTS = [
'cpu-migrations',
DEFAULT_EVENTS = [ 'context-switches',
'migrations',
'cs',
] ]
SIMPLEPERF_DEFAULT_EVENTS = [
'raw-cpu-cycles',
'raw-l1-dcache',
'raw-l1-dcache-refill',
'raw-br-mis-pred',
'raw-instruction-retired',
]
DEFAULT_EVENTS = {'perf':PERF_DEFAULT_EVENTS, 'simpleperf':SIMPLEPERF_DEFAULT_EVENTS}
class PerfCollector(TraceCollector): class PerfCollector(TraceCollector):
""" """
Perf is a Linux profiling with performance counters. Perf is a Linux profiling with performance counters.
Simpleperf is an Android profiling tool with performance counters.
It is highly recomended to use perf_type = simpleperf when using this instrument
on android devices, since it recognises android symbols in record mode and is much more stable
when reporting record .data files. For more information see simpleperf documentation at:
https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md
Performance counters are CPU hardware registers that count hardware events Performance counters are CPU hardware registers that count hardware events
such as instructions executed, cache-misses suffered, or branches such as instructions executed, cache-misses suffered, or branches
@ -43,7 +58,8 @@ class PerfCollector(TraceCollector):
control flow and identify hotspots. control flow and identify hotspots.
pref accepts options and events. If no option is given the default '-a' is pref accepts options and events. If no option is given the default '-a' is
used. For events, the default events are migrations and cs. They both can used. For events, the default events are migrations and cs for perf and raw-cpu-cycles,
raw-l1-dcache, raw-l1-dcache-refill, raw-instructions-retired. They both can
be specified in the config file. be specified in the config file.
Events must be provided as a list that contains them and they will look like Events must be provided as a list that contains them and they will look like
@ -55,6 +71,7 @@ class PerfCollector(TraceCollector):
device :: device ::
perf list perf list
simpleperf list
Whereas options, they can be provided as a single string as following :: Whereas options, they can be provided as a single string as following ::
@ -65,38 +82,59 @@ class PerfCollector(TraceCollector):
man perf-stat man perf-stat
""" """
def __init__(self, target, def __init__(self,
target,
perf_type='perf',
command='stat',
events=None, events=None,
optionstring=None, optionstring=None,
report_options=None,
labels=None, labels=None,
force_install=False): force_install=False):
super(PerfCollector, self).__init__(target) super(PerfCollector, self).__init__(target)
self.events = events if events else DEFAULT_EVENTS
self.force_install = force_install self.force_install = force_install
self.labels = labels self.labels = labels
self.report_options = report_options
# Validate parameters # Validate parameters
if isinstance(optionstring, list): if isinstance(optionstring, list):
self.optionstrings = optionstring self.optionstrings = optionstring
else: else:
self.optionstrings = [optionstring] self.optionstrings = [optionstring]
if self.events and isinstance(self.events, basestring): if perf_type in ['perf', 'simpleperf']:
self.perf_type = perf_type
else:
raise ValueError('Invalid perf type: {}, must be perf or simpleperf'.format(perf_type))
if not events:
self.events = DEFAULT_EVENTS[self.perf_type]
else:
self.events = events
if isinstance(self.events, basestring):
self.events = [self.events] self.events = [self.events]
if not self.labels: if not self.labels:
self.labels = ['perf_{}'.format(i) for i in range(len(self.optionstrings))] self.labels = ['perf_{}'.format(i) for i in range(len(self.optionstrings))]
if len(self.labels) != len(self.optionstrings): if len(self.labels) != len(self.optionstrings):
raise ValueError('The number of labels must match the number of optstrings provided for perf.') raise ValueError('The number of labels must match the number of optstrings provided for perf.')
if command in ['stat', 'record']:
self.command = command
else:
raise ValueError('Unsupported perf command, must be stat or record')
self.binary = self.target.get_installed('perf') self.binary = self.target.get_installed(self.perf_type)
if self.force_install or not self.binary: if self.force_install or not self.binary:
self.binary = self._deploy_perf() self.binary = self._deploy_perf()
self._validate_events(self.events)
self.commands = self._build_commands() self.commands = self._build_commands()
def reset(self): def reset(self):
self.target.killall('perf', as_root=self.target.is_rooted) self.target.killall(self.perf_type, as_root=self.target.is_rooted)
self.target.remove(self.target.get_workpath('TemporaryFile*'))
for label in self.labels: for label in self.labels:
filepath = self._get_target_outfile(label) filepath = self._get_target_file(label, 'data')
self.target.remove(filepath)
filepath = self._get_target_file(label, 'rpt')
self.target.remove(filepath) self.target.remove(filepath)
def start(self): def start(self):
@ -104,7 +142,7 @@ class PerfCollector(TraceCollector):
self.target.kick_off(command) self.target.kick_off(command)
def stop(self): def stop(self):
self.target.killall('perf', signal='SIGINT', self.target.killall(self.perf_type, signal='SIGINT',
as_root=self.target.is_rooted) as_root=self.target.is_rooted)
# perf doesn't transmit the signal to its sleep call so handled here: # perf doesn't transmit the signal to its sleep call so handled here:
self.target.killall('sleep', as_root=self.target.is_rooted) self.target.killall('sleep', as_root=self.target.is_rooted)
@ -113,29 +151,90 @@ class PerfCollector(TraceCollector):
# pylint: disable=arguments-differ # pylint: disable=arguments-differ
def get_trace(self, outdir): def get_trace(self, outdir):
for label in self.labels: for label in self.labels:
target_file = self._get_target_outfile(label) if self.command == 'record':
host_relpath = os.path.basename(target_file) self._wait_for_data_file_write(label, outdir)
host_file = _f(os.path.join(outdir, host_relpath)) self._pull_target_file_to_host(label, 'rpt', outdir)
self.target.pull(target_file, host_file) else:
self._pull_target_file_to_host(label, 'out', outdir)
def _deploy_perf(self): def _deploy_perf(self):
host_executable = os.path.join(PACKAGE_BIN_DIRECTORY, host_executable = os.path.join(PACKAGE_BIN_DIRECTORY,
self.target.abi, 'perf') self.target.abi, self.perf_type)
return self.target.install(host_executable) return self.target.install(host_executable)
def _get_target_file(self, label, extension):
return self.target.get_workpath('{}.{}'.format(label, extension))
def _build_commands(self): def _build_commands(self):
commands = [] commands = []
for opts, label in zip(self.optionstrings, self.labels): for opts, label in zip(self.optionstrings, self.labels):
commands.append(self._build_perf_command(opts, self.events, label)) if self.command == 'stat':
commands.append(self._build_perf_stat_command(opts, self.events, label))
else:
commands.append(self._build_perf_record_command(opts, label))
return commands return commands
def _get_target_outfile(self, label): def _build_perf_stat_command(self, options, events, label):
return self.target.get_workpath('{}.out'.format(label))
def _build_perf_command(self, options, events, label):
event_string = ' '.join(['-e {}'.format(e) for e in events]) event_string = ' '.join(['-e {}'.format(e) for e in events])
command = PERF_COMMAND_TEMPLATE.format(self.binary, command = PERF_COMMAND_TEMPLATE.format(binary = self.binary,
options or '', command = self.command,
event_string, options = options or '',
self._get_target_outfile(label)) events = event_string,
outfile = self._get_target_file(label, 'out'))
return command return command
def _build_perf_report_command(self, report_options, label):
command = PERF_REPORT_COMMAND_TEMPLATE.format(binary=self.binary,
options=report_options or '',
datafile=self._get_target_file(label, 'data'),
outfile=self._get_target_file(label, 'rpt'))
return command
def _build_perf_record_command(self, options, label):
event_string = ' '.join(['-e {}'.format(e) for e in self.events])
command = PERF_RECORD_COMMAND_TEMPLATE.format(binary=self.binary,
options=options or '',
events=event_string,
outfile=self._get_target_file(label, 'data'))
return command
def _pull_target_file_to_host(self, label, extension, outdir):
target_file = self._get_target_file(label, extension)
host_relpath = os.path.basename(target_file)
host_file = _f(os.path.join(outdir, host_relpath))
self.target.pull(target_file, host_file)
def _wait_for_data_file_write(self, label, outdir):
data_file_finished_writing = False
max_tries = 80
current_tries = 0
while not data_file_finished_writing:
files = self.target.execute('cd {} && ls'.format(self.target.get_workpath('')))
# Perf stores data in tempory files whilst writing to data output file. Check if they have been removed.
if 'TemporaryFile' in files and current_tries <= max_tries:
time.sleep(0.25)
current_tries += 1
else:
if current_tries >= max_tries:
self.logger.warning('''writing {}.data file took longer than expected,
file may not have written correctly'''.format(label))
data_file_finished_writing = True
report_command = self._build_perf_report_command(self.report_options, label)
self.target.execute(report_command)
def _validate_events(self, events):
available_events_string = self.target.execute('{} list'.format(self.perf_type))
available_events = available_events_string.splitlines()
for available_event in available_events:
if available_event == '':
continue
if 'OR' in available_event:
available_events.append(available_event.split('OR')[1])
available_events[available_events.index(available_event)] = available_event.split()[0].strip()
# Raw hex event codes can also be passed in that do not appear on perf/simpleperf list, prefixed with 'r'
raw_event_code_regex = re.compile(r"^r(0x|0X)?[A-Fa-f0-9]+$")
for event in events:
if event in available_events or re.match(raw_event_code_regex, event):
continue
else:
raise ValueError('Event: {} is not in available event list for {}'.format(event, self.perf_type))