profile.js 21.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


/**
 * Creates a profile object for processing profiling-related events
 * and calculating function execution times.
 *
 * @constructor
 */
35 36 37 38
function Profile() {
  this.codeMap_ = new CodeMap();
  this.topDownTree_ = new CallTree();
  this.bottomUpTree_ = new CallTree();
39
  this.c_entries_ = {};
40 41 42 43 44 45 46 47 48
};


/**
 * Returns whether a function with the specified name must be skipped.
 * Should be overriden by subclasses.
 *
 * @param {string} name Function name.
 */
49
Profile.prototype.skipThisFunction = function(name) {
50 51 52 53
  return false;
};


54 55 56 57 58 59
/**
 * Enum for profiler operations that involve looking up existing
 * code entries.
 *
 * @enum {number}
 */
60
Profile.Operation = {
61
  MOVE: 0,
62 63
  DELETE: 1,
  TICK: 2
64 65 66
};


67 68 69 70 71 72 73 74 75 76 77 78
/**
 * Enum for code state regarding its dynamic optimization.
 *
 * @enum {number}
 */
Profile.CodeState = {
  COMPILED: 0,
  OPTIMIZABLE: 1,
  OPTIMIZED: 2
};


79 80 81
/**
 * Called whenever the specified operation has failed finding a function
 * containing the specified address. Should be overriden by subclasses.
82
 * See the Profile.Operation enum for the list of
83
 * possible operations.
84
 *
85
 * @param {number} operation Operation.
86
 * @param {number} addr Address of the unknown code.
87 88 89
 * @param {number} opt_stackPos If an unknown address is encountered
 *     during stack strace processing, specifies a position of the frame
 *     containing the address.
90
 */
91
Profile.prototype.handleUnknownCode = function(
92
    operation, addr, opt_stackPos) {
93 94 95 96
};


/**
97 98 99 100 101 102
 * Registers a library.
 *
 * @param {string} name Code entry name.
 * @param {number} startAddr Starting address.
 * @param {number} endAddr Ending address.
 */
103
Profile.prototype.addLibrary = function(
104
    name, startAddr, endAddr) {
105
  var entry = new CodeMap.CodeEntry(
106
      endAddr - startAddr, name, 'SHARED_LIB');
107 108 109 110 111 112 113
  this.codeMap_.addLibrary(startAddr, entry);
  return entry;
};


/**
 * Registers statically compiled code entry.
114 115 116 117 118
 *
 * @param {string} name Code entry name.
 * @param {number} startAddr Starting address.
 * @param {number} endAddr Ending address.
 */
119
Profile.prototype.addStaticCode = function(
120
    name, startAddr, endAddr) {
121
  var entry = new CodeMap.CodeEntry(
122
      endAddr - startAddr, name, 'CPP');
123 124
  this.codeMap_.addStaticCode(startAddr, entry);
  return entry;
125 126 127 128 129 130 131 132 133 134 135
};


/**
 * Registers dynamic (JIT-compiled) code entry.
 *
 * @param {string} type Code entry type.
 * @param {string} name Code entry name.
 * @param {number} start Starting address.
 * @param {number} size Code entry size.
 */
136
Profile.prototype.addCode = function(
137
    type, name, start, size) {
138
  var entry = new Profile.DynamicCodeEntry(size, type, name);
139 140
  this.codeMap_.addCode(start, entry);
  return entry;
141 142 143
};


144
/**
145
 * Registers dynamic (JIT-compiled) code entry.
146
 *
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
 * @param {string} type Code entry type.
 * @param {string} name Code entry name.
 * @param {number} start Starting address.
 * @param {number} size Code entry size.
 * @param {number} funcAddr Shared function object address.
 * @param {Profile.CodeState} state Optimization state.
 */
