mirror of
https://github.com/ARM-software/workload-automation.git
synced 2025-01-18 12:06:08 +00:00
Add simpleperf type to perf instrument
* Added simpleperf type to perf instrument as it's more stable on Android devices. * Added record command to instrument * Added output processing for simpleperf
This commit is contained in:
parent
2f231b5ce5
commit
6beac11ee2
@ -15,13 +15,14 @@
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
|
||||
from devlib.trace.perf import PerfCollector
|
||||
|
||||
from wa import Instrument, Parameter
|
||||
from wa.utils.types import list_or_string, list_of_strs
|
||||
from wa.utils.types import list_or_string, list_of_strs, numeric
|
||||
|
||||
PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$')
|
||||
|
||||
@ -31,29 +32,40 @@ class PerfInstrument(Instrument):
|
||||
name = 'perf'
|
||||
description = """
|
||||
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
|
||||
such as instructions executed, cache-misses suffered, or branches
|
||||
mispredicted. They form a basis for profiling applications to trace dynamic
|
||||
control flow and identify hotspots.
|
||||
|
||||
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
|
||||
be specified in the config file.
|
||||
perf accepts options and events. If no option is given the default '-a' is
|
||||
used. For events, the default events for perf are migrations and cs. The default
|
||||
events for simpleperf are raw-cpu-cycles, raw-l1-dcache, raw-l1-dcache-refill, raw-instructions-retired.
|
||||
They both can be specified in the config file.
|
||||
|
||||
Events must be provided as a list that contains them and they will look like
|
||||
this ::
|
||||
|
||||
perf_events = ['migrations', 'cs']
|
||||
(for perf_type = perf ) perf_events = ['migrations', 'cs']
|
||||
(for perf_type = simpleperf) perf_events = ['raw-cpu-cycles', 'raw-l1-dcache']
|
||||
|
||||
|
||||
Events can be obtained by typing the following in the command line on the
|
||||
device ::
|
||||
|
||||
perf list
|
||||
simpleperf list
|
||||
|
||||
Whereas options, they can be provided as a single string as following ::
|
||||
|
||||
perf_options = '-a -i'
|
||||
perf_options = '--app com.adobe.reader'
|
||||
|
||||
Options can be obtained by running the following in the command line ::
|
||||
|
||||
@ -61,21 +73,32 @@ class PerfInstrument(Instrument):
|
||||
"""
|
||||
|
||||
parameters = [
|
||||
Parameter('events', kind=list_of_strs, default=['migrations', 'cs'],
|
||||
global_alias='perf_events',
|
||||
constraint=(lambda x: x, 'must not be empty.'),
|
||||
Parameter('perf_type', kind=str, allowed_values=['perf', 'simpleperf'], default='perf',
|
||||
global_alias='perf_type', description="""Specifies which type of perf binaries
|
||||
to install. Use simpleperf for collecting perf data on android systems."""),
|
||||
Parameter('command', kind=str, default='stat', allowed_values=['stat', 'record'],
|
||||
global_alias='perf_command', description="""Specifies which perf command to use. If in record mode
|
||||
report command will also be executed and results pulled from target along with raw data
|
||||
file"""),
|
||||
Parameter('events', kind=list_of_strs, global_alias='perf_events',
|
||||
description="""Specifies the events to be counted."""),
|
||||
Parameter('optionstring', kind=list_or_string, default='-a',
|
||||
global_alias='perf_options',
|
||||
description="""Specifies options to be used for the perf command. This
|
||||
may be a list of option strings, in which case, multiple instances of perf
|
||||
will be kicked off -- one for each option string. This may be used to e.g.
|
||||
collected different events from different big.LITTLE clusters.
|
||||
collected different events from different big.LITTLE clusters. In order to
|
||||
profile a particular application process for android with simpleperf use
|
||||
the --app option e.g. --app com.adobe.reader
|
||||
"""),
|
||||
Parameter('report_option_string', kind=str, global_alias='perf_report_options', default=None,
|
||||
description="""Specifies options to be used to gather report when record command
|
||||
is used. It's highly recommended to use perf_type simpleperf when running on
|
||||
android devices as reporting options are unstable with perf"""),
|
||||
Parameter('labels', kind=list_of_strs, default=None,
|
||||
global_alias='perf_labels',
|
||||
description="""Provides labels for pref output. If specified, the number of
|
||||
labels must match the number of ``optionstring``\ s.
|
||||
description="""Provides labels for perf/simpleperf output for each optionstring.
|
||||
If specified, the number of labels must match the number of ``optionstring``\ s.
|
||||
"""),
|
||||
Parameter('force_install', kind=bool, default=False,
|
||||
description="""
|
||||
@ -89,8 +112,11 @@ class PerfInstrument(Instrument):
|
||||
|
||||
def initialize(self, context):
|
||||
self.collector = PerfCollector(self.target,
|
||||
self.perf_type,
|
||||
self.command,
|
||||
self.events,
|
||||
self.optionstring,
|
||||
self.report_option_string,
|
||||
self.labels,
|
||||
self.force_install)
|
||||
|
||||
@ -105,9 +131,30 @@ class PerfInstrument(Instrument):
|
||||
|
||||
def update_output(self, context):
|
||||
self.logger.info('Extracting reports from target...')
|
||||
outdir = os.path.join(context.output_directory, 'perf')
|
||||
outdir = os.path.join(context.output_directory, self.perf_type)
|
||||
self.collector.get_trace(outdir)
|
||||
|
||||
if self.perf_type == 'perf':
|
||||
self._process_perf_output(context, outdir)
|
||||
else:
|
||||
self._process_simpleperf_output(context, outdir)
|
||||
|
||||
def teardown(self, context):
|
||||
self.collector.reset()
|
||||
|
||||
def _process_perf_output(self, context, outdir):
|
||||
if self.command == 'stat':
|
||||
self._process_perf_stat_output(context, outdir)
|
||||
elif self.command == 'record':
|
||||
self._process_perf_record_output(context, outdir)
|
||||
|
||||
def _process_simpleperf_output(self, context, outdir):
|
||||
if self.command == 'stat':
|
||||
self._process_simpleperf_stat_output(context, outdir)
|
||||
elif self.command == 'record':
|
||||
self._process_simpleperf_record_output(context, outdir)
|
||||
|
||||
def _process_perf_stat_output(self, context, outdir):
|
||||
for host_file in os.listdir(outdir):
|
||||
label = host_file.split('.out')[0]
|
||||
host_file_path = os.path.join(outdir, host_file)
|
||||
@ -118,21 +165,150 @@ class PerfInstrument(Instrument):
|
||||
if 'Performance counter stats' in line:
|
||||
in_results_section = True
|
||||
next(fh) # skip the following blank line
|
||||
if in_results_section:
|
||||
if not line.strip(): # blank line
|
||||
in_results_section = False
|
||||
break
|
||||
else:
|
||||
line = line.split('#')[0] # comment
|
||||
match = PERF_COUNT_REGEX.search(line)
|
||||
if match:
|
||||
classifiers = {}
|
||||
cpu = match.group(1)
|
||||
if cpu is not None:
|
||||
classifiers['cpu'] = int(cpu.replace('CPU', ''))
|
||||
count = int(match.group(2))
|
||||
metric = '{}_{}'.format(label, match.group(3))
|
||||
context.add_metric(metric, count, classifiers=classifiers)
|
||||
if not in_results_section:
|
||||
continue
|
||||
if not line.strip(): # blank line
|
||||
in_results_section = False
|
||||
break
|
||||
else:
|
||||
self._add_perf_stat_metric(line, label, context)
|
||||
|
||||
def teardown(self, context):
|
||||
self.collector.reset()
|
||||
@staticmethod
|
||||
def _add_perf_stat_metric(line, label, context):
|
||||
line = line.split('#')[0] # comment
|
||||
match = PERF_COUNT_REGEX.search(line)
|
||||
if not match:
|
||||
return
|
||||
classifiers = {}
|
||||
cpu = match.group(1)
|
||||
if cpu is not None:
|
||||
classifiers['cpu'] = int(cpu.replace('CPU', ''))
|
||||
count = int(match.group(2))
|
||||
metric = '{}_{}'.format(label, match.group(3))
|
||||
context.add_metric(metric, count, classifiers=classifiers)
|
||||
|
||||
def _process_perf_record_output(self, context, outdir):
|
||||
for host_file in os.listdir(outdir):
|
||||
label, ext = os.path.splitext(host_file)
|
||||
context.add_artifact(label, os.path.join(outdir, host_file), 'raw')
|
||||
column_headers = []
|
||||
column_header_indeces = []
|
||||
event_type = ''
|
||||
if ext == '.rpt':
|
||||
with open(os.path.join(outdir, host_file)) as fh:
|
||||
for line in fh:
|
||||
words = line.split()
|
||||
if not words:
|
||||
continue
|
||||
event_type = self._get_report_event_type(words, event_type)
|
||||
column_headers = self._get_report_column_headers(column_headers, words, 'perf')
|
||||
for column_header in column_headers:
|
||||
column_header_indeces.append(line.find(column_header))
|
||||
self._add_report_metric(column_headers,
|
||||
column_header_indeces,
|
||||
line,
|
||||
words,
|
||||
context,
|
||||
event_type,
|
||||
label)
|
||||
|
||||
@staticmethod
|
||||
def _get_report_event_type(words, event_type):
|
||||
if words[0] != '#':
|
||||
return event_type
|
||||
if len(words) == 6 and words[4] == 'event':
|
||||
event_type = words[5]
|
||||
event_type = event_type.strip("'")
|
||||
return event_type
|
||||
|
||||
def _process_simpleperf_stat_output(self, context, outdir):
|
||||
labels = []
|
||||
for host_file in os.listdir(outdir):
|
||||
labels.append(host_file.split('.out')[0])
|
||||
for opts, label in zip(self.optionstring, labels):
|
||||
stat_file = os.path.join(outdir, '{}{}'.format(label, '.out'))
|
||||
if '--csv' in opts:
|
||||
self._process_simpleperf_stat_from_csv(stat_file, context, label)
|
||||
else:
|
||||
self._process_simpleperf_stat_from_raw(stat_file, context, label)
|
||||
|
||||
@staticmethod
|
||||
def _process_simpleperf_stat_from_csv(stat_file, context, label):
|
||||
with open(stat_file) as csv_file:
|
||||
readCSV = csv.reader(csv_file, delimiter=',')
|
||||
line_num = 0
|
||||
for row in readCSV:
|
||||
if line_num > 0 and 'Total test time' not in row:
|
||||
classifiers = {'scaled from(%)': row[len(row) - 2].replace('(', '').replace(')', '').replace('%', '')}
|
||||
context.add_metric('{}_{}'.format(label, row[1]), row[0], 'count', classifiers=classifiers)
|
||||
line_num += 1
|
||||
|
||||
@staticmethod
|
||||
def _process_simpleperf_stat_from_raw(stat_file, context, label):
|
||||
with open(stat_file) as fh:
|
||||
for line in fh:
|
||||
if '#' in line:
|
||||
tmp_line = line.split('#')[0]
|
||||
tmp_line = line.strip()
|
||||
count, metric = tmp_line.split(' ')[0], tmp_line.split(' ')[2]
|
||||
count = int(count.replace(',', ''))
|
||||
scaled_percentage = line.split('(')[1].strip().replace(')', '').replace('%', '')
|
||||
scaled_percentage = int(scaled_percentage)
|
||||
metric = '{}_{}'.format(label, metric)
|
||||
context.add_metric(metric, count, 'count', classifiers={'scaled from(%)': scaled_percentage})
|
||||
|
||||
def _process_simpleperf_record_output(self, context, outdir):
|
||||
for host_file in os.listdir(outdir):
|
||||
label, ext = os.path.splitext(host_file)
|
||||
context.add_artifact(label, os.path.join(outdir, host_file), 'raw')
|
||||
if ext != '.rpt':
|
||||
continue
|
||||
column_headers = []
|
||||
column_header_indeces = []
|
||||
event_type = ''
|
||||
with open(os.path.join(outdir, host_file)) as fh:
|
||||
for line in fh:
|
||||
words = line.split()
|
||||
if not words:
|
||||
continue
|
||||
if words[0] == 'Event:':
|
||||
event_type = words[1]
|
||||
column_headers = self._get_report_column_headers(column_headers,
|
||||
words,
|
||||
'simpleperf')
|
||||
for column_header in column_headers:
|
||||
column_header_indeces.append(line.find(column_header))
|
||||
self._add_report_metric(column_headers,
|
||||
column_header_indeces,
|
||||
line,
|
||||
words,
|
||||
context,
|
||||
event_type,
|
||||
label)
|
||||
|
||||
@staticmethod
|
||||
def _get_report_column_headers(column_headers, words, perf_type):
|
||||
if 'Overhead' not in words:
|
||||
return column_headers
|
||||
if perf_type == 'perf':
|
||||
words.remove('#')
|
||||
column_headers = words
|
||||
# Concatonate Shared Objects header
|
||||
if 'Shared' in column_headers:
|
||||
shared_index = column_headers.index('Shared')
|
||||
column_headers[shared_index:shared_index + 2] = ['{} {}'.format(column_headers[shared_index],
|
||||
column_headers[shared_index + 1])]
|
||||
return column_headers
|
||||
|
||||
@staticmethod
|
||||
def _add_report_metric(column_headers, column_header_indeces, line, words, context, event_type, label):
|
||||
if '%' not in words[0]:
|
||||
return
|
||||
classifiers = {}
|
||||
for i in range(1, len(column_headers)):
|
||||
classifiers[column_headers[i]] = line[column_header_indeces[i]:column_header_indeces[i + 1]].strip()
|
||||
|
||||
context.add_metric('{}_{}_Overhead'.format(label, event_type),
|
||||
numeric(words[0].strip('%')),
|
||||
'percent',
|
||||
classifiers=classifiers)
|
||||
|
Loading…
x
Reference in New Issue
Block a user