blob: b2de6490e0736d9fdbf1b7a96ebcc5fbf81eb876 [file] [log] [blame]
Steve Blocka7e24c12009-10-30 11:49:00 +00001// 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// Initlialize namespaces
30var devtools = devtools || {};
31devtools.profiler = devtools.profiler || {};
32
33
34/**
35 * Creates a profile object for processing profiling-related events
36 * and calculating function execution times.
37 *
38 * @constructor
39 */
40devtools.profiler.Profile = function() {
41 this.codeMap_ = new devtools.profiler.CodeMap();
42 this.topDownTree_ = new devtools.profiler.CallTree();
43 this.bottomUpTree_ = new devtools.profiler.CallTree();
44};
45
Leon Clarked91b9f72010-01-27 17:25:45 +000046/**
47 * Version of profiler log.
48 */
49devtools.profiler.Profile.VERSION = 2;
50
Steve Blocka7e24c12009-10-30 11:49:00 +000051
52/**
53 * Returns whether a function with the specified name must be skipped.
54 * Should be overriden by subclasses.
55 *
56 * @param {string} name Function name.
57 */
58devtools.profiler.Profile.prototype.skipThisFunction = function(name) {
59 return false;
60};
61
62
63/**
64 * Enum for profiler operations that involve looking up existing
65 * code entries.
66 *
67 * @enum {number}
68 */
69devtools.profiler.Profile.Operation = {
70 MOVE: 0,
71 DELETE: 1,
72 TICK: 2
73};
74
75
76/**
77 * Called whenever the specified operation has failed finding a function
78 * containing the specified address. Should be overriden by subclasses.
79 * See the devtools.profiler.Profile.Operation enum for the list of
80 * possible operations.
81 *
82 * @param {number} operation Operation.
83 * @param {number} addr Address of the unknown code.
84 * @param {number} opt_stackPos If an unknown address is encountered
85 * during stack strace processing, specifies a position of the frame
86 * containing the address.
87 */
88devtools.profiler.Profile.prototype.handleUnknownCode = function(
89 operation, addr, opt_stackPos) {
90};
91
92
93/**
94 * Registers a library.
95 *
96 * @param {string} name Code entry name.
97 * @param {number} startAddr Starting address.
98 * @param {number} endAddr Ending address.
99 */
100devtools.profiler.Profile.prototype.addLibrary = function(
101 name, startAddr, endAddr) {
102 var entry = new devtools.profiler.CodeMap.CodeEntry(
103 endAddr - startAddr, name);
104 this.codeMap_.addLibrary(startAddr, entry);
105 return entry;
106};
107
108
109/**
110 * Registers statically compiled code entry.
111 *
112 * @param {string} name Code entry name.
113 * @param {number} startAddr Starting address.
114 * @param {number} endAddr Ending address.
115 */
116devtools.profiler.Profile.prototype.addStaticCode = function(
117 name, startAddr, endAddr) {
118 var entry = new devtools.profiler.CodeMap.CodeEntry(
119 endAddr - startAddr, name);
120 this.codeMap_.addStaticCode(startAddr, entry);
121 return entry;
122};
123
124
125/**
126 * Registers dynamic (JIT-compiled) code entry.
127 *
128 * @param {string} type Code entry type.
129 * @param {string} name Code entry name.
130 * @param {number} start Starting address.
131 * @param {number} size Code entry size.
132 */
133devtools.profiler.Profile.prototype.addCode = function(
134 type, name, start, size) {
135 var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name);
136 this.codeMap_.addCode(start, entry);
137 return entry;
138};
139
140
141/**
Leon Clarked91b9f72010-01-27 17:25:45 +0000142 * Creates an alias entry for a code entry.
143 *
144 * @param {number} aliasAddr Alias address.
145 * @param {number} addr Code entry address.
146 */
147devtools.profiler.Profile.prototype.addCodeAlias = function(
148 aliasAddr, addr) {
149 var entry = this.codeMap_.findDynamicEntryByStartAddress(addr);
150 if (entry) {
151 this.codeMap_.addCode(aliasAddr, entry);
152 }
153};
154
155
156/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000157 * Reports about moving of a dynamic code entry.
158 *
159 * @param {number} from Current code entry address.
160 * @param {number} to New code entry address.
161 */
162devtools.profiler.Profile.prototype.moveCode = function(from, to) {
163 try {
164 this.codeMap_.moveCode(from, to);
165 } catch (e) {
166 this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from);
167 }
168};
169
170
171/**
172 * Reports about deletion of a dynamic code entry.
173 *
174 * @param {number} start Starting address.
175 */
176devtools.profiler.Profile.prototype.deleteCode = function(start) {
177 try {
178 this.codeMap_.deleteCode(start);
179 } catch (e) {
180 this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start);
181 }
182};
183
184
185/**
Leon Clarked91b9f72010-01-27 17:25:45 +0000186 * Reports about moving of a dynamic code entry.
187 *
188 * @param {number} from Current code entry address.
189 * @param {number} to New code entry address.
190 */
191devtools.profiler.Profile.prototype.safeMoveDynamicCode = function(from, to) {
192 if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
193 this.codeMap_.moveCode(from, to);
194 }
195};
196
197
198/**
199 * Reports about deletion of a dynamic code entry.
200 *
201 * @param {number} start Starting address.
202 */
203devtools.profiler.Profile.prototype.safeDeleteDynamicCode = function(start) {
204 if (this.codeMap_.findDynamicEntryByStartAddress(start)) {
205 this.codeMap_.deleteCode(start);
206 }
207};
208
209
210/**
Leon Clarkee46be812010-01-19 14:06:41 +0000211 * Retrieves a code entry by an address.
212 *
213 * @param {number} addr Entry address.
214 */
215devtools.profiler.Profile.prototype.findEntry = function(addr) {
216 return this.codeMap_.findEntry(addr);
217};
218
219
220/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000221 * Records a tick event. Stack must contain a sequence of
222 * addresses starting with the program counter value.
223 *
224 * @param {Array<number>} stack Stack sample.
225 */
226devtools.profiler.Profile.prototype.recordTick = function(stack) {
227 var processedStack = this.resolveAndFilterFuncs_(stack);
228 this.bottomUpTree_.addPath(processedStack);
229 processedStack.reverse();
230 this.topDownTree_.addPath(processedStack);
231};
232
233
234/**
235 * Translates addresses into function names and filters unneeded
236 * functions.
237 *
238 * @param {Array<number>} stack Stack sample.
239 */
240devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
241 var result = [];
242 for (var i = 0; i < stack.length; ++i) {
243 var entry = this.codeMap_.findEntry(stack[i]);
244 if (entry) {
245 var name = entry.getName();
246 if (!this.skipThisFunction(name)) {
247 result.push(name);
248 }
249 } else {
250 this.handleUnknownCode(
251 devtools.profiler.Profile.Operation.TICK, stack[i], i);
252 }
253 }
254 return result;
255};
256
257
258/**
259 * Performs a BF traversal of the top down call graph.
260 *
261 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
262 */
263devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
264 this.topDownTree_.traverse(f);
265};
266
267
268/**
269 * Performs a BF traversal of the bottom up call graph.
270 *
271 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
272 */
273devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
274 this.bottomUpTree_.traverse(f);
275};
276
277
278/**
279 * Calculates a top down profile for a node with the specified label.
280 * If no name specified, returns the whole top down calls tree.
281 *
282 * @param {string} opt_label Node label.
283 */
284devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) {
285 return this.getTreeProfile_(this.topDownTree_, opt_label);
286};
287
288
289/**
290 * Calculates a bottom up profile for a node with the specified label.
291 * If no name specified, returns the whole bottom up calls tree.
292 *
293 * @param {string} opt_label Node label.
294 */
295devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) {
296 return this.getTreeProfile_(this.bottomUpTree_, opt_label);
297};
298
299
300/**
301 * Helper function for calculating a tree profile.
302 *
303 * @param {devtools.profiler.Profile.CallTree} tree Call tree.
304 * @param {string} opt_label Node label.
305 */
306devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
307 if (!opt_label) {
308 tree.computeTotalWeights();
309 return tree;
310 } else {
311 var subTree = tree.cloneSubtree(opt_label);
312 subTree.computeTotalWeights();
313 return subTree;
314 }
315};
316
317
318/**
319 * Calculates a flat profile of callees starting from a node with
320 * the specified label. If no name specified, starts from the root.
321 *
322 * @param {string} opt_label Starting node label.
323 */
324devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) {
325 var counters = new devtools.profiler.CallTree();
326 var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL;
327 var precs = {};
328 precs[rootLabel] = 0;
329 var root = counters.findOrAddChild(rootLabel);
330
331 this.topDownTree_.computeTotalWeights();
332 this.topDownTree_.traverseInDepth(
333 function onEnter(node) {
334 if (!(node.label in precs)) {
335 precs[node.label] = 0;
336 }
337 var nodeLabelIsRootLabel = node.label == rootLabel;
338 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
339 if (precs[rootLabel] == 0) {
340 root.selfWeight += node.selfWeight;
341 root.totalWeight += node.totalWeight;
342 } else {
343 var rec = root.findOrAddChild(node.label);
344 rec.selfWeight += node.selfWeight;
345 if (nodeLabelIsRootLabel || precs[node.label] == 0) {
346 rec.totalWeight += node.totalWeight;
347 }
348 }
349 precs[node.label]++;
350 }
351 },
352 function onExit(node) {
353 if (node.label == rootLabel || precs[rootLabel] > 0) {
354 precs[node.label]--;
355 }
356 },
357 null);
358
359 if (!opt_label) {
360 // If we have created a flat profile for the whole program, we don't
361 // need an explicit root in it. Thus, replace the counters tree
362 // root with the node corresponding to the whole program.
363 counters.root_ = root;
364 } else {
365 // Propagate weights so percents can be calculated correctly.
366 counters.getRoot().selfWeight = root.selfWeight;
367 counters.getRoot().totalWeight = root.totalWeight;
368 }
369 return counters;
370};
371
372
373/**
374 * Creates a dynamic code entry.
375 *
376 * @param {number} size Code size.
377 * @param {string} type Code type.
378 * @param {string} name Function name.
379 * @constructor
380 */
381devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) {
382 devtools.profiler.CodeMap.CodeEntry.call(this, size, name);
383 this.type = type;
384};
385
386
387/**
388 * Returns node name.
389 */
390devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
391 var name = this.name;
392 if (name.length == 0) {
393 name = '<anonymous>';
394 } else if (name.charAt(0) == ' ') {
395 // An anonymous function with location: " aaa.js:10".
396 name = '<anonymous>' + name;
397 }
398 return this.type + ': ' + name;
399};
400
401
402/**
Leon Clarkee46be812010-01-19 14:06:41 +0000403 * Returns raw node name (without type decoration).
404 */
405devtools.profiler.Profile.DynamicCodeEntry.prototype.getRawName = function() {
406 return this.name;
407};
408
409
Leon Clarked91b9f72010-01-27 17:25:45 +0000410devtools.profiler.Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
411 return this.type == "Function" ||
412 this.type == "LazyCompile" ||
413 this.type == "Script";
414};
415
416
Leon Clarkee46be812010-01-19 14:06:41 +0000417/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000418 * Constructs a call graph.
419 *
420 * @constructor
421 */
422devtools.profiler.CallTree = function() {
423 this.root_ = new devtools.profiler.CallTree.Node(
424 devtools.profiler.CallTree.ROOT_NODE_LABEL);
425};
426
427
428/**
429 * The label of the root node.
430 */
431devtools.profiler.CallTree.ROOT_NODE_LABEL = '';
432
433
434/**
435 * @private
436 */
437devtools.profiler.CallTree.prototype.totalsComputed_ = false;
438
439
440/**
441 * Returns the tree root.
442 */
443devtools.profiler.CallTree.prototype.getRoot = function() {
444 return this.root_;
445};
446
447
448/**
449 * Adds the specified call path, constructing nodes as necessary.
450 *
451 * @param {Array<string>} path Call path.
452 */
453devtools.profiler.CallTree.prototype.addPath = function(path) {
454 if (path.length == 0) {
455 return;
456 }
457 var curr = this.root_;
458 for (var i = 0; i < path.length; ++i) {
459 curr = curr.findOrAddChild(path[i]);
460 }
461 curr.selfWeight++;
462 this.totalsComputed_ = false;
463};
464
465
466/**
467 * Finds an immediate child of the specified parent with the specified
468 * label, creates a child node if necessary. If a parent node isn't
469 * specified, uses tree root.
470 *
471 * @param {string} label Child node label.
472 */
473devtools.profiler.CallTree.prototype.findOrAddChild = function(label) {
474 return this.root_.findOrAddChild(label);
475};
476
477
478/**
479 * Creates a subtree by cloning and merging all subtrees rooted at nodes
480 * with a given label. E.g. cloning the following call tree on label 'A'
481 * will give the following result:
482 *
483 * <A>--<B> <B>
484 * / /
485 * <root> == clone on 'A' ==> <root>--<A>
486 * \ \
487 * <C>--<A>--<D> <D>
488 *
489 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
490 * source call tree.
491 *
492 * @param {string} label The label of the new root node.
493 */
494devtools.profiler.CallTree.prototype.cloneSubtree = function(label) {
495 var subTree = new devtools.profiler.CallTree();
496 this.traverse(function(node, parent) {
497 if (!parent && node.label != label) {
498 return null;
499 }
500 var child = (parent ? parent : subTree).findOrAddChild(node.label);
501 child.selfWeight += node.selfWeight;
502 return child;
503 });
504 return subTree;
505};
506
507
508/**
509 * Computes total weights in the call graph.
510 */
511devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
512 if (this.totalsComputed_) {
513 return;
514 }
515 this.root_.computeTotalWeight();
516 this.totalsComputed_ = true;
517};
518
519
520/**
521 * Traverses the call graph in preorder. This function can be used for
522 * building optionally modified tree clones. This is the boilerplate code
523 * for this scenario:
524 *
525 * callTree.traverse(function(node, parentClone) {
526 * var nodeClone = cloneNode(node);
527 * if (parentClone)
528 * parentClone.addChild(nodeClone);
529 * return nodeClone;
530 * });
531 *
532 * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
533 * The second parameter is the result of calling 'f' on the parent node.
534 */
535devtools.profiler.CallTree.prototype.traverse = function(f) {
536 var pairsToProcess = new ConsArray();
537 pairsToProcess.concat([{node: this.root_, param: null}]);
538 while (!pairsToProcess.atEnd()) {
539 var pair = pairsToProcess.next();
540 var node = pair.node;
541 var newParam = f(node, pair.param);
542 var morePairsToProcess = [];
543 node.forEachChild(function (child) {
544 morePairsToProcess.push({node: child, param: newParam}); });
545 pairsToProcess.concat(morePairsToProcess);
546 }
547};
548
549
550/**
551 * Performs an indepth call graph traversal.
552 *
553 * @param {function(devtools.profiler.CallTree.Node)} enter A function called
554 * prior to visiting node's children.
555 * @param {function(devtools.profiler.CallTree.Node)} exit A function called
556 * after visiting node's children.
557 */
558devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) {
559 function traverse(node) {
560 enter(node);
561 node.forEachChild(traverse);
562 exit(node);
563 }
564 traverse(this.root_);
565};
566
567
568/**
569 * Constructs a call graph node.
570 *
571 * @param {string} label Node label.
572 * @param {devtools.profiler.CallTree.Node} opt_parent Node parent.
573 */
574devtools.profiler.CallTree.Node = function(label, opt_parent) {
575 this.label = label;
576 this.parent = opt_parent;
577 this.children = {};
578};
579
580
581/**
582 * Node self weight (how many times this node was the last node in
583 * a call path).
584 * @type {number}
585 */
586devtools.profiler.CallTree.Node.prototype.selfWeight = 0;
587
588
589/**
590 * Node total weight (includes weights of all children).
591 * @type {number}
592 */
593devtools.profiler.CallTree.Node.prototype.totalWeight = 0;
594
595
596/**
597 * Adds a child node.
598 *
599 * @param {string} label Child node label.
600 */
601devtools.profiler.CallTree.Node.prototype.addChild = function(label) {
602 var child = new devtools.profiler.CallTree.Node(label, this);
603 this.children[label] = child;
604 return child;
605};
606
607
608/**
609 * Computes node's total weight.
610 */
611devtools.profiler.CallTree.Node.prototype.computeTotalWeight =
612 function() {
613 var totalWeight = this.selfWeight;
614 this.forEachChild(function(child) {
615 totalWeight += child.computeTotalWeight(); });
616 return this.totalWeight = totalWeight;
617};
618
619
620/**
621 * Returns all node's children as an array.
622 */
623devtools.profiler.CallTree.Node.prototype.exportChildren = function() {
624 var result = [];
625 this.forEachChild(function (node) { result.push(node); });
626 return result;
627};
628
629
630/**
631 * Finds an immediate child with the specified label.
632 *
633 * @param {string} label Child node label.
634 */
635devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
636 return this.children[label] || null;
637};
638
639
640/**
641 * Finds an immediate child with the specified label, creates a child
642 * node if necessary.
643 *
644 * @param {string} label Child node label.
645 */
646devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) {
647 return this.findChild(label) || this.addChild(label);
648};
649
650
651/**
652 * Calls the specified function for every child.
653 *
654 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
655 */
656devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) {
657 for (var c in this.children) {
658 f(this.children[c]);
659 }
660};
661
662
663/**
664 * Walks up from the current node up to the call tree root.
665 *
666 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
667 */
668devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) {
669 for (var curr = this; curr != null; curr = curr.parent) {
670 f(curr);
671 }
672};
673
674
675/**
676 * Tries to find a node with the specified path.
677 *
678 * @param {Array<string>} labels The path.
679 * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function.
680 */
681devtools.profiler.CallTree.Node.prototype.descendToChild = function(
682 labels, opt_f) {
683 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
684 var child = curr.findChild(labels[pos]);
685 if (opt_f) {
686 opt_f(child, pos);
687 }
688 curr = child;
689 }
690 return curr;
691};