Profile.prototype.addFuncCode = function(
    type, name, start, size, funcAddr, state) {
  // As code and functions are in the same address space,
  // it is safe to put them in a single code map.
  var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
  if (!func) {
    func = new Profile.FunctionEntry(name);
    this.codeMap_.addCode(funcAddr, func);
  } else if (func.name !== name) {
    // Function object has been overwritten with a new one.
    func.name = name;
165
  }
166
  var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
167 168 169 170 171 172 173 174
  if (entry) {
    if (entry.size === size && entry.func === func) {
      // Entry state has changed.
      entry.state = state;
    }
  } else {
    entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
    this.codeMap_.addCode(start, entry);
175
  }
176
  return entry;
177 178 179
};


180 181 182 183 184 185
/**
 * Reports about moving of a dynamic code entry.
 *
 * @param {number} from Current code entry address.
 * @param {number} to New code entry address.
 */
186
Profile.prototype.moveCode = function(from, to) {
187 188 189
  try {
    this.codeMap_.moveCode(from, to);
  } catch (e) {
190
    this.handleUnknownCode(Profile.Operation.MOVE, from);
191 192 193 194
  }
};


195 196 197 198 199 200 201 202 203 204 205 206 207 208
/**
 * Reports about deletion of a dynamic code entry.
 *
 * @param {number} start Starting address.
 */
Profile.prototype.deleteCode = function(start) {
  try {
    this.codeMap_.deleteCode(start);
  } catch (e) {
    this.handleUnknownCode(Profile.Operation.DELETE, start);
  }
};


209 210 211 212 213 214
/**
 * Reports about moving of a dynamic code entry.
 *
 * @param {number} from Current code entry address.
 * @param {number} to New code entry address.
 */
215
Profile.prototype.moveFunc = function(from, to) {
216 217 218 219 220 221
  if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
    this.codeMap_.moveCode(from, to);
  }
};


222 223 224 225 226
/**
 * Retrieves a code entry by an address.
 *
 * @param {number} addr Entry address.
 */
227
Profile.prototype.findEntry = function(addr) {
228 229 230 231
  return this.codeMap_.findEntry(addr);
};


232 233 234 235 236 237
/**
 * Records a tick event. Stack must contain a sequence of
 * addresses starting with the program counter value.
 *
 * @param {Array<number>} stack Stack sample.
 */
238
Profile.prototype.recordTick = function(stack) {
239 240 241 242 243 244 245 246 247 248 249 250 251
  var processedStack = this.resolveAndFilterFuncs_(stack);
  this.bottomUpTree_.addPath(processedStack);
  processedStack.reverse();
  this.topDownTree_.addPath(processedStack);
};


/**
 * Translates addresses into function names and filters unneeded
 * functions.
 *
 * @param {Array<number>} stack Stack sample.
 */
252
Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
253
  var result = [];
254 255
  var last_seen_c_function = '';
  var look_for_first_c_function = false;
256 257 258 259
  for (var i = 0; i < stack.length; ++i) {
    var entry = this.codeMap_.findEntry(stack[i]);
    if (entry) {
      var name = entry.getName();
jkummerow's avatar
jkummerow committed
260
      if (i === 0 && (entry.type === 'CPP' || entry.type === 'SHARED_LIB')) {
261 262
        look_for_first_c_function = true;
      }
jkummerow's avatar
jkummerow committed
263 264
      if (look_for_first_c_function && entry.type === 'CPP') {
        last_seen_c_function = name;
265
      }
266 267 268 269
      if (!this.skipThisFunction(name)) {
        result.push(name);
      }
    } else {
jkummerow's avatar
jkummerow committed
270 271 272 273 274 275 276 277 278 279 280 281
      this.handleUnknownCode(Profile.Operation.TICK, stack[i], i);
      if (i === 0) result.push("UNKNOWN");
    }
    if (look_for_first_c_function &&
        i > 0 &&
        (!entry || entry.type !== 'CPP') &&
        last_seen_c_function !== '') {
      if (this.c_entries_[last_seen_c_function] === undefined) {
        this.c_entries_[last_seen_c_function] = 0;
      }
      this.c_entries_[last_seen_c_function]++;
      look_for_first_c_function = false;  // Found it, we're done.
282 283 284 285 286 287 288
    }
  }
  return result;
};


