summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2021-04-15 16:57:01 -0700
committerYabin Cui <yabinc@google.com>2021-04-15 16:59:32 -0700
commitdeb7b5fe22126621f2b596aa841978d2db9bca87 (patch)
tree424798067ab43faa6b6da11b9d78190afea39eee /simpleperf
parentb3d800c52c894064c49f2f506ee1541dd4b42361 (diff)
downloadextras-deb7b5fe22126621f2b596aa841978d2db9bca87.tar.gz
simpleperf: use python3 interpreter by default.
Also format code by autopep8. Bug: 185526771 Test: None Change-Id: If45bfff425463b1f0c71baa0e7f8032502eb1824
Diffstat (limited to 'simpleperf')
-rwxr-xr-xsimpleperf/scripts/annotate.py32
-rwxr-xr-xsimpleperf/scripts/api_profiler.py11
-rwxr-xr-xsimpleperf/scripts/app_profiler.py35
-rwxr-xr-xsimpleperf/scripts/binary_cache_builder.py16
-rwxr-xr-xsimpleperf/scripts/inferno.bat2
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py3
-rwxr-xr-xsimpleperf/scripts/report.py393
-rwxr-xr-xsimpleperf/scripts/report_html.py13
-rwxr-xr-xsimpleperf/scripts/report_sample.py2
-rwxr-xr-xsimpleperf/scripts/run_simpleperf_on_device.py4
-rwxr-xr-xsimpleperf/scripts/run_simpleperf_without_usb_connection.py6
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py7
-rw-r--r--simpleperf/scripts/simpleperf_utils.py2
-rwxr-xr-xsimpleperf/scripts/update.py2
14 files changed, 272 insertions, 256 deletions
diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py
index 8427cc89..b05caa43 100755
--- a/simpleperf/scripts/annotate.py
+++ b/simpleperf/scripts/annotate.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -28,6 +28,7 @@ from simpleperf_report_lib import ReportLib
from simpleperf_utils import (Addr2Nearestline, extant_dir, flatten_arg_list, is_windows,
log_exit, log_info, log_warning, SourceFileSearcher)
+
class SourceLine(object):
def __init__(self, file_id, function, line):
self.file = file_id
@@ -50,6 +51,7 @@ class SourceLine(object):
class Addr2Line(object):
"""collect information of how to map [dso_name, vaddr] to [source_file:line].
"""
+
def __init__(self, ndk_path, binary_cache_path, source_dirs):
self.addr2line = Addr2Nearestline(ndk_path, binary_cache_path, True)
self.source_searcher = SourceFileSearcher(source_dirs)
@@ -85,11 +87,11 @@ class Period(object):
running that line and functions called by that line. Same thing applies
when it is used for a function, a source file, or a binary.
"""
+
def __init__(self, period=0, acc_period=0):
self.period = period
self.acc_period = acc_period
-
def __iadd__(self, other):
self.period += other.period
self.acc_period += other.acc_period
@@ -98,17 +100,18 @@ class Period(object):
class DsoPeriod(object):
"""Period for each shared library"""
+
def __init__(self, dso_name):
self.dso_name = dso_name
self.period = Period()
-
def add_period(self, period):
self.period += period
class FilePeriod(object):
"""Period for each source file"""
+
def __init__(self, file_id):
self.file = file_id
self.period = Period()
@@ -117,18 +120,15 @@ class FilePeriod(object):
# Period for each function in the source file.
self.function_dict = {}
-
def add_period(self, period):
self.period += period
-
def add_line_period(self, line, period):
a = self.line_dict.get(line)
if a is None:
self.line_dict[line] = a = Period()
a += period
-
def add_function_period(self, function_name, function_start_line, period):
a = self.function_dict.get(function_name)
if not a:
@@ -140,6 +140,7 @@ class FilePeriod(object):
class SourceFileAnnotator(object):
"""group code for annotating source files"""
+
def __init__(self, config):
# check config variables
config_names = ['perf_data_list', 'source_dirs', 'comm_filters',
@@ -175,13 +176,11 @@ class SourceFileAnnotator(object):
shutil.rmtree(output_dir)
os.makedirs(output_dir)
-
self.addr2line = Addr2Line(self.config['ndk_path'], symfs_dir, config.get('source_dirs'))
self.period = 0
self.dso_periods = {}
self.file_periods = {}
-
def annotate(self):
self._collect_addrs()
self._convert_addrs_to_lines()
@@ -189,7 +188,6 @@ class SourceFileAnnotator(object):
self._write_summary()
self._annotate_files()
-
def _collect_addrs(self):
"""Read perf.data, collect all addresses we need to convert to
source file:line.
@@ -220,7 +218,6 @@ class SourceFileAnnotator(object):
self.addr2line.add_addr(symbol.dso_name, symbol.symbol_addr,
symbol.symbol_addr)
-
def _filter_sample(self, sample):
"""Return true if the sample can be used."""
if self.comm_filter:
@@ -234,17 +231,14 @@ class SourceFileAnnotator(object):
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 _convert_addrs_to_lines(self):
self.addr2line.convert_addrs_to_lines()
-
def _generate_periods(self):
"""read perf.data, collect Period for all types:
binaries, source files, functions, lines.
@@ -265,7 +259,6 @@ class SourceFileAnnotator(object):
continue
self._generate_periods_for_sample(lib, sample)
-
def _generate_periods_for_sample(self, lib, sample):
symbols = []
symbols.append(lib.GetSymbolOfCurrentSample())
@@ -310,7 +303,6 @@ class SourceFileAnnotator(object):
if is_sample_used:
self.period += sample.period
-
def _add_dso_period(self, dso_name, period, used_dso_dict):
if dso_name not in used_dso_dict:
used_dso_dict[dso_name] = True
@@ -319,7 +311,6 @@ class SourceFileAnnotator(object):
dso_period = self.dso_periods[dso_name] = DsoPeriod(dso_name)
dso_period.add_period(period)
-
def _add_file_period(self, source, period, used_file_dict):
if source.file_key not in used_file_dict:
used_file_dict[source.file_key] = True
@@ -328,21 +319,18 @@ class SourceFileAnnotator(object):
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 source.line_key not in used_line_dict:
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, period, used_function_dict):
if source.function_key not in used_function_dict:
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):
summary = os.path.join(self.config['annotate_dest_dir'], 'summary')
with open(summary, 'w') as f:
@@ -375,12 +363,10 @@ class SourceFileAnnotator(object):
f.write('\tline %d: %s\n' % (
line, self._get_percentage_str(file_period.line_dict[line])))
-
def _get_percentage_str(self, period, short=False):
s = 'acc_p: %f%%, p: %f%%' if short else 'accumulated_period: %f%%, period: %f%%'
return s % self._get_percentage(period)
-
def _get_percentage(self, period):
if self.period == 0:
return (0, 0)
@@ -388,7 +374,6 @@ class SourceFileAnnotator(object):
p = 100.0 * period.period / self.period
return (acc_p, p)
-
def _annotate_files(self):
"""Annotate Source files: add acc_period/period for each source file.
1. Annotate java source files, which have $JAVA_SRC_ROOT prefix.
@@ -409,7 +394,6 @@ class SourceFileAnnotator(object):
is_java = from_path.endswith('.java')
self._annotate_file(from_path, to_path, self.file_periods[key], is_java)
-
def _annotate_file(self, from_path, to_path, file_period, is_java):
"""Annotate a source file.
@@ -457,6 +441,7 @@ class SourceFileAnnotator(object):
wf.write(annotate)
wf.write(lines[line-1])
+
def main():
parser = argparse.ArgumentParser(description="""
Annotate source files based on profiling data. It reads line information from binary_cache
@@ -492,5 +477,6 @@ def main():
annotator.annotate()
log_info('annotate finish successfully, please check result in annotated_files/.')
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/api_profiler.py b/simpleperf/scripts/api_profiler.py
index 64e46580..d39b8e3d 100755
--- a/simpleperf/scripts/api_profiler.py
+++ b/simpleperf/scripts/api_profiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2019 The Android Open Source Project
#
@@ -35,12 +35,14 @@ import zipfile
from simpleperf_utils import AdbHelper, get_target_binary_path, log_exit, log_info, remove
+
def prepare_recording(args):
adb = AdbHelper()
enable_profiling_on_device(adb, args)
upload_simpleperf_to_device(adb)
run_simpleperf_prepare_cmd(adb)
+
def enable_profiling_on_device(adb, args):
android_version = adb.get_android_version()
if android_version >= 10:
@@ -49,12 +51,14 @@ def enable_profiling_on_device(adb, args):
adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0]))
adb.set_property('security.perf_harden', '0')
+
def upload_simpleperf_to_device(adb):
device_arch = adb.get_device_arch()
simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+
def run_simpleperf_prepare_cmd(adb):
adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-prepare'])
@@ -66,6 +70,7 @@ def collect_data(args):
download_recording_data(adb, args)
unzip_recording_data(args)
+
def download_recording_data(adb, args):
""" download recording data to simpleperf_data.zip."""
upload_simpleperf_to_device(adb)
@@ -74,6 +79,7 @@ def download_recording_data(adb, args):
adb.check_run(['pull', '/data/local/tmp/simpleperf_data.zip', args.out_dir])
adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
+
def unzip_recording_data(args):
zip_file_path = os.path.join(args.out_dir, 'simpleperf_data.zip')
with zipfile.ZipFile(zip_file_path, 'r') as zip_fh:
@@ -84,10 +90,12 @@ def unzip_recording_data(args):
zip_fh.extract(name, args.out_dir)
remove(zip_file_path)
+
class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
+
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=ArgumentHelpFormatter)
@@ -113,5 +121,6 @@ def main():
args = parser.parse_args()
args.func(args)
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index bcf018de..6137e108 100755
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -35,8 +35,10 @@ from simpleperf_utils import (
NATIVE_LIBS_DIR_ON_DEVICE = '/data/local/tmp/native_libs/'
+
class HostElfEntry(object):
"""Represent a native lib on host in NativeLibDownloader."""
+
def __init__(self, path, name, score):
self.path = path
self.name = name
@@ -56,6 +58,7 @@ class NativeLibDownloader(object):
2. Check the available native libs in /data/local/tmp/native_libs on device.
3. Sync native libs on device.
"""
+
def __init__(self, ndk_path, device_arch, adb):
self.adb = adb
self.readelf = ReadElf(ndk_path)
@@ -186,6 +189,7 @@ class NativeLibDownloader(object):
class ProfilerBase(object):
"""Base class of all Profilers."""
+
def __init__(self, args):
self.args = args
self.adb = AdbHelper(enable_switch_to_root=not args.disable_adb_root)
@@ -285,6 +289,7 @@ class ProfilerBase(object):
class AppProfiler(ProfilerBase):
"""Profile an Android app."""
+
def prepare(self):
super(AppProfiler, self).prepare()
if self.args.compile_java_code:
@@ -351,39 +356,44 @@ class AppProfiler(ProfilerBase):
class NativeProgramProfiler(ProfilerBase):
"""Profile a native program."""
+
def start(self):
- log_info('Waiting for native process %s' % self.args.native_program)
- while True:
- (result, pid) = self.adb.run_and_return_output(['shell', 'pidof',
- self.args.native_program])
- if not result:
- # Wait for 1 millisecond.
- time.sleep(0.001)
- else:
- self.start_profiling(['-p', str(int(pid))])
- break
+ log_info('Waiting for native process %s' % self.args.native_program)
+ while True:
+ (result, pid) = self.adb.run_and_return_output(['shell', 'pidof',
+ self.args.native_program])
+ if not result:
+ # Wait for 1 millisecond.
+ time.sleep(0.001)
+ else:
+ self.start_profiling(['-p', str(int(pid))])
+ break
class NativeCommandProfiler(ProfilerBase):
"""Profile running a native command."""
+
def start(self):
self.start_profiling([self.args.cmd])
class NativeProcessProfiler(ProfilerBase):
"""Profile processes given their pids."""
+
def start(self):
self.start_profiling(['-p', ','.join(self.args.pid)])
class NativeThreadProfiler(ProfilerBase):
"""Profile threads given their tids."""
+
def start(self):
self.start_profiling(['-t', ','.join(self.args.tid)])
class SystemWideProfiler(ProfilerBase):
"""Profile system wide."""
+
def start(self):
self.start_profiling(['-a'])
@@ -393,7 +403,7 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter)
target_group = parser.add_argument_group(title='Select profiling target'
- ).add_mutually_exclusive_group(required=True)
+ ).add_mutually_exclusive_group(required=True)
target_group.add_argument('-p', '--app', help="""Profile an Android app, given the package name.
Like `-p com.example.android.myapp`.""")
@@ -485,5 +495,6 @@ def main():
profiler = SystemWideProfiler(args)
profiler.profile()
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/binary_cache_builder.py b/simpleperf/scripts/binary_cache_builder.py
index 6acb6936..512fed98 100755
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -29,11 +29,14 @@ from simpleperf_report_lib import ReportLib
from simpleperf_utils import (AdbHelper, extant_dir, extant_file, flatten_arg_list, log_info,
log_warning, ReadElf, set_log_level)
+
def is_jit_symfile(dso_name):
return dso_name.split('/')[-1].startswith('TemporaryFile')
+
class BinaryCacheBuilder(object):
"""Collect all binaries needed by perf.data in binary_cache."""
+
def __init__(self, ndk_path, disable_adb_root):
self.adb = AdbHelper(enable_switch_to_root=not disable_adb_root)
self.readelf = ReadElf(ndk_path)
@@ -42,14 +45,12 @@ class BinaryCacheBuilder(object):
os.makedirs(self.binary_cache_dir)
self.binaries = {}
-
def build_binary_cache(self, perf_data_path, symfs_dirs):
self._collect_used_binaries(perf_data_path)
self.copy_binaries_from_symfs_dirs(symfs_dirs)
self.pull_binaries_from_device()
self._pull_kernel_symbols()
-
def _collect_used_binaries(self, perf_data_path):
"""read perf.data, collect all used binaries and their build id (if available)."""
# A dict mapping from binary name to build_id
@@ -75,7 +76,6 @@ class BinaryCacheBuilder(object):
binaries[dso_name] = lib.GetBuildIdForPath(dso_name)
self.binaries = binaries
-
def copy_binaries_from_symfs_dirs(self, symfs_dirs):
"""collect all files in symfs_dirs."""
if not symfs_dirs:
@@ -113,7 +113,6 @@ class BinaryCacheBuilder(object):
expected_build_id, binary)
break
-
def _copy_to_binary_cache(self, from_path, expected_build_id, target_file):
if target_file[0] == '/':
target_file = target_file[1:]
@@ -128,7 +127,6 @@ class BinaryCacheBuilder(object):
log_info('copy to binary_cache: %s to %s' % (from_path, target_file))
shutil.copy(from_path, target_file)
-
def _need_to_copy(self, source_file, target_file, expected_build_id):
if not os.path.isfile(target_file):
return True
@@ -137,7 +135,6 @@ class BinaryCacheBuilder(object):
return self._get_file_stripped_level(source_file) < self._get_file_stripped_level(
target_file)
-
def _get_file_stripped_level(self, file_path):
"""Return stripped level of an ELF file. Larger value means more stripped."""
sections = self.readelf.get_sections(file_path)
@@ -147,7 +144,6 @@ class BinaryCacheBuilder(object):
return 1
return 2
-
def pull_binaries_from_device(self):
"""pull binaries needed in perf.data to binary_cache."""
for binary in self.binaries:
@@ -159,7 +155,6 @@ class BinaryCacheBuilder(object):
binary_cache_file = os.path.join(self.binary_cache_dir, binary_cache_file)
self._check_and_pull_binary(binary, build_id, binary_cache_file)
-
def _check_and_pull_binary(self, binary, expected_build_id, binary_cache_file):
"""If the binary_cache_file exists and has the expected_build_id, there
is no need to pull the binary from device. Otherwise, pull it.
@@ -182,12 +177,10 @@ class BinaryCacheBuilder(object):
else:
log_info('use current file in binary_cache: %s' % binary_cache_file)
-
def _read_build_id(self, file_path):
"""read build id of a binary on host."""
return self.readelf.get_build_id(file_path)
-
def _pull_file_from_device(self, device_path, host_path):
if self.adb.run(['pull', device_path, host_path]):
return True
@@ -201,7 +194,6 @@ class BinaryCacheBuilder(object):
log_warning('failed to pull %s from device' % device_path)
return False
-
def _pull_kernel_symbols(self):
file_path = os.path.join(self.binary_cache_dir, 'kallsyms')
if os.path.isfile(file_path):
diff --git a/simpleperf/scripts/inferno.bat b/simpleperf/scripts/inferno.bat
index d0dc6d9d..1a8efa49 100755
--- a/simpleperf/scripts/inferno.bat
+++ b/simpleperf/scripts/inferno.bat
@@ -1,2 +1,2 @@
set SCRIPTPATH=%~dp0
-python %SCRIPTPATH%inferno\inferno.py %*
+python3 %SCRIPTPATH%inferno\inferno.py %*
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index 2800319f..db2cde52 100755
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -37,6 +37,7 @@ try:
except ImportError:
log_exit('google.protobuf module is missing. Please install it first.')
+
def load_pprof_profile(filename):
profile = profile_pb2.Profile()
with open(filename, "rb") as f:
diff --git a/simpleperf/scripts/report.py b/simpleperf/scripts/report.py
index 380479e4..e42160c9 100755
--- a/simpleperf/scripts/report.py
+++ b/simpleperf/scripts/report.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2015 The Android Open Source Project
#
@@ -47,224 +47,225 @@ PAD_Y = 3
class CallTreeNode(object):
- """Representing a node in call-graph."""
+ """Representing a node in call-graph."""
- def __init__(self, percentage, function_name):
- self.percentage = percentage
- self.call_stack = [function_name]
- self.children = []
+ def __init__(self, percentage, function_name):
+ self.percentage = percentage
+ self.call_stack = [function_name]
+ self.children = []
- def add_call(self, function_name):
- self.call_stack.append(function_name)
+ def add_call(self, function_name):
+ self.call_stack.append(function_name)
- def add_child(self, node):
- self.children.append(node)
+ def add_child(self, node):
+ self.children.append(node)
- def __str__(self):
- strs = self.dump()
- return '\n'.join(strs)
+ def __str__(self):
+ strs = self.dump()
+ return '\n'.join(strs)
- def dump(self):
- strs = []
- strs.append('CallTreeNode percentage = %.2f' % self.percentage)
- for function_name in self.call_stack:
- strs.append(' %s' % function_name)
- for child in self.children:
- child_strs = child.dump()
- strs.extend([' ' + x for x in child_strs])
- return strs
+ def dump(self):
+ strs = []
+ strs.append('CallTreeNode percentage = %.2f' % self.percentage)
+ for function_name in self.call_stack:
+ strs.append(' %s' % function_name)
+ for child in self.children:
+ child_strs = child.dump()
+ strs.extend([' ' + x for x in child_strs])
+ return strs
class ReportItem(object):
- """Representing one item in report, may contain a CallTree."""
+ """Representing one item in report, may contain a CallTree."""
- def __init__(self, raw_line):
- self.raw_line = raw_line
- self.call_tree = None
+ def __init__(self, raw_line):
+ self.raw_line = raw_line
+ self.call_tree = None
+
+ def __str__(self):
+ strs = []
+ strs.append('ReportItem (raw_line %s)' % self.raw_line)
+ if self.call_tree is not None:
+ strs.append('%s' % self.call_tree)
+ return '\n'.join(strs)
- def __str__(self):
- strs = []
- strs.append('ReportItem (raw_line %s)' % self.raw_line)
- if self.call_tree is not None:
- strs.append('%s' % self.call_tree)
- return '\n'.join(strs)
class EventReport(object):
- """Representing report for one event attr."""
+ """Representing report for one event attr."""
- def __init__(self, common_report_context):
- self.context = common_report_context[:]
- self.title_line = None
- self.report_items = []
+ def __init__(self, common_report_context):
+ self.context = common_report_context[:]
+ self.title_line = None
+ self.report_items = []
def parse_event_reports(lines):
- # Parse common report context
- common_report_context = []
- line_id = 0
- while line_id < len(lines):
- line = lines[line_id]
- if not line or line.find('Event:') == 0:
- break
- common_report_context.append(line)
- line_id += 1
-
- event_reports = []
- in_report_context = True
- cur_event_report = EventReport(common_report_context)
- cur_report_item = None
- call_tree_stack = {}
- vertical_columns = []
- last_node = None
-
- has_skipped_callgraph = False
-
- for line in lines[line_id:]:
- if not line:
- in_report_context = not in_report_context
- if in_report_context:
- cur_event_report = EventReport(common_report_context)
- continue
-
- if in_report_context:
- cur_event_report.context.append(line)
- if line.find('Event:') == 0:
- event_reports.append(cur_event_report)
- continue
-
- if cur_event_report.title_line is None:
- cur_event_report.title_line = line
- elif not line[0].isspace():
- cur_report_item = ReportItem(line)
- cur_event_report.report_items.append(cur_report_item)
- # Each report item can have different column depths.
- vertical_columns = []
- else:
- for i in range(len(line)):
- if line[i] == '|':
- if not vertical_columns or vertical_columns[-1] < i:
- vertical_columns.append(i)
-
- if not line.strip('| \t'):
- continue
- if 'skipped in brief callgraph mode' in line:
- has_skipped_callgraph = True
- continue
-
- if line.find('-') == -1:
- line = line.strip('| \t')
- function_name = line
- last_node.add_call(function_name)
- else:
- pos = line.find('-')
- depth = -1
- for i in range(len(vertical_columns)):
- if pos >= vertical_columns[i]:
- depth = i
- assert depth != -1
-
- line = line.strip('|- \t')
- m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
- if m:
- percentage = float(m.group(1))
- function_name = m.group(2)
- else:
- percentage = 100.0
- function_name = line
-
- node = CallTreeNode(percentage, function_name)
- if depth == 0:
- cur_report_item.call_tree = node
+ # Parse common report context
+ common_report_context = []
+ line_id = 0
+ while line_id < len(lines):
+ line = lines[line_id]
+ if not line or line.find('Event:') == 0:
+ break
+ common_report_context.append(line)
+ line_id += 1
+
+ event_reports = []
+ in_report_context = True
+ cur_event_report = EventReport(common_report_context)
+ cur_report_item = None
+ call_tree_stack = {}
+ vertical_columns = []
+ last_node = None
+
+ has_skipped_callgraph = False
+
+ for line in lines[line_id:]:
+ if not line:
+ in_report_context = not in_report_context
+ if in_report_context:
+ cur_event_report = EventReport(common_report_context)
+ continue
+
+ if in_report_context:
+ cur_event_report.context.append(line)
+ if line.find('Event:') == 0:
+ event_reports.append(cur_event_report)
+ continue
+
+ if cur_event_report.title_line is None:
+ cur_event_report.title_line = line
+ elif not line[0].isspace():
+ cur_report_item = ReportItem(line)
+ cur_event_report.report_items.append(cur_report_item)
+ # Each report item can have different column depths.
+ vertical_columns = []
else:
- call_tree_stack[depth - 1].add_child(node)
- call_tree_stack[depth] = node
- last_node = node
-
- if has_skipped_callgraph:
- log_warning('some callgraphs are skipped in brief callgraph mode')
-
- return event_reports
+ for i in range(len(line)):
+ if line[i] == '|':
+ if not vertical_columns or vertical_columns[-1] < i:
+ vertical_columns.append(i)
+
+ if not line.strip('| \t'):
+ continue
+ if 'skipped in brief callgraph mode' in line:
+ has_skipped_callgraph = True
+ continue
+
+ if line.find('-') == -1:
+ line = line.strip('| \t')
+ function_name = line
+ last_node.add_call(function_name)
+ else:
+ pos = line.find('-')
+ depth = -1
+ for i in range(len(vertical_columns)):
+ if pos >= vertical_columns[i]:
+ depth = i
+ assert depth != -1
+
+ line = line.strip('|- \t')
+ m = re.search(r'^([\d\.]+)%[-\s]+(.+)$', line)
+ if m:
+ percentage = float(m.group(1))
+ function_name = m.group(2)
+ else:
+ percentage = 100.0
+ function_name = line
+
+ node = CallTreeNode(percentage, function_name)
+ if depth == 0:
+ cur_report_item.call_tree = node
+ else:
+ call_tree_stack[depth - 1].add_child(node)
+ call_tree_stack[depth] = node
+ last_node = node
+
+ if has_skipped_callgraph:
+ log_warning('some callgraphs are skipped in brief callgraph mode')
+
+ return event_reports
class ReportWindow(object):
- """A window used to display report file."""
-
- def __init__(self, main, report_context, title_line, report_items):
- frame = Frame(main)
- frame.pack(fill=BOTH, expand=1)
-
- font = Font(family='courier', size=12)
-
- # Report Context
- for line in report_context:
- label = Label(frame, text=line, font=font)
- label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
-
- # Space
- label = Label(frame, text='', font=font)
- label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
-
- # Title
- label = Label(frame, text=' ' + title_line, font=font)
- label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
-
- # Report Items
- report_frame = Frame(frame)
- report_frame.pack(fill=BOTH, expand=1)
-
- yscrollbar = Scrollbar(report_frame)
- yscrollbar.pack(side=RIGHT, fill=Y)
- xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL)
- xscrollbar.pack(side=BOTTOM, fill=X)
-
- tree = Treeview(report_frame, columns=[title_line], show='')
- tree.pack(side=LEFT, fill=BOTH, expand=1)
- tree.tag_configure('set_font', font=font)
-
- tree.config(yscrollcommand=yscrollbar.set)
- yscrollbar.config(command=tree.yview)
- tree.config(xscrollcommand=xscrollbar.set)
- xscrollbar.config(command=tree.xview)
-
- self.display_report_items(tree, report_items)
-
- def display_report_items(self, tree, report_items):
- for report_item in report_items:
- prefix_str = '+ ' if report_item.call_tree is not None else ' '
- id = tree.insert(
- '',
- 'end',
- None,
- values=[
- prefix_str +
- report_item.raw_line],
- tag='set_font')
- if report_item.call_tree is not None:
- self.display_call_tree(tree, id, report_item.call_tree, 1)
-
- def display_call_tree(self, tree, parent_id, node, indent):
- id = parent_id
- indent_str = ' ' * indent
-
- if node.percentage != 100.0:
- percentage_str = '%.2f%% ' % node.percentage
- else:
- percentage_str = ''
-
- for i in range(len(node.call_stack)):
- s = indent_str
- s += '+ ' if node.children and i == len(node.call_stack) - 1 else ' '
- s += percentage_str if i == 0 else ' ' * len(percentage_str)
- s += node.call_stack[i]
- child_open = False if i == len(node.call_stack) - 1 and indent > 1 else True
- id = tree.insert(id, 'end', None, values=[s], open=child_open,
- tag='set_font')
-
- for child in node.children:
- self.display_call_tree(tree, id, child, indent + 1)
+ """A window used to display report file."""
+
+ def __init__(self, main, report_context, title_line, report_items):
+ frame = Frame(main)
+ frame.pack(fill=BOTH, expand=1)
+
+ font = Font(family='courier', size=12)
+
+ # Report Context
+ for line in report_context:
+ label = Label(frame, text=line, font=font)
+ label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+ # Space
+ label = Label(frame, text='', font=font)
+ label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+ # Title
+ label = Label(frame, text=' ' + title_line, font=font)
+ label.pack(anchor=W, padx=PAD_X, pady=PAD_Y)
+
+ # Report Items
+ report_frame = Frame(frame)
+ report_frame.pack(fill=BOTH, expand=1)
+
+ yscrollbar = Scrollbar(report_frame)
+ yscrollbar.pack(side=RIGHT, fill=Y)
+ xscrollbar = Scrollbar(report_frame, orient=HORIZONTAL)
+ xscrollbar.pack(side=BOTTOM, fill=X)
+
+ tree = Treeview(report_frame, columns=[title_line], show='')
+ tree.pack(side=LEFT, fill=BOTH, expand=1)
+ tree.tag_configure('set_font', font=font)
+
+ tree.config(yscrollcommand=yscrollbar.set)
+ yscrollbar.config(command=tree.yview)
+ tree.config(xscrollcommand=xscrollbar.set)
+ xscrollbar.config(command=tree.xview)
+
+ self.display_report_items(tree, report_items)
+
+ def display_report_items(self, tree, report_items):
+ for report_item in report_items:
+ prefix_str = '+ ' if report_item.call_tree is not None else ' '
+ id = tree.insert(
+ '',
+ 'end',
+ None,
+ values=[
+ prefix_str +
+ report_item.raw_line],
+ tag='set_font')
+ if report_item.call_tree is not None:
+ self.display_call_tree(tree, id, report_item.call_tree, 1)
+
+ def display_call_tree(self, tree, parent_id, node, indent):
+ id = parent_id
+ indent_str = ' ' * indent
+
+ if node.percentage != 100.0:
+ percentage_str = '%.2f%% ' % node.percentage
+ else:
+ percentage_str = ''
+
+ for i in range(len(node.call_stack)):
+ s = indent_str
+ s += '+ ' if node.children and i == len(node.call_stack) - 1 else ' '
+ s += percentage_str if i == 0 else ' ' * len(percentage_str)
+ s += node.call_stack[i]
+ child_open = False if i == len(node.call_stack) - 1 and indent > 1 else True
+ id = tree.insert(id, 'end', None, values=[s], open=child_open,
+ tag='set_font')
+
+ for child in node.children:
+ self.display_call_tree(tree, id, child, indent + 1)
def display_report_file(report_file, self_kill_after_sec):
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index b7bbd9a8..17d53e92 100755
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -28,6 +28,7 @@ from simpleperf_utils import (Addr2Nearestline, get_script_dir, log_exit, log_in
MAX_CALLSTACK_LENGTH = 750
+
class HtmlWriter(object):
def __init__(self, output_path):
@@ -60,9 +61,11 @@ class HtmlWriter(object):
self.add(f.read())
return self
+
def modify_text_for_html(text):
return text.replace('>', '&gt;').replace('<', '&lt;')
+
class EventScope(object):
def __init__(self, name):
@@ -384,6 +387,7 @@ class CallNode(object):
class LibSet(object):
""" Collection of shared libraries used in perf.data. """
+
def __init__(self):
self.lib_name_to_id = {}
self.lib_id_to_name = []
@@ -402,6 +406,7 @@ class LibSet(object):
class Function(object):
""" Represent a function in a shared library. """
+
def __init__(self, lib_id, func_name, func_id, start_addr, addr_len):
self.lib_id = lib_id
self.func_name = func_name
@@ -414,6 +419,7 @@ class Function(object):
class FunctionSet(object):
""" Collection of functions used in perf.data. """
+
def __init__(self):
self.name_to_func = {}
self.id_to_func = {}
@@ -440,6 +446,7 @@ class FunctionSet(object):
class SourceFile(object):
""" A source file containing source code hit by samples. """
+
def __init__(self, file_id, abstract_path):
self.file_id = file_id
self.abstract_path = abstract_path # path reported by addr2line
@@ -464,6 +471,7 @@ class SourceFile(object):
class SourceFileSet(object):
""" Collection of source files. """
+
def __init__(self):
self.path_to_source_files = {} # map from file path to SourceFile.
@@ -482,7 +490,6 @@ class SourceFileSet(object):
source_file.add_source_code(real_path)
-
class RecordData(object):
"""RecordData reads perf.data, and generates data used by report.js in json format.
@@ -839,6 +846,7 @@ class RecordData(object):
file_list.append(file_data)
return file_list
+
URLS = {
'jquery': 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js',
'bootstrap4-css': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css',
@@ -851,6 +859,7 @@ URLS = {
'gstatic-charts': 'https://www.gstatic.com/charts/loader.js',
}
+
class ReportGenerator(object):
def __init__(self, html_path):
diff --git a/simpleperf/scripts/report_sample.py b/simpleperf/scripts/report_sample.py
index 31a280ff..d05f1f7c 100755
--- a/simpleperf/scripts/report_sample.py
+++ b/simpleperf/scripts/report_sample.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
diff --git a/simpleperf/scripts/run_simpleperf_on_device.py b/simpleperf/scripts/run_simpleperf_on_device.py
index d63b82c2..4cd167c0 100755
--- a/simpleperf/scripts/run_simpleperf_on_device.py
+++ b/simpleperf/scripts/run_simpleperf_on_device.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -23,6 +23,7 @@ import subprocess
import sys
from simpleperf_utils import AdbHelper, disable_debug_log, get_target_binary_path
+
def main():
disable_debug_log()
adb = AdbHelper()
@@ -33,5 +34,6 @@ def main():
shell_cmd = 'cd /data/local/tmp && ./simpleperf ' + ' '.join(sys.argv[1:])
sys.exit(subprocess.call([adb.adb_path, 'shell', shell_cmd]))
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/run_simpleperf_without_usb_connection.py b/simpleperf/scripts/run_simpleperf_without_usb_connection.py
index e8919e03..19700ee8 100755
--- a/simpleperf/scripts/run_simpleperf_without_usb_connection.py
+++ b/simpleperf/scripts/run_simpleperf_without_usb_connection.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2018 The Android Open Source Project
#
@@ -34,6 +34,7 @@ import time
from simpleperf_utils import AdbHelper, get_target_binary_path, log_warning
+
def start_recording(args):
adb = AdbHelper()
device_arch = adb.get_device_arch()
@@ -59,6 +60,7 @@ def start_recording(args):
adb.run(['shell', 'cat', '/data/local/tmp/simpleperf_output'])
sys.exit(subproc.returncode)
+
def stop_recording(args):
adb = AdbHelper()
result = adb.run(['shell', 'pidof', 'simpleperf'])
@@ -73,6 +75,7 @@ def stop_recording(args):
adb.check_run(['pull', '/data/local/tmp/perf.data', args.perf_data_path])
print('The recording data has been collected in %s.' % args.perf_data_path)
+
def main():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
@@ -95,5 +98,6 @@ def main():
args = parser.parse_args()
args.func(args)
+
if __name__ == '__main__':
main()
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
index 15efef47..1562b4b0 100644
--- a/simpleperf/scripts/simpleperf_report_lib.py
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
@@ -43,6 +43,7 @@ def _char_pt(s):
def _char_pt_to_str(char_pt):
return bytes_to_str(char_pt)
+
def _check(cond, failmsg):
if not cond:
raise RuntimeError(failmsg)
@@ -117,7 +118,7 @@ class TracingFieldFormatStruct(ct.Structure):
length = 0
while length < self.elem_count and bytes_to_str(data[self.offset + length]) != '\x00':
length += 1
- return bytes_to_str(data[self.offset : self.offset + length])
+ return bytes_to_str(data[self.offset: self.offset + length])
unpack_key = self._unpack_key_dict.get(self.elem_size)
if unpack_key:
if not self.is_signed:
@@ -129,7 +130,7 @@ class TracingFieldFormatStruct(ct.Structure):
value = []
offset = self.offset
for _ in range(self.elem_count):
- value.append(data[offset : offset + self.elem_size])
+ value.append(data[offset: offset + self.elem_size])
offset += self.elem_size
if self.elem_count == 1:
value = value[0]
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index 2ef6df66..57017901 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#
diff --git a/simpleperf/scripts/update.py b/simpleperf/scripts/update.py
index 2e56c655..d27fff33 100755
--- a/simpleperf/scripts/update.py
+++ b/simpleperf/scripts/update.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2016 The Android Open Source Project
#