Chromium Code Reviews| Index: tools/ignition_perf_report.py |
| diff --git a/tools/ignition_perf_report.py b/tools/ignition_perf_report.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..7679084ddb49e3febbd275371bd3c3e87ad315ee |
| --- /dev/null |
| +++ b/tools/ignition_perf_report.py |
| @@ -0,0 +1,140 @@ |
| +#! /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. |
| +""" |
| + |
| + |
| +EPILOGUE = """ |
|
rmcilroy
2016/03/11 11:26:49
HELP_EPILOGUE. Also make these all private by addi
Stefano Sanfilippo
2016/03/11 16:33:51
Done.
rmcilroy
2016/03/14 12:26:52
You never made the fields private.
Stefano Sanfilippo
2016/03/14 13:14:20
Wooops, sorry about that. Done for the help string
rmcilroy
2016/03/14 14:48:00
This is fine, thanks.
|
| +examples: |
| + # Get a flamegraph for Ignition bytecode handlers on Octane benchmark, |
| + # without considering time spent compiling JS code. |
| + # |
| + $ tools/run-perf.sh out/x64.release/d8 --ignition octane/run.js |
| + $ tools/ignition_perf_report.py --flamegraph --hide-compile -o out.collapsed |
| + $ flamegraph.pl --colors js out.collapsed > out.svg |
| + |
| + # See the hottest bytecodes on Octane benchmark, by number of samples. |
| + # |
| + $ tools/run-perf.sh out/x64.release/d8 --ignition octane/run.js |
| + $ tools/ignition_perf_report.py |
| +""" |
| + |
| + |
| +COMPILER_SYMBOLS_RE = re.compile(r"Builtin:Compile(?:Lazy|OptimizedConcurrent)" |
| + "$|LazyCompile:|v8::internal::Compile") |
| + |
| + |
| +# Function name, strip parameters |
| +SYMBOL_NAME_RE = re.compile(r"((?:\(anonymous namespace\)|[^(])+)") |
| + |
| + |
| +def yield_collapsed_callchains(perf_stream, hide_compile_time=False): |
|
rmcilroy
2016/03/11 11:26:48
Probably better named collapsed_callchains_generat
Stefano Sanfilippo
2016/03/11 16:33:51
Done.
|
| + current_chain = [] |
| + keep_parsing_chain = True |
| + for line in perf_stream: |
| + if line[0] == "#": |
|
rmcilroy
2016/03/11 11:26:49
Add some comments (e.g., # Skip comments, # Empty
Stefano Sanfilippo
2016/03/11 16:33:51
Done.
|
| + continue |
| + line = line.strip() |
| + if not line: |
| + keep_parsing_chain = True |
| + current_chain = [] |
| + continue |
| + if not keep_parsing_chain: |
| + continue |
| + symbol = SYMBOL_NAME_RE.match(line.split(" ", 1)[1]).group(1) |
|
rmcilroy
2016/03/11 11:26:49
nit newline above
Stefano Sanfilippo
2016/03/11 16:33:51
Done.
|
| + current_chain.append(symbol) |
| + if hide_compile_time and COMPILER_SYMBOLS_RE.match(symbol): |
| + keep_parsing_chain = False |
| + elif symbol.startswith("BytecodeHandler:"): |
| + keep_parsing_chain = False |
| + yield current_chain |
| + |
| + |
| +def count_callchains(callchains): |
|
rmcilroy
2016/03/11 11:26:49
get_callchain_sample_counts ?
Stefano Sanfilippo
2016/03/11 16:33:51
I think calculate_* better conveys the idea that t
|
| + chain_counters = collections.defaultdict(int) |
| + for callchain in callchains: |
| + key = ";".join(reversed(callchain)) |
| + chain_counters[key] += 1 |
| + return chain_counters.items() |
| + |
| + |
| +def count_handler_samples(callchains): |
|
rmcilroy
2016/03/11 11:26:49
get_bytecode_handler_sample_counts ?
Stefano Sanfilippo
2016/03/11 16:33:51
Same as above.
|
| + handler_counters = collections.defaultdict(int) |
| + for callchain in callchains: |
| + # Strip the "BytecodeHandler:" prefix |
| + handler = callchain[-1].split(":", 1)[1] |
| + handler_counters[handler] += 1 |
| + # Sort by decreasing number of samples |
| + return sorted(handler_counters.items(), |
|
rmcilroy
2016/03/11 11:26:49
Maybe just do the sort in main below, to keep thes
Stefano Sanfilippo
2016/03/11 16:33:51
Done.
|
| + key=lambda entry: entry[1], reverse=True) |
| + |
| + |
| +def parse_command_line(): |
| + command_line_parser = argparse.ArgumentParser( |
| + formatter_class=argparse.RawDescriptionHelpFormatter, |
| + description=DESCRIPTION, |
| + epilog=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( |
| + "--hide-compile", "-c", |
| + help="do not count samples inside compiler routines", |
| + action="store_true", |
| + dest="hide_compile_time") |
| + 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() |
| + output_stream = program_options.output_stream |
| + perf = subprocess.Popen(["perf", "script", "-f", "ip,sym", |
| + "-i", program_options.perf_filename], |
| + stdout=subprocess.PIPE) |
| + callchains = yield_collapsed_callchains(perf.stdout, |
| + program_options.hide_compile_time) |
| + if program_options.output_flamegraph: |
| + for callchain, count in count_callchains(callchains): |
| + output_stream.write("{} {}\n".format(callchain, count)) |
|
rmcilroy
2016/03/11 11:26:49
nit - move to a seperate function (for clarity) an
Stefano Sanfilippo
2016/03/11 16:33:51
Done.
|
| + else: |
| + handler_counters = count_handler_samples(callchains) |
| + samples_num = sum(counter for _, counter in handler_counters) |
| + for bytecode_name, count in handler_counters: |
| + output_stream.write( |
| + "{}\t{}\t{:.3f}%\n".format(bytecode_name, count, |
| + 100. * count / samples_num)) |
| + |
| + |
| +if __name__ == "__main__": |
| + main() |