/**
289
 * Performs a BF traversal of the top down call graph.
290
 *
291
 * @param {function(CallTree.Node)} f Visitor function.
292
 */
293
Profile.prototype.traverseTopDownTree = function(f) {
294 295 296 297 298
  this.topDownTree_.traverse(f);
};


/**
299
 * Performs a BF traversal of the bottom up call graph.
300
 *
301
 * @param {function(CallTree.Node)} f Visitor function.
302
 */
303
Profile.prototype.traverseBottomUpTree = function(f) {
304 305 306 307
  this.bottomUpTree_.traverse(f);
};


308
/**
309 310
 * Calculates a top down profile for a node with the specified label.
 * If no name specified, returns the whole top down calls tree.
311
 *
312
 * @param {string} opt_label Node label.
313
 */
314
Profile.prototype.getTopDownProfile = function(opt_label) {
315 316 317 318 319 320 321 322 323 324
  return this.getTreeProfile_(this.topDownTree_, opt_label);
};


/**
 * Calculates a bottom up profile for a node with the specified label.
 * If no name specified, returns the whole bottom up calls tree.
 *
 * @param {string} opt_label Node label.
 */
325
Profile.prototype.getBottomUpProfile = function(opt_label) {
326
  return this.getTreeProfile_(this.bottomUpTree_, opt_label);
327 328 329 330
};


/**
331
 * Helper function for calculating a tree profile.
332
 *
333
 * @param {Profile.CallTree} tree Call tree.
334
 * @param {string} opt_label Node label.
335
 */
336
Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
337 338 339
  if (!opt_label) {
    tree.computeTotalWeights();
    return tree;
340
  } else {
341 342 343
    var subTree = tree.cloneSubtree(opt_label);
    subTree.computeTotalWeights();
    return subTree;
344 345 346 347
  }
};


348
/**
349 350
 * Calculates a flat profile of callees starting from a node with
 * the specified label. If no name specified, starts from the root.
351
 *
352
 * @param {string} opt_label Starting node label.
353
 */
354 355 356
Profile.prototype.getFlatProfile = function(opt_label) {
  var counters = new CallTree();
  var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
357
  var precs = {};
358 359 360
  precs[rootLabel] = 0;
  var root = counters.findOrAddChild(rootLabel);

361 362 363 364 365 366
  this.topDownTree_.computeTotalWeights();
  this.topDownTree_.traverseInDepth(
    function onEnter(node) {
      if (!(node.label in precs)) {
        precs[node.label] = 0;
      }
367 368 369 370 371 372 373 374 375 376 377 378 379
      var nodeLabelIsRootLabel = node.label == rootLabel;
      if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
        if (precs[rootLabel] == 0) {
          root.selfWeight += node.selfWeight;
          root.totalWeight += node.totalWeight;
        } else {
          var rec = root.findOrAddChild(node.label);
          rec.selfWeight += node.selfWeight;
          if (nodeLabelIsRootLabel || precs[node.label] == 0) {
            rec.totalWeight += node.totalWeight;
          }
        }
        precs[node.label]++;
380 381 382
      }
    },
    function onExit(node) {
383 384 385
      if (node.label == rootLabel || precs[rootLabel] > 0) {
        precs[node.label]--;
      }
386
    },
387 388 389 390 391 392 393 394 395 396 397 398
    null);

  if (!opt_label) {
    // If we have created a flat profile for the whole program, we don't
    // need an explicit root in it. Thus, replace the counters tree
    // root with the node corresponding to the whole program.
    counters.root_ = root;
  } else {
    // Propagate weights so percents can be calculated correctly.
    counters.getRoot().selfWeight = root.selfWeight;
    counters.getRoot().totalWeight = root.totalWeight;
  }
