diff options
Diffstat (limited to 'perf2cfg/perf2cfg/analyze.py')
-rw-r--r-- | perf2cfg/perf2cfg/analyze.py | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/perf2cfg/perf2cfg/analyze.py b/perf2cfg/perf2cfg/analyze.py new file mode 100644 index 00000000..90a4e7b7 --- /dev/null +++ b/perf2cfg/perf2cfg/analyze.py @@ -0,0 +1,210 @@ +# Copyright (C) 2020 The Android Open Source Project +# +# 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. +"""Classes for extracting profiling information from simpleperf record files. + +Example: + analyzer = RecordAnalyzer() + analyzer.analyze('perf.data') + + for event_name, event_count in analyzer.event_counts.items(): + print(f'Number of {event_name} events: {event_count}') +""" + +import collections +import logging +import sys + +from typing import DefaultDict, Dict, Iterable, Iterator, Optional + +# Disable import-error as simpleperf_report_lib is not in pylint's `sys.path` +# pylint: disable=import-error +import simpleperf_report_lib # type: ignore + + +class Instruction: + """Instruction records profiling information for an assembly instruction. + + Attributes: + relative_addr (int): The address of an instruction relative to the + start of its method. For arm64, the first instruction of a method + will be at the relative address 0, the second at the relative + address 4, and so on. + event_counts (DefaultDict[str, int]): A mapping of event names to their + total number of events for this instruction. + """ + + def __init__(self, relative_addr: int) -> None: + """Instantiates an Instruction. + + Args: + relative_addr (int): A relative address. + """ + self.relative_addr = relative_addr + + self.event_counts: DefaultDict[str, int] = collections.defaultdict(int) + + def record_sample(self, event_name: str, event_count: int) -> None: + """Records profiling information given by a sample. + + Args: + event_name (str): An event name. + event_count (int): An event count. + """ + self.event_counts[event_name] += event_count + + +class Method: + """Method records profiling information for a compiled method. + + Attributes: + name (str): A method name. + event_counts (DefaultDict[str, int]): A mapping of event names to their + total number of events for this method. + instructions (Dict[int, Instruction]): A mapping of relative + instruction addresses to their Instruction object. + """ + + def __init__(self, name: str) -> None: + """Instantiates a Method. + + Args: + name (str): A method name. + """ + self.name = name + + self.event_counts: DefaultDict[str, int] = collections.defaultdict(int) + self.instructions: Dict[int, Instruction] = {} + + def record_sample(self, relative_addr: int, event_name: str, + event_count: int) -> None: + """Records profiling information given by a sample. + + Args: + relative_addr (int): The relative address of an instruction hit. + event_name (str): An event name. + event_count (int): An event count. + """ + self.event_counts[event_name] += event_count + + if relative_addr not in self.instructions: + self.instructions[relative_addr] = Instruction(relative_addr) + + instruction = self.instructions[relative_addr] + instruction.record_sample(event_name, event_count) + + +class RecordAnalyzer: + """RecordAnalyzer extracts profiling information from simpleperf record + files. + + Multiple record files can be analyzed successively, each containing one or + more event types. Samples from odex files are the only ones analyzed, as + we're interested by the performance of methods generated by the optimizing + compiler. + + Attributes: + event_names (Set[str]): A set of event names to analyze. If empty, all + events are analyzed. + event_counts (DefaultDict[str, int]): A mapping of event names to their + total number of events for the analyzed samples. + methods (Dict[str, Method]): A mapping of method names to their Method + object. + report (simpleperf_report_lib.ReportLib): A ReportLib object. + target_arch (str): A target architecture determined from the first + record file analyzed. + """ + + def __init__(self, event_names: Optional[Iterable[str]] = None) -> None: + """Instantiates a RecordAnalyzer. + + Args: + event_names (Optional[Iterable[str]]): An optional iterable of + event names to analyze. If empty or falsy, all events are + analyzed. + """ + if not event_names: + event_names = [] + + self.event_names = set(event_names) + + self.event_counts: DefaultDict[str, int] = collections.defaultdict(int) + self.methods: Dict[str, Method] = {} + self.report: simpleperf_report_lib.ReportLib + self.target_arch = '' + + def analyze(self, filename: str) -> None: + """Analyzes a perf record file. + + Args: + filename (str): The path to a perf record file. + """ + # One ReportLib object needs to be instantiated per record file + self.report = simpleperf_report_lib.ReportLib() + self.report.SetRecordFile(filename) + + arch = self.report.GetArch() + if not self.target_arch: + self.target_arch = arch + elif self.target_arch != arch: + logging.error( + 'Record file %s is for the architecture %s, expected %s', + filename, arch, self.target_arch) + self.report.Close() + sys.exit(1) + + for sample in self.samples(): + event = self.report.GetEventOfCurrentSample() + if self.event_names and event.name not in self.event_names: + continue + + symbol = self.report.GetSymbolOfCurrentSample() + relative_addr = symbol.vaddr_in_file - symbol.symbol_addr + self.record_sample(symbol.symbol_name, relative_addr, event.name, + sample.period) + + self.report.Close() + logging.info('Analyzed %d event(s) for %d method(s)', + len(self.event_counts), len(self.methods)) + + def samples(self) -> Iterator[simpleperf_report_lib.SampleStruct]: + """Iterates over samples for compiled methods located in odex files. + + Yields: + simpleperf_report_lib.SampleStruct: A sample for a compiled method. + """ + sample = self.report.GetNextSample() + while sample: + symbol = self.report.GetSymbolOfCurrentSample() + if symbol.dso_name.endswith('.odex'): + yield sample + + sample = self.report.GetNextSample() + + def record_sample(self, method_name: str, relative_addr: int, + event_name: str, event_count: int) -> None: + """Records profiling information given by a sample. + + Args: + method_name (str): A method name. + relative_addr (int): The relative address of an instruction hit. + event_name (str): An event name. + event_count (int): An event count. + """ + self.event_counts[event_name] += event_count + + if method_name not in self.methods: + self.methods[method_name] = Method(method_name) + + method = self.methods[method_name] + method.record_sample(relative_addr, event_name, event_count) |