blob: 127e808c1c8b610863831a906bcfe6c9f87a0bd4 [file] [log] [blame]
ager@chromium.org9258b6b2008-09-11 09:11:10 +00001# Copyright 2008 the V8 project authors. All rights reserved.
ager@chromium.orgc27e4e72008-09-04 13:52:27 +00002# Redistribution and use in source and binary forms, with or without
3# modification, are permitted provided that the following conditions are
4# met:
5#
6# * Redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer.
8# * Redistributions in binary form must reproduce the above
9# copyright notice, this list of conditions and the following
10# disclaimer in the documentation and/or other materials provided
11# with the distribution.
12# * Neither the name of Google Inc. nor the names of its
13# contributors may be used to endorse or promote products derived
14# from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
ager@chromium.org381abbb2009-02-25 13:23:22 +000028import csv, splaytree, sys, re
29from operator import itemgetter
30import getopt, os, string
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000031
32class CodeEntry(object):
33
34 def __init__(self, start_addr, name):
35 self.start_addr = start_addr
36 self.tick_count = 0
37 self.name = name
ager@chromium.org381abbb2009-02-25 13:23:22 +000038 self.stacks = {}
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000039
ager@chromium.org381abbb2009-02-25 13:23:22 +000040 def Tick(self, pc, stack):
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000041 self.tick_count += 1
ager@chromium.org381abbb2009-02-25 13:23:22 +000042 if len(stack) > 0:
43 stack.insert(0, self.ToString())
44 stack_key = tuple(stack)
45 self.stacks[stack_key] = self.stacks.setdefault(stack_key, 0) + 1
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000046
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +000047 def RegionTicks(self):
48 return None
49
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000050 def SetStartAddress(self, start_addr):
51 self.start_addr = start_addr
52
53 def ToString(self):
54 return self.name
55
56 def IsSharedLibraryEntry(self):
57 return False
58
ager@chromium.org381abbb2009-02-25 13:23:22 +000059 def IsICEntry(self):
60 return False
61
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000062
63class SharedLibraryEntry(CodeEntry):
64
65 def __init__(self, start_addr, name):
66 CodeEntry.__init__(self, start_addr, name)
67
68 def IsSharedLibraryEntry(self):
69 return True
70
71
72class JSCodeEntry(CodeEntry):
73
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +000074 def __init__(self, start_addr, name, type, size, assembler):
ager@chromium.orgc27e4e72008-09-04 13:52:27 +000075 CodeEntry.__init__(self, start_addr, name)
76 self.type = type
77 self.size = size
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +000078 self.assembler = assembler
79 self.region_ticks = None
ager@chromium.org381abbb2009-02-25 13:23:22 +000080 self.builtin_ic_re = re.compile('^(Keyed)?(Call|Load|Store)IC_')
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +000081
ager@chromium.org381abbb2009-02-25 13:23:22 +000082 def Tick(self, pc, stack):
83 super(JSCodeEntry, self).Tick(pc, stack)
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +000084 if not pc is None:
85 offset = pc - self.start_addr
86 seen = []
87 narrowest = None
88 narrowest_width = None
89 for region in self.Regions():
90 if region.Contains(offset):
91 if (not region.name in seen):
92 seen.append(region.name)
93 if narrowest is None or region.Width() < narrowest.Width():
94 narrowest = region
95 if len(seen) == 0:
96 return
97 if self.region_ticks is None:
98 self.region_ticks = {}
99 for name in seen:
100 if not name in self.region_ticks:
101 self.region_ticks[name] = [0, 0]
102 self.region_ticks[name][0] += 1
103 if name == narrowest.name:
104 self.region_ticks[name][1] += 1
105
106 def RegionTicks(self):
107 return self.region_ticks
108
109 def Regions(self):
110 if self.assembler:
111 return self.assembler.regions
112 else:
113 return []
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000114
115 def ToString(self):
ager@chromium.org7c537e22008-10-16 08:43:32 +0000116 name = self.name
iposva@chromium.org245aa852009-02-10 00:49:54 +0000117 if name == '':
118 name = '<anonymous>'
119 elif name.startswith(' '):
120 name = '<anonymous>' + name
ager@chromium.org7c537e22008-10-16 08:43:32 +0000121 return self.type + ': ' + name
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000122
ager@chromium.org381abbb2009-02-25 13:23:22 +0000123 def IsICEntry(self):
124 return self.type in ('CallIC', 'LoadIC', 'StoreIC') or \
125 (self.type == 'Builtin' and self.builtin_ic_re.match(self.name))
126
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000127
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000128class CodeRegion(object):
129
130 def __init__(self, start_offset, name):
131 self.start_offset = start_offset
132 self.name = name
133 self.end_offset = None
134
135 def Contains(self, pc):
136 return (self.start_offset <= pc) and (pc <= self.end_offset)
137
138 def Width(self):
139 return self.end_offset - self.start_offset
140
141
142class Assembler(object):
143
144 def __init__(self):
145 # Mapping from region ids to open regions
146 self.pending_regions = {}
147 self.regions = []
148
149
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000150VMStates = { 'JS': 0, 'GC': 1, 'COMPILER': 2, 'OTHER': 3 }
151
152
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000153class TickProcessor(object):
154
155 def __init__(self):
156 self.log_file = ''
157 self.deleted_code = []
158 self.vm_extent = {}
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000159 # Map from assembler ids to the pending assembler objects
160 self.pending_assemblers = {}
161 # Map from code addresses the have been allocated but not yet officially
162 # created to their assemblers.
163 self.assemblers = {}
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000164 self.js_entries = splaytree.SplayTree()
165 self.cpp_entries = splaytree.SplayTree()
166 self.total_number_of_ticks = 0
167 self.number_of_library_ticks = 0
168 self.unaccounted_number_of_ticks = 0
169 self.excluded_number_of_ticks = 0
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000170 self.number_of_gc_ticks = 0
ager@chromium.org381abbb2009-02-25 13:23:22 +0000171 # Flag indicating whether to ignore unaccounted ticks in the report
172 self.ignore_unknown = False
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000173
ager@chromium.org381abbb2009-02-25 13:23:22 +0000174 def ProcessLogfile(self, filename, included_state = None, ignore_unknown = False, separate_ic = False):
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000175 self.log_file = filename
176 self.included_state = included_state
ager@chromium.org381abbb2009-02-25 13:23:22 +0000177 self.ignore_unknown = ignore_unknown
178 self.separate_ic = separate_ic
179
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000180 try:
181 logfile = open(filename, 'rb')
182 except IOError:
183 sys.exit("Could not open logfile: " + filename)
184 try:
185 logreader = csv.reader(logfile)
186 for row in logreader:
187 if row[0] == 'tick':
ager@chromium.org381abbb2009-02-25 13:23:22 +0000188 self.ProcessTick(int(row[1], 16), int(row[2], 16), int(row[3]), self.PreprocessStack(row[4:]))
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000189 elif row[0] == 'code-creation':
190 self.ProcessCodeCreation(row[1], int(row[2], 16), int(row[3]), row[4])
191 elif row[0] == 'code-move':
192 self.ProcessCodeMove(int(row[1], 16), int(row[2], 16))
193 elif row[0] == 'code-delete':
194 self.ProcessCodeDelete(int(row[1], 16))
195 elif row[0] == 'shared-library':
196 self.AddSharedLibraryEntry(row[1], int(row[2], 16), int(row[3], 16))
197 self.ParseVMSymbols(row[1], int(row[2], 16), int(row[3], 16))
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000198 elif row[0] == 'begin-code-region':
199 self.ProcessBeginCodeRegion(int(row[1], 16), int(row[2], 16), int(row[3], 16), row[4])
200 elif row[0] == 'end-code-region':
201 self.ProcessEndCodeRegion(int(row[1], 16), int(row[2], 16), int(row[3], 16))
202 elif row[0] == 'code-allocate':
203 self.ProcessCodeAllocate(int(row[1], 16), int(row[2], 16))
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000204 finally:
205 logfile.close()
206
207 def AddSharedLibraryEntry(self, filename, start, end):
208 # Mark the pages used by this library.
209 i = start
210 while i < end:
211 page = i >> 12
212 self.vm_extent[page] = 1
213 i += 4096
214 # Add the library to the entries so that ticks for which we do not
215 # have symbol information is reported as belonging to the library.
216 self.cpp_entries.Insert(start, SharedLibraryEntry(start, filename))
217
218 def ParseVMSymbols(self, filename, start, end):
219 return
220
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000221 def ProcessCodeAllocate(self, addr, assem):
222 if assem in self.pending_assemblers:
223 assembler = self.pending_assemblers.pop(assem)
224 self.assemblers[addr] = assembler
225
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000226 def ProcessCodeCreation(self, type, addr, size, name):
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000227 if addr in self.assemblers:
228 assembler = self.assemblers.pop(addr)
229 else:
230 assembler = None
231 self.js_entries.Insert(addr, JSCodeEntry(addr, name, type, size, assembler))
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000232
233 def ProcessCodeMove(self, from_addr, to_addr):
234 try:
235 removed_node = self.js_entries.Remove(from_addr)
236 removed_node.value.SetStartAddress(to_addr);
237 self.js_entries.Insert(to_addr, removed_node.value)
238 except 'KeyNotFound':
239 print('Code move event for unknown code: 0x%x' % from_addr)
240
241 def ProcessCodeDelete(self, from_addr):
242 try:
243 removed_node = self.js_entries.Remove(from_addr)
244 self.deleted_code.append(removed_node.value)
245 except 'KeyNotFound':
246 print('Code delete event for unknown code: 0x%x' % from_addr)
247
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000248 def ProcessBeginCodeRegion(self, id, assm, start, name):
249 if not assm in self.pending_assemblers:
250 self.pending_assemblers[assm] = Assembler()
251 assembler = self.pending_assemblers[assm]
252 assembler.pending_regions[id] = CodeRegion(start, name)
253
254 def ProcessEndCodeRegion(self, id, assm, end):
255 assm = self.pending_assemblers[assm]
256 region = assm.pending_regions.pop(id)
257 region.end_offset = end
258 assm.regions.append(region)
259
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000260 def IncludeTick(self, pc, sp, state):
261 return (self.included_state is None) or (self.included_state == state)
262
ager@chromium.org381abbb2009-02-25 13:23:22 +0000263 def FindEntry(self, pc):
264 page = pc >> 12
265 if page in self.vm_extent:
266 entry = self.cpp_entries.FindGreatestsLessThan(pc)
267 if entry != None:
268 return entry.value
269 else:
270 return entry
271 max = self.js_entries.FindMax()
272 min = self.js_entries.FindMin()
273 if max != None and pc < (max.key + max.value.size) and pc > min.key:
274 return self.js_entries.FindGreatestsLessThan(pc).value
275 return None
276
277 def PreprocessStack(self, stack):
278 # remove all non-addresses (e.g. 'overflow') and convert to int
279 result = []
280 for frame in stack:
281 if frame.startswith('0x'):
282 result.append(int(frame, 16))
283 return result
284
285 def ProcessStack(self, stack):
286 result = []
287 for frame in stack:
288 entry = self.FindEntry(frame)
289 if entry != None:
290 result.append(entry.ToString())
291 return result
292
293 def ProcessTick(self, pc, sp, state, stack):
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000294 if state == VMStates['GC']:
295 self.number_of_gc_ticks += 1
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000296 if not self.IncludeTick(pc, sp, state):
297 self.excluded_number_of_ticks += 1;
298 return
299 self.total_number_of_ticks += 1
ager@chromium.org381abbb2009-02-25 13:23:22 +0000300 entry = self.FindEntry(pc)
301 if entry == None:
302 self.unaccounted_number_of_ticks += 1
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000303 return
ager@chromium.org381abbb2009-02-25 13:23:22 +0000304 if entry.IsSharedLibraryEntry():
305 self.number_of_library_ticks += 1
306 if entry.IsICEntry() and not self.separate_ic:
307 if len(stack) > 0:
308 caller_pc = stack.pop(0)
309 self.total_number_of_ticks -= 1
310 self.ProcessTick(caller_pc, sp, state, stack)
311 else:
312 self.unaccounted_number_of_ticks += 1
313 else:
314 entry.Tick(pc, self.ProcessStack(stack))
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000315
316 def PrintResults(self):
317 print('Statistical profiling result from %s, (%d ticks, %d unaccounted, %d excluded).' %
318 (self.log_file,
319 self.total_number_of_ticks,
320 self.unaccounted_number_of_ticks,
321 self.excluded_number_of_ticks))
322 if self.total_number_of_ticks > 0:
323 js_entries = self.js_entries.ExportValueList()
324 js_entries.extend(self.deleted_code)
325 cpp_entries = self.cpp_entries.ExportValueList()
ager@chromium.org381abbb2009-02-25 13:23:22 +0000326 # Print the unknown ticks percentage if they are not ignored.
327 if not self.ignore_unknown and self.unaccounted_number_of_ticks > 0:
328 self.PrintHeader('Unknown')
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000329 self.PrintCounter(self.unaccounted_number_of_ticks)
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000330 # Print the library ticks.
331 self.PrintHeader('Shared libraries')
332 self.PrintEntries(cpp_entries, lambda e:e.IsSharedLibraryEntry())
333 # Print the JavaScript ticks.
334 self.PrintHeader('JavaScript')
335 self.PrintEntries(js_entries, lambda e:not e.IsSharedLibraryEntry())
336 # Print the C++ ticks.
337 self.PrintHeader('C++')
338 self.PrintEntries(cpp_entries, lambda e:not e.IsSharedLibraryEntry())
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000339 # Print the GC ticks.
340 self.PrintHeader('GC')
341 self.PrintCounter(self.number_of_gc_ticks)
ager@chromium.org381abbb2009-02-25 13:23:22 +0000342 # Print call profile.
343 print('\n [Call profile]:')
344 print(' total call path')
345 js_entries.extend(cpp_entries)
346 self.PrintCallProfile(js_entries)
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000347
348 def PrintHeader(self, header_title):
349 print('\n [%s]:' % header_title)
ager@chromium.org381abbb2009-02-25 13:23:22 +0000350 print(' ticks total nonlib name')
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000351
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000352 def PrintCounter(self, ticks_count):
353 percentage = ticks_count * 100.0 / self.total_number_of_ticks
354 print(' %(ticks)5d %(total)5.1f%%' % {
355 'ticks' : ticks_count,
356 'total' : percentage,
357 })
358
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000359 def PrintEntries(self, entries, condition):
ager@chromium.org381abbb2009-02-25 13:23:22 +0000360 # If ignoring unaccounted ticks don't include these in percentage
361 # calculations
362 number_of_accounted_ticks = self.total_number_of_ticks
363 if self.ignore_unknown:
364 number_of_accounted_ticks -= self.unaccounted_number_of_ticks
365
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000366 number_of_non_library_ticks = number_of_accounted_ticks - self.number_of_library_ticks
367 entries.sort(key=lambda e:e.tick_count, reverse=True)
368 for entry in entries:
369 if entry.tick_count > 0 and condition(entry):
370 total_percentage = entry.tick_count * 100.0 / number_of_accounted_ticks
371 if entry.IsSharedLibraryEntry():
372 non_library_percentage = 0
373 else:
374 non_library_percentage = entry.tick_count * 100.0 / number_of_non_library_ticks
ager@chromium.org381abbb2009-02-25 13:23:22 +0000375 print(' %(ticks)5d %(total)5.1f%% %(nonlib)6.1f%% %(name)s' % {
376 'ticks' : entry.tick_count,
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000377 'total' : total_percentage,
378 'nonlib' : non_library_percentage,
379 'name' : entry.ToString()
380 })
christian.plesner.hansen@gmail.com37abdec2009-01-06 14:43:28 +0000381 region_ticks = entry.RegionTicks()
382 if not region_ticks is None:
383 items = region_ticks.items()
384 items.sort(key=lambda e: e[1][1], reverse=True)
385 for (name, ticks) in items:
386 print(' flat cum')
387 print(' %(flat)5.1f%% %(accum)5.1f%% %(name)s' % {
388 'flat' : ticks[1] * 100.0 / entry.tick_count,
389 'accum' : ticks[0] * 100.0 / entry.tick_count,
390 'name': name
391 })
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000392
ager@chromium.org381abbb2009-02-25 13:23:22 +0000393 def PrintCallProfile(self, entries):
394 all_stacks = {}
395 total_stacks = 0
396 for entry in entries:
397 all_stacks.update(entry.stacks)
398 for count in entry.stacks.itervalues():
399 total_stacks += count
400 all_stacks_items = all_stacks.items();
401 all_stacks_items.sort(key = itemgetter(1), reverse=True)
402 missing_percentage = (self.total_number_of_ticks - total_stacks) * 100.0 / self.total_number_of_ticks
403 print(' %(ticks)5d %(total)5.1f%% <no call path information>' % {
404 'ticks' : self.total_number_of_ticks - total_stacks,
405 'total' : missing_percentage
406 })
407 for stack, count in all_stacks_items:
408 total_percentage = count * 100.0 / self.total_number_of_ticks
409 print(' %(ticks)5d %(total)5.1f%% %(call_path)s' % {
410 'ticks' : count,
411 'total' : total_percentage,
412 'call_path' : stack[0] + ' <- ' + stack[1]
413 })
414
415
416class CmdLineProcessor(object):
417
418 def __init__(self):
419 self.options = ["js", "gc", "compiler", "other", "ignore-unknown", "separate-ic"]
420 # default values
421 self.state = None
422 self.ignore_unknown = False
423 self.log_file = None
424 self.separate_ic = False
425
426 def ProcessArguments(self):
427 try:
428 opts, args = getopt.getopt(sys.argv[1:], "jgco", self.options)
429 except getopt.GetoptError:
430 self.PrintUsageAndExit()
431 for key, value in opts:
432 if key in ("-j", "--js"):
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000433 self.state = VMStates['JS']
ager@chromium.org381abbb2009-02-25 13:23:22 +0000434 if key in ("-g", "--gc"):
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000435 self.state = VMStates['GC']
ager@chromium.org381abbb2009-02-25 13:23:22 +0000436 if key in ("-c", "--compiler"):
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000437 self.state = VMStates['COMPILER']
ager@chromium.org381abbb2009-02-25 13:23:22 +0000438 if key in ("-o", "--other"):
kasperl@chromium.org061ef742009-02-27 12:16:20 +0000439 self.state = VMStates['OTHER']
ager@chromium.org381abbb2009-02-25 13:23:22 +0000440 if key in ("--ignore-unknown"):
441 self.ignore_unknown = True
442 if key in ("--separate-ic"):
443 self.separate_ic = True
444 self.ProcessRequiredArgs(args)
445
446 def ProcessRequiredArgs(self, args):
447 return
448
449 def GetRequiredArgsNames(self):
450 return
451
452 def PrintUsageAndExit(self):
453 print('Usage: %(script_name)s --{%(opts)s} %(req_opts)s' % {
454 'script_name': os.path.basename(sys.argv[0]),
455 'opts': string.join(self.options, ','),
456 'req_opts': self.GetRequiredArgsNames()
457 })
458 sys.exit(2)
459
460 def RunLogfileProcessing(self, tick_processor):
461 tick_processor.ProcessLogfile(self.log_file, self.state, self.ignore_unknown, self.separate_ic)
462
463
ager@chromium.orgc27e4e72008-09-04 13:52:27 +0000464if __name__ == '__main__':
465 sys.exit('You probably want to run windows-tick-processor.py or linux-tick-processor.py.')