| 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 |