diff options
author | Yabin Cui <yabinc@google.com> | 2017-03-16 13:00:43 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2017-03-17 18:36:23 -0700 |
commit | 129b5d2f72fbee901a504d328da5a9037b03a7b6 (patch) | |
tree | 187b6514e605bee21b61733b3ddd3c336a2c2277 | |
parent | 5b88b5289af87196d46f71b96817e8f9edd5dc35 (diff) | |
download | extras-129b5d2f72fbee901a504d328da5a9037b03a7b6.tar.gz |
simpleperf: add script to generate proto data used by pprof.
Also change Addr2Line to support inline functions.
Bug: http://b/35726123
Test: run gen_pprof_proto.py manually.
Change-Id: Ia2f84ac142e7075ea902f3456235db24567e4fcd
-rw-r--r-- | simpleperf/README.md | 8 | ||||
-rw-r--r-- | simpleperf/report_lib_interface.cpp | 21 | ||||
-rw-r--r-- | simpleperf/scripts/annotate.py | 239 | ||||
-rw-r--r-- | simpleperf/scripts/pprof_proto_generator.config | 39 | ||||
-rw-r--r-- | simpleperf/scripts/pprof_proto_generator.py | 549 | ||||
-rw-r--r-- | simpleperf/scripts/profile_pb2.py | 597 | ||||
-rw-r--r-- | simpleperf/scripts/simpleperf_report_lib.py | 11 |
7 files changed, 1357 insertions, 107 deletions
diff --git a/simpleperf/README.md b/simpleperf/README.md index 6f56c4d8..5412f906 100644 --- a/simpleperf/README.md +++ b/simpleperf/README.md @@ -767,6 +767,14 @@ An example is report_sample.py. $stackcollapse-perf.pl out.perf >out.folded $./flamegraph.pl out.folded >a.svg +### Visualize using pprof +pprof is a tool for visualization and analysis of profiling data. It can +be got from https://github.com/google/pprof. pprof_proto_generator.py can +generate profiling data in a format acceptable by pprof. + + $python pprof_proto_generator.py + $pprof -pdf pprof.profile + ### Annotate source code annotate.py reads perf.data and binaries in binary_cache. Then it knows which source file:line each sample hits. So it can annotate source code. annotate.py diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp index cc793074..0d4380f6 100644 --- a/simpleperf/report_lib_interface.cpp +++ b/simpleperf/report_lib_interface.cpp @@ -47,11 +47,18 @@ struct Event { const char* name; }; +struct Mapping { + uint64_t start; + uint64_t end; + uint64_t pgoff; +}; + struct SymbolEntry { const char* dso_name; uint64_t vaddr_in_file; const char* symbol_name; uint64_t symbol_addr; + Mapping* mapping; }; struct CallChainEntry { @@ -130,6 +137,7 @@ class ReportLib { private: Sample* GetCurrentSample(); bool OpenRecordFileIfNecessary(); + Mapping* AddMapping(const MapEntry& map); std::unique_ptr<android::base::ScopedLogSeverity> log_severity_; std::string record_filename_; @@ -141,6 +149,7 @@ class ReportLib { Event current_event_; SymbolEntry current_symbol_; CallChain current_callchain_; + std::vector<std::unique_ptr<Mapping>> current_mappings_; std::vector<CallChainEntry> callchain_entries_; std::string build_id_string_; int update_flag_; @@ -198,6 +207,7 @@ Sample* ReportLib::GetNextSample() { } } update_flag_ = 0; + current_mappings_.clear(); return GetCurrentSample(); } @@ -250,6 +260,7 @@ SymbolEntry* ReportLib::GetSymbolOfCurrentSample() { current_symbol_.vaddr_in_file = vaddr_in_file; current_symbol_.symbol_name = symbol->DemangledName(); current_symbol_.symbol_addr = symbol->addr; + current_symbol_.mapping = AddMapping(*map); update_flag_ |= UPDATE_FLAG_OF_SYMBOL; } return ¤t_symbol_; @@ -296,6 +307,7 @@ CallChain* ReportLib::GetCallChainOfCurrentSample() { entry.symbol.vaddr_in_file = vaddr_in_file; entry.symbol.symbol_name = symbol->DemangledName(); entry.symbol.symbol_addr = symbol->addr; + entry.symbol.mapping = AddMapping(*map); callchain_entries_.push_back(entry); } } @@ -307,6 +319,15 @@ CallChain* ReportLib::GetCallChainOfCurrentSample() { return ¤t_callchain_; } +Mapping* ReportLib::AddMapping(const MapEntry& map) { + current_mappings_.emplace_back(std::unique_ptr<Mapping>(new Mapping)); + Mapping* mapping = current_mappings_.back().get(); + mapping->start = map.start_addr; + mapping->end = map.start_addr + map.len; + mapping->pgoff = map.pgoff; + return mapping; +} + const char* ReportLib::GetBuildIdForPath(const char* path) { if (!OpenRecordFileIfNecessary()) { build_id_string_.clear(); diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py index b49942ff..d25a6cd2 100644 --- a/simpleperf/scripts/annotate.py +++ b/simpleperf/scripts/annotate.py @@ -29,6 +29,25 @@ import sys from simpleperf_report_lib import * from utils import * +class SourceLine(object): + def __init__(self, file, function, line): + self.file = file + self.function = function + self.line = line + + @property + def file_key(self): + return self.file + + @property + def function_key(self): + return (self.file, self.function) + + @property + def line_key(self): + return (self.file, self.line) + + # TODO: using addr2line can't convert from function_start_address to # source_file:line very well for java code. Because in .debug_line section, # there is some distance between function_start_address and the address @@ -36,10 +55,10 @@ from utils import * class Addr2Line(object): """collect information of how to map [dso_name,vaddr] to [source_file:line]. """ - def __init__(self, annotator, addr2line_path): + def __init__(self, addr2line_path, symfs_dir=None): self.dso_dict = dict() - self.annotator = annotator self.addr2line_path = addr2line_path + self.symfs_dir = symfs_dir def add_addr(self, dso_name, addr): @@ -55,6 +74,8 @@ class Addr2Line(object): self.file_list = [] # map from file to id with file_list[id] == file self.file_dict = {} + self.file_list.append('') + self.file_dict[''] = 0 for dso_name in self.dso_dict.keys(): self._convert_addrs_to_lines(dso_name, self.dso_dict[dso_name]) @@ -62,42 +83,59 @@ class Addr2Line(object): def _convert_addrs_to_lines(self, dso_name, dso): - dso_path = self.annotator.find_dso_path(dso_name) + dso_path = self._find_dso_path(dso_name) if dso_path is None: log_warning("can't find dso '%s'" % dso_name) dso.clear() return - addrs = sorted(dso.keys()); + addrs = sorted(dso.keys()) addr_str = [] for addr in addrs: addr_str.append('0x%x' % addr) addr_str = '\n'.join(addr_str) - subproc = subprocess.Popen([self.addr2line_path, '-e', dso_path], + subproc = subprocess.Popen([self.addr2line_path, '-e', dso_path, '-aifC'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) (stdoutdata, _) = subproc.communicate(addr_str) - stdoutdata = stdoutdata.split('\n') + stdoutdata = stdoutdata.strip().split('\n') if len(stdoutdata) < len(addrs): log_fatal("addr2line didn't output enough lines") - for i in range(len(addrs)): - strs = stdoutdata[i].split(':') - if len(strs) == 2 and strs[0].find('?') == -1: - file = strs[0].strip() - if strs[1].find('?') == -1: + addr_pos = 0 + out_pos = 0 + while addr_pos < len(addrs) and out_pos < len(stdoutdata): + addr_line = stdoutdata[out_pos] + out_pos += 1 + assert addr_line[:2] == "0x" + assert out_pos < len(stdoutdata) + assert addrs[addr_pos] == int(addr_line, 16) + source_lines = [] + while out_pos < len(stdoutdata) and stdoutdata[out_pos][:2] != "0x": + function = stdoutdata[out_pos] + out_pos += 1 + assert out_pos < len(stdoutdata) + file, line = stdoutdata[out_pos].split(':') + line = line.split()[0] # Remove comments after line number + out_pos += 1 + if file.find('?') != -1: + file = 0 + else: + file = self._get_file_id(file) + if line.find('?') != -1: line = 0 - for c in strs[1]: - if c.isdigit(): - line = line * 10 + ord(c) - ord('0') - else: - break else: - line = None - id = self.file_dict.get(file) - if id is None: - id = len(self.file_list) - self.file_list.append(file) - self.file_dict[file] = id - dso[addrs[i]] = (id, line) + line = int(line) + source_lines.append(SourceLine(file, function, line)) + dso[addrs[addr_pos]] = source_lines + addr_pos += 1 + assert addr_pos == len(addrs) + + def _get_file_id(self, file): + id = self.file_dict.get(file) + if id is None: + id = len(self.file_list) + self.file_list.append(file) + self.file_dict[file] = id + return id def _combine_source_files(self): """It is possible that addr2line gives us different names for the same @@ -135,14 +173,28 @@ class Addr2Line(object): self.file_list[from_id] = self.file_list[to_id] - def get_source(self, dso_name, addr): + def get_sources(self, dso_name, addr): dso = self.dso_dict.get(dso_name) if dso is None: - return (None, None) - item = dso.get(addr) - if item is None: - return (None, None) - return (self.file_list[item[0]], item[1]) + return [] + item = dso.get(addr, []) + source_lines = [] + for source in item: + source_lines.append(SourceLine(self.file_list[source.file], + source.function, source.line)) + return source_lines + + + def _find_dso_path(self, dso): + if dso[0] != '/' or dso == '//anon': + return None + if self.symfs_dir: + dso_path = os.path.join(self.symfs_dir, dso[1:]) + if os.path.isfile(dso_path): + return dso_path + if os.path.isfile(dso): + return dso + return None class Period(object): @@ -200,7 +252,7 @@ class FilePeriod(object): def add_function_period(self, function_name, function_start_line, period): a = self.function_dict.get(function_name) - if a is None: + if not a: if function_start_line is None: function_start_line = -1 self.function_dict[function_name] = a = [function_start_line, Period()] @@ -230,41 +282,25 @@ class SourceFileAnnotator(object): # init member variables self.config = config - self.symfs_dir = None - self.kallsyms = None - self.comm_filter = None - self.pid_filter = None - self.tid_filter = None - self.dso_filter = None - symfs_dir = config['symfs_dir'] - if symfs_dir: - self.symfs_dir = symfs_dir - kallsyms = config['kallsyms'] - if kallsyms: - self.kallsyms = kallsyms - comm_filter = config['comm_filters'] - if comm_filter: - self.comm_filter = set(comm_filter) - pid_filter = config['pid_filters'] - if pid_filter: - self.pid_filter = set() - for pid in pid_filter: - self.pid_filter.add(int(pid)) - tid_filter = config['tid_filters'] - if tid_filter: - self.tid_filter = set() - for tid in tid_filter: - self.tid_filter.add(int(tid)) - dso_filter = config['dso_filters'] - if dso_filter: - self.dso_filter = set(dso_filter) + self.symfs_dir = config.get('symfs_dir') + self.kallsyms = config.get('kallsyms') + self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None + if config.get('pid_filters'): + self.pid_filter = {int(x) for x in config['pid_filters']} + else: + self.pid_filter = None + if config.get('tid_filters'): + self.tid_filter = {int(x) for x in config['tid_filters']} + else: + self.tid_filter = None + self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None output_dir = config['annotate_dest_dir'] if os.path.isdir(output_dir): shutil.rmtree(output_dir) os.makedirs(output_dir) - self.addr2line = Addr2Line(self, self.config['addr2line_path']) + self.addr2line = Addr2Line(self.config['addr2line_path'], symfs_dir) def annotate(self): @@ -282,9 +318,10 @@ class SourceFileAnnotator(object): """ for perf_data in self.config['perf_data_list']: lib = ReportLib() - if self.symfs_dir is not None: + lib.SetRecordFile(perf_data) + if self.symfs_dir: lib.SetSymfs(self.symfs_dir) - if self.kallsyms is not None: + if self.kallsyms: lib.SetKallsymsFile(self.kallsyms) while True: sample = lib.GetNextSample() @@ -306,20 +343,20 @@ class SourceFileAnnotator(object): def _filter_sample(self, sample): """Return true if the sample can be used.""" - if self.comm_filter is not None: + if self.comm_filter: if sample.thread_comm not in self.comm_filter: return False - if self.pid_filter is not None: + if self.pid_filter: if sample.pid not in self.pid_filter: return False - if self.tid_filter is not None: + if self.tid_filter: if sample.tid not in self.tid_filter: return False return True def _filter_symbol(self, symbol): - if self.dso_filter is None or symbol.dso_name in self.dso_filter: + if not self.dso_filter or symbol.dso_name in self.dso_filter: return True return False @@ -328,18 +365,6 @@ class SourceFileAnnotator(object): self.addr2line.convert_addrs_to_lines() - def find_dso_path(self, dso): - if dso[0] != '/' or dso == '//anon': - return None - if self.symfs_dir is not None: - dso_path = os.path.join(self.symfs_dir, dso[1:]) - if os.path.isfile(dso_path): - return dso_path - if os.path.isfile(dso): - return dso - return None - - def _generate_periods(self): """read perf.data, collect Period for all types: binaries, source files, functions, lines. @@ -349,9 +374,10 @@ class SourceFileAnnotator(object): self.file_periods = dict() for perf_data in self.config['perf_data_list']: lib = ReportLib() - if self.symfs_dir is not None: + lib.SetRecordFile(perf_data) + if self.symfs_dir: lib.SetSymfs(self.symfs_dir) - if self.kallsyms is not None: + if self.kallsyms: lib.SetKallsymsFile(self.kallsyms) while True: sample = lib.GetNextSample() @@ -386,18 +412,20 @@ class SourceFileAnnotator(object): # Add period to dso. self._add_dso_period(symbol.dso_name, period, used_dso_dict) # Add period to source file. - source = self.addr2line.get_source(symbol.dso_name, symbol.vaddr_in_file) - if source[0] is not None: - self._add_file_period(source[0], period, used_file_dict) - # Add period to line. - if source[1] is not None: - self._add_line_period(source, period, used_line_dict) + sources = self.addr2line.get_sources(symbol.dso_name, symbol.vaddr_in_file) + for source in sources: + if source.file: + self._add_file_period(source, period, used_file_dict) + # Add period to line. + if source.line: + self._add_line_period(source, period, used_line_dict) # Add period to function. - source = self.addr2line.get_source(symbol.dso_name, symbol.symbol_addr) - if source[0] is not None: - self._add_file_period(source[0], period, used_file_dict) - self._add_function_period(source, symbol.symbol_name, period, - used_function_dict) + sources = self.addr2line.get_sources(symbol.dso_name, symbol.symbol_addr) + for source in sources: + if source.file: + self._add_file_period(source, period, used_file_dict) + if source.function: + self._add_function_period(source, period, used_function_dict) if is_sample_used: self.period += sample.period @@ -412,27 +440,27 @@ class SourceFileAnnotator(object): dso_period.add_period(period) - def _add_file_period(self, file, period, used_file_dict): - if not used_file_dict.has_key(file): - used_file_dict[file] = True - file_period = self.file_periods.get(file) + def _add_file_period(self, source, period, used_file_dict): + if not used_file_dict.has_key(source.file_key): + used_file_dict[source.file_key] = True + file_period = self.file_periods.get(source.file) if file_period is None: - file_period = self.file_periods[file] = FilePeriod(file) + file_period = self.file_periods[source.file] = FilePeriod(source.file) file_period.add_period(period) def _add_line_period(self, source, period, used_line_dict): - if not used_line_dict.has_key(source): - used_line_dict[source] = True - file_period = self.file_periods[source[0]] - file_period.add_line_period(source[1], period) + if not used_line_dict.has_key(source.line_key): + used_line_dict[source.line_key] = True + file_period = self.file_periods[source.file] + file_period.add_line_period(source.line, period) - def _add_function_period(self, source, function_name, period, used_function_dict): - if not used_function_dict.has_key((source[0], function_name)): - used_function_dict[(source[0], function_name)] = True - file_period = self.file_periods[source[0]] - file_period.add_function_period(function_name, source[1], period) + def _add_function_period(self, source, period, used_function_dict): + if not used_function_dict.has_key(source.function_key): + used_function_dict[source.function_key] = True + file_period = self.file_periods[source.file] + file_period.add_function_period(source.function, source.line, period) def _write_summary(self): @@ -461,7 +489,6 @@ class SourceFileAnnotator(object): values = sorted(values, cmp=lambda x, y: cmp(y[2].acc_period, x[2].acc_period)) for value in values: - func = file_period.function_dict[func_name] f.write('\tfunction (%s): line %d, %s\n' % ( value[0], value[1], self._get_percentage_str(value[2]))) f.write('\n') @@ -596,4 +623,4 @@ if __name__ == '__main__': args = parser.parse_args() config = load_config(args.config) annotator = SourceFileAnnotator(config) - annotator.annotate()
\ No newline at end of file + annotator.annotate() diff --git a/simpleperf/scripts/pprof_proto_generator.config b/simpleperf/scripts/pprof_proto_generator.config new file mode 100644 index 00000000..aa82b92c --- /dev/null +++ b/simpleperf/scripts/pprof_proto_generator.config @@ -0,0 +1,39 @@ +# This configuration is written in python and used by binary_cache_builder.py. + +import os +import os.path + +# path of profiling record data. +perf_data_path = "perf.data" + +# output path. +output_file = "pprof.profile" + + +# directory to cache binaries with symbols and debug information. +# Can be generated by binary_cache_builder.py. +binary_cache_dir = "binary_cache" + + +# path to find kernel symbols. +kallsyms = "" + + +if binary_cache_dir: + path = os.path.join(binary_cache_dir, 'kallsyms') + if os.path.isfile(path): + kallsyms = path + +# Sample Filters +# Use samples only in threads with selected names. +comm_filters = [] +# Use samples only in processes with selected process ids. +pid_filters = [] +# Use samples only in threads with selected thread ids. +tid_filters = [] +# Use samples only in selected binaries. +dso_filters = [] + +# We use addr2line to map virtual address to source file and source line. +# So set the path to addr2line here. +addr2line_path = "addr2line"
\ No newline at end of file diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py new file mode 100644 index 00000000..5f8d143b --- /dev/null +++ b/simpleperf/scripts/pprof_proto_generator.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python +# +# Copyright (C) 2017 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. +# + +"""pprof_proto_generator.py: read perf.data, generate pprof.profile, which can be + used by pprof. + + Example: + python app_profiler.py + python pprof_proto_generator.py + pprof -text pprof.profile +""" + +from __future__ import print_function +import argparse +import os +import os.path +import profile_pb2 +import re +import shutil +import subprocess +import sys +import time + +from annotate import Addr2Line +from simpleperf_report_lib import * +from utils import * + + +def load_pprof_profile(filename): + profile = profile_pb2.Profile() + with open(filename, "rb") as f: + profile.ParseFromString(f.read()) + return profile + + +def store_pprof_profile(filename, profile): + with open(filename, 'wb') as f: + f.write(profile.SerializeToString()) + + +class PprofProfilePrinter(object): + + def __init__(self, profile): + self.profile = profile + self.string_table = profile.string_table + + def show(self): + p = self.profile + sub_space = ' ' + print('Profile {') + print('%d sample_types' % len(p.sample_type)) + for i in range(len(p.sample_type)): + print('sample_type[%d] = ' % i, end='') + self.show_value_type(p.sample_type[i]) + print('%d samples' % len(p.sample)) + for i in range(len(p.sample)): + print('sample[%d]:' % i) + self.show_sample(p.sample[i], sub_space) + print('%d mappings' % len(p.mapping)) + for i in range(len(p.mapping)): + print('mapping[%d]:' % i) + self.show_mapping(p.mapping[i], sub_space) + print('%d locations' % len(p.location)) + for i in range(len(p.location)): + print('location[%d]:' % i) + self.show_location(p.location[i], sub_space) + for i in range(len(p.function)): + print('function[%d]:' % i) + self.show_function(p.function[i], sub_space) + print('%d strings' % len(p.string_table)) + for i in range(len(p.string_table)): + print('string[%d]: %s' % (i, p.string_table[i])) + print('drop_frames: %s' % self.string(p.drop_frames)) + print('keep_frames: %s' % self.string(p.keep_frames)) + print('time_nanos: %u' % p.time_nanos) + print('duration_nanos: %u' % p.duration_nanos) + print('period_type: ', end='') + self.show_value_type(p.period_type) + print('period: %u' % p.period) + for i in range(len(p.comment)): + print('comment[%d] = %s' % (i, self.string(p.comment[i]))) + print('default_sample_type: %d' % p.default_sample_type) + print('} // Profile') + print() + + def show_value_type(self, value_type, space=''): + print('%sValueType(typeID=%d, unitID=%d, type=%s, unit=%s)' % + (space, value_type.type, value_type.unit, + self.string(value_type.type), self.string(value_type.unit))) + + def show_sample(self, sample, space=''): + sub_space = space + ' ' + for i in range(len(sample.location_id)): + print('%slocation_id[%d]: id %d' % (space, i, sample.location_id[i])) + self.show_location_id(sample.location_id[i], sub_space) + for i in range(len(sample.value)): + print('%svalue[%d] = %d' % (space, i, sample.value[i])) + for i in range(len(sample.label)): + print('%slabel[%d] = ', (space, i)) + + def show_location_id(self, location_id, space=''): + location = self.profile.location[location_id - 1] + self.show_location(location, space) + + def show_location(self, location, space=''): + sub_space = space + ' ' + print('%sid: %d' % (space, location.id)) + print('%smapping_id: %d' % (space, location.mapping_id)) + self.show_mapping_id(location.mapping_id, sub_space) + print('%saddress: %x' % (space, location.address)) + for i in range(len(location.line)): + print('%sline[%d]:' % (space, i)) + self.show_line(location.line[i], sub_space) + + def show_mapping_id(self, mapping_id, space=''): + mapping = self.profile.mapping[mapping_id - 1] + self.show_mapping(mapping, space) + + def show_mapping(self, mapping, space=''): + print('%sid: %d' % (space, mapping.id)) + print('%smemory_start: %x' % (space, mapping.memory_start)) + print('%smemory_limit: %x' % (space, mapping.memory_limit)) + print('%sfile_offset: %x' % (space, mapping.file_offset)) + print('%sfilename: %s(%d)' % (space, self.string(mapping.filename), + mapping.filename)) + print('%sbuild_id: %s(%d)' % (space, self.string(mapping.build_id), + mapping.build_id)) + print('%shas_functions: %s' % (space, mapping.has_functions)) + print('%shas_filenames: %s' % (space, mapping.has_filenames)) + print('%shas_line_numbers: %s' % (space, mapping.has_line_numbers)) + print('%shas_inline_frames: %s' % (space, mapping.has_inline_frames)) + + def show_line(self, line, space=''): + sub_space = space + ' ' + print('%sfunction_id: %d' % (space, line.function_id)) + self.show_function_id(line.function_id, sub_space) + print('%sline: %d' % (space, line.line)) + + def show_function_id(self, function_id, space=''): + function = self.profile.function[function_id - 1] + self.show_function(function, space) + + def show_function(self, function, space=''): + print('%sid: %d' % (space, function.id)) + print('%sname: %s' % (space, self.string(function.name))) + print('%ssystem_name: %s' % (space, self.string(function.system_name))) + print('%sfilename: %s' % (space, self.string(function.filename))) + print('%sstart_line: %d' % (space, function.start_line)) + + def show_label(self, label, space=''): + print('%sLabel(%s =', space, self.string(label.key), end='') + if label.HasField('str'): + print('%s)' % self.get_string(label.str)) + else: + print('%d)' % label.num) + + def string(self, id): + return self.string_table[id] + + +class Sample(object): + + def __init__(self): + self.location_ids = [] + self.values = {} + + def add_location_id(self, location_id): + self.location_ids.append(location_id) + + def add_value(self, id, value): + self.values[id] = self.values.get(id, 0) + value + + def add_values(self, values): + for id in values.keys(): + value = values[id] + self.add_value(id, value) + + @property + def key(self): + return tuple(self.location_ids) + + +class Location(object): + + def __init__(self, mapping_id, address, vaddr_in_dso): + self.id = -1 # unset + self.mapping_id = mapping_id + self.address = address + self.vaddr_in_dso = vaddr_in_dso + self.lines = [] + + @property + def key(self): + return (self.mapping_id, self.address) + + +class Line(object): + + def __init__(self): + self.function_id = 0 + self.line = 0 + + +class Mapping(object): + + def __init__(self, start, end, pgoff, filename_id, build_id_id): + self.id = -1 # unset + self.memory_start = start + self.memory_limit = end + self.file_offset = pgoff + self.filename_id = filename_id + self.build_id_id = build_id_id + + @property + def key(self): + return ( + self.memory_start, + self.memory_limit, + self.file_offset, + self.filename_id, + self.build_id_id) + + +class Function(object): + + def __init__(self, name_id, dso_name_id, vaddr_in_dso): + self.id = -1 # unset + self.name_id = name_id + self.dso_name_id = dso_name_id + self.vaddr_in_dso = vaddr_in_dso + self.source_filename_id = 0 + self.start_line = 0 + + @property + def key(self): + return (self.name_id, self.dso_name_id) + + +class PprofProfileGenerator(object): + + def __init__(self, config): + self.config = config + self.lib = ReportLib() + + if config.get('binary_cache_dir'): + self.lib.SetSymfs(config['binary_cache_dir']) + if config.get('record_file'): + self.lib.SetRecordFile(config['record_file']) + if config.get('kallsyms'): + self.lib.SetKallsymsFile(config['kallsyms']) + self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None + if config.get('pid_filters'): + self.pid_filter = {int(x) for x in config['pid_filters']} + else: + self.pid_filter = None + if config.get('tid_filters'): + self.tid_filter = {int(x) for x in config['tid_filters']} + else: + self.tid_filter = None + self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None + + def gen(self): + self.profile = profile_pb2.Profile() + self.profile.string_table.append('') + self.string_table = {} + self.sample_types = {} + self.sample_map = {} + self.sample_list = [] + self.location_map = {} + self.location_list = [] + self.mapping_map = {} + self.mapping_list = [] + self.function_map = {} + self.function_list = [] + + # 1. Process all samples in perf.data, aggregate samples. + while True: + report_sample = self.lib.GetNextSample() + if report_sample is None: + self.lib.Close() + break + event = self.lib.GetEventOfCurrentSample() + symbol = self.lib.GetSymbolOfCurrentSample() + callchain = self.lib.GetCallChainOfCurrentSample() + + if not self._filter_report_sample(report_sample): + continue + + sample_type_id = self.get_sample_type_id(event.name) + sample = Sample() + sample.add_value(sample_type_id, 1) + sample.add_value(sample_type_id + 1, report_sample.period) + if self._filter_symbol(symbol): + location_id = self.get_location_id(symbol.vaddr_in_file, symbol) + sample.add_location_id(location_id) + for i in range(callchain.nr): + entry = callchain.entries[i] + if self._filter_symbol(symbol): + location_id = self.get_location_id(entry.ip, entry.symbol) + sample.add_location_id(location_id) + if sample.location_ids: + self.add_sample(sample) + + # 2. Generate line info for locations and functions. + self.gen_source_lines() + + # 3. Produce samples/locations/functions in profile + for sample in self.sample_list: + self.gen_profile_sample(sample) + for mapping in self.mapping_list: + self.gen_profile_mapping(mapping) + for location in self.location_list: + self.gen_profile_location(location) + for function in self.function_list: + self.gen_profile_function(function) + + return self.profile + + def _filter_report_sample(self, sample): + """Return true if the sample can be used.""" + if self.comm_filter: + if sample.thread_comm not in self.comm_filter: + return False + if self.pid_filter: + if sample.pid not in self.pid_filter: + return False + if self.tid_filter: + if sample.tid not in self.tid_filter: + return False + return True + + def _filter_symbol(self, symbol): + if not self.dso_filter or symbol.dso_name in self.dso_filter: + return True + return False + + def get_string_id(self, str): + if len(str) == 0: + return 0 + id = self.string_table.get(str) + if id is not None: + return id + id = len(self.string_table) + 1 + self.string_table[str] = id + self.profile.string_table.append(str) + return id + + def get_string(self, string_id): + return self.profile.string_table[string_id] + + def get_sample_type_id(self, name): + id = self.sample_types.get(name) + if id is not None: + return id + id = len(self.profile.sample_type) + sample_type = self.profile.sample_type.add() + sample_type.type = self.get_string_id('event_' + name + '_samples') + sample_type.unit = self.get_string_id('count') + sample_type = self.profile.sample_type.add() + sample_type.type = self.get_string_id('event_' + name + '_count') + sample_type.unit = self.get_string_id('count') + self.sample_types[name] = id + return id + + def get_location_id(self, ip, symbol): + mapping_id = self.get_mapping_id(symbol.mapping[0], symbol.dso_name) + location = Location(mapping_id, ip, symbol.vaddr_in_file) + # Default line info only contains the function name + line = Line() + line.function_id = self.get_function_id(symbol.symbol_name, symbol.dso_name, + symbol.symbol_addr) + location.lines.append(line) + + exist_location = self.location_map.get(location.key) + if exist_location: + return exist_location.id + # location_id starts from 1 + location.id = len(self.location_list) + 1 + self.location_list.append(location) + self.location_map[location.key] = location + return location.id + + def get_mapping_id(self, report_mapping, filename): + filename_id = self.get_string_id(filename) + build_id = self.lib.GetBuildIdForPath(filename) + if build_id and build_id[0:2] == "0x": + build_id = build_id[2:] + build_id_id = self.get_string_id(build_id) + mapping = Mapping(report_mapping.start, report_mapping.end, + report_mapping.pgoff, filename_id, build_id_id) + exist_mapping = self.mapping_map.get(mapping.key) + if exist_mapping: + return exist_mapping.id + # mapping_id starts from 1 + mapping.id = len(self.mapping_list) + 1 + self.mapping_list.append(mapping) + self.mapping_map[mapping.key] = mapping + return mapping.id + + def get_mapping(self, mapping_id): + return self.mapping_list[mapping_id - 1] if mapping_id > 0 else None + + def get_function_id(self, name, dso_name, vaddr_in_file): + if name == 'unknown': + return 0 + function = Function(self.get_string_id(name), self.get_string_id(dso_name), vaddr_in_file) + exist_function = self.function_map.get(function.key) + if exist_function: + return exist_function.id + # function_id starts from 1 + function.id = len(self.function_list) + 1 + self.function_list.append(function) + self.function_map[function.key] = function + return function.id + + def get_function(self, function_id): + return self.function_list[function_id - 1] if function_id > 0 else None + + def add_sample(self, sample): + exist_sample = self.sample_map.get(sample.key) + if exist_sample: + exist_sample.add_values(sample.values) + else: + self.sample_list.append(sample) + self.sample_map[sample.key] = sample + + def gen_source_lines(self): + # 1. Create Addr2line instance + addr2line = Addr2Line(self.config['addr2line_path'], self.config['binary_cache_dir']) + + # 2. Put all needed addresses to it. + for location in self.location_list: + mapping = self.get_mapping(location.mapping_id) + dso_name = self.get_string(mapping.filename_id) + addr2line.add_addr(dso_name, location.vaddr_in_dso) + for function in self.function_list: + dso_name = self.get_string(function.dso_name_id) + addr2line.add_addr(dso_name, function.vaddr_in_dso) + + # 3. Generate source lines. + addr2line.convert_addrs_to_lines() + + # 4. Annotate locations and functions. + for location in self.location_list: + mapping = self.get_mapping(location.mapping_id) + dso_name = self.get_string(mapping.filename_id) + sources = addr2line.get_sources(dso_name, location.vaddr_in_dso) + source_id = 0 + for source in sources: + if source.file and source.function and source.line: + if source_id == 0: + # Clear default line info + location.lines = [] + location.lines.append(self.add_line(source, dso_name)) + source_id += 1 + + for function in self.function_list: + dso_name = self.get_string(function.dso_name_id) + if function.vaddr_in_dso: + sources = addr2line.get_sources(dso_name, function.vaddr_in_dso) + source = sources[0] if sources else None + if source and source.file: + function.source_filename_id = self.get_string_id(source.file) + if source.line: + function.start_line = source.line + + def add_line(self, source, dso_name): + line = Line() + function_id = self.get_function_id(source.function, dso_name, 0) + function = self.get_function(function_id) + function.source_filename_id = self.get_string_id(source.file) + line.function_id = function_id + line.line = source.line + return line + + def gen_profile_sample(self, sample): + profile_sample = self.profile.sample.add() + profile_sample.location_id.extend(sample.location_ids) + sample_type_count = len(self.sample_types) * 2 + values = [0] * sample_type_count + for id in sample.values.keys(): + values[id] = sample.values[id] + profile_sample.value.extend(values) + + def gen_profile_mapping(self, mapping): + profile_mapping = self.profile.mapping.add() + profile_mapping.id = mapping.id + profile_mapping.memory_start = mapping.memory_start + profile_mapping.memory_limit = mapping.memory_limit + profile_mapping.file_offset = mapping.file_offset + profile_mapping.filename = mapping.filename_id + profile_mapping.build_id = mapping.build_id_id + profile_mapping.has_filenames = True + profile_mapping.has_functions = True + profile_mapping.has_line_numbers = True + profile_mapping.has_inline_frames = True + + def gen_profile_location(self, location): + profile_location = self.profile.location.add() + profile_location.id = location.id + profile_location.mapping_id = location.mapping_id + profile_location.address = location.address + for i in range(len(location.lines)): + line = profile_location.line.add() + line.function_id = location.lines[i].function_id + line.line = location.lines[i].line + + def gen_profile_function(self, function): + profile_function = self.profile.function.add() + profile_function.id = function.id + profile_function.name = function.name_id + profile_function.system_name = function.name_id + profile_function.filename = function.source_filename_id + profile_function.start_line = function.start_line + + +def main(): + parser = argparse.ArgumentParser(description='Generate pprof profile data in pprof.profile.') + parser.add_argument('--show', nargs=1, help='print existing profile.pprof') + parser.add_argument('--config', nargs=1, default='pprof_proto_generator.config', + help='Set config file, default is gen_pprof_proto.config.') + args = parser.parse_args(sys.argv[1:]) + if args.show: + profile = load_pprof_profile(args.show[0]) + printer = PprofProfilePrinter(profile) + printer.show() + return + config = load_config(args.config) + generator = PprofProfileGenerator(config) + profile = generator.gen() + store_pprof_profile(config['output_file'], profile) + + +if __name__ == '__main__': + main() diff --git a/simpleperf/scripts/profile_pb2.py b/simpleperf/scripts/profile_pb2.py new file mode 100644 index 00000000..707b23ac --- /dev/null +++ b/simpleperf/scripts/profile_pb2.py @@ -0,0 +1,597 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: profile.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='profile.proto', + package='perftools.profiles', + syntax='proto3', + serialized_pb=_b('\n\rprofile.proto\x12\x12perftools.profiles\"\xd5\x03\n\x07Profile\x12\x32\n\x0bsample_type\x18\x01 \x03(\x0b\x32\x1d.perftools.profiles.ValueType\x12*\n\x06sample\x18\x02 \x03(\x0b\x32\x1a.perftools.profiles.Sample\x12,\n\x07mapping\x18\x03 \x03(\x0b\x32\x1b.perftools.profiles.Mapping\x12.\n\x08location\x18\x04 \x03(\x0b\x32\x1c.perftools.profiles.Location\x12.\n\x08\x66unction\x18\x05 \x03(\x0b\x32\x1c.perftools.profiles.Function\x12\x14\n\x0cstring_table\x18\x06 \x03(\t\x12\x13\n\x0b\x64rop_frames\x18\x07 \x01(\x03\x12\x13\n\x0bkeep_frames\x18\x08 \x01(\x03\x12\x12\n\ntime_nanos\x18\t \x01(\x03\x12\x16\n\x0e\x64uration_nanos\x18\n \x01(\x03\x12\x32\n\x0bperiod_type\x18\x0b \x01(\x0b\x32\x1d.perftools.profiles.ValueType\x12\x0e\n\x06period\x18\x0c \x01(\x03\x12\x0f\n\x07\x63omment\x18\r \x03(\x03\x12\x1b\n\x13\x64\x65\x66\x61ult_sample_type\x18\x0e \x01(\x03\"\'\n\tValueType\x12\x0c\n\x04type\x18\x01 \x01(\x03\x12\x0c\n\x04unit\x18\x02 \x01(\x03\"V\n\x06Sample\x12\x13\n\x0blocation_id\x18\x01 \x03(\x04\x12\r\n\x05value\x18\x02 \x03(\x03\x12(\n\x05label\x18\x03 \x03(\x0b\x32\x19.perftools.profiles.Label\".\n\x05Label\x12\x0b\n\x03key\x18\x01 \x01(\x03\x12\x0b\n\x03str\x18\x02 \x01(\x03\x12\x0b\n\x03num\x18\x03 \x01(\x03\"\xdd\x01\n\x07Mapping\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x14\n\x0cmemory_start\x18\x02 \x01(\x04\x12\x14\n\x0cmemory_limit\x18\x03 \x01(\x04\x12\x13\n\x0b\x66ile_offset\x18\x04 \x01(\x04\x12\x10\n\x08\x66ilename\x18\x05 \x01(\x03\x12\x10\n\x08\x62uild_id\x18\x06 \x01(\x03\x12\x15\n\rhas_functions\x18\x07 \x01(\x08\x12\x15\n\rhas_filenames\x18\x08 \x01(\x08\x12\x18\n\x10has_line_numbers\x18\t \x01(\x08\x12\x19\n\x11has_inline_frames\x18\n \x01(\x08\"c\n\x08Location\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nmapping_id\x18\x02 \x01(\x04\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x01(\x04\x12&\n\x04line\x18\x04 \x03(\x0b\x32\x18.perftools.profiles.Line\")\n\x04Line\x12\x13\n\x0b\x66unction_id\x18\x01 \x01(\x04\x12\x0c\n\x04line\x18\x02 \x01(\x03\"_\n\x08\x46unction\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\x03\x12\x13\n\x0bsystem_name\x18\x03 \x01(\x03\x12\x10\n\x08\x66ilename\x18\x04 \x01(\x03\x12\x12\n\nstart_line\x18\x05 \x01(\x03\x42-\n\x1d\x63om.google.perftools.profilesB\x0cProfileProtob\x06proto3') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_PROFILE = _descriptor.Descriptor( + name='Profile', + full_name='perftools.profiles.Profile', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='sample_type', full_name='perftools.profiles.Profile.sample_type', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='sample', full_name='perftools.profiles.Profile.sample', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mapping', full_name='perftools.profiles.Profile.mapping', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='location', full_name='perftools.profiles.Profile.location', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='function', full_name='perftools.profiles.Profile.function', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='string_table', full_name='perftools.profiles.Profile.string_table', index=5, + number=6, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='drop_frames', full_name='perftools.profiles.Profile.drop_frames', index=6, + number=7, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='keep_frames', full_name='perftools.profiles.Profile.keep_frames', index=7, + number=8, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='time_nanos', full_name='perftools.profiles.Profile.time_nanos', index=8, + number=9, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='duration_nanos', full_name='perftools.profiles.Profile.duration_nanos', index=9, + number=10, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='period_type', full_name='perftools.profiles.Profile.period_type', index=10, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='period', full_name='perftools.profiles.Profile.period', index=11, + number=12, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='comment', full_name='perftools.profiles.Profile.comment', index=12, + number=13, type=3, cpp_type=2, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='default_sample_type', full_name='perftools.profiles.Profile.default_sample_type', index=13, + number=14, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=38, + serialized_end=507, +) + + +_VALUETYPE = _descriptor.Descriptor( + name='ValueType', + full_name='perftools.profiles.ValueType', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='perftools.profiles.ValueType.type', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='unit', full_name='perftools.profiles.ValueType.unit', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=509, + serialized_end=548, +) + + +_SAMPLE = _descriptor.Descriptor( + name='Sample', + full_name='perftools.profiles.Sample', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='location_id', full_name='perftools.profiles.Sample.location_id', index=0, + number=1, type=4, cpp_type=4, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='perftools.profiles.Sample.value', index=1, + number=2, type=3, cpp_type=2, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='label', full_name='perftools.profiles.Sample.label', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=550, + serialized_end=636, +) + + +_LABEL = _descriptor.Descriptor( + name='Label', + full_name='perftools.profiles.Label', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='perftools.profiles.Label.key', index=0, + number=1, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='str', full_name='perftools.profiles.Label.str', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num', full_name='perftools.profiles.Label.num', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=638, + serialized_end=684, +) + + +_MAPPING = _descriptor.Descriptor( + name='Mapping', + full_name='perftools.profiles.Mapping', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='perftools.profiles.Mapping.id', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='memory_start', full_name='perftools.profiles.Mapping.memory_start', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='memory_limit', full_name='perftools.profiles.Mapping.memory_limit', index=2, + number=3, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='file_offset', full_name='perftools.profiles.Mapping.file_offset', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='filename', full_name='perftools.profiles.Mapping.filename', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='build_id', full_name='perftools.profiles.Mapping.build_id', index=5, + number=6, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='has_functions', full_name='perftools.profiles.Mapping.has_functions', index=6, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='has_filenames', full_name='perftools.profiles.Mapping.has_filenames', index=7, + number=8, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='has_line_numbers', full_name='perftools.profiles.Mapping.has_line_numbers', index=8, + number=9, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='has_inline_frames', full_name='perftools.profiles.Mapping.has_inline_frames', index=9, + number=10, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=687, + serialized_end=908, +) + + +_LOCATION = _descriptor.Descriptor( + name='Location', + full_name='perftools.profiles.Location', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='perftools.profiles.Location.id', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='mapping_id', full_name='perftools.profiles.Location.mapping_id', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='address', full_name='perftools.profiles.Location.address', index=2, + number=3, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='line', full_name='perftools.profiles.Location.line', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=910, + serialized_end=1009, +) + + +_LINE = _descriptor.Descriptor( + name='Line', + full_name='perftools.profiles.Line', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='function_id', full_name='perftools.profiles.Line.function_id', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='line', full_name='perftools.profiles.Line.line', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1011, + serialized_end=1052, +) + + +_FUNCTION = _descriptor.Descriptor( + name='Function', + full_name='perftools.profiles.Function', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='perftools.profiles.Function.id', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='name', full_name='perftools.profiles.Function.name', index=1, + number=2, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='system_name', full_name='perftools.profiles.Function.system_name', index=2, + number=3, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='filename', full_name='perftools.profiles.Function.filename', index=3, + number=4, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='start_line', full_name='perftools.profiles.Function.start_line', index=4, + number=5, type=3, cpp_type=2, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1054, + serialized_end=1149, +) + +_PROFILE.fields_by_name['sample_type'].message_type = _VALUETYPE +_PROFILE.fields_by_name['sample'].message_type = _SAMPLE +_PROFILE.fields_by_name['mapping'].message_type = _MAPPING +_PROFILE.fields_by_name['location'].message_type = _LOCATION +_PROFILE.fields_by_name['function'].message_type = _FUNCTION +_PROFILE.fields_by_name['period_type'].message_type = _VALUETYPE +_SAMPLE.fields_by_name['label'].message_type = _LABEL +_LOCATION.fields_by_name['line'].message_type = _LINE +DESCRIPTOR.message_types_by_name['Profile'] = _PROFILE +DESCRIPTOR.message_types_by_name['ValueType'] = _VALUETYPE +DESCRIPTOR.message_types_by_name['Sample'] = _SAMPLE +DESCRIPTOR.message_types_by_name['Label'] = _LABEL +DESCRIPTOR.message_types_by_name['Mapping'] = _MAPPING +DESCRIPTOR.message_types_by_name['Location'] = _LOCATION +DESCRIPTOR.message_types_by_name['Line'] = _LINE +DESCRIPTOR.message_types_by_name['Function'] = _FUNCTION + +Profile = _reflection.GeneratedProtocolMessageType('Profile', (_message.Message,), dict( + DESCRIPTOR = _PROFILE, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Profile) + )) +_sym_db.RegisterMessage(Profile) + +ValueType = _reflection.GeneratedProtocolMessageType('ValueType', (_message.Message,), dict( + DESCRIPTOR = _VALUETYPE, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.ValueType) + )) +_sym_db.RegisterMessage(ValueType) + +Sample = _reflection.GeneratedProtocolMessageType('Sample', (_message.Message,), dict( + DESCRIPTOR = _SAMPLE, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Sample) + )) +_sym_db.RegisterMessage(Sample) + +Label = _reflection.GeneratedProtocolMessageType('Label', (_message.Message,), dict( + DESCRIPTOR = _LABEL, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Label) + )) +_sym_db.RegisterMessage(Label) + +Mapping = _reflection.GeneratedProtocolMessageType('Mapping', (_message.Message,), dict( + DESCRIPTOR = _MAPPING, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Mapping) + )) +_sym_db.RegisterMessage(Mapping) + +Location = _reflection.GeneratedProtocolMessageType('Location', (_message.Message,), dict( + DESCRIPTOR = _LOCATION, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Location) + )) +_sym_db.RegisterMessage(Location) + +Line = _reflection.GeneratedProtocolMessageType('Line', (_message.Message,), dict( + DESCRIPTOR = _LINE, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Line) + )) +_sym_db.RegisterMessage(Line) + +Function = _reflection.GeneratedProtocolMessageType('Function', (_message.Message,), dict( + DESCRIPTOR = _FUNCTION, + __module__ = 'profile_pb2' + # @@protoc_insertion_point(class_scope:perftools.profiles.Function) + )) +_sym_db.RegisterMessage(Function) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\035com.google.perftools.profilesB\014ProfileProto')) +# @@protoc_insertion_point(module_scope) diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py index 3fdc234b..27ce08a9 100644 --- a/simpleperf/scripts/simpleperf_report_lib.py +++ b/simpleperf/scripts/simpleperf_report_lib.py @@ -17,6 +17,7 @@ """simpleperf_report_lib.py: a python wrapper of libsimpleperf_report.so. Used to access samples in perf.data. + """ import ctypes as ct @@ -64,11 +65,18 @@ class EventStruct(ct.Structure): _fields_ = [('name', ct.c_char_p)] +class MappingStruct(ct.Structure): + _fields_ = [('start', ct.c_uint64), + ('end', ct.c_uint64), + ('pgoff', ct.c_uint64)] + + class SymbolStruct(ct.Structure): _fields_ = [('dso_name', ct.c_char_p), ('vaddr_in_file', ct.c_uint64), ('symbol_name', ct.c_char_p), - ('symbol_addr', ct.c_uint64)] + ('symbol_addr', ct.c_uint64), + ('mapping', ct.POINTER(MappingStruct))] class CallChainEntryStructure(ct.Structure): @@ -105,6 +113,7 @@ class SymbolStructUsingStr(object): self.vaddr_in_file = symbol.vaddr_in_file self.symbol_name = _char_pt_to_str(symbol.symbol_name) self.symbol_addr = symbol.symbol_addr + self.mapping = symbol.mapping class CallChainEntryStructureUsingStr(object): |