Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #! /usr/bin/python2 | |
| 2 # | |
| 3 # Copyright 2016 the V8 project authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 # | |
| 7 | |
| 8 import argparse | |
| 9 import collections | |
| 10 import re | |
| 11 import subprocess | |
| 12 import sys | |
| 13 | |
| 14 | |
| 15 __DESCRIPTION = """ | |
| 16 Processes a perf.data sample file and reports the hottest Ignition bytecodes, | |
| 17 or write an input file for flamegraph.pl. | |
| 18 """ | |
| 19 | |
| 20 | |
| 21 __HELP_EPILOGUE = """ | |
| 22 examples: | |
| 23 # Get a flamegraph for Ignition bytecode handlers on Octane benchmark, | |
| 24 # 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.
| |
| 25 # entry trampoline samples and other non-Ignition samples. | |
| 26 # | |
| 27 $ tools/run-perf.sh out/x64.release/d8 \\ | |
| 28 --ignition --noturbo --nocrankshaft run.js | |
| 29 $ tools/ignition/linux_perf_report.py --flamegraph -o out.collapsed | |
| 30 $ flamegraph.pl --colors js out.collapsed > out.svg | |
| 31 | |
| 32 # Same as above, but show all samples, including time spent compiling JS code, | |
| 33 # misattributed samples, entry trampoline samples and other samples. | |
| 34 $ # ... | |
| 35 $ tools/ignition/linux_perf_report.py \\ | |
| 36 --flamegraph --show-other -o out.collapsed | |
| 37 $ # ... | |
| 38 | |
| 39 # Same as above, but show full function signatures in the flamegraph. | |
| 40 $ # ... | |
| 41 $ tools/ignition/linux_perf_report.py \\ | |
| 42 --flamegraph --show-full-signatures -o out.collapsed | |
| 43 $ # ... | |
| 44 | |
| 45 # See the hottest bytecodes on Octane benchmark, by number of samples. | |
| 46 # | |
| 47 $ tools/run-perf.sh out/x64.release/d8 \\ | |
| 48 --ignition --noturbo --nocrankshaft octane/run.js | |
| 49 $ tools/ignition/linux_perf_report.py | |
| 50 """ | |
| 51 | |
| 52 | |
| 53 COMPILER_SYMBOLS_RE = re.compile( | |
| 54 r"v8::internal::(?:\(anonymous namespace\)::)?Compile|v8::internal::Parser") | |
| 55 | |
| 56 | |
| 57 def get_function_name(symbol): | |
| 58 if symbol[-1] != ')': return symbol | |
| 59 pos = 1 | |
| 60 parenthesis_count = 0 | |
| 61 for c in reversed(symbol): | |
| 62 if c == ')': | |
| 63 parenthesis_count += 1 | |
| 64 elif c == '(': | |
| 65 parenthesis_count -= 1 | |
| 66 if parenthesis_count == 0: | |
| 67 break | |
| 68 else: | |
| 69 pos += 1 | |
| 70 return symbol[:-pos] | |
| 71 | |
| 72 | |
| 73 def collapsed_callchains_generator(perf_stream, show_other=False, | |
| 74 show_full_signatures=False): | |
| 75 current_chain = [] | |
| 76 skip_until_end_of_chain = False | |
| 77 compiler_symbol_in_chain = False | |
| 78 | |
| 79 for line in perf_stream: | |
| 80 # Lines starting with a "#" are comments, skip them. | |
| 81 if line[0] == "#": | |
| 82 continue | |
| 83 | |
| 84 line = line.strip() | |
| 85 | |
| 86 # Empty line signals the end of the callchain. | |
| 87 if not line: | |
| 88 if not skip_until_end_of_chain and current_chain and show_other: | |
| 89 current_chain.append("[other]") | |
| 90 yield current_chain | |
| 91 # Reset parser status. | |
| 92 current_chain = [] | |
| 93 skip_until_end_of_chain = False | |
| 94 compiler_symbol_in_chain = False | |
| 95 continue | |
| 96 | |
| 97 if skip_until_end_of_chain: | |
| 98 continue | |
| 99 | |
| 100 symbol = line.split(" ", 1)[1] | |
| 101 if not show_full_signatures: | |
| 102 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.
| |
| 103 current_chain.append(symbol) | |
| 104 | |
| 105 if symbol.startswith("BytecodeHandler:"): | |
| 106 yield current_chain | |
| 107 skip_until_end_of_chain = True | |
| 108 elif symbol == "Stub:CEntryStub" and compiler_symbol_in_chain: | |
| 109 if show_other: | |
| 110 current_chain[-1] = "[compiler]" | |
| 111 yield current_chain | |
| 112 skip_until_end_of_chain = True | |
| 113 elif COMPILER_SYMBOLS_RE.match(symbol): | |
| 114 compiler_symbol_in_chain = True | |
| 115 elif symbol == "Builtin:InterpreterEntryTrampoline": | |
| 116 # 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.
| |
| 117 if len(current_chain) == 1: | |
| 118 yield ["[entry trampoline]"] | |
| 119 elif show_other: | |
| 120 current_chain[-1] = "[misattributed]" | |
| 121 yield current_chain | |
| 122 skip_until_end_of_chain = True | |
| 123 | |
| 124 | |
| 125 def calculate_samples_count_per_callchain(callchains): | |
| 126 chain_counters = collections.defaultdict(int) | |
| 127 for callchain in callchains: | |
| 128 # flamegraph.pl appears to have problems when no ; is in the chain, | |
| 129 # so we add a dummy one at the end if there is only one entry in the chain. | |
| 130 if len(callchain) == 1: | |
| 131 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.
| |
| 132 else: | |
| 133 key = ";".join(reversed(callchain)) | |
| 134 chain_counters[key] += 1 | |
| 135 return chain_counters.items() | |
| 136 | |
| 137 | |
| 138 def calculate_samples_count_per_handler(callchains): | |
| 139 def strip_handler_prefix_if_any(handler): | |
| 140 return handler if handler[0] == "[" else handler.split(":", 1)[1] | |
| 141 | |
| 142 handler_counters = collections.defaultdict(int) | |
| 143 for callchain in callchains: | |
| 144 handler = strip_handler_prefix_if_any(callchain[-1]) | |
| 145 handler_counters[handler] += 1 | |
| 146 return handler_counters.items() | |
| 147 | |
| 148 | |
| 149 def write_flamegraph_input_file(output_stream, callchains): | |
| 150 for callchain, count in calculate_samples_count_per_callchain(callchains): | |
| 151 output_stream.write("{} {}\n".format(callchain, count)) | |
| 152 | |
| 153 | |
| 154 def write_handlers_report(output_stream, callchains): | |
| 155 handler_counters = calculate_samples_count_per_handler(callchains) | |
| 156 samples_num = sum(counter for _, counter in handler_counters) | |
| 157 # Sort by decreasing number of samples | |
| 158 handler_counters.sort(key=lambda entry: entry[1], reverse=True) | |
| 159 for bytecode_name, count in handler_counters: | |
| 160 output_stream.write( | |
| 161 "{}\t{}\t{:.3f}%\n".format(bytecode_name, count, | |
| 162 100. * count / samples_num)) | |
| 163 | |
| 164 | |
| 165 def parse_command_line(): | |
| 166 command_line_parser = argparse.ArgumentParser( | |
| 167 formatter_class=argparse.RawDescriptionHelpFormatter, | |
| 168 description=__DESCRIPTION, | |
| 169 epilog=__HELP_EPILOGUE) | |
| 170 | |
| 171 command_line_parser.add_argument( | |
| 172 "perf_filename", | |
| 173 help="perf sample file to process (default: perf.data)", | |
| 174 nargs="?", | |
| 175 default="perf.data", | |
| 176 metavar="<perf filename>" | |
| 177 ) | |
| 178 command_line_parser.add_argument( | |
| 179 "--flamegraph", "-f", | |
| 180 help="output an input file for flamegraph.pl, not a report", | |
| 181 action="store_true", | |
| 182 dest="output_flamegraph" | |
| 183 ) | |
| 184 command_line_parser.add_argument( | |
| 185 "--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.
| |
| 186 help="show samples outside Ignition interpreter", | |
| 187 action="store_true" | |
| 188 ) | |
| 189 command_line_parser.add_argument( | |
| 190 "--show-full-signatures", "-s", | |
| 191 help="show full signatures instead of function names", | |
| 192 action="store_true" | |
| 193 ) | |
| 194 command_line_parser.add_argument( | |
| 195 "--output", "-o", | |
| 196 help="output file name (stdout if omitted)", | |
| 197 type=argparse.FileType('wt'), | |
| 198 default=sys.stdout, | |
| 199 metavar="<output filename>", | |
| 200 dest="output_stream" | |
| 201 ) | |
| 202 | |
| 203 return command_line_parser.parse_args() | |
| 204 | |
| 205 | |
| 206 def main(): | |
| 207 program_options = parse_command_line() | |
| 208 | |
| 209 perf = subprocess.Popen(["perf", "script", "-f", "ip,sym", | |
| 210 "-i", program_options.perf_filename], | |
| 211 stdout=subprocess.PIPE) | |
| 212 | |
| 213 callchains = collapsed_callchains_generator( | |
| 214 perf.stdout, program_options.show_other, | |
| 215 program_options.show_full_signatures) | |
| 216 | |
| 217 if program_options.output_flamegraph: | |
| 218 write_flamegraph_input_file(program_options.output_stream, callchains) | |
| 219 else: | |
| 220 write_handlers_report(program_options.output_stream, callchains) | |
| 221 | |
| 222 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.
| |
| 223 | |
| 224 | |
| 225 if __name__ == "__main__": | |
| 226 main() | |
| OLD | NEW |