blob: f0814a2f0d9cc3303cf5ae8f5633058a1e7243cd [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();
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000260 if (i === 0 && (entry.type === 'CPP' || entry.type === 'SHARED_LIB')) {
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400261 look_for_first_c_function = true;
262 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000263 if (look_for_first_c_function && entry.type === 'CPP') {
264 last_seen_c_function = name;
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400265 }
Steve Blocka7e24c12009-10-30 11:49:00 +0000266 if (!this.skipThisFunction(name)) {
267 result.push(name);
268 }
269 } else {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000270 this.handleUnknownCode(Profile.Operation.TICK, stack[i], i);
271 if (i === 0) result.push("UNKNOWN");
272 }
273 if (look_for_first_c_function &&
274 i > 0 &&
275 (!entry || entry.type !== 'CPP') &&
276 last_seen_c_function !== '') {
277 if (this.c_entries_[last_seen_c_function] === undefined) {
278 this.c_entries_[last_seen_c_function] = 0;
279 }
280 this.c_entries_[last_seen_c_function]++;
281 look_for_first_c_function = false; // Found it, we're done.
Steve Blocka7e24c12009-10-30 11:49:00 +0000282 }
283 }
284 return result;
285};
286
287
288/**
289 * Performs a BF traversal of the top down call graph.
290 *
Steve Block1e0659c2011-05-24 12:43:12 +0100291 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000292 */
Steve Block1e0659c2011-05-24 12:43:12 +0100293Profile.prototype.traverseTopDownTree = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000294 this.topDownTree_.traverse(f);
295};
296
297
298/**
299 * Performs a BF traversal of the bottom up call graph.
300 *
Steve Block1e0659c2011-05-24 12:43:12 +0100301 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000302 */
Steve Block1e0659c2011-05-24 12:43:12 +0100303Profile.prototype.traverseBottomUpTree = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000304 this.bottomUpTree_.traverse(f);
305};
306
307
308/**
309 * Calculates a top down profile for a node with the specified label.
310 * If no name specified, returns the whole top down calls tree.
311 *
312 * @param {string} opt_label Node label.
313 */
Steve Block1e0659c2011-05-24 12:43:12 +0100314Profile.prototype.getTopDownProfile = function(opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000315 return this.getTreeProfile_(this.topDownTree_, opt_label);
316};
317
318
319/**
320 * Calculates a bottom up profile for a node with the specified label.
321 * If no name specified, returns the whole bottom up calls tree.
322 *
323 * @param {string} opt_label Node label.
324 */
Steve Block1e0659c2011-05-24 12:43:12 +0100325Profile.prototype.getBottomUpProfile = function(opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000326 return this.getTreeProfile_(this.bottomUpTree_, opt_label);
327};
328
329
330/**
331 * Helper function for calculating a tree profile.
332 *
Steve Block1e0659c2011-05-24 12:43:12 +0100333 * @param {Profile.CallTree} tree Call tree.
Steve Blocka7e24c12009-10-30 11:49:00 +0000334 * @param {string} opt_label Node label.
335 */
Steve Block1e0659c2011-05-24 12:43:12 +0100336Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000337 if (!opt_label) {
338 tree.computeTotalWeights();
339 return tree;
340 } else {
341 var subTree = tree.cloneSubtree(opt_label);
342 subTree.computeTotalWeights();
343 return subTree;
344 }
345};
346
347
348/**
349 * Calculates a flat profile of callees starting from a node with
350 * the specified label. If no name specified, starts from the root.
351 *
352 * @param {string} opt_label Starting node label.
353 */
Steve Block1e0659c2011-05-24 12:43:12 +0100354Profile.prototype.getFlatProfile = function(opt_label) {
355 var counters = new CallTree();
356 var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
Steve Blocka7e24c12009-10-30 11:49:00 +0000357 var precs = {};
358 precs[rootLabel] = 0;
359 var root = counters.findOrAddChild(rootLabel);
360
361 this.topDownTree_.computeTotalWeights();
362 this.topDownTree_.traverseInDepth(
363 function onEnter(node) {
364 if (!(node.label in precs)) {
365 precs[node.label] = 0;
366 }
367 var nodeLabelIsRootLabel = node.label == rootLabel;
368 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
369 if (precs[rootLabel] == 0) {
370 root.selfWeight += node.selfWeight;
371 root.totalWeight += node.totalWeight;
372 } else {
373 var rec = root.findOrAddChild(node.label);
374 rec.selfWeight += node.selfWeight;
375 if (nodeLabelIsRootLabel || precs[node.label] == 0) {
376 rec.totalWeight += node.totalWeight;
377 }
378 }
379 precs[node.label]++;
380 }
381 },
382 function onExit(node) {
383 if (node.label == rootLabel || precs[rootLabel] > 0) {
384 precs[node.label]--;
385 }
386 },
387 null);
388
389 if (!opt_label) {
390 // If we have created a flat profile for the whole program, we don't
391 // need an explicit root in it. Thus, replace the counters tree
392 // root with the node corresponding to the whole program.
393 counters.root_ = root;
394 } else {
395 // Propagate weights so percents can be calculated correctly.
396 counters.getRoot().selfWeight = root.selfWeight;
397 counters.getRoot().totalWeight = root.totalWeight;
398 }
399 return counters;
400};
401
402
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400403Profile.CEntryNode = function(name, ticks) {
404 this.name = name;
405 this.ticks = ticks;
406}
407
408
409Profile.prototype.getCEntryProfile = function() {
410 var result = [new Profile.CEntryNode("TOTAL", 0)];
411 var total_ticks = 0;
412 for (var f in this.c_entries_) {
413 var ticks = this.c_entries_[f];
414 total_ticks += ticks;
415 result.push(new Profile.CEntryNode(f, ticks));
416 }
417 result[0].ticks = total_ticks; // Sorting will keep this at index 0.
418 result.sort(function(n1, n2) {
419 return n2.ticks - n1.ticks || (n2.name < n1.name ? -1 : 1)
420 });
421 return result;
422}
423
424
Steve Blocka7e24c12009-10-30 11:49:00 +0000425/**
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000426 * Cleans up function entries that are not referenced by code entries.
427 */
428Profile.prototype.cleanUpFuncEntries = function() {
429 var referencedFuncEntries = [];
430 var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
431 for (var i = 0, l = entries.length; i < l; ++i) {
432 if (entries[i][1].constructor === Profile.FunctionEntry) {
433 entries[i][1].used = false;
434 }
435 }
436 for (var i = 0, l = entries.length; i < l; ++i) {
437 if ("func" in entries[i][1]) {
438 entries[i][1].func.used = true;
439 }
440 }
441 for (var i = 0, l = entries.length; i < l; ++i) {
442 if (entries[i][1].constructor === Profile.FunctionEntry &&
443 !entries[i][1].used) {
444 this.codeMap_.deleteCode(entries[i][0]);
445 }
446 }
447};
448
449
450/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000451 * Creates a dynamic code entry.
452 *
453 * @param {number} size Code size.
454 * @param {string} type Code type.
455 * @param {string} name Function name.
456 * @constructor
457 */
Steve Block1e0659c2011-05-24 12:43:12 +0100458Profile.DynamicCodeEntry = function(size, type, name) {
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400459 CodeMap.CodeEntry.call(this, size, name, type);
Steve Blocka7e24c12009-10-30 11:49:00 +0000460};
461
462
463/**
464 * Returns node name.
465 */
Steve Block1e0659c2011-05-24 12:43:12 +0100466Profile.DynamicCodeEntry.prototype.getName = function() {
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100467 return this.type + ': ' + this.name;
Steve Blocka7e24c12009-10-30 11:49:00 +0000468};
469
470
471/**
Leon Clarkee46be812010-01-19 14:06:41 +0000472 * Returns raw node name (without type decoration).
473 */
Steve Block1e0659c2011-05-24 12:43:12 +0100474Profile.DynamicCodeEntry.prototype.getRawName = function() {
Leon Clarkee46be812010-01-19 14:06:41 +0000475 return this.name;
476};
477
478
Steve Block1e0659c2011-05-24 12:43:12 +0100479Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100480 return false;
481};
482
483
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000484Profile.DynamicCodeEntry.prototype.toString = function() {
485 return this.getName() + ': ' + this.size.toString(16);
486};
487
488
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100489/**
490 * Creates a dynamic code entry.
491 *
492 * @param {number} size Code size.
493 * @param {string} type Code type.
494 * @param {Profile.FunctionEntry} func Shared function entry.
495 * @param {Profile.CodeState} state Code optimization state.
496 * @constructor
497 */
498Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
Emily Bernierd0a1eb72015-03-24 16:35:39 -0400499 CodeMap.CodeEntry.call(this, size, '', type);
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100500 this.func = func;
501 this.state = state;
502};
503
504Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];
505
506/**
507 * Returns node name.
508 */
509Profile.DynamicFuncCodeEntry.prototype.getName = function() {
510 var name = this.func.getName();
511 return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name;
512};
513
514
515/**
516 * Returns raw node name (without type decoration).
517 */
518Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
519 return this.func.getName();
520};
521
522
523Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
524 return true;
525};
526
527
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000528Profile.DynamicFuncCodeEntry.prototype.toString = function() {
529 return this.getName() + ': ' + this.size.toString(16);
530};
531
532
Ben Murdoche0cee9b2011-05-25 10:26:03 +0100533/**
534 * Creates a shared function object entry.
535 *
536 * @param {string} name Function name.
537 * @constructor
538 */
539Profile.FunctionEntry = function(name) {
540 CodeMap.CodeEntry.call(this, 0, name);
541};
542
543
544/**
545 * Returns node name.
546 */
547Profile.FunctionEntry.prototype.getName = function() {
548 var name = this.name;
549 if (name.length == 0) {
550 name = '<anonymous>';
551 } else if (name.charAt(0) == ' ') {
552 // An anonymous function with location: " aaa.js:10".
553 name = '<anonymous>' + name;
554 }
555 return name;
Leon Clarked91b9f72010-01-27 17:25:45 +0000556};
557
Ben Murdoch3fb3ca82011-12-02 17:19:32 +0000558Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
Leon Clarked91b9f72010-01-27 17:25:45 +0000559
Leon Clarkee46be812010-01-19 14:06:41 +0000560/**
Steve Blocka7e24c12009-10-30 11:49:00 +0000561 * Constructs a call graph.
562 *
563 * @constructor
564 */
Steve Block1e0659c2011-05-24 12:43:12 +0100565function CallTree() {
566 this.root_ = new CallTree.Node(
567 CallTree.ROOT_NODE_LABEL);
Steve Blocka7e24c12009-10-30 11:49:00 +0000568};
569
570
571/**
572 * The label of the root node.
573 */
Steve Block1e0659c2011-05-24 12:43:12 +0100574CallTree.ROOT_NODE_LABEL = '';
Steve Blocka7e24c12009-10-30 11:49:00 +0000575
576
577/**
578 * @private
579 */
Steve Block1e0659c2011-05-24 12:43:12 +0100580CallTree.prototype.totalsComputed_ = false;
Steve Blocka7e24c12009-10-30 11:49:00 +0000581
582
583/**
584 * Returns the tree root.
585 */
Steve Block1e0659c2011-05-24 12:43:12 +0100586CallTree.prototype.getRoot = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000587 return this.root_;
588};
589
590
591/**
592 * Adds the specified call path, constructing nodes as necessary.
593 *
594 * @param {Array<string>} path Call path.
595 */
Steve Block1e0659c2011-05-24 12:43:12 +0100596CallTree.prototype.addPath = function(path) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000597 if (path.length == 0) {
598 return;
599 }
600 var curr = this.root_;
601 for (var i = 0; i < path.length; ++i) {
602 curr = curr.findOrAddChild(path[i]);
603 }
604 curr.selfWeight++;
605 this.totalsComputed_ = false;
606};
607
608
609/**
610 * Finds an immediate child of the specified parent with the specified
611 * label, creates a child node if necessary. If a parent node isn't
612 * specified, uses tree root.
613 *
614 * @param {string} label Child node label.
615 */
Steve Block1e0659c2011-05-24 12:43:12 +0100616CallTree.prototype.findOrAddChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000617 return this.root_.findOrAddChild(label);
618};
619
620
621/**
622 * Creates a subtree by cloning and merging all subtrees rooted at nodes
623 * with a given label. E.g. cloning the following call tree on label 'A'
624 * will give the following result:
625 *
626 * <A>--<B> <B>
627 * / /
628 * <root> == clone on 'A' ==> <root>--<A>
629 * \ \
630 * <C>--<A>--<D> <D>
631 *
632 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
633 * source call tree.
634 *
635 * @param {string} label The label of the new root node.
636 */
Steve Block1e0659c2011-05-24 12:43:12 +0100637CallTree.prototype.cloneSubtree = function(label) {
638 var subTree = new CallTree();
Steve Blocka7e24c12009-10-30 11:49:00 +0000639 this.traverse(function(node, parent) {
640 if (!parent && node.label != label) {
641 return null;
642 }
643 var child = (parent ? parent : subTree).findOrAddChild(node.label);
644 child.selfWeight += node.selfWeight;
645 return child;
646 });
647 return subTree;
648};
649
650
651/**
652 * Computes total weights in the call graph.
653 */
Steve Block1e0659c2011-05-24 12:43:12 +0100654CallTree.prototype.computeTotalWeights = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000655 if (this.totalsComputed_) {
656 return;
657 }
658 this.root_.computeTotalWeight();
659 this.totalsComputed_ = true;
660};
661
662
663/**
664 * Traverses the call graph in preorder. This function can be used for
665 * building optionally modified tree clones. This is the boilerplate code
666 * for this scenario:
667 *
668 * callTree.traverse(function(node, parentClone) {
669 * var nodeClone = cloneNode(node);
670 * if (parentClone)
671 * parentClone.addChild(nodeClone);
672 * return nodeClone;
673 * });
674 *
Steve Block1e0659c2011-05-24 12:43:12 +0100675 * @param {function(CallTree.Node, *)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000676 * The second parameter is the result of calling 'f' on the parent node.
677 */
Steve Block1e0659c2011-05-24 12:43:12 +0100678CallTree.prototype.traverse = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000679 var pairsToProcess = new ConsArray();
680 pairsToProcess.concat([{node: this.root_, param: null}]);
681 while (!pairsToProcess.atEnd()) {
682 var pair = pairsToProcess.next();
683 var node = pair.node;
684 var newParam = f(node, pair.param);
685 var morePairsToProcess = [];
686 node.forEachChild(function (child) {
687 morePairsToProcess.push({node: child, param: newParam}); });
688 pairsToProcess.concat(morePairsToProcess);
689 }
690};
691
692
693/**
694 * Performs an indepth call graph traversal.
695 *
Steve Block1e0659c2011-05-24 12:43:12 +0100696 * @param {function(CallTree.Node)} enter A function called
Steve Blocka7e24c12009-10-30 11:49:00 +0000697 * prior to visiting node's children.
Steve Block1e0659c2011-05-24 12:43:12 +0100698 * @param {function(CallTree.Node)} exit A function called
Steve Blocka7e24c12009-10-30 11:49:00 +0000699 * after visiting node's children.
700 */
Steve Block1e0659c2011-05-24 12:43:12 +0100701CallTree.prototype.traverseInDepth = function(enter, exit) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000702 function traverse(node) {
703 enter(node);
704 node.forEachChild(traverse);
705 exit(node);
706 }
707 traverse(this.root_);
708};
709
710
711/**
712 * Constructs a call graph node.
713 *
714 * @param {string} label Node label.
Steve Block1e0659c2011-05-24 12:43:12 +0100715 * @param {CallTree.Node} opt_parent Node parent.
Steve Blocka7e24c12009-10-30 11:49:00 +0000716 */
Steve Block1e0659c2011-05-24 12:43:12 +0100717CallTree.Node = function(label, opt_parent) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000718 this.label = label;
719 this.parent = opt_parent;
720 this.children = {};
721};
722
723
724/**
725 * Node self weight (how many times this node was the last node in
726 * a call path).
727 * @type {number}
728 */
Steve Block1e0659c2011-05-24 12:43:12 +0100729CallTree.Node.prototype.selfWeight = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000730
731
732/**
733 * Node total weight (includes weights of all children).
734 * @type {number}
735 */
Steve Block1e0659c2011-05-24 12:43:12 +0100736CallTree.Node.prototype.totalWeight = 0;
Steve Blocka7e24c12009-10-30 11:49:00 +0000737
738
739/**
740 * Adds a child node.
741 *
742 * @param {string} label Child node label.
743 */
Steve Block1e0659c2011-05-24 12:43:12 +0100744CallTree.Node.prototype.addChild = function(label) {
745 var child = new CallTree.Node(label, this);
Steve Blocka7e24c12009-10-30 11:49:00 +0000746 this.children[label] = child;
747 return child;
748};
749
750
751/**
752 * Computes node's total weight.
753 */
Steve Block1e0659c2011-05-24 12:43:12 +0100754CallTree.Node.prototype.computeTotalWeight =
Steve Blocka7e24c12009-10-30 11:49:00 +0000755 function() {
756 var totalWeight = this.selfWeight;
757 this.forEachChild(function(child) {
758 totalWeight += child.computeTotalWeight(); });
759 return this.totalWeight = totalWeight;
760};
761
762
763/**
764 * Returns all node's children as an array.
765 */
Steve Block1e0659c2011-05-24 12:43:12 +0100766CallTree.Node.prototype.exportChildren = function() {
Steve Blocka7e24c12009-10-30 11:49:00 +0000767 var result = [];
768 this.forEachChild(function (node) { result.push(node); });
769 return result;
770};
771
772
773/**
774 * Finds an immediate child with the specified label.
775 *
776 * @param {string} label Child node label.
777 */
Steve Block1e0659c2011-05-24 12:43:12 +0100778CallTree.Node.prototype.findChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000779 return this.children[label] || null;
780};
781
782
783/**
784 * Finds an immediate child with the specified label, creates a child
785 * node if necessary.
786 *
787 * @param {string} label Child node label.
788 */
Steve Block1e0659c2011-05-24 12:43:12 +0100789CallTree.Node.prototype.findOrAddChild = function(label) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000790 return this.findChild(label) || this.addChild(label);
791};
792
793
794/**
795 * Calls the specified function for every child.
796 *
Steve Block1e0659c2011-05-24 12:43:12 +0100797 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000798 */
Steve Block1e0659c2011-05-24 12:43:12 +0100799CallTree.Node.prototype.forEachChild = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000800 for (var c in this.children) {
801 f(this.children[c]);
802 }
803};
804
805
806/**
807 * Walks up from the current node up to the call tree root.
808 *
Steve Block1e0659c2011-05-24 12:43:12 +0100809 * @param {function(CallTree.Node)} f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000810 */
Steve Block1e0659c2011-05-24 12:43:12 +0100811CallTree.Node.prototype.walkUpToRoot = function(f) {
Steve Blocka7e24c12009-10-30 11:49:00 +0000812 for (var curr = this; curr != null; curr = curr.parent) {
813 f(curr);
814 }
815};
816
817
818/**
819 * Tries to find a node with the specified path.
820 *
821 * @param {Array<string>} labels The path.
Steve Block1e0659c2011-05-24 12:43:12 +0100822 * @param {function(CallTree.Node)} opt_f Visitor function.
Steve Blocka7e24c12009-10-30 11:49:00 +0000823 */
Steve Block1e0659c2011-05-24 12:43:12 +0100824CallTree.Node.prototype.descendToChild = function(
Steve Blocka7e24c12009-10-30 11:49:00 +0000825 labels, opt_f) {
826 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
827 var child = curr.findChild(labels[pos]);
828 if (opt_f) {
829 opt_f(child, pos);
830 }
831 curr = child;
832 }
833 return curr;
834};