# # Copyright (C) 2016 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. # """ Inferno is a tool to generate flamegraphs for android programs. It was originally written to profile surfaceflinger (Android compositor) but it can be used for other C++ program. It uses simpleperf to collect data. Programs have to be compiled with frame pointers which excludes ART based programs for the time being. Here is how it works: 1/ Data collection is started via simpleperf and pulled locally as "perf.data". 2/ The raw format is parsed, callstacks are merged to form a flamegraph data structure. 3/ The data structure is used to generate a SVG embedded into an HTML page. 4/ Javascript is injected to allow flamegraph navigation, search, coloring model. """ from scripts.simpleperf_report_lib import * import argparse from data_types import * from svg_renderer import * import datetime import webbrowser from adb_non_root import AdbNonRoot from adb_root import AdbRoot def create_process(adb_client, args): """ Retrieves target process pid and create a process contained. :param args: Argument as parsed by argparse :return: Process objectk """ process_id = adb_client.get_process_pid(args.process_name) process = Process(args.process_name, process_id) return process def collect_data(adb_client, process): """ Start simpleperf on device and collect data. Pull perf.data into cwd. :param process: Process object :return: Populated Process object """ if process.args.dwarf_unwinding: unwinding_parameter = "-g" print "Unwinding with dwarf." else: unwinding_parameter = "--call-graph fp" print "Unwinding with frame pointers." # Check whether sampling will be frequency based or event based. sampling_parameter = "-f %s" % process.args.sample_frequency if process.args.events: tokens = process.args.events.split(" ") if len(tokens) == 2: num_events = tokens[0] event_name = tokens[1] sampling_parameter = "-c %s -e '%s'" % (num_events, event_name) else: print "Event format string not recognized. Expected \"requency event_name\"." print "Got : [" + ",".join(tokens) + "]" return False print "Using event sampling (%s)." % sampling_parameter else: print "Using frequency sampling (%s)." % sampling_parameter process.cmd = "./simpleperf record \ -o /data/local/tmp/perf.data \ %s \ -p %s \ --duration %s \ %s" % ( unwinding_parameter, process.pid, process.args.capture_duration, sampling_parameter) print("Process '%s' PID = %d" % (process.name, process.pid)) if process.args.skip_collection: print("Skipping data collection, expecting perf.data in folder") return True print("Sampling for %s seconds..." % process.args.capture_duration) adb_client.delete_previous_data() success = adb_client.collect_data(process) if not success: return False err = adb_client.pull_data() if err: return False return True def parse_samples(process, args): """ read record_file, and print each sample""" record_file = args.record_file symfs_dir = args.symfs kallsyms_file = args.kallsyms lib = ReportLib() lib.ShowIpForUnknownSymbol() if symfs_dir is not None: lib.SetSymfs(symfs_dir) if record_file is not None: lib.SetRecordFile(record_file) if kallsyms_file is not None: lib.SetKallsymsFile(kallsyms_file) while True: sample = lib.GetNextSample() if sample is None: lib.Close() break symbol = lib.GetSymbolOfCurrentSample() callchain = lib.GetCallChainOfCurrentSample() process.get_thread(sample.tid).add_callchain(callchain, symbol, sample) process.num_samples += 1 print("Parsed %s callchains." % process.num_samples) def collapse_callgraphs(process): """ For each thread, collapse all callgraph into one flamegraph. :param process: Process object :return: None """ for _, thread in process.threads.items(): thread.collapse_flamegraph() def get_local_asset_content(local_path): """ Retrieves local package text content :param local_path: str, filename of local asset :return: str, the content of local_path """ f = open(os.path.join(os.path.dirname(__file__), local_path), 'r') content = f.read() f.close() return content def output_report(process): """ Generates a HTML report representing the result of simpleperf sampling as flamegraph :param process: Process object :return: str, absolute path to the file """ f = open('report.html', 'w') filepath = os.path.realpath(f.name) f.write("") f.write("
") f.write('') f.write('') f.write('