diff --git a/wa/instruments/perf.py b/wa/instruments/perf.py new file mode 100644 index 00000000..c5179cdf --- /dev/null +++ b/wa/instruments/perf.py @@ -0,0 +1,138 @@ +# Copyright 2013-2015 ARM Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +# pylint: disable=unused-argument +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 + +PERF_COUNT_REGEX = re.compile(r'^(CPU\d+)?\s*(\d+)\s*(.*?)\s*(\[\s*\d+\.\d+%\s*\])?\s*$') + + +class PerfInstrument(Instrument): + + name = 'perf' + description = """ + Perf is a Linux profiling with performance counters. + + 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. + + Events must be provided as a list that contains them and they will look like + this :: + + perf_events = ['migrations', 'cs'] + + Events can be obtained by typing the following in the command line on the + device :: + + perf list + + Whereas options, they can be provided as a single string as following :: + + perf_options = '-a -i' + + Options can be obtained by running the following in the command line :: + + man perf-stat + """ + + parameters = [ + Parameter('events', kind=list_of_strs, default=['migrations', 'cs'], + global_alias='perf_events', + constraint=(lambda x: x, 'must not be empty.'), + 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. + """), + 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. + """), + Parameter('force_install', kind=bool, default=False, + description=""" + always install perf binary even if perf is already present on the device. + """), + ] + + def __init__(self, target, **kwargs): + super(PerfInstrument, self).__init__(target, **kwargs) + self.collector = None + + def initialize(self, context): + self.collector = PerfCollector(self.target, + self.events, + self.optionstring, + self.labels, + self.force_install) + + def setup(self, context): + self.collector.reset() + + def start(self, context): + self.collector.start() + + def stop(self, context): + self.collector.stop() + + def update_output(self, context): + self.logger.info('Extracting reports from target...') + outdir = os.path.join(context.output_directory, 'perf') + self.collector.get_trace(outdir) + + for host_file in os.listdir(outdir): + label = host_file.split('.out')[0] + host_file_path = os.path.join(outdir, host_file) + context.add_artifact(label, host_file_path, 'raw') + with open(host_file_path) as fh: + in_results_section = False + for line in fh: + 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) + + def teardown(self, context): + self.collector.reset()