blob: 614c63557697b5a73858d38d0af8026e4e7b78ea [file] [log] [blame]
ager@chromium.org65dad4b2009-04-23 08:48:43 +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/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000059 * 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/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +000072 * Called whenever the specified operation has failed finding a function
73 * containing the specified address. Should be overriden by subclasses.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000074 * See the devtools.profiler.Profile.Operation enum for the list of
75 * possible operations.
ager@chromium.org65dad4b2009-04-23 08:48:43 +000076 *
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000077 * @param {number} operation Operation.
ager@chromium.org65dad4b2009-04-23 08:48:43 +000078 * @param {number} addr Address of the unknown code.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000079 * @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.
ager@chromium.org65dad4b2009-04-23 08:48:43 +000082 */
83devtools.profiler.Profile.prototype.handleUnknownCode = function(
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000084 operation, addr, opt_stackPos) {
ager@chromium.org65dad4b2009-04-23 08:48:43 +000085};
86
87
88/**
89 * Registers static (library) code entry.
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.addStaticCode = function(
96 name, startAddr, endAddr) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000097 var entry = new devtools.profiler.CodeMap.CodeEntry(
98 endAddr - startAddr, name);
99 this.codeMap_.addStaticCode(startAddr, entry);
100 return entry;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000101};
102
103
104/**
105 * Registers dynamic (JIT-compiled) code entry.
106 *
107 * @param {string} type Code entry type.
108 * @param {string} name Code entry name.
109 * @param {number} start Starting address.
110 * @param {number} size Code entry size.
111 */
112devtools.profiler.Profile.prototype.addCode = function(
113 type, name, start, size) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000114 var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name);
115 this.codeMap_.addCode(start, entry);
116 return entry;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000117};
118
119
120/**
121 * Reports about moving of a dynamic code entry.
122 *
123 * @param {number} from Current code entry address.
124 * @param {number} to New code entry address.
125 */
126devtools.profiler.Profile.prototype.moveCode = function(from, to) {
127 try {
128 this.codeMap_.moveCode(from, to);
129 } catch (e) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000130 this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000131 }
132};
133
134
135/**
136 * Reports about deletion of a dynamic code entry.
137 *
138 * @param {number} start Starting address.
139 */
140devtools.profiler.Profile.prototype.deleteCode = function(start) {
141 try {
142 this.codeMap_.deleteCode(start);
143 } catch (e) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000144 this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000145 }
146};
147
148
149/**
150 * Records a tick event. Stack must contain a sequence of
151 * addresses starting with the program counter value.
152 *
153 * @param {Array<number>} stack Stack sample.
154 */
155devtools.profiler.Profile.prototype.recordTick = function(stack) {
156 var processedStack = this.resolveAndFilterFuncs_(stack);
157 this.bottomUpTree_.addPath(processedStack);
158 processedStack.reverse();
159 this.topDownTree_.addPath(processedStack);
160};
161
162
163/**
164 * Translates addresses into function names and filters unneeded
165 * functions.
166 *
167 * @param {Array<number>} stack Stack sample.
168 */
169devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
170 var result = [];
171 for (var i = 0; i < stack.length; ++i) {
172 var entry = this.codeMap_.findEntry(stack[i]);
173 if (entry) {
174 var name = entry.getName();
175 if (!this.skipThisFunction(name)) {
176 result.push(name);
177 }
178 } else {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000179 this.handleUnknownCode(
180 devtools.profiler.Profile.Operation.TICK, stack[i], i);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000181 }
182 }
183 return result;
184};
185
186
187/**
ager@chromium.org5ec48922009-05-05 07:25:34 +0000188 * Performs a BF traversal of the top down call graph.
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000189 *
190 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
191 */
192devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
193 this.topDownTree_.traverse(f);
194};
195
196
197/**
ager@chromium.org5ec48922009-05-05 07:25:34 +0000198 * Performs a BF traversal of the bottom up call graph.
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000199 *
200 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
201 */
202devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
203 this.bottomUpTree_.traverse(f);
204};
205
206
207/**
ager@chromium.org5ec48922009-05-05 07:25:34 +0000208 * Calculates a top down profile for a node with the specified label.
209 * If no name specified, returns the whole top down calls tree.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000210 *
ager@chromium.org5ec48922009-05-05 07:25:34 +0000211 * @param {string} opt_label Node label.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000212 */
ager@chromium.org5ec48922009-05-05 07:25:34 +0000213devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) {
214 return this.getTreeProfile_(this.topDownTree_, opt_label);
215};
216
217
218/**
219 * Calculates a bottom up profile for a node with the specified label.
220 * If no name specified, returns the whole bottom up calls tree.
221 *
222 * @param {string} opt_label Node label.
223 */
224devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) {
225 return this.getTreeProfile_(this.bottomUpTree_, opt_label);
226};
227
228
229/**
230 * Helper function for calculating a tree profile.
231 *
232 * @param {devtools.profiler.Profile.CallTree} tree Call tree.
233 * @param {string} opt_label Node label.
234 */
235devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
236 if (!opt_label) {
237 tree.computeTotalWeights();
238 return tree;
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000239 } else {
ager@chromium.org5ec48922009-05-05 07:25:34 +0000240 var subTree = tree.cloneSubtree(opt_label);
241 subTree.computeTotalWeights();
242 return subTree;
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000243 }
244};
245
246
247/**
ager@chromium.org5ec48922009-05-05 07:25:34 +0000248 * Calculates a flat profile of callees starting from a node with
249 * the specified label. If no name specified, starts from the root.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000250 *
ager@chromium.org5ec48922009-05-05 07:25:34 +0000251 * @param {string} opt_label Starting node label.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000252 */
ager@chromium.org5ec48922009-05-05 07:25:34 +0000253devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000254 var counters = new devtools.profiler.CallTree();
ager@chromium.org5ec48922009-05-05 07:25:34 +0000255 var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000256 var precs = {};
ager@chromium.org5ec48922009-05-05 07:25:34 +0000257 precs[rootLabel] = 0;
258 var root = counters.findOrAddChild(rootLabel);
259
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000260 this.topDownTree_.computeTotalWeights();
261 this.topDownTree_.traverseInDepth(
262 function onEnter(node) {
263 if (!(node.label in precs)) {
264 precs[node.label] = 0;
265 }
ager@chromium.org5ec48922009-05-05 07:25:34 +0000266 var nodeLabelIsRootLabel = node.label == rootLabel;
267 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
268 if (precs[rootLabel] == 0) {
269 root.selfWeight += node.selfWeight;
270 root.totalWeight += node.totalWeight;
271 } else {
272 var rec = root.findOrAddChild(node.label);
273 rec.selfWeight += node.selfWeight;
274 if (nodeLabelIsRootLabel || precs[node.label] == 0) {
275 rec.totalWeight += node.totalWeight;
276 }
277 }
278 precs[node.label]++;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000279 }
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000280 },
281 function onExit(node) {
ager@chromium.org5ec48922009-05-05 07:25:34 +0000282 if (node.label == rootLabel || precs[rootLabel] > 0) {
283 precs[node.label]--;
284 }
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000285 },
ager@chromium.org5ec48922009-05-05 07:25:34 +0000286 null);
287
288 if (!opt_label) {
289 // If we have created a flat profile for the whole program, we don't
290 // need an explicit root in it. Thus, replace the counters tree
291 // root with the node corresponding to the whole program.
292 counters.root_ = root;
293 } else {
294 // Propagate weights so percents can be calculated correctly.
295 counters.getRoot().selfWeight = root.selfWeight;
296 counters.getRoot().totalWeight = root.totalWeight;
297 }
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000298 return counters;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000299};
300
301
302/**
303 * Creates a dynamic code entry.
304 *
305 * @param {number} size Code size.
306 * @param {string} type Code type.
307 * @param {string} name Function name.
308 * @constructor
309 */
310devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) {
311 devtools.profiler.CodeMap.CodeEntry.call(this, size, name);
312 this.type = type;
313};
314
315
316/**
317 * Returns node name.
318 */
319devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
320 var name = this.name;
321 if (name.length == 0) {
322 name = '<anonymous>';
323 } else if (name.charAt(0) == ' ') {
324 // An anonymous function with location: " aaa.js:10".
325 name = '<anonymous>' + name;
326 }
327 return this.type + ': ' + name;
328};
329
330
331/**
332 * Constructs a call graph.
333 *
334 * @constructor
335 */
336devtools.profiler.CallTree = function() {
ager@chromium.org5ec48922009-05-05 07:25:34 +0000337 this.root_ = new devtools.profiler.CallTree.Node(
338 devtools.profiler.CallTree.ROOT_NODE_LABEL);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000339};
340
341
342/**
ager@chromium.org5ec48922009-05-05 07:25:34 +0000343 * The label of the root node.
344 */
345devtools.profiler.CallTree.ROOT_NODE_LABEL = '';
346
347
348/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000349 * @private
350 */
351devtools.profiler.CallTree.prototype.totalsComputed_ = false;
352
353
354/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000355 * Returns the tree root.
356 */
357devtools.profiler.CallTree.prototype.getRoot = function() {
358 return this.root_;
359};
360
361
362/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000363 * Adds the specified call path, constructing nodes as necessary.
364 *
365 * @param {Array<string>} path Call path.
366 */
367devtools.profiler.CallTree.prototype.addPath = function(path) {
368 if (path.length == 0) {
369 return;
370 }
371 var curr = this.root_;
372 for (var i = 0; i < path.length; ++i) {
373 curr = curr.findOrAddChild(path[i]);
374 }
375 curr.selfWeight++;
376 this.totalsComputed_ = false;
377};
378
379
380/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000381 * Finds an immediate child of the specified parent with the specified
382 * label, creates a child node if necessary. If a parent node isn't
383 * specified, uses tree root.
384 *
385 * @param {string} label Child node label.
386 */
ager@chromium.org5ec48922009-05-05 07:25:34 +0000387devtools.profiler.CallTree.prototype.findOrAddChild = function(label) {
388 return this.root_.findOrAddChild(label);
389};
390
391
392/**
393 * Creates a subtree by cloning and merging all subtrees rooted at nodes
394 * with a given label. E.g. cloning the following call tree on label 'A'
395 * will give the following result:
396 *
397 * <A>--<B> <B>
398 * / /
399 * <root> == clone on 'A' ==> <root>--<A>
400 * \ \
401 * <C>--<A>--<D> <D>
402 *
403 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
404 * source call tree.
405 *
406 * @param {string} label The label of the new root node.
407 */
408devtools.profiler.CallTree.prototype.cloneSubtree = function(label) {
409 var subTree = new devtools.profiler.CallTree();
410 this.traverse(function(node, parent) {
411 if (!parent && node.label != label) {
412 return null;
413 }
414 var child = (parent ? parent : subTree).findOrAddChild(node.label);
415 child.selfWeight += node.selfWeight;
416 return child;
417 });
418 return subTree;
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000419};
420
421
422/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000423 * Computes total weights in the call graph.
424 */
425devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
426 if (this.totalsComputed_) {
427 return;
428 }
429 this.root_.computeTotalWeight();
430 this.totalsComputed_ = true;
431};
432
433
434/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000435 * Traverses the call graph in preorder. This function can be used for
436 * building optionally modified tree clones. This is the boilerplate code
437 * for this scenario:
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000438 *
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000439 * callTree.traverse(function(node, parentClone) {
440 * var nodeClone = cloneNode(node);
441 * if (parentClone)
442 * parentClone.addChild(nodeClone);
443 * return nodeClone;
444 * });
445 *
446 * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
447 * The second parameter is the result of calling 'f' on the parent node.
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000448 */
ager@chromium.org5ec48922009-05-05 07:25:34 +0000449devtools.profiler.CallTree.prototype.traverse = function(f) {
450 var pairsToProcess = new ConsArray();
451 pairsToProcess.concat([{node: this.root_, param: null}]);
452 while (!pairsToProcess.atEnd()) {
453 var pair = pairsToProcess.next();
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000454 var node = pair.node;
455 var newParam = f(node, pair.param);
ager@chromium.org5ec48922009-05-05 07:25:34 +0000456 var morePairsToProcess = [];
457 node.forEachChild(function (child) {
458 morePairsToProcess.push({node: child, param: newParam}); });
459 pairsToProcess.concat(morePairsToProcess);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000460 }
461};
462
463
464/**
465 * Performs an indepth call graph traversal.
466 *
467 * @param {function(devtools.profiler.CallTree.Node)} enter A function called
468 * prior to visiting node's children.
469 * @param {function(devtools.profiler.CallTree.Node)} exit A function called
470 * after visiting node's children.
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000471 */
ager@chromium.org5ec48922009-05-05 07:25:34 +0000472devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) {
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000473 function traverse(node) {
474 enter(node);
475 node.forEachChild(traverse);
476 exit(node);
477 }
ager@chromium.org5ec48922009-05-05 07:25:34 +0000478 traverse(this.root_);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000479};
480
481
482/**
483 * Constructs a call graph node.
484 *
485 * @param {string} label Node label.
486 * @param {devtools.profiler.CallTree.Node} opt_parent Node parent.
487 */
488devtools.profiler.CallTree.Node = function(label, opt_parent) {
489 this.label = label;
490 this.parent = opt_parent;
491 this.children = {};
492};
493
494
495/**
496 * Node self weight (how many times this node was the last node in
497 * a call path).
498 * @type {number}
499 */
500devtools.profiler.CallTree.Node.prototype.selfWeight = 0;
501
502
503/**
504 * Node total weight (includes weights of all children).
505 * @type {number}
506 */
507devtools.profiler.CallTree.Node.prototype.totalWeight = 0;
508
509
510/**
511 * Adds a child node.
512 *
513 * @param {string} label Child node label.
514 */
515devtools.profiler.CallTree.Node.prototype.addChild = function(label) {
516 var child = new devtools.profiler.CallTree.Node(label, this);
517 this.children[label] = child;
518 return child;
519};
520
521
522/**
523 * Computes node's total weight.
524 */
525devtools.profiler.CallTree.Node.prototype.computeTotalWeight =
526 function() {
527 var totalWeight = this.selfWeight;
528 this.forEachChild(function(child) {
529 totalWeight += child.computeTotalWeight(); });
530 return this.totalWeight = totalWeight;
531};
532
533
534/**
535 * Returns all node's children as an array.
536 */
537devtools.profiler.CallTree.Node.prototype.exportChildren = function() {
538 var result = [];
539 this.forEachChild(function (node) { result.push(node); });
540 return result;
541};
542
543
544/**
545 * Finds an immediate child with the specified label.
546 *
547 * @param {string} label Child node label.
548 */
549devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
550 return this.children[label] || null;
551};
552
553
554/**
555 * Finds an immediate child with the specified label, creates a child
556 * node if necessary.
557 *
558 * @param {string} label Child node label.
559 */
ager@chromium.org5ec48922009-05-05 07:25:34 +0000560devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) {
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000561 return this.findChild(label) || this.addChild(label);
562};
563
564
565/**
566 * Calls the specified function for every child.
567 *
568 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
569 */
570devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) {
571 for (var c in this.children) {
572 f(this.children[c]);
573 }
574};
575
576
577/**
578 * Walks up from the current node up to the call tree root.
579 *
580 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
581 */
582devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) {
583 for (var curr = this; curr != null; curr = curr.parent) {
584 f(curr);
585 }
586};
587
588
589/**
590 * Tries to find a node with the specified path.
591 *
592 * @param {Array<string>} labels The path.
593 * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function.
594 */
595devtools.profiler.CallTree.Node.prototype.descendToChild = function(
596 labels, opt_f) {
597 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
598 var child = curr.findChild(labels[pos]);
599 if (opt_f) {
600 opt_f(child, pos);
601 }
602 curr = child;
603 }
604 return curr;
605};