diff options
author | Yabin Cui <yabinc@google.com> | 2017-08-17 21:30:19 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2017-08-17 21:30:19 +0000 |
commit | 001e76be635c208886a5370e51b95913d8c7bb09 (patch) | |
tree | 9e5073dec715ece725a77aab769440ab39c01266 | |
parent | 72dd31f56711a34b9fa1d89859d79142ef888c41 (diff) | |
parent | d3c40e701e2638596bf8bd2d4f8da746e005e144 (diff) | |
download | extras-001e76be635c208886a5370e51b95913d8c7bb09.tar.gz |
Merge "simpleperf: fix inferno and test content of report.html."
-rw-r--r-- | simpleperf/scripts/inferno/data_types.py | 96 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/inferno.py | 106 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/script.js | 2 | ||||
-rw-r--r-- | simpleperf/scripts/inferno/svg_renderer.py | 173 | ||||
-rw-r--r-- | simpleperf/scripts/test.py | 55 |
5 files changed, 233 insertions, 199 deletions
diff --git a/simpleperf/scripts/inferno/data_types.py b/simpleperf/scripts/inferno/data_types.py index 80e633f4..4f07ba73 100644 --- a/simpleperf/scripts/inferno/data_types.py +++ b/simpleperf/scripts/inferno/data_types.py @@ -25,15 +25,20 @@ class CallSite: class Thread: - def __init__(self, tid): + def __init__(self, tid, pid): self.tid = tid + self.pid = pid + self.name = "" self.samples = [] - self.flamegraph = {} + self.flamegraph = FlameGraphCallSite("root", "", 0) self.num_samples = 0 + self.event_count = 0 def add_callchain(self, callchain, symbol, sample): - chain = [] + self.name = sample.thread_comm self.num_samples += 1 + self.event_count += sample.period + chain = [] for j in range(callchain.nr): entry = callchain.entries[callchain.nr - j - 1] if entry.ip == 0: @@ -41,20 +46,7 @@ class Thread: chain.append(CallSite(entry.ip, entry.symbol.symbol_name, entry.symbol.dso_name)) chain.append(CallSite(sample.ip, symbol.symbol_name, symbol.dso_name)) - self.samples.append(chain) - - def collapse_flamegraph(self): - flamegraph = FlameGraphCallSite("root", "") - flamegraph.id = 0 # This is used for wasd navigation, 0 = not a valid target. - self.flamegraph = flamegraph - for sample in self.samples: - flamegraph = self.flamegraph - for callsite in sample: - flamegraph = flamegraph.get_callsite(callsite.method, callsite.dso) - - # Populate root note. - for node in self.flamegraph.callsites: - self.flamegraph.num_samples += node.num_samples + self.flamegraph.add_callchain(chain, sample.period) class Process: @@ -65,51 +57,57 @@ class Process: self.threads = {} self.cmd = "" self.props = {} - self.args = None self.num_samples = 0 - def get_thread(self, tid): - if (tid not in self.threads.keys()): - self.threads[tid] = Thread(tid) + def get_thread(self, tid, pid): + if tid not in self.threads.keys(): + self.threads[tid] = Thread(tid, pid) return self.threads[tid] -CALLSITE_COUNTER = 0 - - -def get_callsite_id(): - global CALLSITE_COUNTER - CALLSITE_COUNTER += 1 - toReturn = CALLSITE_COUNTER - return toReturn - class FlameGraphCallSite: - def __init__(self, method, dso): - self.callsites = [] + callsite_counter = 0 + @classmethod + def _get_next_callsite_id(cls): + cls.callsite_counter += 1 + return cls.callsite_counter + + def __init__(self, method, dso, id): + self.children = [] self.method = method self.dso = dso - self.num_samples = 0 + self.event_count = 0 self.offset = 0 # Offset allows position nodes in different branches. - self.id = get_callsite_id() + self.id = id + + def weight(self): + return float(self.event_count) + + def add_callchain(self, chain, event_count): + self.event_count += event_count + current = self + for callsite in chain: + current = current._get_child(callsite) + current.event_count += event_count - def get_callsite(self, name, dso): - for c in self.callsites: - if c.equivalent(name, dso): - c.num_samples += 1 + def _get_child(self, callsite): + for c in self.children: + if c._equivalent(callsite.method, callsite.dso): return c - callsite = FlameGraphCallSite(name, dso) - callsite.num_samples = 1 - self.callsites.append(callsite) - return callsite + new_child = FlameGraphCallSite(callsite.method, callsite.dso, self._get_next_callsite_id()) + self.children.append(new_child) + return new_child - def equivalent(self, method, dso): + def _equivalent(self, method, dso): return self.method == method and self.dso == dso def get_max_depth(self): - max = 0 - for c in self.callsites: - depth = c.get_max_depth() - if depth > max: - max = depth - return max + 1 + return max([c.get_max_depth() for c in self.children]) + 1 if self.children else 1 + + def generate_offset(self, start_offset): + self.offset = start_offset + child_offset = start_offset + for child in self.children: + child_offset = child.generate_offset(child_offset) + return self.offset + self.event_count diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py index 0b592965..15d7776a 100644 --- a/simpleperf/scripts/inferno/inferno.py +++ b/simpleperf/scripts/inferno/inferno.py @@ -47,7 +47,7 @@ from data_types import * from svg_renderer import * -def collect_data(args, process): +def collect_data(args): app_profiler_args = [sys.executable, "app_profiler.py", "-nb"] if args.app: app_profiler_args += ["-p", args.app] @@ -77,7 +77,6 @@ def collect_data(args, process): 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 @@ -93,12 +92,19 @@ def parse_samples(process, args): lib = ReportLib() lib.ShowIpForUnknownSymbol() - if symfs_dir is not None: + if symfs_dir: lib.SetSymfs(symfs_dir) - if record_file is not None: + if record_file: lib.SetRecordFile(record_file) - if kallsyms_file is not None: + if kallsyms_file: lib.SetKallsymsFile(kallsyms_file) + process.cmd = lib.GetRecordCmd() + product_props = lib.MetaInfo().get("product_props") + if product_props: + tuple = product_props.split(':') + process.props['ro.product.manufacturer'] = tuple[0] + process.props['ro.product.model'] = tuple[1] + process.props['ro.product.name'] = tuple[2] while True: sample = lib.GetNextSample() @@ -107,20 +113,16 @@ def parse_samples(process, args): break symbol = lib.GetSymbolOfCurrentSample() callchain = lib.GetCallChainOfCurrentSample() - process.get_thread(sample.tid).add_callchain(callchain, symbol, sample) + process.get_thread(sample.tid, sample.pid).add_callchain(callchain, symbol, sample) process.num_samples += 1 - log_info("Parsed %s callchains." % process.num_samples) - + if process.pid == 0: + main_threads = [thread for thread in process.threads.values() if thread.tid == thread.pid] + if main_threads: + process.name = main_threads[0].name + process.pid = main_threads[0].pid -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() + log_info("Parsed %s callchains." % process.num_samples) def get_local_asset_content(local_path): @@ -129,13 +131,11 @@ def get_local_asset_content(local_path): :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 + with open(os.path.join(os.path.dirname(__file__), local_path), 'r') as f: + return f.read() -def output_report(process): +def output_report(process, args): """ Generates a HTML report representing the result of simpleperf sampling as flamegraph :param process: Process object @@ -151,19 +151,23 @@ def output_report(process): f.write('<img height="180" alt = "Embedded Image" src ="data') f.write(get_local_asset_content("inferno.b64")) f.write('"/>') + process_entry = ("Process : %s (%d)<br/>" % (process.name, process.pid)) if process.pid else "" + # TODO: collect capture duration info from perf.data. + duration_entry = ("Duration: %s seconds<br/>" % args.capture_duration + ) if args.capture_duration else "" f.write("""<div style='display:inline-block;'> <font size='8'> Inferno Flamegraph Report</font><br/><br/> - Process : %s (%d)<br/> + %s Date : %s<br/> Threads : %d <br/> Samples : %d</br> - Duration: %s seconds<br/>""" % ( - process.name, process.pid, + %s""" % ( + process_entry, datetime.datetime.now().strftime("%Y-%m-%d (%A) %H:%M:%S"), len(process.threads), process.num_samples, - process.args.capture_duration)) + duration_entry)) if 'ro.product.model' in process.props: f.write( "Machine : %s (%s) by %s<br/>" % @@ -178,17 +182,17 @@ def output_report(process): f.write("<script>%s</script>" % 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] + main_thread = [x for x in process.threads.values() 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)) - renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width) + f.write("<br/><br/><b>Main Thread %d (%s) (%d samples):</b><br/>\n\n\n\n" % ( + thread.tid, thread.name, thread.num_samples)) + renderSVG(thread.flamegraph, f, args.color, args.svg_width) - other_threads = [x for _, x in process.threads.items() if x.tid != process.pid] + other_threads = [x for x in process.threads.values() 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)) - renderSVG(thread.flamegraph, f, process.args.color, process.args.svg_width) + f.write("<br/><br/><b>Thread %d (%s) (%d samples):</b><br/>\n\n\n\n" % ( + thread.tid, thread.name, thread.num_samples)) + renderSVG(thread.flamegraph, f, args.color, args.svg_width) f.write("</body>") f.write("</html>") @@ -196,17 +200,9 @@ def output_report(process): return "file://" + filepath -def generate_flamegraph_offsets(flamegraph): - rover = flamegraph.offset - for callsite in flamegraph.callsites: - callsite.offset = rover - rover += callsite.num_samples - generate_flamegraph_offsets(callsite) - - def generate_threads_offsets(process): - for _, thread in process.threads.items(): - generate_flamegraph_offsets(thread.flamegraph) + for thread in process.threads.values(): + thread.flamegraph.generate_offset(0) def collect_machine_info(process): @@ -266,26 +262,26 @@ def main(): parser.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run in non root mode.""") args = parser.parse_args() - process_name = args.app or args.native_program - process = Process(process_name, 0) - process.args = args + process = Process("", 0) if not args.skip_collection: - log_info("Starting data collection stage for process '%s'." % process_name) - if not collect_data(args, process): + process.name = args.app or args.native_program + log_info("Starting data collection stage for process '%s'." % process.name) + if not collect_data(args): log_exit("Unable to collect data.") - try: - result, output = AdbHelper().run_and_return_output(['shell', 'pidof', process_name]) - if result: + result, output = AdbHelper().run_and_return_output(['shell', 'pidof', process.name]) + if result: + try: process.pid = int(output) - except: - raise + except: + process.pid = 0 collect_machine_info(process) + else: + args.capture_duration = 0 parse_samples(process, args) - collapse_callgraphs(process) generate_threads_offsets(process) - report_path = output_report(process) + report_path = output_report(process, args) open_report_in_browser(report_path) log_info("Report generated at '%s'." % report_path) diff --git a/simpleperf/scripts/inferno/script.js b/simpleperf/scripts/inferno/script.js index 3288d8be..078100d6 100644 --- a/simpleperf/scripts/inferno/script.js +++ b/simpleperf/scripts/inferno/script.js @@ -30,7 +30,7 @@ function adjust_node_text_size(x) { let width = parseFloat(rect.attributes['width'].value); // Don't even bother trying to find a best fit. The area is too small. - if (width < 25) { + if (width < 28) { text.textContent = ''; return; } diff --git a/simpleperf/scripts/inferno/svg_renderer.py b/simpleperf/scripts/inferno/svg_renderer.py index 68559499..b84e058c 100644 --- a/simpleperf/scripts/inferno/svg_renderer.py +++ b/simpleperf/scripts/inferno/svg_renderer.py @@ -39,20 +39,20 @@ def getDSOColor(method): return (r, g, b) -def getHeatColor(callsite, num_samples): - r = 245 + 10 * (1 - float(callsite.num_samples) / num_samples) - g = 110 + 105 * (1 - float(callsite.num_samples) / num_samples) +def getHeatColor(callsite, total_weight): + r = 245 + 10 * (1 - callsite.weight() / total_weight) + g = 110 + 105 * (1 - callsite.weight() / total_weight) b = 100 return (r, g, b) -def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav): - x = float(callsite.offset) / float(num_samples) * SVG_CANVAS_WIDTH - y = height - (depth * SVG_NODE_HEIGHT) - SVG_NODE_HEIGHT - width = float(callsite.num_samples) / float(num_samples) * SVG_CANVAS_WIDTH +def createSVGNode(callsite, depth, f, total_weight, height, color_scheme, nav): + x = float(callsite.offset) / total_weight * SVG_CANVAS_WIDTH + y = height - (depth + 1) * SVG_NODE_HEIGHT + width = callsite.weight() / total_weight * SVG_CANVAS_WIDTH method = callsite.method.replace(">", ">").replace("<", "<") - if (width <= 0): + if width <= 0: return if color_scheme == "dso": @@ -60,119 +60,103 @@ def createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav): elif color_scheme == "legacy": r, g, b = getLegacyColor(method) else: - r, g, b = getHeatColor(callsite, num_samples) + r, g, b = getHeatColor(callsite, total_weight) - r_border = (r - 50) - if r_border < 0: - r_border = 0 - - g_border = (g - 50) - if g_border < 0: - g_border = 0 - - b_border = (b - 50) - if (b_border < 0): - b_border = 0 + r_border, g_border, b_border = [max(0, color - 50) for color in [r, g, b]] f.write( - '<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s"> \n\ - <title>%s | %s (%d samples: %3.2f%%)</title>\n \ - <rect x="%f" y="%f" ox="%f" oy="%f" width="%f" owidth="%f" height="15.0" ofill="rgb(%d,%d,%d)" \ - fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/>\n \ - <text x="%f" y="%f" font-size="%d" font-family="Monospace"></text>\n \ - </g>\n' % + """<g id=%d class="n" onclick="zoom(this);" onmouseenter="select(this);" nav="%s"> + <title>%s | %s (%.0f events: %3.2f%%)</title> + <rect x="%f" y="%f" ox="%f" oy="%f" width="%f" owidth="%f" height="15.0" + ofill="rgb(%d,%d,%d)" fill="rgb(%d,%d,%d)" style="stroke:rgb(%d,%d,%d)"/> + <text x="%f" y="%f" font-size="%d" font-family="Monospace"></text> + </g>""" % (callsite.id, - ','.join( - str(x) for x in nav), - method, - callsite.dso, - callsite.num_samples, - callsite.num_samples / - float(num_samples) * - 100, - x, - y, - x, - y, - width, - width, - r, - g, - b, - r, - g, - b, - r_border, - g_border, - b_border, - x + - 2, - y + - 12, - FONT_SIZE)) - - -def renderSVGNodes(flamegraph, depth, f, num_samples, height, color_scheme): - for i, callsite in enumerate(flamegraph.callsites): + ','.join(str(x) for x in nav), + method, + callsite.dso, + callsite.weight(), + callsite.weight() / total_weight * 100, + x, + y, + x, + y, + width, + width, + r, + g, + b, + r, + g, + b, + r_border, + g_border, + b_border, + x + 2, + y + 12, + FONT_SIZE)) + + +def renderSVGNodes(flamegraph, depth, f, total_weight, height, color_scheme): + for i, child in enumerate(flamegraph.children): # Prebuild navigation target for wasd if i == 0: left_index = 0 else: - left_index = flamegraph.callsites[i - 1].id + left_index = flamegraph.children[i - 1].id - if i == len(flamegraph.callsites) - 1: + if i == len(flamegraph.children) - 1: right_index = 0 else: - right_index = flamegraph.callsites[i + 1].id + right_index = flamegraph.children[i + 1].id - up_index = 0 - max_up = 0 - for upcallsite in callsite.callsites: - if upcallsite.num_samples > max_up: - max_up = upcallsite.num_samples - up_index = upcallsite.id + up_index = max(child.children, key=lambda x: x.weight()).id if child.children else 0 # up, left, down, right nav = [up_index, left_index, flamegraph.id, right_index] - createSVGNode(callsite, depth, f, num_samples, height, color_scheme, nav) + createSVGNode(child, depth, f, total_weight, height, color_scheme, nav) # Recurse down - renderSVGNodes(callsite, depth + 1, f, num_samples, height, color_scheme) + renderSVGNodes(child, depth + 1, f, total_weight, height, color_scheme) def renderSearchNode(f): f.write( - '<rect id="search_rect" style="stroke:rgb(0,0,0);" onclick="search(this);" class="t" rx="10" ry="10" \ - x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/> \ - <text id="search_text" class="t" x="%d" y="30" onclick="search(this);">Search</text>\n' % - (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80)) + """<rect id="search_rect" style="stroke:rgb(0,0,0);" onclick="search(this);" class="t" + rx="10" ry="10" x="%d" y="10" width="80" height="30" fill="rgb(255,255,255)""/> + <text id="search_text" class="t" x="%d" y="30" onclick="search(this);">Search</text> + """ % (SVG_CANVAS_WIDTH - 95, SVG_CANVAS_WIDTH - 80)) def renderUnzoomNode(f): f.write( - '<rect id="zoom_rect" style="display:none;stroke:rgb(0,0,0);" class="t" onclick="unzoom(this);" \ - rx="10" ry="10" x="10" y="10" width="80" height="30" fill="rgb(255,255,255)"/> \ - <text id="zoom_text" style="display:none;" class="t" x="19" y="30" \ - onclick="unzoom(this);">Zoom out</text>\n' + """<rect id="zoom_rect" style="display:none;stroke:rgb(0,0,0);" class="t" + onclick="unzoom(this);" rx="10" ry="10" x="10" y="10" width="80" height="30" + fill="rgb(255,255,255)"/> + <text id="zoom_text" style="display:none;" class="t" x="19" y="30" + onclick="unzoom(this);">Zoom out</text>""" ) def renderInfoNode(f): f.write( - '<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);" \ - rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/></clipPath> \ - <rect id="info_rect" style="stroke:rgb(0,0,0);" \ - rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/> \ - <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text>\n' % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325) + """<clipPath id="info_clip_path"> <rect id="info_rect" style="stroke:rgb(0,0,0);" + rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/> + </clipPath> + <rect id="info_rect" style="stroke:rgb(0,0,0);" + rx="10" ry="10" x="120" y="10" width="%d" height="30" fill="rgb(255,255,255)"/> + <text clip-path="url(#info_clip_path)" id="info_text" x="128" y="30"></text> + """ % (SVG_CANVAS_WIDTH - 335, SVG_CANVAS_WIDTH - 325) ) def renderPercentNode(f): f.write( - '<rect id="percent_rect" style="stroke:rgb(0,0,0);" \ - rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/> \ - <text id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text>\n' % (SVG_CANVAS_WIDTH - (95 * 2), SVG_CANVAS_WIDTH - (125)) + """<rect id="percent_rect" style="stroke:rgb(0,0,0);" + rx="10" ry="10" x="%d" y="10" width="82" height="30" fill="rgb(255,255,255)"/> + <text id="percent_text" text-anchor="end" x="%d" y="30">100.00%%</text> + """ % (SVG_CANVAS_WIDTH - (95 * 2), SVG_CANVAS_WIDTH - (125)) ) @@ -180,14 +164,17 @@ def renderSVG(flamegraph, f, color_scheme, width): global SVG_CANVAS_WIDTH SVG_CANVAS_WIDTH = width height = (flamegraph.get_max_depth() + 2) * SVG_NODE_HEIGHT - f.write('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" \ - width="%d" height="%d" style="border: 1px solid black;" \ - onload="adjust_text_size(this);" rootid="%d">\n' % (SVG_CANVAS_WIDTH, height, flamegraph.callsites[0].id)) - f.write('<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" > \ - <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" /> </linearGradient> </defs>') - f.write('<rect x="0.0" y="0" width="%d" height="%d" fill="url(#background_gradiant)" />' % - (SVG_CANVAS_WIDTH, height)) - renderSVGNodes(flamegraph, 0, f, flamegraph.num_samples, height, color_scheme) + f.write("""<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" + width="%d" height="%d" style="border: 1px solid black;" + onload="adjust_text_size(this);" rootid="%d"> + """ % (SVG_CANVAS_WIDTH, height, flamegraph.children[0].id)) + f.write("""<defs > <linearGradient id="background_gradiant" y1="0" y2="1" x1="0" x2="0" > + <stop stop-color="#eeeeee" offset="5%" /> <stop stop-color="#efefb1" offset="90%" /> + </linearGradient> </defs>""") + f.write("""<rect x="0.0" y="0" width="%d" height="%d" fill="url(#background_gradiant)" /> + """ % (SVG_CANVAS_WIDTH, height)) + renderSVGNodes(flamegraph, 0, f, flamegraph.weight(), height, color_scheme) renderSearchNode(f) renderUnzoomNode(f) renderInfoNode(f) diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index 942243cc..7efd44ce 100644 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -142,7 +142,7 @@ class TestExampleBase(TestBase): def cleanupTestFiles(cls): remove("binary_cache") remove("annotated_files") - #remove("perf.data") + remove("perf.data") remove("report.txt") remove("pprof.profile") @@ -215,6 +215,23 @@ class TestExampleBase(TestBase): fulfilled[i] = True self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled) + def check_inferno_report_html(self, check_entries): + file = "report.html" + self.check_exist(file=file) + with open(file, 'r') as fh: + data = fh.read() + fulfilled = [False for x in check_entries] + for line in data.split('\n'): + # each entry is a (function_name, min_percentage) pair. + for i, entry in enumerate(check_entries): + if fulfilled[i] or line.find(entry[0]) == -1: + continue + m = re.search(r'(\d+\.\d+)%', line) + if m and float(m.group(1)) >= entry[1]: + fulfilled[i] = True + break + self.assertEqual(fulfilled, [True for x in check_entries]) + def common_test_app_profiler(self): self.run_cmd(["app_profiler.py", "-h"]) remove("binary_cache") @@ -306,8 +323,16 @@ class TestExamplePureJava(TestExampleBase): def test_app_profiler_with_ctrl_c(self): if is_windows(): return + # `adb root` and `adb unroot` may consumes more time than 3 sec. So + # do it in advance to make sure ctrl-c happens when recording. + if self.adb_root: + self.adb.switch_to_root() + else: + self.adb._unroot() args = [sys.executable, "app_profiler.py", "--app", self.package_name, "-r", "--duration 10000", "-nc"] + if not self.adb_root: + args.append("--disable_adb_root") subproc = subprocess.Popen(args) time.sleep(3) @@ -348,6 +373,10 @@ class TestExamplePureJava(TestExampleBase): def test_inferno(self): self.common_test_inferno() + self.run_app_profiler() + self.run_cmd([inferno_script, "-sc"]) + self.check_inferno_report_html( + [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)]) class TestExamplePureJavaRoot(TestExampleBase): @@ -390,6 +419,12 @@ class TestExamplePureJavaTraceOffCpu(TestExampleBase): ("line 24", 20, 0), ("line 32", 20, 0)]) self.run_cmd([inferno_script, "-sc"]) + self.check_inferno_report_html( + [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run() ', 80), + ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()', + 20), + ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)', + 20)]) class TestExampleWithNative(TestExampleBase): @@ -435,6 +470,9 @@ class TestExampleWithNative(TestExampleBase): def test_inferno(self): self.common_test_inferno() + self.run_app_profiler() + self.run_cmd([inferno_script, "-sc"]) + self.check_inferno_report_html([('BusyLoopThread', 80)]) class TestExampleWithNativeRoot(TestExampleBase): @@ -478,6 +516,9 @@ class TestExampleWithNativeTraceOffCpu(TestExampleBase): ("line 73", 20, 0), ("line 83", 20, 0)]) self.run_cmd([inferno_script, "-sc"]) + self.check_inferno_report_html([('SleepThread', 80), + ('RunFunction', 20), + ('SleepFunction', 20)]) class TestExampleWithNativeJniCall(TestExampleBase): @@ -580,6 +621,11 @@ class TestExampleOfKotlin(TestExampleBase): def test_inferno(self): self.common_test_inferno() + self.run_app_profiler() + self.run_cmd([inferno_script, "-sc"]) + self.check_inferno_report_html( + [('com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()', + 80)]) class TestExampleOfKotlinRoot(TestExampleBase): @@ -622,6 +668,13 @@ class TestExampleOfKotlinTraceOffCpu(TestExampleBase): ("line 24", 20, 0), ("line 32", 20, 0)]) self.run_cmd([inferno_script, "-sc"]) + self.check_inferno_report_html( + [('void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()', + 80), + ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()', + 20), + ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)', + 20)]) class TestProfilingNativeProgram(TestExampleBase): |