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