• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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