"""
-from simpleperf_report_lib import ReportLib
import argparse
-from data_types import *
-from svg_renderer import *
import datetime
import os
import subprocess
+import sys
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
+try:
+ from simpleperf_report_lib import ReportLib
+ from utils import log_exit, log_info, AdbHelper
+except:
+ print("Please go to the parent directory, and run inferno.sh or inferno.bat.")
+ sys.exit(1)
+from data_types import *
+from svg_renderer import *
-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."
+def collect_data(args, process):
+ app_profiler_args = [sys.executable, "app_profiler.py", "-nb"]
+ if args.app:
+ app_profiler_args += ["-p", args.app]
+ elif args.native_program:
+ app_profiler_args += ["-np", args.native_program]
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(" ")
+ log_exit("Please set profiling target with -p or -np option.")
+ if args.skip_recompile:
+ app_profiler_args.append("-nc")
+ if args.disable_adb_root:
+ app_profiler_args.append("--disable_adb_root")
+ record_arg_str = ""
+ if args.dwarf_unwinding:
+ record_arg_str += "-g "
+ else:
+ record_arg_str += "--call-graph fp "
+ if args.events:
+ tokens = 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)
+ record_arg_str += "-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
+ log_exit("Event format string of -e option cann't be recognized.")
+ log_info("Using event sampling (-c %s -e %s)." % (num_events, event_name))
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
+ record_arg_str += "-f %d " % args.sample_frequency
+ log_info("Using frequency sampling (-f %d)." % args.sample_frequency)
+ record_arg_str += "--duration %d " % args.capture_duration
+ process.cmd = " ".join(app_profiler_args) + ' -r "%s"' % record_arg_str
+ app_profiler_args += ["-r", record_arg_str]
+ returncode = subprocess.call(app_profiler_args)
+ return returncode == 0
def parse_samples(process, args):
process.get_thread(sample.tid).add_callchain(callchain, symbol, sample)
process.num_samples += 1
- print("Parsed %s callchains." % process.num_samples)
+ log_info("Parsed %s callchains." % process.num_samples)
def collapse_callgraphs(process):
filepath = os.path.realpath(f.name)
f.write("<html>")
f.write("<body style='font-family: Monospace;' onload='init()'>")
- f.write('<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;} </style>')
+ f.write("""<style type="text/css"> .s { stroke:black; stroke-width:0.5; cursor:pointer;}
+ </style>""")
f.write('<style type="text/css"> .t:hover { cursor:pointer; } </style>')
f.write('<img height="180" alt = "Embedded Image" src ="data')
f.write(get_local_asset_content("inferno.b64"))
f.write('"/>')
- f.write("<div style='display:inline-block;'> \
- <font size='8'>\
- Inferno Flamegraph Report</font><br/><br/> \
- Process : %s (%d)<br/>\
- Date : %s<br/>\
- Threads : %d <br/>\
- Samples : %d</br>\
- Duration: %s seconds<br/>\
- Machine : %s (%s) by %s<br/>\
- Capture : %s<br/><br/></div>"
- % (
+ f.write("""<div style='display:inline-block;'>
+ <font size='8'>
+ Inferno Flamegraph Report</font><br/><br/>
+ Process : %s (%d)<br/>
+ Date : %s<br/>
+ Threads : %d <br/>
+ Samples : %d</br>
+ Duration: %s seconds<br/>""" % (
process.name,process.pid,
datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"),
len(process.threads),
process.num_samples,
- process.args.capture_duration,
- process.props["ro.product.model"], process.props["ro.product.name"],
- process.props["ro.product.manufacturer"],
- process.cmd))
- f.write("<br/><br/><div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>")
+ process.args.capture_duration))
+ if 'ro.product.model' in process.props:
+ f.write("Machine : %s (%s) by %s<br/>" % (process.props["ro.product.model"],
+ process.props["ro.product.name"], process.props["ro.product.manufacturer"]))
+ if process.cmd:
+ f.write("Capture : %s<br/><br/>" % process.cmd)
+ f.write("</div>")
+ f.write("""<br/><br/>
+ <div>Navigate with WASD, zoom in with SPACE, zoom out with BACKSPACE.</div>""")
f.write(get_local_asset_content("script.js"))
# Output tid == pid Thread first.
main_thread = [x for _, x in process.threads.items() if x.tid == process.pid]
for thread in main_thread:
- f.write("<br/><br/><b>Main Thread %d (%d samples):</b><br/>\n\n\n\n" % (thread.tid, thread.num_samples))
+ f.write("<br/><br/><b>Main Thread %d (%d samples):</b><br/>\n\n\n\n" % (
+ thread.tid, thread.num_samples))
renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width)
other_threads = [x for _, x in process.threads.items() if x.tid != process.pid]
for thread in other_threads:
- f.write("<br/><br/><b>Thread %d (%d samples):</b><br/>\n\n\n\n" % (thread.tid, thread.num_samples))
+ f.write("<br/><br/><b>Thread %d (%d samples):</b><br/>\n\n\n\n" % (
+ thread.tid, thread.num_samples))
renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width)
f.write("</body>")
generate_flamegraph_offsets(thread.flamegraph)
-def collect_machine_info(adb_client, process):
- process.props = adb_client.get_props()
-
+def collect_machine_info(process):
+ adb = AdbHelper()
+ process.props = {}
+ process.props['ro.product.model'] = adb.get_property('ro.product.model')
+ process.props['ro.product.name'] = adb.get_property('ro.product.name')
+ process.props['ro.product.manufacturer'] = adb.get_property('ro.product.manufacturer')
-def setup_adb():
- err = subprocess.call(["adb", "root"])
- if err == 0:
- return AdbRoot()
- else:
- return AdbNonRoot()
def open_report_in_browser(report_path):
# Try to open the report with Chrome
def main():
parser = argparse.ArgumentParser(description='Report samples in perf.data.')
- parser.add_argument('--symfs', help='Set the path to find binaries with symbols and debug info.')
+ parser.add_argument('--symfs', help="""Set the path to find binaries with symbols and debug
+ info.""")
parser.add_argument('--kallsyms', help='Set the path to find kernel symbols.')
parser.add_argument('--record_file', default='perf.data', help='Default is perf.data.')
- parser.add_argument('-t', '--capture_duration', default=10, help='Capture duration in seconds.')
- parser.add_argument('-p', '--process_name', default='surfaceflinger', help='Default is surfaceflinger.')
+ parser.add_argument('-t', '--capture_duration', type=int, default=10,
+ help="""Capture duration in seconds.""")
+ parser.add_argument('-p', '--app', help="""Profile an Android app, given the package name.
+ Like -p com.example.android.myapp.""")
+ parser.add_argument('-np', '--native_program', default="surfaceflinger",
+ help="""Profile a native program. The program should be running on the
+ device. Like -np surfaceflinger.""")
parser.add_argument('-c', '--color', default='hot', choices=['hot', 'dso', 'legacy'],
- help='Color theme: hot=percentage of samples, dso=callsite DSO name, legacy=brendan style')
- parser.add_argument('-sc','--skip_collection', default=False, help='Skip data collection', action="store_true")
- parser.add_argument('-f', '--sample_frequency', default=6000, help='Sample frequency')
+ help="""Color theme: hot=percentage of samples, dso=callsite DSO name,
+ legacy=brendan style""")
+ parser.add_argument('-sc','--skip_collection', default=False, help='Skip data collection',
+ action="store_true")
+ parser.add_argument('-nc', '--skip_recompile', action='store_true', help="""When profiling
+ an Android app, by default we recompile java bytecode to native
+ instructions to profile java code. It takes some time. You can skip it
+ if the code has been compiled or you don't need to profile java code.""")
+ parser.add_argument('-f', '--sample_frequency', type=int, default=6000, help='Sample frequency')
parser.add_argument('-w', '--svg_width', type=int, default=1124)
- parser.add_argument('-sb', '--skip_push_binary', help='Skip pushing simpleperf before profiling',
- default=False, action="store_true")
parser.add_argument('-du', '--dwarf_unwinding', help='Perform unwinding using dwarf instead of fp.',
default=False, action='store_true')
- parser.add_argument('-e', '--events',
- help='Sample based on event occurences instead of frequency. '
- 'Format expected is "event_counts event_name". e.g: "10000 cpu-cyles". A few examples of \
- nmames: cpu-cycles, cache-references, cache-misses, branch-instructions, branch-misses',
+ parser.add_argument('-e', '--events', help="""Sample based on event occurences instead of
+ frequency. Format expected is "event_counts event_name".
+ e.g: "10000 cpu-cyles". A few examples of event_name: cpu-cycles,
+ cache-references, cache-misses, branch-instructions, branch-misses""",
default="")
+ parser.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run in non
+ root mode.""")
args = parser.parse_args()
-
- # Since we may attempt to sample privileged process, let's try to be root.
- adb_client = setup_adb()
-
- # Create a process object
- process = create_process(adb_client, args)
- if process.pid == 0:
- print("Unable to retrive pid for process '%s'. Terminating." % process.name)
- return
+ process_name = args.app or args.native_program
+ process = Process(process_name, 0)
process.args = args
+ if not args.skip_collection:
+ log_info("Starting data collection stage for process '%s'." % process_name)
+ if not collect_data(args, process):
+ log_exit("Unable to collect data.")
+ try:
+ result, output = AdbHelper().run_and_return_output(['shell', 'pidof', process_name])
+ if result:
+ process.pid = int(output)
+ except:
+ raise
+ collect_machine_info(process)
- print("Starting data collection stage for process '%s'." % args.process_name)
- success = collect_data(adb_client, process)
- if not success:
- print "Unable to collect data"
- return
-
- collect_machine_info(adb_client, process)
parse_samples(process, args)
collapse_callgraphs(process)
generate_threads_offsets(process)
report_path = output_report(process)
open_report_in_browser(report_path)
- print "Report generated at '%s'." % report_path
+ log_info("Report generated at '%s'." % report_path)
if __name__ == "__main__":
main()
\ No newline at end of file
except:
has_google_protobuf = False
+inferno_script = "inferno.bat" if is_windows() else "./inferno.sh"
+
support_trace_offcpu = None
def is_trace_offcpu_supported():
def cleanupTestFiles(cls):
remove("binary_cache")
remove("annotated_files")
- remove("perf.data")
+ #remove("perf.data")
remove("report.txt")
remove("pprof.profile")
def run_cmd(self, args, return_output=False):
- args = [sys.executable] + args
+ if args[0].endswith('.py'):
+ args = [sys.executable] + args
try:
if not return_output:
returncode = subprocess.call(args)
self.check_strings_in_content(output, check_strings_without_lines +
["has_line_numbers: False"])
+ def common_test_inferno(self):
+ self.run_cmd([inferno_script, "-h"])
+ remove("perf.data")
+ append_args = [] if self.adb_root else ["--disable_adb_root"]
+ self.run_cmd([inferno_script, "-p", self.package_name, "-t", "3"] + append_args)
+ self.check_exist(file="perf.data")
+ self.run_cmd([inferno_script, "-p", self.package_name, "-f", "1000", "-du", "-t", "1",
+ "-nc"] + append_args)
+ self.run_cmd([inferno_script, "-p", self.package_name, "-e", "100000 cpu-cycles",
+ "-t", "1", "-nc"] + append_args)
+ self.run_cmd([inferno_script, "-sc"])
+
class TestExamplePureJava(TestExampleBase):
@classmethod
check_strings_without_lines=
["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()"])
+ def test_inferno(self):
+ self.common_test_inferno()
+
class TestExamplePureJavaRoot(TestExampleBase):
@classmethod
("SleepFunction", 20, 0),
("line 24", 20, 0),
("line 32", 20, 0)])
+ self.run_cmd([inferno_script, "-sc"])
class TestExampleWithNative(TestExampleBase):
check_strings_without_lines=
["BusyLoopThread"])
+ def test_inferno(self):
+ self.common_test_inferno()
+
class TestExampleWithNativeRoot(TestExampleBase):
@classmethod
("SleepFunction", 20, 0),
("line 73", 20, 0),
("line 83", 20, 0)])
+ self.run_cmd([inferno_script, "-sc"])
class TestExampleWithNativeJniCall(TestExampleBase):
("line 26", 20, 0),
("native-lib.cpp", 10, 0),
("line 40", 10, 0)])
+ self.run_cmd([inferno_script, "-sc"])
class TestExampleWithNativeForceArm(TestExampleWithNative):
check_strings_without_lines=
["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()"])
+ def test_inferno(self):
+ self.common_test_inferno()
+
class TestExampleOfKotlinRoot(TestExampleBase):
@classmethod
("SleepFunction", 20, 0),
("line 24", 20, 0),
("line 32", 20, 0)])
+ self.run_cmd([inferno_script, "-sc"])
class TestProfilingNativeProgram(TestExampleBase):
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+class TestProfilingNativeProgram(TestExampleBase):
+ def test_smoke(self):
+ adb = AdbHelper()
+ if adb.switch_to_root():
+ self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.run_cmd([inferno_script, "-sc"])
+ self.run_cmd([inferno_script, "-np", "surfaceflinger"])
+
+
class TestReportLib(unittest.TestCase):
def setUp(self):
self.report_lib = ReportLib()