OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2010 the V8 project authors. All rights reserved. | 3 # Copyright 2010 the V8 project authors. All rights reserved. |
4 # Redistribution and use in source and binary forms, with or without | 4 # Redistribution and use in source and binary forms, with or without |
5 # modification, are permitted provided that the following conditions are | 5 # modification, are permitted provided that the following conditions are |
6 # met: | 6 # met: |
7 # | 7 # |
8 # * Redistributions of source code must retain the above copyright | 8 # * Redistributions of source code must retain the above copyright |
9 # notice, this list of conditions and the following disclaimer. | 9 # notice, this list of conditions and the following disclaimer. |
10 # * Redistributions in binary form must reproduce the above | 10 # * Redistributions in binary form must reproduce the above |
(...skipping 12 matching lines...) Expand all Loading... |
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | 29 |
30 import bisect | 30 import bisect |
31 import collections | 31 import collections |
32 import ctypes | 32 import ctypes |
| 33 import disasm |
33 import mmap | 34 import mmap |
34 import optparse | 35 import optparse |
35 import os | 36 import os |
36 import re | 37 import re |
37 import subprocess | 38 import subprocess |
38 import sys | 39 import sys |
39 import tempfile | |
40 import time | 40 import time |
41 | 41 |
42 | 42 |
43 USAGE="""usage: %prog [OPTION]... | 43 USAGE="""usage: %prog [OPTION]... |
44 | 44 |
45 Analyses V8 and perf logs to produce profiles. | 45 Analyses V8 and perf logs to produce profiles. |
46 | 46 |
47 Perf logs can be collected using a command like: | 47 Perf logs can be collected using a command like: |
48 $ perf record -R -e cycles -c 10000 -f -i ./shell bench.js --ll-prof | 48 $ perf record -R -e cycles -c 10000 -f -i ./shell bench.js --ll-prof |
49 # -R: collect all data | 49 # -R: collect all data |
(...skipping 17 matching lines...) Expand all Loading... |
67 $ %prog --log=foo.log --snapshot-log=snap-foo.log --trace=foo.data --snapshot | 67 $ %prog --log=foo.log --snapshot-log=snap-foo.log --trace=foo.data --snapshot |
68 """ | 68 """ |
69 | 69 |
70 | 70 |
71 # Must match kGcFakeMmap. | 71 # Must match kGcFakeMmap. |
72 V8_GC_FAKE_MMAP = "/tmp/__v8_gc__" | 72 V8_GC_FAKE_MMAP = "/tmp/__v8_gc__" |
73 | 73 |
74 JS_ORIGIN = "js" | 74 JS_ORIGIN = "js" |
75 JS_SNAPSHOT_ORIGIN = "js-snapshot" | 75 JS_SNAPSHOT_ORIGIN = "js-snapshot" |
76 | 76 |
77 # Avoid using the slow (google-specific) wrapper around objdump. | 77 OBJDUMP_BIN = disasm.OBJDUMP_BIN |
78 OBJDUMP_BIN = "/usr/bin/objdump" | |
79 if not os.path.exists(OBJDUMP_BIN): | |
80 OBJDUMP_BIN = "objdump" | |
81 | 78 |
82 | 79 |
83 class Code(object): | 80 class Code(object): |
84 """Code object.""" | 81 """Code object.""" |
85 | 82 |
86 _COMMON_DISASM_OPTIONS = ["-M", "intel-mnemonic", "-C"] | |
87 | |
88 _DISASM_HEADER_RE = re.compile(r"[a-f0-9]+\s+<.*:$") | |
89 _DISASM_LINE_RE = re.compile(r"\s*([a-f0-9]+):.*") | |
90 | |
91 # Keys must match constants in Logger::LogCodeInfo. | |
92 _ARCH_MAP = { | |
93 "ia32": "-m i386", | |
94 "x64": "-m i386 -M x86-64", | |
95 "arm": "-m arm" # Not supported by our objdump build. | |
96 } | |
97 | |
98 _id = 0 | 83 _id = 0 |
99 | 84 |
100 def __init__(self, name, start_address, end_address, origin, origin_offset): | 85 def __init__(self, name, start_address, end_address, origin, origin_offset): |
101 self.id = Code._id | 86 self.id = Code._id |
102 Code._id += 1 | 87 Code._id += 1 |
103 self.name = name | 88 self.name = name |
104 self.other_names = None | 89 self.other_names = None |
105 self.start_address = start_address | 90 self.start_address = start_address |
106 self.end_address = end_address | 91 self.end_address = end_address |
107 self.origin = origin | 92 self.origin = origin |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
143 if self.self_ticks_map is None: | 128 if self.self_ticks_map is None: |
144 ticks_map = [] | 129 ticks_map = [] |
145 else: | 130 else: |
146 ticks_map = self.self_ticks_map.items() | 131 ticks_map = self.self_ticks_map.items() |
147 # Convert the ticks map to offsets and counts arrays so that later | 132 # Convert the ticks map to offsets and counts arrays so that later |
148 # we can do binary search in the offsets array. | 133 # we can do binary search in the offsets array. |
149 ticks_map.sort(key=lambda t: t[0]) | 134 ticks_map.sort(key=lambda t: t[0]) |
150 ticks_offsets = [t[0] for t in ticks_map] | 135 ticks_offsets = [t[0] for t in ticks_map] |
151 ticks_counts = [t[1] for t in ticks_map] | 136 ticks_counts = [t[1] for t in ticks_map] |
152 # Get a list of disassembled lines and their addresses. | 137 # Get a list of disassembled lines and their addresses. |
153 lines = [] | 138 lines = self._GetDisasmLines(code_info, options): |
154 for line in self._GetDisasmLines(code_info, options): | |
155 match = Code._DISASM_LINE_RE.match(line) | |
156 if match: | |
157 line_address = int(match.group(1), 16) | |
158 lines.append((line_address, line)) | |
159 if len(lines) == 0: | 139 if len(lines) == 0: |
160 return | 140 return |
161 # Print annotated lines. | 141 # Print annotated lines. |
162 address = lines[0][0] | 142 address = lines[0][0] |
163 total_count = 0 | 143 total_count = 0 |
164 for i in xrange(len(lines)): | 144 for i in xrange(len(lines)): |
165 start_offset = lines[i][0] - address | 145 start_offset = lines[i][0] - address |
166 if i == len(lines) - 1: | 146 if i == len(lines) - 1: |
167 end_offset = self.end_address - self.start_address | 147 end_offset = self.end_address - self.start_address |
168 else: | 148 else: |
169 end_offset = lines[i + 1][0] - address | 149 end_offset = lines[i + 1][0] - address |
170 # Ticks (reported pc values) are not always precise, i.e. not | 150 # Ticks (reported pc values) are not always precise, i.e. not |
171 # necessarily point at instruction starts. So we have to search | 151 # necessarily point at instruction starts. So we have to search |
172 # for ticks that touch the current instruction line. | 152 # for ticks that touch the current instruction line. |
173 j = bisect.bisect_left(ticks_offsets, end_offset) | 153 j = bisect.bisect_left(ticks_offsets, end_offset) |
174 count = 0 | 154 count = 0 |
175 for offset, cnt in reversed(zip(ticks_offsets[:j], ticks_counts[:j])): | 155 for offset, cnt in reversed(zip(ticks_offsets[:j], ticks_counts[:j])): |
176 if offset < start_offset: | 156 if offset < start_offset: |
177 break | 157 break |
178 count += cnt | 158 count += cnt |
179 total_count += count | 159 total_count += count |
180 count = 100.0 * count / self.self_ticks | 160 count = 100.0 * count / self.self_ticks |
181 if count >= 0.01: | 161 if count >= 0.01: |
182 print "%15.2f %s" % (count, lines[i][1]) | 162 print "%15.2f %x: %s" % (count, lines[i][0], lines[i][1]) |
183 else: | 163 else: |
184 print "%s %s" % (" " * 15, lines[i][1]) | 164 print "%s %x: %s" % (" " * 15, lines[i][0], lines[i][1]) |
185 print | 165 print |
186 assert total_count == self.self_ticks, \ | 166 assert total_count == self.self_ticks, \ |
187 "Lost ticks (%d != %d) in %s" % (total_count, self.self_ticks, self) | 167 "Lost ticks (%d != %d) in %s" % (total_count, self.self_ticks, self) |
188 | 168 |
189 def __str__(self): | 169 def __str__(self): |
190 return "%s [0x%x, 0x%x) size: %d origin: %s" % ( | 170 return "%s [0x%x, 0x%x) size: %d origin: %s" % ( |
191 self.name, | 171 self.name, |
192 self.start_address, | 172 self.start_address, |
193 self.end_address, | 173 self.end_address, |
194 self.end_address - self.start_address, | 174 self.end_address - self.start_address, |
195 self.origin) | 175 self.origin) |
196 | 176 |
197 def _GetDisasmLines(self, code_info, options): | 177 def _GetDisasmLines(self, code_info, options): |
198 tmp_name = None | |
199 if self.origin == JS_ORIGIN or self.origin == JS_SNAPSHOT_ORIGIN: | 178 if self.origin == JS_ORIGIN or self.origin == JS_SNAPSHOT_ORIGIN: |
200 assert code_info.arch in Code._ARCH_MAP, \ | 179 inplace = False |
201 "Unsupported architecture '%s'" % arch | 180 filename = options.log + ".code" |
202 arch_flags = Code._ARCH_MAP[code_info.arch] | |
203 # Create a temporary file just with this code object. | |
204 tmp_name = tempfile.mktemp(".v8code") | |
205 size = self.end_address - self.start_address | |
206 command = "dd if=%s.code of=%s bs=1 count=%d skip=%d && " \ | |
207 "%s %s -D -b binary %s %s" % ( | |
208 options.log, tmp_name, size, self.origin_offset, | |
209 OBJDUMP_BIN, ' '.join(Code._COMMON_DISASM_OPTIONS), arch_flags, | |
210 tmp_name) | |
211 else: | 181 else: |
212 command = "%s %s --start-address=%d --stop-address=%d -d %s " % ( | 182 inplace = True |
213 OBJDUMP_BIN, ' '.join(Code._COMMON_DISASM_OPTIONS), | 183 filename = self.origin |
214 self.origin_offset, | 184 return disasm.GetDisasmLines(filename, |
215 self.origin_offset + self.end_address - self.start_address, | 185 self.origin_offset, |
216 self.origin) | 186 self.end_address - self.start_address, |
217 process = subprocess.Popen(command, | 187 code_info.arch, |
218 shell=True, | 188 inplace) |
219 stdout=subprocess.PIPE, | |
220 stderr=subprocess.STDOUT) | |
221 out, err = process.communicate() | |
222 lines = out.split("\n") | |
223 header_line = 0 | |
224 for i, line in enumerate(lines): | |
225 if Code._DISASM_HEADER_RE.match(line): | |
226 header_line = i | |
227 break | |
228 if tmp_name: | |
229 os.unlink(tmp_name) | |
230 return lines[header_line + 1:] | |
231 | 189 |
232 | 190 |
233 class CodePage(object): | 191 class CodePage(object): |
234 """Group of adjacent code objects.""" | 192 """Group of adjacent code objects.""" |
235 | 193 |
236 SHIFT = 12 # 4K pages | 194 SHIFT = 12 # 4K pages |
237 SIZE = (1 << SHIFT) | 195 SIZE = (1 << SHIFT) |
238 MASK = ~(SIZE - 1) | 196 MASK = ~(SIZE - 1) |
239 | 197 |
240 @staticmethod | 198 @staticmethod |
(...skipping 705 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
946 print "%10d total ticks" % ticks | 904 print "%10d total ticks" % ticks |
947 print "%10d ticks not in symbols" % missed_ticks | 905 print "%10d ticks not in symbols" % missed_ticks |
948 print "%10d unaccounted ticks" % really_missed_ticks | 906 print "%10d unaccounted ticks" % really_missed_ticks |
949 print "%10d total symbols" % len([c for c in code_map.AllCode()]) | 907 print "%10d total symbols" % len([c for c in code_map.AllCode()]) |
950 print "%10d used symbols" % len([c for c in code_map.UsedCode()]) | 908 print "%10d used symbols" % len([c for c in code_map.UsedCode()]) |
951 print "%9.2fs library processing time" % mmap_time | 909 print "%9.2fs library processing time" % mmap_time |
952 print "%9.2fs tick processing time" % sample_time | 910 print "%9.2fs tick processing time" % sample_time |
953 | 911 |
954 log_reader.Dispose() | 912 log_reader.Dispose() |
955 trace_reader.Dispose() | 913 trace_reader.Dispose() |
OLD | NEW |