diff options
author | Yabin Cui <yabinc@google.com> | 2021-04-15 16:57:01 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2021-04-15 16:59:32 -0700 |
commit | deb7b5fe22126621f2b596aa841978d2db9bca87 (patch) | |
tree | 424798067ab43faa6b6da11b9d78190afea39eee /simpleperf | |
parent | b3d800c52c894064c49f2f506ee1541dd4b42361 (diff) | |
download | extras-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-x | simpleperf/scripts/annotate.py | 32 | ||||
-rwxr-xr-x | simpleperf/scripts/api_profiler.py | 11 | ||||
-rwxr-xr-x | simpleperf/scripts/app_profiler.py | 35 | ||||
-rwxr-xr-x | simpleperf/scripts/binary_cache_builder.py | 16 | ||||
-rwxr-xr-x | simpleperf/scripts/inferno.bat | 2 | ||||
-rwxr-xr-x | simpleperf/scripts/pprof_proto_generator.py | 3 | ||||
-rwxr-xr-x | simpleperf/scripts/report.py | 393 | ||||
-rwxr-xr-x | simpleperf/scripts/report_html.py | 13 | ||||
-rwxr-xr-x | simpleperf/scripts/report_sample.py | 2 | ||||
-rwxr-xr-x | simpleperf/scripts/run_simpleperf_on_device.py | 4 | ||||
-rwxr-xr-x | simpleperf/scripts/run_simpleperf_without_usb_connection.py | 6 | ||||
-rw-r--r-- | simpleperf/scripts/simpleperf_report_lib.py | 7 | ||||
-rw-r--r-- | simpleperf/scripts/simpleperf_utils.py | 2 | ||||
-rwxr-xr-x | simpleperf/scripts/update.py | 2 |
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('>', '>').replace('<', '<') + 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 # |