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

Side by Side Diff: tools/tickprocessor.py

Issue 6614010: [Isolates] Merge 6700:7030 from bleeding_edge to isolates. (Closed) Base URL: http://v8.googlecode.com/svn/branches/experimental/isolates/
Patch Set: '' Created 9 years, 9 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 | Annotate | Revision Log
« no previous file with comments | « tools/tickprocessor.js ('k') | tools/utils.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 # Copyright 2008 the V8 project authors. All rights reserved.
2 # 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
28 import csv, splaytree, sys, re
29 from operator import itemgetter
30 import getopt, os, string
31
32 class 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
38 self.stacks = {}
39
40 def Tick(self, pc, stack):
41 self.tick_count += 1
42 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
46
47 def RegionTicks(self):
48 return None
49
50 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
59 def IsICEntry(self):
60 return False
61
62 def IsJSFunction(self):
63 return False
64
65 class SharedLibraryEntry(CodeEntry):
66
67 def __init__(self, start_addr, name):
68 CodeEntry.__init__(self, start_addr, name)
69
70 def IsSharedLibraryEntry(self):
71 return True
72
73
74 class JSCodeEntry(CodeEntry):
75
76 def __init__(self, start_addr, name, type, size, assembler):
77 CodeEntry.__init__(self, start_addr, name)
78 self.type = type
79 self.size = size
80 self.assembler = assembler
81 self.region_ticks = None
82 self.builtin_ic_re = re.compile('^(Keyed)?(Call|Load|Store)IC_')
83
84 def Tick(self, pc, stack):
85 super(JSCodeEntry, self).Tick(pc, stack)
86 if not pc is None:
87 offset = pc - self.start_addr
88 seen = []
89 narrowest = None
90 narrowest_width = None
91 for region in self.Regions():
92 if region.Contains(offset):
93 if (not region.name in seen):
94 seen.append(region.name)
95 if narrowest is None or region.Width() < narrowest.Width():
96 narrowest = region
97 if len(seen) == 0:
98 return
99 if self.region_ticks is None:
100 self.region_ticks = {}
101 for name in seen:
102 if not name in self.region_ticks:
103 self.region_ticks[name] = [0, 0]
104 self.region_ticks[name][0] += 1
105 if name == narrowest.name:
106 self.region_ticks[name][1] += 1
107
108 def RegionTicks(self):
109 return self.region_ticks
110
111 def Regions(self):
112 if self.assembler:
113 return self.assembler.regions
114 else:
115 return []
116
117 def ToString(self):
118 name = self.name
119 if name == '':
120 name = '<anonymous>'
121 elif name.startswith(' '):
122 name = '<anonymous>' + name
123 return self.type + ': ' + name
124
125 def IsICEntry(self):
126 return self.type in ('CallIC', 'LoadIC', 'StoreIC') or \
127 (self.type == 'Builtin' and self.builtin_ic_re.match(self.name))
128
129 def IsJSFunction(self):
130 return self.type in ('Function', 'LazyCompile', 'Script')
131
132 class CodeRegion(object):
133
134 def __init__(self, start_offset, name):
135 self.start_offset = start_offset
136 self.name = name
137 self.end_offset = None
138
139 def Contains(self, pc):
140 return (self.start_offset <= pc) and (pc <= self.end_offset)
141
142 def Width(self):
143 return self.end_offset - self.start_offset
144
145
146 class Assembler(object):
147
148 def __init__(self):
149 # Mapping from region ids to open regions
150 self.pending_regions = {}
151 self.regions = []
152
153
154 class FunctionEnumerator(object):
155
156 def __init__(self):
157 self.known_funcs = {}
158 self.next_func_id = 0
159
160 def GetFunctionId(self, name):
161 if not self.known_funcs.has_key(name):
162 self.known_funcs[name] = self.next_func_id
163 self.next_func_id += 1
164 return self.known_funcs[name]
165
166 def GetKnownFunctions(self):
167 known_funcs_items = self.known_funcs.items();
168 known_funcs_items.sort(key = itemgetter(1))
169 result = []
170 for func, id_not_used in known_funcs_items:
171 result.append(func)
172 return result
173
174
175 VMStates = { 'JS': 0, 'GC': 1, 'COMPILER': 2, 'OTHER': 3, 'EXTERNAL' : 4 }
176
177
178 class TickProcessor(object):
179
180 def __init__(self):
181 self.log_file = ''
182 self.deleted_code = []
183 self.vm_extent = {}
184 # Map from assembler ids to the pending assembler objects
185 self.pending_assemblers = {}
186 # Map from code addresses the have been allocated but not yet officially
187 # created to their assemblers.
188 self.assemblers = {}
189 self.js_entries = splaytree.SplayTree()
190 self.cpp_entries = splaytree.SplayTree()
191 self.total_number_of_ticks = 0
192 self.number_of_library_ticks = 0
193 self.unaccounted_number_of_ticks = 0
194 self.excluded_number_of_ticks = 0
195 self.number_of_gc_ticks = 0
196 # Flag indicating whether to ignore unaccounted ticks in the report
197 self.ignore_unknown = False
198 self.func_enum = FunctionEnumerator()
199 self.packed_stacks = []
200
201 def ProcessLogfile(self, filename, included_state = None, ignore_unknown = Fal se, separate_ic = False, call_graph_json = False):
202 self.log_file = filename
203 self.included_state = included_state
204 self.ignore_unknown = ignore_unknown
205 self.separate_ic = separate_ic
206 self.call_graph_json = call_graph_json
207
208 try:
209 logfile = open(filename, 'rb')
210 except IOError:
211 sys.exit("Could not open logfile: " + filename)
212 try:
213 try:
214 logreader = csv.reader(logfile)
215 row_num = 1
216 for row in logreader:
217 row_num += 1
218 if row[0] == 'tick':
219 self.ProcessTick(int(row[1], 16), int(row[2], 16), int(row[3], 16), int(row[4]), self.PreprocessStack(row[5:]))
220 elif row[0] == 'code-creation':
221 self.ProcessCodeCreation(row[1], int(row[2], 16), int(row[3]), row[4 ])
222 elif row[0] == 'code-move':
223 self.ProcessCodeMove(int(row[1], 16), int(row[2], 16))
224 elif row[0] == 'code-delete':
225 self.ProcessCodeDelete(int(row[1], 16))
226 elif row[0] == 'function-creation':
227 self.ProcessFunctionCreation(int(row[1], 16), int(row[2], 16))
228 elif row[0] == 'function-move':
229 self.ProcessFunctionMove(int(row[1], 16), int(row[2], 16))
230 elif row[0] == 'function-delete':
231 self.ProcessFunctionDelete(int(row[1], 16))
232 elif row[0] == 'shared-library':
233 self.AddSharedLibraryEntry(row[1], int(row[2], 16), int(row[3], 16))
234 self.ParseVMSymbols(row[1], int(row[2], 16), int(row[3], 16))
235 elif row[0] == 'begin-code-region':
236 self.ProcessBeginCodeRegion(int(row[1], 16), int(row[2], 16), int(ro w[3], 16), row[4])
237 elif row[0] == 'end-code-region':
238 self.ProcessEndCodeRegion(int(row[1], 16), int(row[2], 16), int(row[ 3], 16))
239 elif row[0] == 'code-allocate':
240 self.ProcessCodeAllocate(int(row[1], 16), int(row[2], 16))
241 except csv.Error:
242 print("parse error in line " + str(row_num))
243 raise
244 finally:
245 logfile.close()
246
247 def AddSharedLibraryEntry(self, filename, start, end):
248 # Mark the pages used by this library.
249 i = start
250 while i < end:
251 page = i >> 12
252 self.vm_extent[page] = 1
253 i += 4096
254 # Add the library to the entries so that ticks for which we do not
255 # have symbol information is reported as belonging to the library.
256 self.cpp_entries.Insert(start, SharedLibraryEntry(start, filename))
257
258 def ParseVMSymbols(self, filename, start, end):
259 return
260
261 def ProcessCodeAllocate(self, addr, assem):
262 if assem in self.pending_assemblers:
263 assembler = self.pending_assemblers.pop(assem)
264 self.assemblers[addr] = assembler
265
266 def ProcessCodeCreation(self, type, addr, size, name):
267 if addr in self.assemblers:
268 assembler = self.assemblers.pop(addr)
269 else:
270 assembler = None
271 self.js_entries.Insert(addr, JSCodeEntry(addr, name, type, size, assembler))
272
273 def ProcessCodeMove(self, from_addr, to_addr):
274 try:
275 removed_node = self.js_entries.Remove(from_addr)
276 removed_node.value.SetStartAddress(to_addr);
277 self.js_entries.Insert(to_addr, removed_node.value)
278 except splaytree.KeyNotFoundError:
279 print('Code move event for unknown code: 0x%x' % from_addr)
280
281 def ProcessCodeDelete(self, from_addr):
282 try:
283 removed_node = self.js_entries.Remove(from_addr)
284 self.deleted_code.append(removed_node.value)
285 except splaytree.KeyNotFoundError:
286 print('Code delete event for unknown code: 0x%x' % from_addr)
287
288 def ProcessFunctionCreation(self, func_addr, code_addr):
289 js_entry_node = self.js_entries.Find(code_addr)
290 if js_entry_node:
291 js_entry = js_entry_node.value
292 self.js_entries.Insert(func_addr, JSCodeEntry(func_addr, js_entry.name, js _entry.type, 1, None))
293
294 def ProcessFunctionMove(self, from_addr, to_addr):
295 try:
296 removed_node = self.js_entries.Remove(from_addr)
297 removed_node.value.SetStartAddress(to_addr);
298 self.js_entries.Insert(to_addr, removed_node.value)
299 except splaytree.KeyNotFoundError:
300 return
301
302 def ProcessFunctionDelete(self, from_addr):
303 try:
304 removed_node = self.js_entries.Remove(from_addr)
305 self.deleted_code.append(removed_node.value)
306 except splaytree.KeyNotFoundError:
307 return
308
309 def ProcessBeginCodeRegion(self, id, assm, start, name):
310 if not assm in self.pending_assemblers:
311 self.pending_assemblers[assm] = Assembler()
312 assembler = self.pending_assemblers[assm]
313 assembler.pending_regions[id] = CodeRegion(start, name)
314
315 def ProcessEndCodeRegion(self, id, assm, end):
316 assm = self.pending_assemblers[assm]
317 region = assm.pending_regions.pop(id)
318 region.end_offset = end
319 assm.regions.append(region)
320
321 def IncludeTick(self, pc, sp, state):
322 return (self.included_state is None) or (self.included_state == state)
323
324 def FindEntry(self, pc):
325 page = pc >> 12
326 if page in self.vm_extent:
327 entry = self.cpp_entries.FindGreatestsLessThan(pc)
328 if entry != None:
329 return entry.value
330 else:
331 return entry
332 max = self.js_entries.FindMax()
333 min = self.js_entries.FindMin()
334 if max != None and pc < (max.key + max.value.size) and pc > min.key:
335 return self.js_entries.FindGreatestsLessThan(pc).value
336 return None
337
338 def PreprocessStack(self, stack):
339 # remove all non-addresses (e.g. 'overflow') and convert to int
340 result = []
341 for frame in stack:
342 if frame.startswith('0x'):
343 result.append(int(frame, 16))
344 return result
345
346 def ProcessStack(self, stack):
347 result = []
348 for frame in stack:
349 entry = self.FindEntry(frame)
350 if entry != None:
351 result.append(entry.ToString())
352 return result
353
354 def ProcessTick(self, pc, sp, func, state, stack):
355 if state == VMStates['GC']:
356 self.number_of_gc_ticks += 1
357 if not self.IncludeTick(pc, sp, state):
358 self.excluded_number_of_ticks += 1;
359 return
360 self.total_number_of_ticks += 1
361 entry = self.FindEntry(pc)
362 if entry == None:
363 self.unaccounted_number_of_ticks += 1
364 return
365 if entry.IsSharedLibraryEntry():
366 self.number_of_library_ticks += 1
367 if entry.IsICEntry() and not self.separate_ic:
368 if len(stack) > 0:
369 caller_pc = stack.pop(0)
370 self.total_number_of_ticks -= 1
371 self.ProcessTick(caller_pc, sp, func, state, stack)
372 else:
373 self.unaccounted_number_of_ticks += 1
374 else:
375 processed_stack = self.ProcessStack(stack)
376 if not entry.IsSharedLibraryEntry() and not entry.IsJSFunction():
377 func_entry_node = self.js_entries.Find(func)
378 if func_entry_node and func_entry_node.value.IsJSFunction():
379 processed_stack.insert(0, func_entry_node.value.ToString())
380 entry.Tick(pc, processed_stack)
381 if self.call_graph_json:
382 self.AddToPackedStacks(pc, stack)
383
384 def AddToPackedStacks(self, pc, stack):
385 full_stack = stack
386 full_stack.insert(0, pc)
387 func_names = self.ProcessStack(full_stack)
388 func_ids = []
389 for func in func_names:
390 func_ids.append(self.func_enum.GetFunctionId(func))
391 self.packed_stacks.append(func_ids)
392
393 def PrintResults(self):
394 if not self.call_graph_json:
395 self.PrintStatistics()
396 else:
397 self.PrintCallGraphJSON()
398
399 def PrintStatistics(self):
400 print('Statistical profiling result from %s, (%d ticks, %d unaccounted, %d e xcluded).' %
401 (self.log_file,
402 self.total_number_of_ticks,
403 self.unaccounted_number_of_ticks,
404 self.excluded_number_of_ticks))
405 if self.total_number_of_ticks > 0:
406 js_entries = self.js_entries.ExportValueList()
407 js_entries.extend(self.deleted_code)
408 cpp_entries = self.cpp_entries.ExportValueList()
409 # Print the unknown ticks percentage if they are not ignored.
410 if not self.ignore_unknown and self.unaccounted_number_of_ticks > 0:
411 self.PrintHeader('Unknown')
412 self.PrintCounter(self.unaccounted_number_of_ticks)
413 # Print the library ticks.
414 self.PrintHeader('Shared libraries')
415 self.PrintEntries(cpp_entries, lambda e:e.IsSharedLibraryEntry())
416 # Print the JavaScript ticks.
417 self.PrintHeader('JavaScript')
418 self.PrintEntries(js_entries, lambda e:not e.IsSharedLibraryEntry())
419 # Print the C++ ticks.
420 self.PrintHeader('C++')
421 self.PrintEntries(cpp_entries, lambda e:not e.IsSharedLibraryEntry())
422 # Print the GC ticks.
423 self.PrintHeader('GC')
424 self.PrintCounter(self.number_of_gc_ticks)
425 # Print call profile.
426 print('\n [Call profile]:')
427 print(' total call path')
428 js_entries.extend(cpp_entries)
429 self.PrintCallProfile(js_entries)
430
431 def PrintHeader(self, header_title):
432 print('\n [%s]:' % header_title)
433 print(' ticks total nonlib name')
434
435 def PrintCounter(self, ticks_count):
436 percentage = ticks_count * 100.0 / self.total_number_of_ticks
437 print(' %(ticks)5d %(total)5.1f%%' % {
438 'ticks' : ticks_count,
439 'total' : percentage,
440 })
441
442 def PrintEntries(self, entries, condition):
443 # If ignoring unaccounted ticks don't include these in percentage
444 # calculations
445 number_of_accounted_ticks = self.total_number_of_ticks
446 if self.ignore_unknown:
447 number_of_accounted_ticks -= self.unaccounted_number_of_ticks
448
449 number_of_non_library_ticks = number_of_accounted_ticks - self.number_of_lib rary_ticks
450 entries.sort(key=lambda e: (e.tick_count, e.ToString()), reverse=True)
451 for entry in entries:
452 if entry.tick_count > 0 and condition(entry):
453 total_percentage = entry.tick_count * 100.0 / number_of_accounted_ticks
454 if entry.IsSharedLibraryEntry():
455 non_library_percentage = 0
456 else:
457 non_library_percentage = entry.tick_count * 100.0 / number_of_non_libr ary_ticks
458 print(' %(ticks)5d %(total)5.1f%% %(nonlib)6.1f%% %(name)s' % {
459 'ticks' : entry.tick_count,
460 'total' : total_percentage,
461 'nonlib' : non_library_percentage,
462 'name' : entry.ToString()
463 })
464 region_ticks = entry.RegionTicks()
465 if not region_ticks is None:
466 items = region_ticks.items()
467 items.sort(key=lambda e: e[1][1], reverse=True)
468 for (name, ticks) in items:
469 print(' flat cum')
470 print(' %(flat)5.1f%% %(accum)5.1f%% %(name)s' % {
471 'flat' : ticks[1] * 100.0 / entry.tick_count,
472 'accum' : ticks[0] * 100.0 / entry.tick_count,
473 'name': name
474 })
475
476 def PrintCallProfile(self, entries):
477 all_stacks = {}
478 total_stacks = 0
479 for entry in entries:
480 all_stacks.update(entry.stacks)
481 for count in entry.stacks.itervalues():
482 total_stacks += count
483 all_stacks_items = all_stacks.items();
484 all_stacks_items.sort(key = itemgetter(1), reverse=True)
485 missing_percentage = (self.total_number_of_ticks - total_stacks) * 100.0 / s elf.total_number_of_ticks
486 print(' %(ticks)5d %(total)5.1f%% <no call path information>' % {
487 'ticks' : self.total_number_of_ticks - total_stacks,
488 'total' : missing_percentage
489 })
490 for stack, count in all_stacks_items:
491 total_percentage = count * 100.0 / self.total_number_of_ticks
492 print(' %(ticks)5d %(total)5.1f%% %(call_path)s' % {
493 'ticks' : count,
494 'total' : total_percentage,
495 'call_path' : stack[0] + ' <- ' + stack[1]
496 })
497
498 def PrintCallGraphJSON(self):
499 print('\nvar __profile_funcs = ["' +
500 '",\n"'.join(self.func_enum.GetKnownFunctions()) +
501 '"];')
502 print('var __profile_ticks = [')
503 str_packed_stacks = []
504 for stack in self.packed_stacks:
505 str_packed_stacks.append('[' + ','.join(map(str, stack)) + ']')
506 print(',\n'.join(str_packed_stacks))
507 print('];')
508
509 class CmdLineProcessor(object):
510
511 def __init__(self):
512 self.options = ["js",
513 "gc",
514 "compiler",
515 "other",
516 "external",
517 "ignore-unknown",
518 "separate-ic",
519 "call-graph-json"]
520 # default values
521 self.state = None
522 self.ignore_unknown = False
523 self.log_file = None
524 self.separate_ic = False
525 self.call_graph_json = False
526
527 def ProcessArguments(self):
528 try:
529 opts, args = getopt.getopt(sys.argv[1:], "jgcoe", self.options)
530 except getopt.GetoptError:
531 self.PrintUsageAndExit()
532 for key, value in opts:
533 if key in ("-j", "--js"):
534 self.state = VMStates['JS']
535 if key in ("-g", "--gc"):
536 self.state = VMStates['GC']
537 if key in ("-c", "--compiler"):
538 self.state = VMStates['COMPILER']
539 if key in ("-o", "--other"):
540 self.state = VMStates['OTHER']
541 if key in ("-e", "--external"):
542 self.state = VMStates['EXTERNAL']
543 if key in ("--ignore-unknown"):
544 self.ignore_unknown = True
545 if key in ("--separate-ic"):
546 self.separate_ic = True
547 if key in ("--call-graph-json"):
548 self.call_graph_json = True
549 self.ProcessRequiredArgs(args)
550
551 def ProcessRequiredArgs(self, args):
552 return
553
554 def GetRequiredArgsNames(self):
555 return
556
557 def PrintUsageAndExit(self):
558 print('Usage: %(script_name)s --{%(opts)s} %(req_opts)s' % {
559 'script_name': os.path.basename(sys.argv[0]),
560 'opts': string.join(self.options, ','),
561 'req_opts': self.GetRequiredArgsNames()
562 })
563 sys.exit(2)
564
565 def RunLogfileProcessing(self, tick_processor):
566 tick_processor.ProcessLogfile(self.log_file, self.state, self.ignore_unknown ,
567 self.separate_ic, self.call_graph_json)
568
569
570 if __name__ == '__main__':
571 sys.exit('You probably want to run windows-tick-processor.py or linux-tick-pro cessor.py.')
OLDNEW
« no previous file with comments | « tools/tickprocessor.js ('k') | tools/utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698