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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « no previous file | tools/ignition/linux_perf_report_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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()
OLDNEW
« 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