blob: 10a07f8246a5b0da30b006d36be734f3ee40ffa0 [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
Steve Blocka7e24c12009-10-30 11:49:00 +000029/**
30 * Creates a profile object for processing profiling-related events
31 * and calculating function execution times.
32 *
33 * @constructor
34 */
Steve Block1e0659c2011-05-24 12:43:12 +010035function Profile() {
36 this.codeMap_ = new CodeMap();
37 this.topDownTree_ = new CallTree();
38 this.bottomUpTree_ = new CallTree();
Steve Blocka7e24c12009-10-30 11:49:00 +000039};
40
41
42/**
43 * Returns whether a function with the specified name must be skipped.
44 * Should be overriden by subclasses.
45 *
46 * @param {string} name Function name.
47 */
Steve Block1e0659c2011-05-24 12:43:12 +010048Profile.prototype.skipThisFunction = function(name) {
Steve Blocka7e24c12009-10-30 11:49:00 +000049 return false;
50};
51
52
53/**
54 * Enum for profiler operations that involve looking up existing
55 * code entries.
56 *
57 * @enum {number}
58 */
Steve Block1e0659c2011-05-24 12:43:12 +010059Profile.Operation = {
Steve Blocka7e24c12009-10-30 11:49:00 +000060 MOVE: 0,
61 DELETE: 1,
62 TICK: 2
63};
64
65
66/**
Ben Murdoche0cee9b2011-05-25 10:26:03 +010067 * Enum for code state regarding its dynamic optimization.
68 *
69 * @enum {number}
70 */
71Profile.CodeState = {
72 COMPILED: 0,
73 OPTIMIZABLE: 1,
74 OPTIMIZED: 2
75};
76
77
78/**
Steve Blocka7e24c12009-10-30 11:49:00 +000079 * Called whenever the specified operation has failed finding a function
80 * containing the specified address. Should be overriden by subclasses.
Steve Block1e0659c2011-05-24 12:43:12 +010081 * See the Profile.Operation enum for the list of
Steve Blocka7e24c12009-10-30 11:49:00 +000082 * possible operations.
83 *
84 * @param {number} operation Operation.
85 * @param {number} addr Address of the unknown code.
86 * @param {number} opt_stackPos If an unknown address is encountered
87 * during stack strace processing, specifies a position of the frame
88 * containing the address.
89 */
Steve Block1e0659c2011-05-24 12:43:12 +010090Profile.prototype.handleUnknownCode = function(
Steve Blocka7e24c12009-10-30 11:49:00 +000091 operation, addr, opt_stackPos) {
92};
93
94
95/**
96 * Registers a library.
97 *
98 * @param {string} name Code entry name.
99 * @param {number} startAddr Starting address.
100 * @param {number} endAddr Ending address.
101 */
Steve Block1e0659c2011-05-24 12:43:12 +0100102Profile.prototype.addLibrary = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000103 name, startAddr, endAddr) {
Steve Block1e0659c2011-05-24 12:43:12 +0100104 var entry = new CodeMap.CodeEntry(
Steve Blocka7e24c12009-10-30 11:49:00 +0000105 endAddr - startAddr, name);
106 this.codeMap_.addLibrary(startAddr, entry);
107 return entry;
108};
109
110
111/**
112 * Registers statically compiled code entry.
113 *
114 * @param {string} name Code entry name.
115 * @param {number} startAddr Starting address.
116 * @param {number} endAddr Ending address.
117 */
Steve Block1e0659c2011-05-24 12:43:12 +0100118Profile.prototype.addStaticCode = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000119 name, startAddr, endAddr) {
Steve Block1e0659c2011-05-24 12:43:12 +0100120 var entry = new CodeMap.CodeEntry(
Steve Blocka7e24c12009-10-30 11:49:00 +0000121 endAddr - startAddr, name);
122 this.codeMap_.addStaticCode(startAddr, entry);
123 return entry;
124};
125
126
127/**
128 * Registers dynamic (JIT-compiled) code entry.
129 *
130 * @param {string} type Code entry type.
131 * @param {string} name Code entry name.
132 * @param {number} start Starting address.
133 * @param {number} size Code entry size.
134 */
Steve Block1e0659c2011-05-24 12:43:12 +0100135Profile.prototype.addCode = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000136 type, name, start, size) {
Steve Block1e0659c2011-05-24 12:43:12 +0100137 var entry = new Profile.DynamicCodeEntry(size, type, name);
Steve Blocka7e24c12009-10-30 11:49:00 +0000138 this.codeMap_.addCode(start, entry);
139 return entry;
140};
141
142
143/**
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100144 * Registers dynamic (JIT-compiled) code entry.
Leon Clarked91b9f72010-01-27 17:25:45 +0000145 *
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100146 * @param {string} type Code entry type.
147 * @param {string} name Code entry name.
148 * @param {number} start Starting address.
149 * @param {number} size Code entry size.
150 * @param {number} funcAddr Shared function object address.
151 * @param {Profile.CodeState} state Optimization state.
Leon Clarked91b9f72010-01-27 17:25:45 +0000152 */
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100153Profile.prototype.addFuncCode = function(
154 type, name, start, size, funcAddr, state) {
155 // As code and functions are in the same address space,
156 // it is safe to put them in a single code map.
157 var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
158 if (!func) {
159 func = new Profile.FunctionEntry(name);
160 this.codeMap_.addCode(funcAddr, func);
161 } else if (func.name !== name) {
162 // Function object has been overwritten with a new one.
163 func.name = name;
Leon Clarked91b9f72010-01-27 17:25:45 +0000164 }
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000165 var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
166 if (entry) {
167 if (entry.size === size && entry.func === func) {
168 // Entry state has changed.
169 entry.state = state;
170 }
171 } else {
172 entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
173 this.codeMap_.addCode(start, entry);
174 }
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100175 return entry;
Leon Clarked91b9f72010-01-27 17:25:45 +0000176};
177
178
179/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000180 * Reports about moving of a dynamic code entry.
181 *
182 * @param {number} from Current code entry address.
183 * @param {number} to New code entry address.
184 */
Steve Block1e0659c2011-05-24 12:43:12 +0100185Profile.prototype.moveCode = function(from, to) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000186 try {
187 this.codeMap_.moveCode(from, to);
188 } catch (e) {
Steve Block1e0659c2011-05-24 12:43:12 +0100189 this.handleUnknownCode(Profile.Operation.MOVE, from);
Steve Blocka7e24c12009-10-30 11:49:00 +0000190 }
191};
192
193
194/**
195 * Reports about deletion of a dynamic code entry.
196 *
197 * @param {number} start Starting address.
198 */
Steve Block1e0659c2011-05-24 12:43:12 +0100199Profile.prototype.deleteCode = function(start) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000200 try {
201 this.codeMap_.deleteCode(start);
202 } catch (e) {
Steve Block1e0659c2011-05-24 12:43:12 +0100203 this.handleUnknownCode(Profile.Operation.DELETE, start);
Steve Blocka7e24c12009-10-30 11:49:00 +0000204 }
205};
206
207
208/**
Leon Clarked91b9f72010-01-27 17:25:45 +0000209 * Reports about moving of a dynamic code entry.
210 *
211 * @param {number} from Current code entry address.
212 * @param {number} to New code entry address.
213 */
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100214Profile.prototype.moveFunc = function(from, to) {
Leon Clarked91b9f72010-01-27 17:25:45 +0000215 if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
216 this.codeMap_.moveCode(from, to);
217 }
218};
219
220
221/**
Leon Clarkee46be812010-01-19 14:06:41 +0000222 * Retrieves a code entry by an address.
223 *
224 * @param {number} addr Entry address.
225 */
Steve Block1e0659c2011-05-24 12:43:12 +0100226Profile.prototype.findEntry = function(addr) {
Leon Clarkee46be812010-01-19 14:06:41 +0000227 return this.codeMap_.findEntry(addr);
228};
229
230
231/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000232 * Records a tick event. Stack must contain a sequence of
233 * addresses starting with the program counter value.
234 *
235 * @param {Array<number>} stack Stack sample.
236 */
Steve Block1e0659c2011-05-24 12:43:12 +0100237Profile.prototype.recordTick = function(stack) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000238 var processedStack = this.resolveAndFilterFuncs_(stack);
239 this.bottomUpTree_.addPath(processedStack);
240 processedStack.reverse();
241 this.topDownTree_.addPath(processedStack);
242};
243
244
245/**
246 * Translates addresses into function names and filters unneeded
247 * functions.
248 *
249 * @param {Array<number>} stack Stack sample.
250 */
Steve Block1e0659c2011-05-24 12:43:12 +0100251Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000252 var result = [];
253 for (var i = 0; i < stack.length; ++i) {
254 var entry = this.codeMap_.findEntry(stack[i]);
255 if (entry) {
256 var name = entry.getName();
257 if (!this.skipThisFunction(name)) {
258 result.push(name);
259 }
260 } else {
261 this.handleUnknownCode(
Steve Block1e0659c2011-05-24 12:43:12 +0100262 Profile.Operation.TICK, stack[i], i);
Steve Blocka7e24c12009-10-30 11:49:00 +0000263 }
264 }
265 return result;
266};
267
268
269/**
270 * Performs a BF traversal of the top down call graph.
271 *
Steve Block1e0659c2011-05-24 12:43:12 +0100272 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000273 */
Steve Block1e0659c2011-05-24 12:43:12 +0100274Profile.prototype.traverseTopDownTree = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000275 this.topDownTree_.traverse(f);
276};
277
278
279/**
280 * Performs a BF traversal of the bottom up call graph.
281 *
Steve Block1e0659c2011-05-24 12:43:12 +0100282 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000283 */
Steve Block1e0659c2011-05-24 12:43:12 +0100284Profile.prototype.traverseBottomUpTree = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000285 this.bottomUpTree_.traverse(f);
286};
287
288
289/**
290 * Calculates a top down profile for a node with the specified label.
291 * If no name specified, returns the whole top down calls tree.
292 *
293 * @param {string} opt_label Node label.
294 */
Steve Block1e0659c2011-05-24 12:43:12 +0100295Profile.prototype.getTopDownProfile = function(opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000296 return this.getTreeProfile_(this.topDownTree_, opt_label);
297};
298
299
300/**
301 * Calculates a bottom up profile for a node with the specified label.
302 * If no name specified, returns the whole bottom up calls tree.
303 *
304 * @param {string} opt_label Node label.
305 */
Steve Block1e0659c2011-05-24 12:43:12 +0100306Profile.prototype.getBottomUpProfile = function(opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000307 return this.getTreeProfile_(this.bottomUpTree_, opt_label);
308};
309
310
311/**
312 * Helper function for calculating a tree profile.
313 *
Steve Block1e0659c2011-05-24 12:43:12 +0100314 * @param {Profile.CallTree} tree Call tree.
Steve Blocka7e24c12009-10-30 11:49:00 +0000315 * @param {string} opt_label Node label.
316 */
Steve Block1e0659c2011-05-24 12:43:12 +0100317Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000318 if (!opt_label) {
319 tree.computeTotalWeights();
320 return tree;
321 } else {
322 var subTree = tree.cloneSubtree(opt_label);
323 subTree.computeTotalWeights();
324 return subTree;
325 }
326};
327
328
329/**
330 * Calculates a flat profile of callees starting from a node with
331 * the specified label. If no name specified, starts from the root.
332 *
333 * @param {string} opt_label Starting node label.
334 */
Steve Block1e0659c2011-05-24 12:43:12 +0100335Profile.prototype.getFlatProfile = function(opt_label) {
336 var counters = new CallTree();
337 var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
Steve Blocka7e24c12009-10-30 11:49:00 +0000338 var precs = {};
339 precs[rootLabel] = 0;
340 var root = counters.findOrAddChild(rootLabel);
341
342 this.topDownTree_.computeTotalWeights();
343 this.topDownTree_.traverseInDepth(
344 function onEnter(node) {
345 if (!(node.label in precs)) {
346 precs[node.label] = 0;
347 }
348 var nodeLabelIsRootLabel = node.label == rootLabel;
349 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
350 if (precs[rootLabel] == 0) {
351 root.selfWeight += node.selfWeight;
352 root.totalWeight += node.totalWeight;
353 } else {
354 var rec = root.findOrAddChild(node.label);
355 rec.selfWeight += node.selfWeight;
356 if (nodeLabelIsRootLabel || precs[node.label] == 0) {
357 rec.totalWeight += node.totalWeight;
358 }
359 }
360 precs[node.label]++;
361 }
362 },
363 function onExit(node) {
364 if (node.label == rootLabel || precs[rootLabel] > 0) {
365 precs[node.label]--;
366 }
367 },
368 null);
369
370 if (!opt_label) {
371 // If we have created a flat profile for the whole program, we don't
372 // need an explicit root in it. Thus, replace the counters tree
373 // root with the node corresponding to the whole program.
374 counters.root_ = root;
375 } else {
376 // Propagate weights so percents can be calculated correctly.
377 counters.getRoot().selfWeight = root.selfWeight;
378 counters.getRoot().totalWeight = root.totalWeight;
379 }
380 return counters;
381};
382
383
384/**
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000385 * Cleans up function entries that are not referenced by code entries.
386 */
387Profile.prototype.cleanUpFuncEntries = function() {
388 var referencedFuncEntries = [];
389 var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
390 for (var i = 0, l = entries.length; i < l; ++i) {
391 if (entries[i][1].constructor === Profile.FunctionEntry) {
392 entries[i][1].used = false;
393 }
394 }
395 for (var i = 0, l = entries.length; i < l; ++i) {
396 if ("func" in entries[i][1]) {
397 entries[i][1].func.used = true;
398 }
399 }
400 for (var i = 0, l = entries.length; i < l; ++i) {
401 if (entries[i][1].constructor === Profile.FunctionEntry &&
402 !entries[i][1].used) {
403 this.codeMap_.deleteCode(entries[i][0]);
404 }
405 }
406};
407
408
409/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000410 * Creates a dynamic code entry.
411 *
412 * @param {number} size Code size.
413 * @param {string} type Code type.
414 * @param {string} name Function name.
415 * @constructor
416 */
Steve Block1e0659c2011-05-24 12:43:12 +0100417Profile.DynamicCodeEntry = function(size, type, name) {
418 CodeMap.CodeEntry.call(this, size, name);
Steve Blocka7e24c12009-10-30 11:49:00 +0000419 this.type = type;
420};
421
422
423/**
424 * Returns node name.
425 */
Steve Block1e0659c2011-05-24 12:43:12 +0100426Profile.DynamicCodeEntry.prototype.getName = function() {
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100427 return this.type + ': ' + this.name;
Steve Blocka7e24c12009-10-30 11:49:00 +0000428};
429
430
431/**
Leon Clarkee46be812010-01-19 14:06:41 +0000432 * Returns raw node name (without type decoration).
433 */
Steve Block1e0659c2011-05-24 12:43:12 +0100434Profile.DynamicCodeEntry.prototype.getRawName = function() {
Leon Clarkee46be812010-01-19 14:06:41 +0000435 return this.name;
436};
437
438
Steve Block1e0659c2011-05-24 12:43:12 +0100439Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100440 return false;
441};
442
443
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000444Profile.DynamicCodeEntry.prototype.toString = function() {
445 return this.getName() + ': ' + this.size.toString(16);
446};
447
448
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100449/**
450 * Creates a dynamic code entry.
451 *
452 * @param {number} size Code size.
453 * @param {string} type Code type.
454 * @param {Profile.FunctionEntry} func Shared function entry.
455 * @param {Profile.CodeState} state Code optimization state.
456 * @constructor
457 */
458Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
459 CodeMap.CodeEntry.call(this, size);
460 this.type = type;
461 this.func = func;
462 this.state = state;
463};
464
465Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];
466
467/**
468 * Returns node name.
469 */
470Profile.DynamicFuncCodeEntry.prototype.getName = function() {
471 var name = this.func.getName();
472 return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name;
473};
474
475
476/**
477 * Returns raw node name (without type decoration).
478 */
479Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
480 return this.func.getName();
481};
482
483
484Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
485 return true;
486};
487
488
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000489Profile.DynamicFuncCodeEntry.prototype.toString = function() {
490 return this.getName() + ': ' + this.size.toString(16);
491};
492
493
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100494/**
495 * Creates a shared function object entry.
496 *
497 * @param {string} name Function name.
498 * @constructor
499 */
500Profile.FunctionEntry = function(name) {
501 CodeMap.CodeEntry.call(this, 0, name);
502};
503
504
505/**
506 * Returns node name.
507 */
508Profile.FunctionEntry.prototype.getName = function() {
509 var name = this.name;
510 if (name.length == 0) {
511 name = '<anonymous>';
512 } else if (name.charAt(0) == ' ') {
513 // An anonymous function with location: " aaa.js:10".
514 name = '<anonymous>' + name;
515 }
516 return name;
Leon Clarked91b9f72010-01-27 17:25:45 +0000517};
518
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000519Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
Leon Clarked91b9f72010-01-27 17:25:45 +0000520
Leon Clarkee46be812010-01-19 14:06:41 +0000521/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000522 * Constructs a call graph.
523 *
524 * @constructor
525 */
Steve Block1e0659c2011-05-24 12:43:12 +0100526function CallTree() {
527 this.root_ = new CallTree.Node(
528 CallTree.ROOT_NODE_LABEL);
Steve Blocka7e24c12009-10-30 11:49:00 +0000529};
530
531
532/**
533 * The label of the root node.
534 */
Steve Block1e0659c2011-05-24 12:43:12 +0100535CallTree.ROOT_NODE_LABEL = '';
Steve Blocka7e24c12009-10-30 11:49:00 +0000536
537
538/**
539 * @private
540 */
Steve Block1e0659c2011-05-24 12:43:12 +0100541CallTree.prototype.totalsComputed_ = false;
Steve Blocka7e24c12009-10-30 11:49:00 +0000542
543
544/**
545 * Returns the tree root.
546 */
Steve Block1e0659c2011-05-24 12:43:12 +0100547CallTree.prototype.getRoot = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000548 return this.root_;
549};
550
551
552/**
553 * Adds the specified call path, constructing nodes as necessary.
554 *
555 * @param {Array<string>} path Call path.
556 */
Steve Block1e0659c2011-05-24 12:43:12 +0100557CallTree.prototype.addPath = function(path) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000558 if (path.length == 0) {
559 return;
560 }
561 var curr = this.root_;
562 for (var i = 0; i < path.length; ++i) {
563 curr = curr.findOrAddChild(path[i]);
564 }
565 curr.selfWeight++;
566 this.totalsComputed_ = false;
567};
568
569
570/**
571 * Finds an immediate child of the specified parent with the specified
572 * label, creates a child node if necessary. If a parent node isn't
573 * specified, uses tree root.
574 *
575 * @param {string} label Child node label.
576 */
Steve Block1e0659c2011-05-24 12:43:12 +0100577CallTree.prototype.findOrAddChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000578 return this.root_.findOrAddChild(label);
579};
580
581
582/**
583 * Creates a subtree by cloning and merging all subtrees rooted at nodes
584 * with a given label. E.g. cloning the following call tree on label 'A'
585 * will give the following result:
586 *
587 * <A>--<B> <B>
588 * / /
589 * <root> == clone on 'A' ==> <root>--<A>
590 * \ \
591 * <C>--<A>--<D> <D>
592 *
593 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
594 * source call tree.
595 *
596 * @param {string} label The label of the new root node.
597 */
Steve Block1e0659c2011-05-24 12:43:12 +0100598CallTree.prototype.cloneSubtree = function(label) {
599 var subTree = new CallTree();
Steve Blocka7e24c12009-10-30 11:49:00 +0000600 this.traverse(function(node, parent) {
601 if (!parent && node.label != label) {
602 return null;
603 }
604 var child = (parent ? parent : subTree).findOrAddChild(node.label);
605 child.selfWeight += node.selfWeight;
606 return child;
607 });
608 return subTree;
609};
610
611
612/**
613 * Computes total weights in the call graph.
614 */
Steve Block1e0659c2011-05-24 12:43:12 +0100615CallTree.prototype.computeTotalWeights = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000616 if (this.totalsComputed_) {
617 return;
618 }
619 this.root_.computeTotalWeight();
620 this.totalsComputed_ = true;
621};
622
623
624/**
625 * Traverses the call graph in preorder. This function can be used for
626 * building optionally modified tree clones. This is the boilerplate code
627 * for this scenario:
628 *
629 * callTree.traverse(function(node, parentClone) {
630 * var nodeClone = cloneNode(node);
631 * if (parentClone)
632 * parentClone.addChild(nodeClone);
633 * return nodeClone;
634 * });
635 *
Steve Block1e0659c2011-05-24 12:43:12 +0100636 * @param {function(CallTree.Node, *)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000637 * The second parameter is the result of calling 'f' on the parent node.
638 */
Steve Block1e0659c2011-05-24 12:43:12 +0100639CallTree.prototype.traverse = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000640 var pairsToProcess = new ConsArray();
641 pairsToProcess.concat([{node: this.root_, param: null}]);
642 while (!pairsToProcess.atEnd()) {
643 var pair = pairsToProcess.next();
644 var node = pair.node;
645 var newParam = f(node, pair.param);
646 var morePairsToProcess = [];
647 node.forEachChild(function (child) {
648 morePairsToProcess.push({node: child, param: newParam}); });
649 pairsToProcess.concat(morePairsToProcess);
650 }
651};
652
653
654/**
655 * Performs an indepth call graph traversal.
656 *
Steve Block1e0659c2011-05-24 12:43:12 +0100657 * @param {function(CallTree.Node)} enter A function called
Steve Blocka7e24c12009-10-30 11:49:00 +0000658 * prior to visiting node's children.
Steve Block1e0659c2011-05-24 12:43:12 +0100659 * @param {function(CallTree.Node)} exit A function called
Steve Blocka7e24c12009-10-30 11:49:00 +0000660 * after visiting node's children.
661 */
Steve Block1e0659c2011-05-24 12:43:12 +0100662CallTree.prototype.traverseInDepth = function(enter, exit) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000663 function traverse(node) {
664 enter(node);
665 node.forEachChild(traverse);
666 exit(node);
667 }
668 traverse(this.root_);
669};
670
671
672/**
673 * Constructs a call graph node.
674 *
675 * @param {string} label Node label.
Steve Block1e0659c2011-05-24 12:43:12 +0100676 * @param {CallTree.Node} opt_parent Node parent.
Steve Blocka7e24c12009-10-30 11:49:00 +0000677 */
Steve Block1e0659c2011-05-24 12:43:12 +0100678CallTree.Node = function(label, opt_parent) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000679 this.label = label;
680 this.parent = opt_parent;
681 this.children = {};
682};
683
684
685/**
686 * Node self weight (how many times this node was the last node in
687 * a call path).
688 * @type {number}
689 */
Steve Block1e0659c2011-05-24 12:43:12 +0100690CallTree.Node.prototype.selfWeight = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000691
692
693/**
694 * Node total weight (includes weights of all children).
695 * @type {number}
696 */
Steve Block1e0659c2011-05-24 12:43:12 +0100697CallTree.Node.prototype.totalWeight = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000698
699
700/**
701 * Adds a child node.
702 *
703 * @param {string} label Child node label.
704 */
Steve Block1e0659c2011-05-24 12:43:12 +0100705CallTree.Node.prototype.addChild = function(label) {
706 var child = new CallTree.Node(label, this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000707 this.children[label] = child;
708 return child;
709};
710
711
712/**
713 * Computes node's total weight.
714 */
Steve Block1e0659c2011-05-24 12:43:12 +0100715CallTree.Node.prototype.computeTotalWeight =
Steve Blocka7e24c12009-10-30 11:49:00 +0000716 function() {
717 var totalWeight = this.selfWeight;
718 this.forEachChild(function(child) {
719 totalWeight += child.computeTotalWeight(); });
720 return this.totalWeight = totalWeight;
721};
722
723
724/**
725 * Returns all node's children as an array.
726 */
Steve Block1e0659c2011-05-24 12:43:12 +0100727CallTree.Node.prototype.exportChildren = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000728 var result = [];
729 this.forEachChild(function (node) { result.push(node); });
730 return result;
731};
732
733
734/**
735 * Finds an immediate child with the specified label.
736 *
737 * @param {string} label Child node label.
738 */
Steve Block1e0659c2011-05-24 12:43:12 +0100739CallTree.Node.prototype.findChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000740 return this.children[label] || null;
741};
742
743
744/**
745 * Finds an immediate child with the specified label, creates a child
746 * node if necessary.
747 *
748 * @param {string} label Child node label.
749 */
Steve Block1e0659c2011-05-24 12:43:12 +0100750CallTree.Node.prototype.findOrAddChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000751 return this.findChild(label) || this.addChild(label);
752};
753
754
755/**
756 * Calls the specified function for every child.
757 *
Steve Block1e0659c2011-05-24 12:43:12 +0100758 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000759 */
Steve Block1e0659c2011-05-24 12:43:12 +0100760CallTree.Node.prototype.forEachChild = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000761 for (var c in this.children) {
762 f(this.children[c]);
763 }
764};
765
766
767/**
768 * Walks up from the current node up to the call tree root.
769 *
Steve Block1e0659c2011-05-24 12:43:12 +0100770 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000771 */
Steve Block1e0659c2011-05-24 12:43:12 +0100772CallTree.Node.prototype.walkUpToRoot = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000773 for (var curr = this; curr != null; curr = curr.parent) {
774 f(curr);
775 }
776};
777
778
779/**
780 * Tries to find a node with the specified path.
781 *
782 * @param {Array<string>} labels The path.
Steve Block1e0659c2011-05-24 12:43:12 +0100783 * @param {function(CallTree.Node)} opt_f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000784 */
Steve Block1e0659c2011-05-24 12:43:12 +0100785CallTree.Node.prototype.descendToChild = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000786 labels, opt_f) {
787 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
788 var child = curr.findChild(labels[pos]);
789 if (opt_f) {
790 opt_f(child, pos);
791 }
792 curr = child;
793 }
794 return curr;
795};