399
  return counters;
400 401 402
};


403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
Profile.CEntryNode = function(name, ticks) {
  this.name = name;
  this.ticks = ticks;
}


Profile.prototype.getCEntryProfile = function() {
  var result = [new Profile.CEntryNode("TOTAL", 0)];
  var total_ticks = 0;
  for (var f in this.c_entries_) {
    var ticks = this.c_entries_[f];
    total_ticks += ticks;
    result.push(new Profile.CEntryNode(f, ticks));
  }
  result[0].ticks = total_ticks;  // Sorting will keep this at index 0.
  result.sort(function(n1, n2) {
    return n2.ticks - n1.ticks || (n2.name < n1.name ? -1 : 1)
  });
  return result;
}


425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
/**
 * Cleans up function entries that are not referenced by code entries.
 */
Profile.prototype.cleanUpFuncEntries = function() {
  var referencedFuncEntries = [];
  var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
  for (var i = 0, l = entries.length; i < l; ++i) {
    if (entries[i][1].constructor === Profile.FunctionEntry) {
      entries[i][1].used = false;
    }
  }
  for (var i = 0, l = entries.length; i < l; ++i) {
    if ("func" in entries[i][1]) {
      entries[i][1].func.used = true;
    }
  }
  for (var i = 0, l = entries.length; i < l; ++i) {
    if (entries[i][1].constructor === Profile.FunctionEntry &&
        !entries[i][1].used) {
      this.codeMap_.deleteCode(entries[i][0]);
    }
  }
};


450 451 452 453 454 455 456 457
/**
 * Creates a dynamic code entry.
 *
 * @param {number} size Code size.
 * @param {string} type Code type.
 * @param {string} name Function name.
 * @constructor
 */
458
Profile.DynamicCodeEntry = function(size, type, name) {
459
  CodeMap.CodeEntry.call(this, size, name, type);
460 461 462 463 464 465
};


/**
 * Returns node name.
 */
466
Profile.DynamicCodeEntry.prototype.getName = function() {
467
  return this.type + ': ' + this.name;
468 469 470
};


471 472 473
/**
 * Returns raw node name (without type decoration).
 */
474
Profile.DynamicCodeEntry.prototype.getRawName = function() {
475 476 477 478
  return this.name;
};


479
Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
480 481 482 483
  return false;
};


484 485 486 487 488
Profile.DynamicCodeEntry.prototype.toString = function() {
  return this.getName() + ': ' + this.size.toString(16);
};


489 490 491 492 493 494 495 496 497 498
/**
 * Creates a dynamic code entry.
 *
 * @param {number} size Code size.
 * @param {string} type Code type.
 * @param {Profile.FunctionEntry} func Shared function entry.
 * @param {Profile.CodeState} state Code optimization state.
 * @constructor
 */
Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
499
  CodeMap.CodeEntry.call(this, size, '', type);
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
  this.func = func;
  this.state = state;
};

Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];

/**
 * Returns node name.
 */
Profile.DynamicFuncCodeEntry.prototype.getName = function() {
  var name = this.func.getName();
  return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name;
};


/**
 * Returns raw node name (without type decoration).
 */
Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
  return this.func.getName();
};


Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
  return true;
};


528 529 530 531 532
Profile.DynamicFuncCodeEntry.prototype.toString = function() {
  return this.getName() + ': ' + this.size.toString(16);
};


533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
/**
 * Creates a shared function object entry.
 *
 * @param {string} name Function name.
 * @constructor
 */
Profile.FunctionEntry = function(name) {
  CodeMap.CodeEntry.call(this, 0, name);
};


/**
 * Returns node name.
 */
Profile.FunctionEntry.prototype.getName = function() {
  var name = this.name;
  if (name.length == 0) {
    name = '<anonymous>';
  } else if (name.charAt(0) == ' ') {
    // An anonymous function with location: " aaa.js:10".
    name = '<anonymous>' + name;
  }
  return name;
556 557
};

