Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(615)

Unified Diff: tools/ignition/linux_perf_report.py

Issue 1783503002: [Interpreter] Add Ignition profile visualization tool. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@run-perf
Patch Set: Group other, misattributed and compiler samples, fix split blocks issue w/ workaround, flatten tram… Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/ignition/linux_perf_report_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/ignition/linux_perf_report.py
diff --git a/tools/ignition/linux_perf_report.py b/tools/ignition/linux_perf_report.py
new file mode 100755
index 0000000000000000000000000000000000000000..83a703273dec59066015f017438617952a7a8e88
--- /dev/null
+++ b/tools/ignition/linux_perf_report.py
@@ -0,0 +1,226 @@
+#! /usr/bin/python2
+#
+# Copyright 2016 the V8 project authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+
+import argparse
+import collections
+import re
+import subprocess
+import sys
+
+
+__DESCRIPTION = """
+Processes a perf.data sample file and reports the hottest Ignition bytecodes,
+or write an input file for flamegraph.pl.
+"""
+
+
+__HELP_EPILOGUE = """
+examples:
+ # Get a flamegraph for Ignition bytecode handlers on Octane benchmark,
+ # without considering the time spent compiling JS code, misattributed samples
rmcilroy 2016/04/15 14:58:41 nit - I wouldn't mention misattributed samples her
Stefano Sanfilippo 2016/04/15 16:13:18 Done.
+ # entry trampoline samples and other non-Ignition samples.
+ #
+ $ tools/run-perf.sh out/x64.release/d8 \\
+ --ignition --noturbo --nocrankshaft run.js
+ $ tools/ignition/linux_perf_report.py --flamegraph -o out.collapsed
+ $ flamegraph.pl --colors js out.collapsed > out.svg
+
+ # Same as above, but show all samples, including time spent compiling JS code,
+ # misattributed samples, entry trampoline samples and other samples.
+ $ # ...
+ $ tools/ignition/linux_perf_report.py \\
+ --flamegraph --show-other -o out.collapsed
+ $ # ...
+
+ # Same as above, but show full function signatures in the flamegraph.
+ $ # ...
+ $ tools/ignition/linux_perf_report.py \\
+ --flamegraph --show-full-signatures -o out.collapsed
+ $ # ...
+
+ # See the hottest bytecodes on Octane benchmark, by number of samples.
+ #
+ $ tools/run-perf.sh out/x64.release/d8 \\
+ --ignition --noturbo --nocrankshaft octane/run.js
+ $ tools/ignition/linux_perf_report.py
+"""
+
+
+COMPILER_SYMBOLS_RE = re.compile(
+ r"v8::internal::(?:\(anonymous namespace\)::)?Compile|v8::internal::Parser")
+
+
+def get_function_name(symbol):
+ if symbol[-1] != ')': return symbol
+ pos = 1
+ parenthesis_count = 0
+ for c in reversed(symbol):
+ if c == ')':
+ parenthesis_count += 1
+ elif c == '(':
+ parenthesis_count -= 1
+ if parenthesis_count == 0:
+ break
+ else:
+ pos += 1
+ return symbol[:-pos]
+
+
+def collapsed_callchains_generator(perf_stream, show_other=False,
+ show_full_signatures=False):
+ current_chain = []
+ skip_until_end_of_chain = False
+ compiler_symbol_in_chain = False
+
+ for line in perf_stream:
+ # Lines starting with a "#" are comments, skip them.
+ if line[0] == "#":
+ continue
+
+ line = line.strip()
+
+ # Empty line signals the end of the callchain.
+ if not line:
+ if not skip_until_end_of_chain and current_chain and show_other:
+ current_chain.append("[other]")
+ yield current_chain
+ # Reset parser status.
+ current_chain = []
+ skip_until_end_of_chain = False
+ compiler_symbol_in_chain = False
+ continue
+
+ if skip_until_end_of_chain:
+ continue
+
+ symbol = line.split(" ", 1)[1]
+ if not show_full_signatures:
+ symbol = get_function_name(symbol)
rmcilroy 2016/04/15 14:58:41 nit - s/get_function_name/strip_function_parameter
Stefano Sanfilippo 2016/04/15 16:13:19 Done.
+ current_chain.append(symbol)
+
+ if symbol.startswith("BytecodeHandler:"):
+ yield current_chain
+ skip_until_end_of_chain = True
+ elif symbol == "Stub:CEntryStub" and compiler_symbol_in_chain:
+ if show_other:
+ current_chain[-1] = "[compiler]"
+ yield current_chain
+ skip_until_end_of_chain = True
+ elif COMPILER_SYMBOLS_RE.match(symbol):
+ compiler_symbol_in_chain = True
+ elif symbol == "Builtin:InterpreterEntryTrampoline":
+ # The chain cannot be empty at this point
rmcilroy 2016/04/15 14:58:41 Not sure what this comment means. I think what you
Stefano Sanfilippo 2016/04/15 16:13:18 The comment was because the else branch assumes th
rmcilroy 2016/04/18 09:22:24 I don't think it is necessary, you unconditinally
Stefano Sanfilippo 2016/04/18 10:37:23 Acknowledged.
+ if len(current_chain) == 1:
+ yield ["[entry trampoline]"]
+ elif show_other:
+ current_chain[-1] = "[misattributed]"
+ yield current_chain
+ skip_until_end_of_chain = True
+
+
+def calculate_samples_count_per_callchain(callchains):
+ chain_counters = collections.defaultdict(int)
+ for callchain in callchains:
+ # flamegraph.pl appears to have problems when no ; is in the chain,
+ # so we add a dummy one at the end if there is only one entry in the chain.
+ if len(callchain) == 1:
+ key = callchain[0] + ";"
rmcilroy 2016/04/15 14:58:42 Could you just always add a ";" to the end rather
Stefano Sanfilippo 2016/04/15 16:13:19 This is not the format shown in flamegraph example
rmcilroy 2016/04/18 09:22:24 I think it would be better to make things consiste
Stefano Sanfilippo 2016/04/18 10:37:23 No problem, done.
+ else:
+ key = ";".join(reversed(callchain))
+ chain_counters[key] += 1
+ return chain_counters.items()
+
+
+def calculate_samples_count_per_handler(callchains):
+ def strip_handler_prefix_if_any(handler):
+ return handler if handler[0] == "[" else handler.split(":", 1)[1]
+
+ handler_counters = collections.defaultdict(int)
+ for callchain in callchains:
+ handler = strip_handler_prefix_if_any(callchain[-1])
+ handler_counters[handler] += 1
+ return handler_counters.items()
+
+
+def write_flamegraph_input_file(output_stream, callchains):
+ for callchain, count in calculate_samples_count_per_callchain(callchains):
+ output_stream.write("{} {}\n".format(callchain, count))
+
+
+def write_handlers_report(output_stream, callchains):
+ handler_counters = calculate_samples_count_per_handler(callchains)
+ samples_num = sum(counter for _, counter in handler_counters)
+ # Sort by decreasing number of samples
+ handler_counters.sort(key=lambda entry: entry[1], reverse=True)
+ for bytecode_name, count in handler_counters:
+ output_stream.write(
+ "{}\t{}\t{:.3f}%\n".format(bytecode_name, count,
+ 100. * count / samples_num))
+
+
+def parse_command_line():
+ command_line_parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=__DESCRIPTION,
+ epilog=__HELP_EPILOGUE)
+
+ command_line_parser.add_argument(
+ "perf_filename",
+ help="perf sample file to process (default: perf.data)",
+ nargs="?",
+ default="perf.data",
+ metavar="<perf filename>"
+ )
+ command_line_parser.add_argument(
+ "--flamegraph", "-f",
+ help="output an input file for flamegraph.pl, not a report",
+ action="store_true",
+ dest="output_flamegraph"
+ )
+ command_line_parser.add_argument(
+ "--show-other", "-r",
rmcilroy 2016/04/15 14:58:41 -r is a strange shortening. How about "--show-all"
Stefano Sanfilippo 2016/04/15 16:13:18 Done.
+ help="show samples outside Ignition interpreter",
+ action="store_true"
+ )
+ command_line_parser.add_argument(
+ "--show-full-signatures", "-s",
+ help="show full signatures instead of function names",
+ action="store_true"
+ )
+ command_line_parser.add_argument(
+ "--output", "-o",
+ help="output file name (stdout if omitted)",
+ type=argparse.FileType('wt'),
+ default=sys.stdout,
+ metavar="<output filename>",
+ dest="output_stream"
+ )
+
+ return command_line_parser.parse_args()
+
+
+def main():
+ program_options = parse_command_line()
+
+ perf = subprocess.Popen(["perf", "script", "-f", "ip,sym",
+ "-i", program_options.perf_filename],
+ stdout=subprocess.PIPE)
+
+ callchains = collapsed_callchains_generator(
+ perf.stdout, program_options.show_other,
+ program_options.show_full_signatures)
+
+ if program_options.output_flamegraph:
+ write_flamegraph_input_file(program_options.output_stream, callchains)
+ else:
+ write_handlers_report(program_options.output_stream, callchains)
+
+ perf.communicate()
rmcilroy 2016/04/15 14:58:42 Why is this needed? Shouldn't it be before reading
Stefano Sanfilippo 2016/04/15 16:13:18 This was intended to work as a wait() to ensure we
rmcilroy 2016/04/18 09:22:24 I don't see why this is necessary at all. You are
Stefano Sanfilippo 2016/04/18 10:37:23 Agreed, removed.
+
+
+if __name__ == "__main__":
+ main()
« no previous file with comments | « no previous file | tools/ignition/linux_perf_report_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698