mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 10:10:46 +00: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:
parent
9e6cfde832
commit
5e69f06d77
BIN
devlib/bin/arm/simpleperf
Executable file
BIN
devlib/bin/arm/simpleperf
Executable file
Binary file not shown.
BIN
devlib/bin/arm64/simpleperf
Executable file
BIN
devlib/bin/arm64/simpleperf
Executable file
Binary file not shown.
BIN
devlib/bin/x86/simpleperf
Executable file
BIN
devlib/bin/x86/simpleperf
Executable file
Binary file not shown.
BIN
devlib/bin/x86_64/simpleperf
Executable file
BIN
devlib/bin/x86_64/simpleperf
Executable file
Binary file not shown.
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user