558
Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
559

560 561 562 563 564
/**
 * Constructs a call graph.
 *
 * @constructor
 */
565 566 567
function CallTree() {
  this.root_ = new CallTree.Node(
      CallTree.ROOT_NODE_LABEL);
568 569 570
};


571 572 573
/**
 * The label of the root node.
 */
574
CallTree.ROOT_NODE_LABEL = '';
575 576


577 578 579
/**
 * @private
 */
580
CallTree.prototype.totalsComputed_ = false;
581 582


583 584 585
/**
 * Returns the tree root.
 */
586
CallTree.prototype.getRoot = function() {
587 588 589 590
  return this.root_;
};


591 592 593 594 595
/**
 * Adds the specified call path, constructing nodes as necessary.
 *
 * @param {Array<string>} path Call path.
 */
596
CallTree.prototype.addPath = function(path) {
597 598 599 600 601 602 603 604 605 606 607 608
  if (path.length == 0) {
    return;
  }
  var curr = this.root_;
  for (var i = 0; i < path.length; ++i) {
    curr = curr.findOrAddChild(path[i]);
  }
  curr.selfWeight++;
  this.totalsComputed_ = false;
};


609 610 611 612 613 614 615
/**
 * Finds an immediate child of the specified parent with the specified
 * label, creates a child node if necessary. If a parent node isn't
 * specified, uses tree root.
 *
 * @param {string} label Child node label.
 */
616
CallTree.prototype.findOrAddChild = function(label) {
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
  return this.root_.findOrAddChild(label);
};


/**
 * Creates a subtree by cloning and merging all subtrees rooted at nodes
 * with a given label. E.g. cloning the following call tree on label 'A'
 * will give the following result:
 *
 *           <A>--<B>                                     <B>
 *          /                                            /
 *     <root>             == clone on 'A' ==>  <root>--<A>
 *          \                                            \
 *           <C>--<A>--<D>                                <D>
 *
 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
 * source call tree.
 *
 * @param {string} label The label of the new root node.
 */
637 638
CallTree.prototype.cloneSubtree = function(label) {
  var subTree = new CallTree();
639 640 641 642 643 644 645 646 647
  this.traverse(function(node, parent) {
    if (!parent && node.label != label) {
      return null;
    }
    var child = (parent ? parent : subTree).findOrAddChild(node.label);
    child.selfWeight += node.selfWeight;
    return child;
  });
  return subTree;
648 649 650
};


651 652 653
/**
 * Computes total weights in the call graph.
 */
654
CallTree.prototype.computeTotalWeights = function() {
655 656 657 658 659 660 661 662 663
  if (this.totalsComputed_) {
    return;
  }
  this.root_.computeTotalWeight();
  this.totalsComputed_ = true;
};


/**
664 665 666
 * Traverses the call graph in preorder. This function can be used for
 * building optionally modified tree clones. This is the boilerplate code
 * for this scenario:
667
 *
668 669 670 671 672 673 674
 * callTree.traverse(function(node, parentClone) {
 *   var nodeClone = cloneNode(node);
 *   if (parentClone)
 *     parentClone.addChild(nodeClone);
 *   return nodeClone;
 * });
 *
675
 * @param {function(CallTree.Node, *)} f Visitor function.
676
 *    The second parameter is the result of calling 'f' on the parent node.
677
 */
678
CallTree.prototype.traverse = function(f) {
679
  var pairsToProcess = new ConsArray();
680
  pairsToProcess.concat([{node: this.root_, param: null}]);
681 682
  while (!pairsToProcess.atEnd()) {
    var pair = pairsToProcess.next();
683 684
    var node = pair.node;
    var newParam = f(node, pair.param);
685 686 687 688
    var morePairsToProcess = [];
    node.forEachChild(function (child) {
        morePairsToProcess.push({node: child, param: newParam}); });
    pairsToProcess.concat(morePairsToProcess);
689 690 691 692 693 694 695
  }
};


