blob: a06cd3a5ff0fff19232fee60bf78e5ba2a7a1129 [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();
Emily Bernierd0a1eb72015-03-24 16:35:39 -040039 this.c_entries_ = {};
Steve Blocka7e24c12009-10-30 11:49:00 +000040};
41
42
43/**
44 * Returns whether a function with the specified name must be skipped.
45 * Should be overriden by subclasses.
46 *
47 * @param {string} name Function name.
48 */
Steve Block1e0659c2011-05-24 12:43:12 +010049Profile.prototype.skipThisFunction = function(name) {
Steve Blocka7e24c12009-10-30 11:49:00 +000050 return false;
51};
52
53
54/**
55 * Enum for profiler operations that involve looking up existing
56 * code entries.
57 *
58 * @enum {number}
59 */
Steve Block1e0659c2011-05-24 12:43:12 +010060Profile.Operation = {
Steve Blocka7e24c12009-10-30 11:49:00 +000061 MOVE: 0,
62 DELETE: 1,
63 TICK: 2
64};
65
66
67/**
Ben Murdoche0cee9b2011-05-25 10:26:03 +010068 * Enum for code state regarding its dynamic optimization.
69 *
70 * @enum {number}
71 */
72Profile.CodeState = {
73 COMPILED: 0,
74 OPTIMIZABLE: 1,
75 OPTIMIZED: 2
76};
77
78
79/**
Steve Blocka7e24c12009-10-30 11:49:00 +000080 * Called whenever the specified operation has failed finding a function
81 * containing the specified address. Should be overriden by subclasses.
Steve Block1e0659c2011-05-24 12:43:12 +010082 * See the Profile.Operation enum for the list of
Steve Blocka7e24c12009-10-30 11:49:00 +000083 * possible operations.
84 *
85 * @param {number} operation Operation.
86 * @param {number} addr Address of the unknown code.
87 * @param {number} opt_stackPos If an unknown address is encountered
88 * during stack strace processing, specifies a position of the frame
89 * containing the address.
90 */
Steve Block1e0659c2011-05-24 12:43:12 +010091Profile.prototype.handleUnknownCode = function(
Steve Blocka7e24c12009-10-30 11:49:00 +000092 operation, addr, opt_stackPos) {
93};
94
95
96/**
97 * Registers a library.
98 *
99 * @param {string} name Code entry name.
100 * @param {number} startAddr Starting address.
101 * @param {number} endAddr Ending address.
102 */
Steve Block1e0659c2011-05-24 12:43:12 +0100103Profile.prototype.addLibrary = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000104 name, startAddr, endAddr) {
Steve Block1e0659c2011-05-24 12:43:12 +0100105 var entry = new CodeMap.CodeEntry(
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400106 endAddr - startAddr, name, 'SHARED_LIB');
Steve Blocka7e24c12009-10-30 11:49:00 +0000107 this.codeMap_.addLibrary(startAddr, entry);
108 return entry;
109};
110
111
112/**
113 * Registers statically compiled code entry.
114 *
115 * @param {string} name Code entry name.
116 * @param {number} startAddr Starting address.
117 * @param {number} endAddr Ending address.
118 */
Steve Block1e0659c2011-05-24 12:43:12 +0100119Profile.prototype.addStaticCode = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000120 name, startAddr, endAddr) {
Steve Block1e0659c2011-05-24 12:43:12 +0100121 var entry = new CodeMap.CodeEntry(
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400122 endAddr - startAddr, name, 'CPP');
Steve Blocka7e24c12009-10-30 11:49:00 +0000123 this.codeMap_.addStaticCode(startAddr, entry);
124 return entry;
125};
126
127
128/**
129 * Registers dynamic (JIT-compiled) code entry.
130 *
131 * @param {string} type Code entry type.
132 * @param {string} name Code entry name.
133 * @param {number} start Starting address.
134 * @param {number} size Code entry size.
135 */
Steve Block1e0659c2011-05-24 12:43:12 +0100136Profile.prototype.addCode = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000137 type, name, start, size) {
Steve Block1e0659c2011-05-24 12:43:12 +0100138 var entry = new Profile.DynamicCodeEntry(size, type, name);
Steve Blocka7e24c12009-10-30 11:49:00 +0000139 this.codeMap_.addCode(start, entry);
140 return entry;
141};
142
143
144/**
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100145 * Registers dynamic (JIT-compiled) code entry.
Leon Clarked91b9f72010-01-27 17:25:45 +0000146 *
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100147 * @param {string} type Code entry type.
148 * @param {string} name Code entry name.
149 * @param {number} start Starting address.
150 * @param {number} size Code entry size.
151 * @param {number} funcAddr Shared function object address.
152 * @param {Profile.CodeState} state Optimization state.
Leon Clarked91b9f72010-01-27 17:25:45 +0000153 */
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100154Profile.prototype.addFuncCode = function(
155 type, name, start, size, funcAddr, state) {
156 // As code and functions are in the same address space,
157 // it is safe to put them in a single code map.
158 var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
159 if (!func) {
160 func = new Profile.FunctionEntry(name);
161 this.codeMap_.addCode(funcAddr, func);
162 } else if (func.name !== name) {
163 // Function object has been overwritten with a new one.
164 func.name = name;
Leon Clarked91b9f72010-01-27 17:25:45 +0000165 }
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000166 var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
167 if (entry) {
168 if (entry.size === size && entry.func === func) {
169 // Entry state has changed.
170 entry.state = state;
171 }
172 } else {
173 entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
174 this.codeMap_.addCode(start, entry);
175 }
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100176 return entry;
Leon Clarked91b9f72010-01-27 17:25:45 +0000177};
178
179
180/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000181 * Reports about moving of a dynamic code entry.
182 *
183 * @param {number} from Current code entry address.
184 * @param {number} to New code entry address.
185 */
Steve Block1e0659c2011-05-24 12:43:12 +0100186Profile.prototype.moveCode = function(from, to) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000187 try {
188 this.codeMap_.moveCode(from, to);
189 } catch (e) {
Steve Block1e0659c2011-05-24 12:43:12 +0100190 this.handleUnknownCode(Profile.Operation.MOVE, from);
Steve Blocka7e24c12009-10-30 11:49:00 +0000191 }
192};
193
194
195/**
196 * Reports about deletion of a dynamic code entry.
197 *
198 * @param {number} start Starting address.
199 */
Steve Block1e0659c2011-05-24 12:43:12 +0100200Profile.prototype.deleteCode = function(start) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000201 try {
202 this.codeMap_.deleteCode(start);
203 } catch (e) {
Steve Block1e0659c2011-05-24 12:43:12 +0100204 this.handleUnknownCode(Profile.Operation.DELETE, start);
Steve Blocka7e24c12009-10-30 11:49:00 +0000205 }
206};
207
208
209/**
Leon Clarked91b9f72010-01-27 17:25:45 +0000210 * Reports about moving of a dynamic code entry.
211 *
212 * @param {number} from Current code entry address.
213 * @param {number} to New code entry address.
214 */
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100215Profile.prototype.moveFunc = function(from, to) {
Leon Clarked91b9f72010-01-27 17:25:45 +0000216 if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
217 this.codeMap_.moveCode(from, to);
218 }
219};
220
221
222/**
Leon Clarkee46be812010-01-19 14:06:41 +0000223 * Retrieves a code entry by an address.
224 *
225 * @param {number} addr Entry address.
226 */
Steve Block1e0659c2011-05-24 12:43:12 +0100227Profile.prototype.findEntry = function(addr) {
Leon Clarkee46be812010-01-19 14:06:41 +0000228 return this.codeMap_.findEntry(addr);
229};
230
231
232/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000233 * Records a tick event. Stack must contain a sequence of
234 * addresses starting with the program counter value.
235 *
236 * @param {Array<number>} stack Stack sample.
237 */
Steve Block1e0659c2011-05-24 12:43:12 +0100238Profile.prototype.recordTick = function(stack) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000239 var processedStack = this.resolveAndFilterFuncs_(stack);
240 this.bottomUpTree_.addPath(processedStack);
241 processedStack.reverse();
242 this.topDownTree_.addPath(processedStack);
243};
244
245
246/**
247 * Translates addresses into function names and filters unneeded
248 * functions.
249 *
250 * @param {Array<number>} stack Stack sample.
251 */
Steve Block1e0659c2011-05-24 12:43:12 +0100252Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000253 var result = [];
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400254 var last_seen_c_function = '';
255 var look_for_first_c_function = false;
Steve Blocka7e24c12009-10-30 11:49:00 +0000256 for (var i = 0; i < stack.length; ++i) {
257 var entry = this.codeMap_.findEntry(stack[i]);
258 if (entry) {
259 var name = entry.getName();
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400260 if (i == 0 && (entry.type == 'CPP' || entry.type == 'SHARED_LIB')) {
261 look_for_first_c_function = true;
262 }
263 if (look_for_first_c_function) {
264 if (entry.type == 'CPP') {
265 last_seen_c_function = name;
266 } else if (i > 0 && last_seen_c_function != '') {
267 if (this.c_entries_[last_seen_c_function] === undefined) {
268 this.c_entries_[last_seen_c_function] = 0;
269 }
270 this.c_entries_[last_seen_c_function]++;
271 look_for_first_c_function = false; // Found it, we're done.
272 }
273 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000274 if (!this.skipThisFunction(name)) {
275 result.push(name);
276 }
277 } else {
278 this.handleUnknownCode(
Steve Block1e0659c2011-05-24 12:43:12 +0100279 Profile.Operation.TICK, stack[i], i);
Steve Blocka7e24c12009-10-30 11:49:00 +0000280 }
281 }
282 return result;
283};
284
285
286/**
287 * Performs a BF traversal of the top down call graph.
288 *
Steve Block1e0659c2011-05-24 12:43:12 +0100289 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000290 */
Steve Block1e0659c2011-05-24 12:43:12 +0100291Profile.prototype.traverseTopDownTree = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000292 this.topDownTree_.traverse(f);
293};
294
295
296/**
297 * Performs a BF traversal of the bottom up call graph.
298 *
Steve Block1e0659c2011-05-24 12:43:12 +0100299 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000300 */
Steve Block1e0659c2011-05-24 12:43:12 +0100301Profile.prototype.traverseBottomUpTree = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000302 this.bottomUpTree_.traverse(f);
303};
304
305
306/**
307 * Calculates a top down profile for a node with the specified label.
308 * If no name specified, returns the whole top down calls tree.
309 *
310 * @param {string} opt_label Node label.
311 */
Steve Block1e0659c2011-05-24 12:43:12 +0100312Profile.prototype.getTopDownProfile = function(opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000313 return this.getTreeProfile_(this.topDownTree_, opt_label);
314};
315
316
317/**
318 * Calculates a bottom up profile for a node with the specified label.
319 * If no name specified, returns the whole bottom up calls tree.
320 *
321 * @param {string} opt_label Node label.
322 */
Steve Block1e0659c2011-05-24 12:43:12 +0100323Profile.prototype.getBottomUpProfile = function(opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000324 return this.getTreeProfile_(this.bottomUpTree_, opt_label);
325};
326
327
328/**
329 * Helper function for calculating a tree profile.
330 *
Steve Block1e0659c2011-05-24 12:43:12 +0100331 * @param {Profile.CallTree} tree Call tree.
Steve Blocka7e24c12009-10-30 11:49:00 +0000332 * @param {string} opt_label Node label.
333 */
Steve Block1e0659c2011-05-24 12:43:12 +0100334Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000335 if (!opt_label) {
336 tree.computeTotalWeights();
337 return tree;
338 } else {
339 var subTree = tree.cloneSubtree(opt_label);
340 subTree.computeTotalWeights();
341 return subTree;
342 }
343};
344
345
346/**
347 * Calculates a flat profile of callees starting from a node with
348 * the specified label. If no name specified, starts from the root.
349 *
350 * @param {string} opt_label Starting node label.
351 */
Steve Block1e0659c2011-05-24 12:43:12 +0100352Profile.prototype.getFlatProfile = function(opt_label) {
353 var counters = new CallTree();
354 var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
Steve Blocka7e24c12009-10-30 11:49:00 +0000355 var precs = {};
356 precs[rootLabel] = 0;
357 var root = counters.findOrAddChild(rootLabel);
358
359 this.topDownTree_.computeTotalWeights();
360 this.topDownTree_.traverseInDepth(
361 function onEnter(node) {
362 if (!(node.label in precs)) {
363 precs[node.label] = 0;
364 }
365 var nodeLabelIsRootLabel = node.label == rootLabel;
366 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
367 if (precs[rootLabel] == 0) {
368 root.selfWeight += node.selfWeight;
369 root.totalWeight += node.totalWeight;
370 } else {
371 var rec = root.findOrAddChild(node.label);
372 rec.selfWeight += node.selfWeight;
373 if (nodeLabelIsRootLabel || precs[node.label] == 0) {
374 rec.totalWeight += node.totalWeight;
375 }
376 }
377 precs[node.label]++;
378 }
379 },
380 function onExit(node) {
381 if (node.label == rootLabel || precs[rootLabel] > 0) {
382 precs[node.label]--;
383 }
384 },
385 null);
386
387 if (!opt_label) {
388 // If we have created a flat profile for the whole program, we don't
389 // need an explicit root in it. Thus, replace the counters tree
390 // root with the node corresponding to the whole program.
391 counters.root_ = root;
392 } else {
393 // Propagate weights so percents can be calculated correctly.
394 counters.getRoot().selfWeight = root.selfWeight;
395 counters.getRoot().totalWeight = root.totalWeight;
396 }
397 return counters;
398};
399
400
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400401Profile.CEntryNode = function(name, ticks) {
402 this.name = name;
403 this.ticks = ticks;
404}
405
406
407Profile.prototype.getCEntryProfile = function() {
408 var result = [new Profile.CEntryNode("TOTAL", 0)];
409 var total_ticks = 0;
410 for (var f in this.c_entries_) {
411 var ticks = this.c_entries_[f];
412 total_ticks += ticks;
413 result.push(new Profile.CEntryNode(f, ticks));
414 }
415 result[0].ticks = total_ticks; // Sorting will keep this at index 0.
416 result.sort(function(n1, n2) {
417 return n2.ticks - n1.ticks || (n2.name < n1.name ? -1 : 1)
418 });
419 return result;
420}
421
422
Steve Blocka7e24c12009-10-30 11:49:00 +0000423/**
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000424 * Cleans up function entries that are not referenced by code entries.
425 */
426Profile.prototype.cleanUpFuncEntries = function() {
427 var referencedFuncEntries = [];
428 var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
429 for (var i = 0, l = entries.length; i < l; ++i) {
430 if (entries[i][1].constructor === Profile.FunctionEntry) {
431 entries[i][1].used = false;
432 }
433 }
434 for (var i = 0, l = entries.length; i < l; ++i) {
435 if ("func" in entries[i][1]) {
436 entries[i][1].func.used = true;
437 }
438 }
439 for (var i = 0, l = entries.length; i < l; ++i) {
440 if (entries[i][1].constructor === Profile.FunctionEntry &&
441 !entries[i][1].used) {
442 this.codeMap_.deleteCode(entries[i][0]);
443 }
444 }
445};
446
447
448/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000449 * Creates a dynamic code entry.
450 *
451 * @param {number} size Code size.
452 * @param {string} type Code type.
453 * @param {string} name Function name.
454 * @constructor
455 */
Steve Block1e0659c2011-05-24 12:43:12 +0100456Profile.DynamicCodeEntry = function(size, type, name) {
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400457 CodeMap.CodeEntry.call(this, size, name, type);
Steve Blocka7e24c12009-10-30 11:49:00 +0000458};
459
460
461/**
462 * Returns node name.
463 */
Steve Block1e0659c2011-05-24 12:43:12 +0100464Profile.DynamicCodeEntry.prototype.getName = function() {
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100465 return this.type + ': ' + this.name;
Steve Blocka7e24c12009-10-30 11:49:00 +0000466};
467
468
469/**
Leon Clarkee46be812010-01-19 14:06:41 +0000470 * Returns raw node name (without type decoration).
471 */
Steve Block1e0659c2011-05-24 12:43:12 +0100472Profile.DynamicCodeEntry.prototype.getRawName = function() {
Leon Clarkee46be812010-01-19 14:06:41 +0000473 return this.name;
474};
475
476
Steve Block1e0659c2011-05-24 12:43:12 +0100477Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100478 return false;
479};
480
481
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000482Profile.DynamicCodeEntry.prototype.toString = function() {
483 return this.getName() + ': ' + this.size.toString(16);
484};
485
486
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100487/**
488 * Creates a dynamic code entry.
489 *
490 * @param {number} size Code size.
491 * @param {string} type Code type.
492 * @param {Profile.FunctionEntry} func Shared function entry.
493 * @param {Profile.CodeState} state Code optimization state.
494 * @constructor
495 */
496Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400497 CodeMap.CodeEntry.call(this, size, '', type);
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100498 this.func = func;
499 this.state = state;
500};
501
502Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];
503
504/**
505 * Returns node name.
506 */
507Profile.DynamicFuncCodeEntry.prototype.getName = function() {
508 var name = this.func.getName();
509 return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name;
510};
511
512
513/**
514 * Returns raw node name (without type decoration).
515 */
516Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
517 return this.func.getName();
518};
519
520
521Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
522 return true;
523};
524
525
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000526Profile.DynamicFuncCodeEntry.prototype.toString = function() {
527 return this.getName() + ': ' + this.size.toString(16);
528};
529
530
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100531/**
532 * Creates a shared function object entry.
533 *
534 * @param {string} name Function name.
535 * @constructor
536 */
537Profile.FunctionEntry = function(name) {
538 CodeMap.CodeEntry.call(this, 0, name);
539};
540
541
542/**
543 * Returns node name.
544 */
545Profile.FunctionEntry.prototype.getName = function() {
546 var name = this.name;
547 if (name.length == 0) {
548 name = '<anonymous>';
549 } else if (name.charAt(0) == ' ') {
550 // An anonymous function with location: " aaa.js:10".
551 name = '<anonymous>' + name;
552 }
553 return name;
Leon Clarked91b9f72010-01-27 17:25:45 +0000554};
555
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000556Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
Leon Clarked91b9f72010-01-27 17:25:45 +0000557
Leon Clarkee46be812010-01-19 14:06:41 +0000558/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000559 * Constructs a call graph.
560 *
561 * @constructor
562 */
Steve Block1e0659c2011-05-24 12:43:12 +0100563function CallTree() {
564 this.root_ = new CallTree.Node(
565 CallTree.ROOT_NODE_LABEL);
Steve Blocka7e24c12009-10-30 11:49:00 +0000566};
567
568
569/**
570 * The label of the root node.
571 */
Steve Block1e0659c2011-05-24 12:43:12 +0100572CallTree.ROOT_NODE_LABEL = '';
Steve Blocka7e24c12009-10-30 11:49:00 +0000573
574
575/**
576 * @private
577 */
Steve Block1e0659c2011-05-24 12:43:12 +0100578CallTree.prototype.totalsComputed_ = false;
Steve Blocka7e24c12009-10-30 11:49:00 +0000579
580
581/**
582 * Returns the tree root.
583 */
Steve Block1e0659c2011-05-24 12:43:12 +0100584CallTree.prototype.getRoot = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000585 return this.root_;
586};
587
588
589/**
590 * Adds the specified call path, constructing nodes as necessary.
591 *
592 * @param {Array<string>} path Call path.
593 */
Steve Block1e0659c2011-05-24 12:43:12 +0100594CallTree.prototype.addPath = function(path) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000595 if (path.length == 0) {
596 return;
597 }
598 var curr = this.root_;
599 for (var i = 0; i < path.length; ++i) {
600 curr = curr.findOrAddChild(path[i]);
601 }
602 curr.selfWeight++;
603 this.totalsComputed_ = false;
604};
605
606
607/**
608 * Finds an immediate child of the specified parent with the specified
609 * label, creates a child node if necessary. If a parent node isn't
610 * specified, uses tree root.
611 *
612 * @param {string} label Child node label.
613 */
Steve Block1e0659c2011-05-24 12:43:12 +0100614CallTree.prototype.findOrAddChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000615 return this.root_.findOrAddChild(label);
616};
617
618
619/**
620 * Creates a subtree by cloning and merging all subtrees rooted at nodes
621 * with a given label. E.g. cloning the following call tree on label 'A'
622 * will give the following result:
623 *
624 * <A>--<B> <B>
625 * / /
626 * <root> == clone on 'A' ==> <root>--<A>
627 * \ \
628 * <C>--<A>--<D> <D>
629 *
630 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
631 * source call tree.
632 *
633 * @param {string} label The label of the new root node.
634 */
Steve Block1e0659c2011-05-24 12:43:12 +0100635CallTree.prototype.cloneSubtree = function(label) {
636 var subTree = new CallTree();
Steve Blocka7e24c12009-10-30 11:49:00 +0000637 this.traverse(function(node, parent) {
638 if (!parent && node.label != label) {
639 return null;
640 }
641 var child = (parent ? parent : subTree).findOrAddChild(node.label);
642 child.selfWeight += node.selfWeight;
643 return child;
644 });
645 return subTree;
646};
647
648
649/**
650 * Computes total weights in the call graph.
651 */
Steve Block1e0659c2011-05-24 12:43:12 +0100652CallTree.prototype.computeTotalWeights = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000653 if (this.totalsComputed_) {
654 return;
655 }
656 this.root_.computeTotalWeight();
657 this.totalsComputed_ = true;
658};
659
660
661/**
662 * Traverses the call graph in preorder. This function can be used for
663 * building optionally modified tree clones. This is the boilerplate code
664 * for this scenario:
665 *
666 * callTree.traverse(function(node, parentClone) {
667 * var nodeClone = cloneNode(node);
668 * if (parentClone)
669 * parentClone.addChild(nodeClone);
670 * return nodeClone;
671 * });
672 *
Steve Block1e0659c2011-05-24 12:43:12 +0100673 * @param {function(CallTree.Node, *)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000674 * The second parameter is the result of calling 'f' on the parent node.
675 */
Steve Block1e0659c2011-05-24 12:43:12 +0100676CallTree.prototype.traverse = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000677 var pairsToProcess = new ConsArray();
678 pairsToProcess.concat([{node: this.root_, param: null}]);
679 while (!pairsToProcess.atEnd()) {
680 var pair = pairsToProcess.next();
681 var node = pair.node;
682 var newParam = f(node, pair.param);
683 var morePairsToProcess = [];
684 node.forEachChild(function (child) {
685 morePairsToProcess.push({node: child, param: newParam}); });
686 pairsToProcess.concat(morePairsToProcess);
687 }
688};
689
690
691/**
692 * Performs an indepth call graph traversal.
693 *
Steve Block1e0659c2011-05-24 12:43:12 +0100694 * @param {function(CallTree.Node)} enter A function called
Steve Blocka7e24c12009-10-30 11:49:00 +0000695 * prior to visiting node's children.
Steve Block1e0659c2011-05-24 12:43:12 +0100696 * @param {function(CallTree.Node)} exit A function called
Steve Blocka7e24c12009-10-30 11:49:00 +0000697 * after visiting node's children.
698 */
Steve Block1e0659c2011-05-24 12:43:12 +0100699CallTree.prototype.traverseInDepth = function(enter, exit) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000700 function traverse(node) {
701 enter(node);
702 node.forEachChild(traverse);
703 exit(node);
704 }
705 traverse(this.root_);
706};
707
708
709/**
710 * Constructs a call graph node.
711 *
712 * @param {string} label Node label.
Steve Block1e0659c2011-05-24 12:43:12 +0100713 * @param {CallTree.Node} opt_parent Node parent.
Steve Blocka7e24c12009-10-30 11:49:00 +0000714 */
Steve Block1e0659c2011-05-24 12:43:12 +0100715CallTree.Node = function(label, opt_parent) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000716 this.label = label;
717 this.parent = opt_parent;
718 this.children = {};
719};
720
721
722/**
723 * Node self weight (how many times this node was the last node in
724 * a call path).
725 * @type {number}
726 */
Steve Block1e0659c2011-05-24 12:43:12 +0100727CallTree.Node.prototype.selfWeight = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000728
729
730/**
731 * Node total weight (includes weights of all children).
732 * @type {number}
733 */
Steve Block1e0659c2011-05-24 12:43:12 +0100734CallTree.Node.prototype.totalWeight = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000735
736
737/**
738 * Adds a child node.
739 *
740 * @param {string} label Child node label.
741 */
Steve Block1e0659c2011-05-24 12:43:12 +0100742CallTree.Node.prototype.addChild = function(label) {
743 var child = new CallTree.Node(label, this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000744 this.children[label] = child;
745 return child;
746};
747
748
749/**
750 * Computes node's total weight.
751 */
Steve Block1e0659c2011-05-24 12:43:12 +0100752CallTree.Node.prototype.computeTotalWeight =
Steve Blocka7e24c12009-10-30 11:49:00 +0000753 function() {
754 var totalWeight = this.selfWeight;
755 this.forEachChild(function(child) {
756 totalWeight += child.computeTotalWeight(); });
757 return this.totalWeight = totalWeight;
758};
759
760
761/**
762 * Returns all node's children as an array.
763 */
Steve Block1e0659c2011-05-24 12:43:12 +0100764CallTree.Node.prototype.exportChildren = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000765 var result = [];
766 this.forEachChild(function (node) { result.push(node); });
767 return result;
768};
769
770
771/**
772 * Finds an immediate child with the specified label.
773 *
774 * @param {string} label Child node label.
775 */
Steve Block1e0659c2011-05-24 12:43:12 +0100776CallTree.Node.prototype.findChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000777 return this.children[label] || null;
778};
779
780
781/**
782 * Finds an immediate child with the specified label, creates a child
783 * node if necessary.
784 *
785 * @param {string} label Child node label.
786 */
Steve Block1e0659c2011-05-24 12:43:12 +0100787CallTree.Node.prototype.findOrAddChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000788 return this.findChild(label) || this.addChild(label);
789};
790
791
792/**
793 * Calls the specified function for every child.
794 *
Steve Block1e0659c2011-05-24 12:43:12 +0100795 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000796 */
Steve Block1e0659c2011-05-24 12:43:12 +0100797CallTree.Node.prototype.forEachChild = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000798 for (var c in this.children) {
799 f(this.children[c]);
800 }
801};
802
803
804/**
805 * Walks up from the current node up to the call tree root.
806 *
Steve Block1e0659c2011-05-24 12:43:12 +0100807 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000808 */
Steve Block1e0659c2011-05-24 12:43:12 +0100809CallTree.Node.prototype.walkUpToRoot = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000810 for (var curr = this; curr != null; curr = curr.parent) {
811 f(curr);
812 }
813};
814
815
816/**
817 * Tries to find a node with the specified path.
818 *
819 * @param {Array<string>} labels The path.
Steve Block1e0659c2011-05-24 12:43:12 +0100820 * @param {function(CallTree.Node)} opt_f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000821 */
Steve Block1e0659c2011-05-24 12:43:12 +0100822CallTree.Node.prototype.descendToChild = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000823 labels, opt_f) {
824 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
825 var child = curr.findChild(labels[pos]);
826 if (opt_f) {
827 opt_f(child, pos);
828 }
829 curr = child;
830 }
831 return curr;
832};