1# Copyright (C) 2020 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Classes for extracting profiling information from simpleperf record files. 15 16Example: 17 analyzer = RecordAnalyzer() 18 analyzer.analyze('perf.data') 19 20 for event_name, event_count in analyzer.event_counts.items(): 21 print(f'Number of {event_name} events: {event_count}') 22""" 23 24import collections 25import logging 26import sys 27 28from typing import DefaultDict, Dict, Iterable, Iterator, Optional 29 30# Disable import-error as simpleperf_report_lib is not in pylint's `sys.path` 31# pylint: disable=import-error 32import simpleperf_report_lib # type: ignore 33 34 35class Instruction: 36 """Instruction records profiling information for an assembly instruction. 37 38 Attributes: 39 relative_addr (int): The address of an instruction relative to the 40 start of its method. For arm64, the first instruction of a method 41 will be at the relative address 0, the second at the relative 42 address 4, and so on. 43 event_counts (DefaultDict[str, int]): A mapping of event names to their 44 total number of events for this instruction. 45 """ 46 47 def __init__(self, relative_addr: int) -> None: 48 """Instantiates an Instruction. 49 50 Args: 51 relative_addr (int): A relative address. 52 """ 53 self.relative_addr = relative_addr 54 55 self.event_counts: DefaultDict[str, int] = collections.defaultdict(int) 56 57 def record_sample(self, event_name: str, event_count: int) -> None: 58 """Records profiling information given by a sample. 59 60 Args: 61 event_name (str): An event name. 62 event_count (int): An event count. 63 """ 64 self.event_counts[event_name] += event_count 65 66 67class Method: 68 """Method records profiling information for a compiled method. 69 70 Attributes: 71 name (str): A method name. 72 event_counts (DefaultDict[str, int]): A mapping of event names to their 73 total number of events for this method. 74 instructions (Dict[int, Instruction]): A mapping of relative 75 instruction addresses to their Instruction object. 76 """ 77 78 def __init__(self, name: str) -> None: 79 """Instantiates a Method. 80 81 Args: 82 name (str): A method name. 83 """ 84 self.name = name 85 86 self.event_counts: DefaultDict[str, int] = collections.defaultdict(int) 87 self.instructions: Dict[int, Instruction] = {} 88 89 def record_sample(self, relative_addr: int, event_name: str, 90 event_count: int) -> None: 91 """Records profiling information given by a sample. 92 93 Args: 94 relative_addr (int): The relative address of an instruction hit. 95 event_name (str): An event name. 96 event_count (int): An event count. 97 """ 98 self.event_counts[event_name] += event_count 99 100 if relative_addr not in self.instructions: 101 self.instructions[relative_addr] = Instruction(relative_addr) 102 103 instruction = self.instructions[relative_addr] 104 instruction.record_sample(event_name, event_count) 105 106 107class RecordAnalyzer: 108 """RecordAnalyzer extracts profiling information from simpleperf record 109 files. 110 111 Multiple record files can be analyzed successively, each containing one or 112 more event types. Samples from odex files are the only ones analyzed, as 113 we're interested by the performance of methods generated by the optimizing 114 compiler. 115 116 Attributes: 117 event_names (Set[str]): A set of event names to analyze. If empty, all 118 events are analyzed. 119 event_counts (DefaultDict[str, int]): A mapping of event names to their 120 total number of events for the analyzed samples. 121 methods (Dict[str, Method]): A mapping of method names to their Method 122 object. 123 report (simpleperf_report_lib.ReportLib): A ReportLib object. 124 target_arch (str): A target architecture determined from the first 125 record file analyzed. 126 """ 127 128 def __init__(self, event_names: Optional[Iterable[str]] = None) -> None: 129 """Instantiates a RecordAnalyzer. 130 131 Args: 132 event_names (Optional[Iterable[str]]): An optional iterable of 133 event names to analyze. If empty or falsy, all events are 134 analyzed. 135 """ 136 if not event_names: 137 event_names = [] 138 139 self.event_names = set(event_names) 140 141 self.event_counts: DefaultDict[str, int] = collections.defaultdict(int) 142 self.methods: Dict[str, Method] = {} 143 self.report: simpleperf_report_lib.ReportLib 144 self.target_arch = '' 145 146 def analyze(self, filename: str) -> None: 147 """Analyzes a perf record file. 148 149 Args: 150 filename (str): The path to a perf record file. 151 """ 152 # One ReportLib object needs to be instantiated per record file 153 self.report = simpleperf_report_lib.ReportLib() 154 self.report.SetRecordFile(filename) 155 156 arch = self.report.GetArch() 157 if not self.target_arch: 158 self.target_arch = arch 159 elif self.target_arch != arch: 160 logging.error( 161 'Record file %s is for the architecture %s, expected %s', 162 filename, arch, self.target_arch) 163 self.report.Close() 164 sys.exit(1) 165 166 for sample in self.samples(): 167 event = self.report.GetEventOfCurrentSample() 168 if self.event_names and event.name not in self.event_names: 169 continue 170 171 symbol = self.report.GetSymbolOfCurrentSample() 172 relative_addr = symbol.vaddr_in_file - symbol.symbol_addr 173 self.record_sample(symbol.symbol_name, relative_addr, event.name, 174 sample.period) 175 176 self.report.Close() 177 logging.info('Analyzed %d event(s) for %d method(s)', 178 len(self.event_counts), len(self.methods)) 179 180 def samples(self) -> Iterator[simpleperf_report_lib.SampleStruct]: 181 """Iterates over samples for compiled methods located in odex files. 182 183 Yields: 184 simpleperf_report_lib.SampleStruct: A sample for a compiled method. 185 """ 186 sample = self.report.GetNextSample() 187 while sample: 188 symbol = self.report.GetSymbolOfCurrentSample() 189 if symbol.dso_name.endswith('.odex'): 190 yield sample 191 192 sample = self.report.GetNextSample() 193 194 def record_sample(self, method_name: str, relative_addr: int, 195 event_name: str, event_count: int) -> None: 196 """Records profiling information given by a sample. 197 198 Args: 199 method_name (str): A method name. 200 relative_addr (int): The relative address of an instruction hit. 201 event_name (str): An event name. 202 event_count (int): An event count. 203 """ 204 self.event_counts[event_name] += event_count 205 206 if method_name not in self.methods: 207 self.methods[method_name] = Method(method_name) 208 209 method = self.methods[method_name] 210 method.record_sample(relative_addr, event_name, event_count) 211