/**
 * Performs an indepth call graph traversal.
 *
696
 * @param {function(CallTree.Node)} enter A function called
697
 *     prior to visiting node's children.
698
 * @param {function(CallTree.Node)} exit A function called
699 700
 *     after visiting node's children.
 */
701
CallTree.prototype.traverseInDepth = function(enter, exit) {
702 703 704 705 706
  function traverse(node) {
    enter(node);
    node.forEachChild(traverse);
    exit(node);
  }
707
  traverse(this.root_);
708 709 710 711 712 713 714
};


/**
 * Constructs a call graph node.
 *
 * @param {string} label Node label.
715
 * @param {CallTree.Node} opt_parent Node parent.
716
 */
717
CallTree.Node = function(label, opt_parent) {
718 719 720 721 722 723 724 725 726 727 728
  this.label = label;
  this.parent = opt_parent;
  this.children = {};
};


/**
 * Node self weight (how many times this node was the last node in
 * a call path).
 * @type {number}
 */
729
CallTree.Node.prototype.selfWeight = 0;
730 731 732 733 734 735


/**
 * Node total weight (includes weights of all children).
 * @type {number}
 */
736
CallTree.Node.prototype.totalWeight = 0;
737 738 739 740 741 742 743


/**
 * Adds a child node.
 *
 * @param {string} label Child node label.
 */
744 745
CallTree.Node.prototype.addChild = function(label) {
  var child = new CallTree.Node(label, this);
746 747 748 749 750 751 752 753
  this.children[label] = child;
  return child;
};


/**
 * Computes node's total weight.
 */
754
CallTree.Node.prototype.computeTotalWeight =
755 756 757 758 759 760 761 762 763 764 765
    function() {
  var totalWeight = this.selfWeight;
  this.forEachChild(function(child) {
      totalWeight += child.computeTotalWeight(); });
  return this.totalWeight = totalWeight;
};


/**
 * Returns all node's children as an array.
 */
766
CallTree.Node.prototype.exportChildren = function() {
767 768 769 770 771 772 773 774 775 776 777
  var result = [];
  this.forEachChild(function (node) { result.push(node); });
  return result;
};


/**
 * Finds an immediate child with the specified label.
 *
 * @param {string} label Child node label.
 */
778
CallTree.Node.prototype.findChild = function(label) {
779 780 781 782 783 784 785 786 787 788
  return this.children[label] || null;
};


/**
 * Finds an immediate child with the specified label, creates a child
 * node if necessary.
 *
 * @param {string} label Child node label.
 */
789
CallTree.Node.prototype.findOrAddChild = function(label) {
790 791 792 793 794 795 796
  return this.findChild(label) || this.addChild(label);
};


/**
 * Calls the specified function for every child.
 *
797
 * @param {function(CallTree.Node)} f Visitor function.
798
 */
799
CallTree.Node.prototype.forEachChild = function(f) {
800 801 802 803 804 805 806 807 808
  for (var c in this.children) {
    f(this.children[c]);
  }
};


/**
 * Walks up from the current node up to the call tree root.
 *
809
 * @param {function(CallTree.Node)} f Visitor function.
810
 */
811
CallTree.Node.prototype.walkUpToRoot = function(f) {
812 813 814 815 816 817 818 819 820 821
  for (var curr = this; curr != null; curr = curr.parent) {
    f(curr);
  }
};


/**
 * Tries to find a node with the specified path.
 *
 * @param {Array<string>} labels The path.
822
 * @param {function(CallTree.Node)} opt_f Visitor function.
823
 */
824
CallTree.Node.prototype.descendToChild = function(
825 826 827 828 829 830 831 832 833 834
    labels, opt_f) {
  for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
    var child = curr.findChild(labels[pos]);
    if (opt_f) {
      opt_f(child, pos);
    }
    curr = child;
  }
  return curr;
};