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

Side by Side Diff: tools/tickprocessor.js

Issue 99054: TickProcessor script reimplemented in JavaScript. (Closed)
Patch Set: Addressed Soeren's comments Created 11 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 | « tools/profileview.js ('k') | tools/tickprocessor.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 2009 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
29 function Profile(separateIc) {
30 devtools.profiler.Profile.call(this);
31 if (!separateIc) {
32 this.skipThisFunction = function(name) { return Profile.IC_RE.test(name); };
33 }
34 };
35 Profile.prototype = devtools.profiler.Profile.prototype;
36
37
38 Profile.IC_RE =
39 /^(?:CallIC|LoadIC|StoreIC)|(?:Builtin: (?:Keyed)?(?:Call|Load|Store)IC_)/;
40
41
42 /**
43 * A thin wrapper around shell's 'read' function showing a file name on error.
44 */
45 function readFile(fileName) {
46 try {
47 return read(fileName);
48 } catch (e) {
49 print(fileName + ': ' + (e.message || e));
50 throw e;
51 }
52 }
53
54
55 function TickProcessor(
56 cppEntriesProvider, separateIc, ignoreUnknown, stateFilter) {
57 this.cppEntriesProvider_ = cppEntriesProvider;
58 this.ignoreUnknown_ = ignoreUnknown;
59 this.stateFilter_ = stateFilter;
60 var ticks = this.ticks_ =
61 { total: 0, unaccounted: 0, excluded: 0, gc: 0 };
62
63 Profile.prototype.handleUnknownCode = function(
64 operation, addr, opt_stackPos) {
65 var op = devtools.profiler.Profile.Operation;
66 switch (operation) {
67 case op.MOVE:
68 print('Code move event for unknown code: 0x' + addr.toString(16));
69 break;
70 case op.DELETE:
71 print('Code delete event for unknown code: 0x' + addr.toString(16));
72 break;
73 case op.TICK:
74 // Only unknown PCs (the first frame) are reported as unaccounted,
75 // otherwise tick balance will be corrupted (this behavior is compatible
76 // with the original tickprocessor.py script.)
77 if (opt_stackPos == 0) {
78 ticks.unaccounted++;
79 }
80 break;
81 }
82 };
83
84 this.profile_ = new Profile(separateIc);
85 this.codeTypes_ = {};
86 // Count each tick as a time unit.
87 this.viewBuilder_ = new devtools.profiler.ViewBuilder(1);
88 this.lastLogFileName_ = null;
89 };
90
91
92 TickProcessor.VmStates = {
93 JS: 0,
94 GC: 1,
95 COMPILER: 2,
96 OTHER: 3,
97 EXTERNAL: 4
98 };
99
100
101 TickProcessor.CodeTypes = {
102 JS: 0,
103 CPP: 1,
104 SHARED_LIB: 2
105 };
106
107
108 TickProcessor.RecordsDispatch = {
109 'shared-library': { parsers: [null, parseInt, parseInt],
110 processor: 'processSharedLibrary' },
111 'code-creation': { parsers: [null, parseInt, parseInt, null],
112 processor: 'processCodeCreation' },
113 'code-move': { parsers: [parseInt, parseInt],
114 processor: 'processCodeMove' },
115 'code-delete': { parsers: [parseInt], processor: 'processCodeDelete' },
116 'tick': { parsers: [parseInt, parseInt, parseInt, 'var-args'],
117 processor: 'processTick' },
118 'profiler': null,
119 // Obsolete row types.
120 'code-allocate': null,
121 'begin-code-region': null,
122 'end-code-region': null
123 };
124
125
126 TickProcessor.CALL_PROFILE_CUTOFF_PCT = 2.0;
127
128
129 TickProcessor.prototype.setCodeType = function(name, type) {
130 this.codeTypes_[name] = TickProcessor.CodeTypes[type];
131 };
132
133
134 TickProcessor.prototype.isSharedLibrary = function(name) {
135 return this.codeTypes_[name] == TickProcessor.CodeTypes.SHARED_LIB;
136 };
137
138
139 TickProcessor.prototype.isCppCode = function(name) {
140 return this.codeTypes_[name] == TickProcessor.CodeTypes.CPP;
141 };
142
143
144 TickProcessor.prototype.isJsCode = function(name) {
145 return this.codeTypes_[name] == TickProcessor.CodeTypes.JS;
146 };
147
148
149 TickProcessor.prototype.processLogFile = function(fileName) {
150 this.lastLogFileName_ = fileName;
151 var contents = readFile(fileName);
152 this.processLog(contents.split('\n'));
153 };
154
155
156 TickProcessor.prototype.processLog = function(lines) {
157 var csvParser = new devtools.profiler.CsvParser();
158 try {
159 for (var i = 0, n = lines.length; i < n; ++i) {
160 var line = lines[i];
161 if (!line) {
162 continue;
163 }
164 var fields = csvParser.parseLine(line);
165 this.dispatchLogRow(fields);
166 }
167 } catch (e) {
168 print('line ' + (i + 1) + ': ' + (e.message || e));
169 throw e;
170 }
171 };
172
173
174 TickProcessor.prototype.dispatchLogRow = function(fields) {
175 // Obtain the dispatch.
176 var command = fields[0];
177 if (!(command in TickProcessor.RecordsDispatch)) {
178 throw new Error('unknown command: ' + command);
179 }
180 var dispatch = TickProcessor.RecordsDispatch[command];
181
182 if (dispatch === null) {
183 return;
184 }
185
186 // Parse fields.
187 var parsedFields = [];
188 for (var i = 0; i < dispatch.parsers.length; ++i) {
189 var parser = dispatch.parsers[i];
190 if (parser === null) {
191 parsedFields.push(fields[1 + i]);
192 } else if (typeof parser == 'function') {
193 parsedFields.push(parser(fields[1 + i]));
194 } else {
195 // var-args
196 parsedFields.push(fields.slice(1 + i));
197 break;
198 }
199 }
200
201 // Run the processor.
202 this[dispatch.processor].apply(this, parsedFields);
203 };
204
205
206 TickProcessor.prototype.processSharedLibrary = function(
207 name, startAddr, endAddr) {
208 var entry = this.profile_.addStaticCode(name, startAddr, endAddr);
209 this.setCodeType(entry.getName(), 'SHARED_LIB');
210
211 var self = this;
212 var libFuncs = this.cppEntriesProvider_.parseVmSymbols(
213 name, startAddr, endAddr, function(fName, fStart, fEnd) {
214 self.profile_.addStaticCode(fName, fStart, fEnd);
215 self.setCodeType(fName, 'CPP');
216 });
217 };
218
219
220 TickProcessor.prototype.processCodeCreation = function(
221 type, start, size, name) {
222 var entry = this.profile_.addCode(type, name, start, size);
223 this.setCodeType(entry.getName(), 'JS');
224 };
225
226
227 TickProcessor.prototype.processCodeMove = function(from, to) {
228 this.profile_.moveCode(from, to);
229 };
230
231
232 TickProcessor.prototype.processCodeDelete = function(start) {
233 this.profile_.deleteCode(start);
234 };
235
236
237 TickProcessor.prototype.includeTick = function(vmState) {
238 return this.stateFilter_ == null || this.stateFilter_ == vmState;
239 };
240
241
242 TickProcessor.prototype.processTick = function(pc, sp, vmState, stack) {
243 this.ticks_.total++;
244 if (vmState == TickProcessor.VmStates.GC) this.ticks_.gc++;
245 if (!this.includeTick(vmState)) {
246 this.ticks_.excluded++;
247 return;
248 }
249
250 var fullStack = [pc];
251 for (var i = 0, n = stack.length; i < n; ++i) {
252 var frame = stack[i];
253 // Leave only numbers starting with 0x. Filter possible 'overflow' string.
254 if (frame.charAt(0) == '0') {
255 fullStack.push(parseInt(frame, 16));
256 }
257 }
258 this.profile_.recordTick(fullStack);
259 };
260
261
262 TickProcessor.prototype.printStatistics = function() {
263 print('Statistical profiling result from ' + this.lastLogFileName_ +
264 ', (' + this.ticks_.total +
265 ' ticks, ' + this.ticks_.unaccounted + ' unaccounted, ' +
266 this.ticks_.excluded + ' excluded).');
267
268 if (this.ticks_.total == 0) return;
269
270 // Print the unknown ticks percentage if they are not ignored.
271 if (!this.ignoreUnknown_ && this.ticks_.unaccounted > 0) {
272 this.printHeader('Unknown');
273 this.printCounter(this.ticks_.unaccounted, this.ticks_.total);
274 }
275
276 var flatProfile = this.profile_.getFlatProfile();
277 var flatView = this.viewBuilder_.buildView(flatProfile);
278 // Sort by self time, desc, then by name, desc.
279 flatView.sort(function(rec1, rec2) {
280 return rec2.selfTime - rec1.selfTime ||
281 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
282 var totalTicks = this.ticks_.total;
283 if (this.ignoreUnknown_) {
284 totalTicks -= this.ticks_.unaccounted;
285 }
286 // Our total time contains all the ticks encountered,
287 // while profile only knows about the filtered ticks.
288 flatView.head.totalTime = totalTicks;
289
290 // Count library ticks
291 var flatViewNodes = flatView.head.children;
292 var self = this;
293 var libraryTicks = 0;
294 this.processProfile(flatViewNodes,
295 function(name) { return self.isSharedLibrary(name); },
296 function(rec) { libraryTicks += rec.selfTime; });
297 var nonLibraryTicks = totalTicks - libraryTicks;
298
299 this.printHeader('Shared libraries');
300 this.printEntries(flatViewNodes, null,
301 function(name) { return self.isSharedLibrary(name); });
302
303 this.printHeader('JavaScript');
304 this.printEntries(flatViewNodes, nonLibraryTicks,
305 function(name) { return self.isJsCode(name); });
306
307 this.printHeader('C++');
308 this.printEntries(flatViewNodes, nonLibraryTicks,
309 function(name) { return self.isCppCode(name); });
310
311 this.printHeader('GC');
312 this.printCounter(this.ticks_.gc, totalTicks);
313
314 this.printHeavyProfHeader();
315 var heavyProfile = this.profile_.getBottomUpProfile();
316 var heavyView = this.viewBuilder_.buildView(heavyProfile);
317 // To show the same percentages as in the flat profile.
318 heavyView.head.totalTime = totalTicks;
319 // Sort by total time, desc, then by name, desc.
320 heavyView.sort(function(rec1, rec2) {
321 return rec2.totalTime - rec1.totalTime ||
322 (rec2.internalFuncName < rec1.internalFuncName ? -1 : 1); });
323 this.printHeavyProfile(heavyView.head.children);
324 };
325
326
327 function padLeft(s, len) {
328 s = s.toString();
329 if (s.length < len) {
330 s = (new Array(len - s.length + 1).join(' ')) + s;
331 }
332 return s;
333 };
334
335
336 TickProcessor.prototype.printHeader = function(headerTitle) {
337 print('\n [' + headerTitle + ']:');
338 print(' ticks total nonlib name');
339 };
340
341
342 TickProcessor.prototype.printHeavyProfHeader = function() {
343 print('\n [Bottom up (heavy) profile]:');
344 print(' Note: percentage shows a share of a particular caller in the ' +
345 'total\n' +
346 ' amount of its parent calls.');
347 print(' Callers occupying less than ' +
348 TickProcessor.CALL_PROFILE_CUTOFF_PCT.toFixed(1) +
349 '% are not shown.\n');
350 print(' ticks parent name');
351 };
352
353
354 TickProcessor.prototype.printCounter = function(ticksCount, totalTicksCount) {
355 var pct = ticksCount * 100.0 / totalTicksCount;
356 print(' ' + padLeft(ticksCount, 5) + ' ' + padLeft(pct.toFixed(1), 5) + '%') ;
357 };
358
359
360 TickProcessor.prototype.processProfile = function(
361 profile, filterP, func) {
362 for (var i = 0, n = profile.length; i < n; ++i) {
363 var rec = profile[i];
364 // An empty record corresponds to a tree root.
365 if (!rec.internalFuncName || !filterP(rec.internalFuncName)) {
366 continue;
367 }
368 func(rec);
369 }
370 };
371
372
373 TickProcessor.prototype.printEntries = function(
374 profile, nonLibTicks, filterP) {
375 this.processProfile(profile, filterP, function (rec) {
376 if (rec.selfTime == 0) return;
377 var nonLibPct = nonLibTicks != null ?
378 rec.selfTime * 100.0 / nonLibTicks : 0.0;
379 print(' ' + padLeft(rec.selfTime, 5) + ' ' +
380 padLeft(rec.selfPercent.toFixed(1), 5) + '% ' +
381 padLeft(nonLibPct.toFixed(1), 5) + '% ' +
382 rec.internalFuncName);
383 });
384 };
385
386
387 TickProcessor.prototype.printHeavyProfile = function(profile, opt_indent) {
388 var self = this;
389 var indent = opt_indent || 0;
390 var indentStr = padLeft('', indent);
391 this.processProfile(profile, function() { return true; }, function (rec) {
392 // Cut off too infrequent callers.
393 if (rec.parentTotalPercent < TickProcessor.CALL_PROFILE_CUTOFF_PCT) return;
394 print(' ' + padLeft(rec.totalTime, 5) + ' ' +
395 padLeft(rec.parentTotalPercent.toFixed(1), 5) + '% ' +
396 indentStr + rec.internalFuncName);
397 // Limit backtrace depth.
398 if (indent < 10) {
399 self.printHeavyProfile(rec.children, indent + 2);
400 }
401 // Delimit top-level functions.
402 if (indent == 0) {
403 print('');
404 }
405 });
406 };
407
408
409 function CppEntriesProvider() {
410 };
411
412
413 CppEntriesProvider.prototype.parseVmSymbols = function(
414 libName, libStart, libEnd, processorFunc) {
415 var syms = this.loadSymbols(libName);
416 if (syms.length == 0) return;
417
418 var prevEntry;
419
420 function addPrevEntry(end) {
421 // Several functions can be mapped onto the same address. To avoid
422 // creating zero-sized entries, skip such duplicates.
423 if (prevEntry && prevEntry.start != end) {
424 processorFunc(prevEntry.name, prevEntry.start, end);
425 }
426 }
427
428 for (var i = 0, n = syms.length; i < n; ++i) {
429 var line = syms[i];
430 var funcInfo = this.parseLine(line);
431 if (!funcInfo) {
432 continue;
433 }
434 if (funcInfo.start < libStart && funcInfo.start < libEnd - libStart) {
435 funcInfo.start += libStart;
436 }
437 addPrevEntry(funcInfo.start);
438 prevEntry = funcInfo;
439 }
440 addPrevEntry(libEnd);
441 };
442
443
444 CppEntriesProvider.prototype.loadSymbols = function(libName) {
445 return [];
446 };
447
448
449 CppEntriesProvider.prototype.parseLine = function(line) {
450 return { name: '', start: 0 };
451 };
452
453
454 function inherits(childCtor, parentCtor) {
455 function tempCtor() {};
456 tempCtor.prototype = parentCtor.prototype;
457 childCtor.prototype = new tempCtor();
458 };
459
460
461 function UnixCppEntriesProvider() {
462 };
463 inherits(UnixCppEntriesProvider, CppEntriesProvider);
464
465
466 UnixCppEntriesProvider.FUNC_RE = /^([0-9a-fA-F]{8}) . (.*)$/;
467
468
469 UnixCppEntriesProvider.prototype.loadSymbols = function(libName) {
470 var normalSyms = os.system('nm', ['-C', '-n', libName], -1, -1);
471 var dynaSyms = os.system('nm', ['-C', '-n', '-D', libName], -1, -1);
472 var syms = (normalSyms + dynaSyms).split('\n');
473 return syms;
474 };
475
476
477 UnixCppEntriesProvider.prototype.parseLine = function(line) {
478 var fields = line.match(UnixCppEntriesProvider.FUNC_RE);
479 return fields ? { name: fields[2], start: parseInt(fields[1], 16) } : null;
480 };
481
482
483 function WindowsCppEntriesProvider() {
484 };
485 inherits(WindowsCppEntriesProvider, CppEntriesProvider);
486
487
488 WindowsCppEntriesProvider.FILENAME_RE = /^(.*)\.exe$/;
489
490
491 WindowsCppEntriesProvider.FUNC_RE =
492 /^ 0001:[0-9a-fA-F]{8}\s+([_\?@$0-9a-zA-Z]+)\s+([0-9a-fA-F]{8}).*$/;
493
494
495 WindowsCppEntriesProvider.prototype.loadSymbols = function(libName) {
496 var fileNameFields = libName.match(WindowsCppEntriesProvider.FILENAME_RE);
497 // Only try to load symbols for the .exe file.
498 if (!fileNameFields) return [];
499 var mapFileName = fileNameFields[1] + '.map';
500 return readFile(mapFileName).split('\r\n');
501 };
502
503
504 WindowsCppEntriesProvider.prototype.parseLine = function(line) {
505 var fields = line.match(WindowsCppEntriesProvider.FUNC_RE);
506 return fields ?
507 { name: this.unmangleName(fields[1]), start: parseInt(fields[2], 16) } :
508 null;
509 };
510
511
512 /**
513 * Performs very simple unmangling of C++ names.
514 *
515 * Does not handle arguments and template arguments. The mangled names have
516 * the form:
517 *
518 * ?LookupInDescriptor@JSObject@internal@v8@@...arguments info...
519 */
520 WindowsCppEntriesProvider.prototype.unmangleName = function(name) {
521 // Empty or non-mangled name.
522 if (name.length < 1 || name.charAt(0) != '?') return name;
523 var nameEndPos = name.indexOf('@@');
524 var components = name.substring(1, nameEndPos).split('@');
525 components.reverse();
526 return components.join('::');
527 };
528
529
530 function padRight(s, len) {
531 s = s.toString();
532 if (s.length < len) {
533 s = s + (new Array(len - s.length + 1).join(' '));
534 }
535 return s;
536 };
537
538
539 function processArguments(args) {
540 var result = {
541 logFileName: 'v8.log',
542 platform: 'unix',
543 stateFilter: null,
544 ignoreUnknown: false,
545 separateIc: false
546 };
547 var argsDispatch = {
548 '-j': ['stateFilter', TickProcessor.VmStates.JS,
549 'Show only ticks from JS VM state'],
550 '-g': ['stateFilter', TickProcessor.VmStates.GC,
551 'Show only ticks from GC VM state'],
552 '-c': ['stateFilter', TickProcessor.VmStates.COMPILER,
553 'Show only ticks from COMPILER VM state'],
554 '-o': ['stateFilter', TickProcessor.VmStates.OTHER,
555 'Show only ticks from OTHER VM state'],
556 '-e': ['stateFilter', TickProcessor.VmStates.EXTERNAL,
557 'Show only ticks from EXTERNAL VM state'],
558 '--ignore-unknown': ['ignoreUnknown', true,
559 'Exclude ticks of unknown code entries from processing'],
560 '--separate-ic': ['separateIc', true,
561 'Separate IC entries'],
562 '--unix': ['platform', 'unix',
563 'Specify that we are running on *nix platform'],
564 '--windows': ['platform', 'windows',
565 'Specify that we are running on Windows platform']
566 };
567 argsDispatch['--js'] = argsDispatch['-j'];
568 argsDispatch['--gc'] = argsDispatch['-g'];
569 argsDispatch['--compiler'] = argsDispatch['-c'];
570 argsDispatch['--other'] = argsDispatch['-o'];
571 argsDispatch['--external'] = argsDispatch['-e'];
572
573 function printUsageAndExit() {
574 print('Cmdline args: [options] [log-file-name]\n' +
575 'Default log file name is "v8.log".\n');
576 print('Options:');
577 for (var arg in argsDispatch) {
578 var synonims = [arg];
579 var dispatch = argsDispatch[arg];
580 for (var synArg in argsDispatch) {
581 if (arg !== synArg && dispatch === argsDispatch[synArg]) {
582 synonims.push(synArg);
583 delete argsDispatch[synArg];
584 }
585 }
586 print(' ' + padRight(synonims.join(', '), 20) + dispatch[2]);
587 }
588 quit(2);
589 }
590
591 while (args.length) {
592 var arg = args[0];
593 if (arg.charAt(0) != '-') {
594 break;
595 }
596 args.shift();
597 if (arg in argsDispatch) {
598 var dispatch = argsDispatch[arg];
599 result[dispatch[0]] = dispatch[1];
600 } else {
601 printUsageAndExit();
602 }
603 }
604
605 if (args.length >= 1) {
606 result.logFileName = args.shift();
607 }
608 return result;
609 };
610
611
612 var params = processArguments(arguments);
613 var tickProcessor = new TickProcessor(
614 params.platform == 'unix' ? new UnixCppEntriesProvider() :
615 new WindowsCppEntriesProvider(),
616 params.separateIc,
617 params.ignoreUnknown,
618 params.stateFilter);
619 tickProcessor.processLogFile(params.logFileName);
620 tickProcessor.printStatistics();
OLDNEW
« no previous file with comments | « tools/profileview.js ('k') | tools/tickprocessor.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698