summaryrefslogtreecommitdiff
path: root/perf2cfg/perf2cfg/analyze.py
diff options
context:
space:
mode:
Diffstat (limited to 'perf2cfg/perf2cfg/analyze.py')
-rw-r--r--perf2cfg/perf2cfg/analyze.py210
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)