summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2017-03-21 17:17:01 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-03-21 17:17:01 +0000
commit50c4ddad23a407875925cad86c8895047f543310 (patch)
tree187b6514e605bee21b61733b3ddd3c336a2c2277
parentc2d829fe8782a232e8faa029ae96782ad50a423d (diff)
parent94892d4ead41682d60cc5ef35e9cf82c9ac1c876 (diff)
downloadextras-50c4ddad23a407875925cad86c8895047f543310.tar.gz
Merge "simpleperf: add script to generate proto data used by pprof."
am: 94892d4ead Change-Id: I73afb3476a3cef47b9e9a88556745f07362f44d9
-rw-r--r--simpleperf/README.md8
-rw-r--r--simpleperf/report_lib_interface.cpp21
-rw-r--r--simpleperf/scripts/annotate.py239
-rw-r--r--simpleperf/scripts/pprof_proto_generator.config39
-rw-r--r--simpleperf/scripts/pprof_proto_generator.py549
-rw-r--r--simpleperf/scripts/profile_pb2.py597
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py11
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 &current_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 &current_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):