profile.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. // 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. * Creates a profile object for processing profiling-related events
  29. * and calculating function execution times.
  30. *
  31. * @constructor
  32. */
  33. function Profile() {
  34. this.codeMap_ = new CodeMap();
  35. this.topDownTree_ = new CallTree();
  36. this.bottomUpTree_ = new CallTree();
  37. };
  38. /**
  39. * Returns whether a function with the specified name must be skipped.
  40. * Should be overriden by subclasses.
  41. *
  42. * @param {string} name Function name.
  43. */
  44. Profile.prototype.skipThisFunction = function(name) {
  45. return false;
  46. };
  47. /**
  48. * Enum for profiler operations that involve looking up existing
  49. * code entries.
  50. *
  51. * @enum {number}
  52. */
  53. Profile.Operation = {
  54. MOVE: 0,
  55. DELETE: 1,
  56. TICK: 2
  57. };
  58. /**
  59. * Enum for code state regarding its dynamic optimization.
  60. *
  61. * @enum {number}
  62. */
  63. Profile.CodeState = {
  64. COMPILED: 0,
  65. OPTIMIZABLE: 1,
  66. OPTIMIZED: 2
  67. };
  68. /**
  69. * Called whenever the specified operation has failed finding a function
  70. * containing the specified address. Should be overriden by subclasses.
  71. * See the Profile.Operation enum for the list of
  72. * possible operations.
  73. *
  74. * @param {number} operation Operation.
  75. * @param {number} addr Address of the unknown code.
  76. * @param {number} opt_stackPos If an unknown address is encountered
  77. * during stack strace processing, specifies a position of the frame
  78. * containing the address.
  79. */
  80. Profile.prototype.handleUnknownCode = function(
  81. operation, addr, opt_stackPos) {
  82. };
  83. /**
  84. * Registers a library.
  85. *
  86. * @param {string} name Code entry name.
  87. * @param {number} startAddr Starting address.
  88. * @param {number} endAddr Ending address.
  89. */
  90. Profile.prototype.addLibrary = function(
  91. name, startAddr, endAddr) {
  92. var entry = new CodeMap.CodeEntry(
  93. endAddr - startAddr, name);
  94. this.codeMap_.addLibrary(startAddr, entry);
  95. return entry;
  96. };
  97. /**
  98. * Registers statically compiled code entry.
  99. *
  100. * @param {string} name Code entry name.
  101. * @param {number} startAddr Starting address.
  102. * @param {number} endAddr Ending address.
  103. */
  104. Profile.prototype.addStaticCode = function(
  105. name, startAddr, endAddr) {
  106. var entry = new CodeMap.CodeEntry(
  107. endAddr - startAddr, name);
  108. this.codeMap_.addStaticCode(startAddr, entry);
  109. return entry;
  110. };
  111. /**
  112. * Registers dynamic (JIT-compiled) code entry.
  113. *
  114. * @param {string} type Code entry type.
  115. * @param {string} name Code entry name.
  116. * @param {number} start Starting address.
  117. * @param {number} size Code entry size.
  118. */
  119. Profile.prototype.addCode = function(
  120. type, name, start, size) {
  121. var entry = new Profile.DynamicCodeEntry(size, type, name);
  122. this.codeMap_.addCode(start, entry);
  123. return entry;
  124. };
  125. /**
  126. * Registers dynamic (JIT-compiled) code entry.
  127. *
  128. * @param {string} type Code entry type.
  129. * @param {string} name Code entry name.
  130. * @param {number} start Starting address.
  131. * @param {number} size Code entry size.
  132. * @param {number} funcAddr Shared function object address.
  133. * @param {Profile.CodeState} state Optimization state.
  134. */
  135. Profile.prototype.addFuncCode = function(
  136. type, name, start, size, funcAddr, state) {
  137. // As code and functions are in the same address space,
  138. // it is safe to put them in a single code map.
  139. var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
  140. if (!func) {
  141. func = new Profile.FunctionEntry(name);
  142. this.codeMap_.addCode(funcAddr, func);
  143. } else if (func.name !== name) {
  144. // Function object has been overwritten with a new one.
  145. func.name = name;
  146. }
  147. var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
  148. if (entry) {
  149. if (entry.size === size && entry.func === func) {
  150. // Entry state has changed.
  151. entry.state = state;
  152. }
  153. } else {
  154. entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
  155. this.codeMap_.addCode(start, entry);
  156. }
  157. return entry;
  158. };
  159. /**
  160. * Reports about moving of a dynamic code entry.
  161. *
  162. * @param {number} from Current code entry address.
  163. * @param {number} to New code entry address.
  164. */
  165. Profile.prototype.moveCode = function(from, to) {
  166. try {
  167. this.codeMap_.moveCode(from, to);
  168. } catch (e) {
  169. this.handleUnknownCode(Profile.Operation.MOVE, from);
  170. }
  171. };
  172. /**
  173. * Reports about deletion of a dynamic code entry.
  174. *
  175. * @param {number} start Starting address.
  176. */
  177. Profile.prototype.deleteCode = function(start) {
  178. try {
  179. this.codeMap_.deleteCode(start);
  180. } catch (e) {
  181. this.handleUnknownCode(Profile.Operation.DELETE, start);
  182. }
  183. };
  184. /**
  185. * Reports about moving of a dynamic code entry.
  186. *
  187. * @param {number} from Current code entry address.
  188. * @param {number} to New code entry address.
  189. */
  190. Profile.prototype.moveFunc = function(from, to) {
  191. if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
  192. this.codeMap_.moveCode(from, to);
  193. }
  194. };
  195. /**
  196. * Retrieves a code entry by an address.
  197. *
  198. * @param {number} addr Entry address.
  199. */
  200. Profile.prototype.findEntry = function(addr) {
  201. return this.codeMap_.findEntry(addr);
  202. };
  203. /**
  204. * Records a tick event. Stack must contain a sequence of
  205. * addresses starting with the program counter value.
  206. *
  207. * @param {Array<number>} stack Stack sample.
  208. */
  209. Profile.prototype.recordTick = function(stack) {
  210. var processedStack = this.resolveAndFilterFuncs_(stack);
  211. this.bottomUpTree_.addPath(processedStack);
  212. processedStack.reverse();
  213. this.topDownTree_.addPath(processedStack);
  214. };
  215. /**
  216. * Translates addresses into function names and filters unneeded
  217. * functions.
  218. *
  219. * @param {Array<number>} stack Stack sample.
  220. */
  221. Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
  222. var result = [];
  223. for (var i = 0; i < stack.length; ++i) {
  224. var entry = this.codeMap_.findEntry(stack[i]);
  225. if (entry) {
  226. var name = entry.getName();
  227. if (!this.skipThisFunction(name)) {
  228. result.push(name);
  229. }
  230. } else {
  231. this.handleUnknownCode(
  232. Profile.Operation.TICK, stack[i], i);
  233. }
  234. }
  235. return result;
  236. };
  237. /**
  238. * Performs a BF traversal of the top down call graph.
  239. *
  240. * @param {function(CallTree.Node)} f Visitor function.
  241. */
  242. Profile.prototype.traverseTopDownTree = function(f) {
  243. this.topDownTree_.traverse(f);
  244. };
  245. /**
  246. * Performs a BF traversal of the bottom up call graph.
  247. *
  248. * @param {function(CallTree.Node)} f Visitor function.
  249. */
  250. Profile.prototype.traverseBottomUpTree = function(f) {
  251. this.bottomUpTree_.traverse(f);
  252. };
  253. /**
  254. * Calculates a top down profile for a node with the specified label.
  255. * If no name specified, returns the whole top down calls tree.
  256. *
  257. * @param {string} opt_label Node label.
  258. */
  259. Profile.prototype.getTopDownProfile = function(opt_label) {
  260. return this.getTreeProfile_(this.topDownTree_, opt_label);
  261. };
  262. /**
  263. * Calculates a bottom up profile for a node with the specified label.
  264. * If no name specified, returns the whole bottom up calls tree.
  265. *
  266. * @param {string} opt_label Node label.
  267. */
  268. Profile.prototype.getBottomUpProfile = function(opt_label) {
  269. return this.getTreeProfile_(this.bottomUpTree_, opt_label);
  270. };
  271. /**
  272. * Helper function for calculating a tree profile.
  273. *
  274. * @param {Profile.CallTree} tree Call tree.
  275. * @param {string} opt_label Node label.
  276. */
  277. Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
  278. if (!opt_label) {
  279. tree.computeTotalWeights();
  280. return tree;
  281. } else {
  282. var subTree = tree.cloneSubtree(opt_label);
  283. subTree.computeTotalWeights();
  284. return subTree;
  285. }
  286. };
  287. /**
  288. * Calculates a flat profile of callees starting from a node with
  289. * the specified label. If no name specified, starts from the root.
  290. *
  291. * @param {string} opt_label Starting node label.
  292. */
  293. Profile.prototype.getFlatProfile = function(opt_label) {
  294. var counters = new CallTree();
  295. var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
  296. var precs = {};
  297. precs[rootLabel] = 0;
  298. var root = counters.findOrAddChild(rootLabel);
  299. this.topDownTree_.computeTotalWeights();
  300. this.topDownTree_.traverseInDepth(
  301. function onEnter(node) {
  302. if (!(node.label in precs)) {
  303. precs[node.label] = 0;
  304. }
  305. var nodeLabelIsRootLabel = node.label == rootLabel;
  306. if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
  307. if (precs[rootLabel] == 0) {
  308. root.selfWeight += node.selfWeight;
  309. root.totalWeight += node.totalWeight;
  310. } else {
  311. var rec = root.findOrAddChild(node.label);
  312. rec.selfWeight += node.selfWeight;
  313. if (nodeLabelIsRootLabel || precs[node.label] == 0) {
  314. rec.totalWeight += node.totalWeight;
  315. }
  316. }
  317. precs[node.label]++;
  318. }
  319. },
  320. function onExit(node) {
  321. if (node.label == rootLabel || precs[rootLabel] > 0) {
  322. precs[node.label]--;
  323. }
  324. },
  325. null);
  326. if (!opt_label) {
  327. // If we have created a flat profile for the whole program, we don't
  328. // need an explicit root in it. Thus, replace the counters tree
  329. // root with the node corresponding to the whole program.
  330. counters.root_ = root;
  331. } else {
  332. // Propagate weights so percents can be calculated correctly.
  333. counters.getRoot().selfWeight = root.selfWeight;
  334. counters.getRoot().totalWeight = root.totalWeight;
  335. }
  336. return counters;
  337. };
  338. /**
  339. * Cleans up function entries that are not referenced by code entries.
  340. */
  341. Profile.prototype.cleanUpFuncEntries = function() {
  342. var referencedFuncEntries = [];
  343. var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
  344. for (var i = 0, l = entries.length; i < l; ++i) {
  345. if (entries[i][1].constructor === Profile.FunctionEntry) {
  346. entries[i][1].used = false;
  347. }
  348. }
  349. for (var i = 0, l = entries.length; i < l; ++i) {
  350. if ("func" in entries[i][1]) {
  351. entries[i][1].func.used = true;
  352. }
  353. }
  354. for (var i = 0, l = entries.length; i < l; ++i) {
  355. if (entries[i][1].constructor === Profile.FunctionEntry &&
  356. !entries[i][1].used) {
  357. this.codeMap_.deleteCode(entries[i][0]);
  358. }
  359. }
  360. };
  361. /**
  362. * Creates a dynamic code entry.
  363. *
  364. * @param {number} size Code size.
  365. * @param {string} type Code type.
  366. * @param {string} name Function name.
  367. * @constructor
  368. */
  369. Profile.DynamicCodeEntry = function(size, type, name) {
  370. CodeMap.CodeEntry.call(this, size, name);
  371. this.type = type;
  372. };
  373. /**
  374. * Returns node name.
  375. */
  376. Profile.DynamicCodeEntry.prototype.getName = function() {
  377. return this.type + ': ' + this.name;
  378. };
  379. /**
  380. * Returns raw node name (without type decoration).
  381. */
  382. Profile.DynamicCodeEntry.prototype.getRawName = function() {
  383. return this.name;
  384. };
  385. Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
  386. return false;
  387. };
  388. Profile.DynamicCodeEntry.prototype.toString = function() {
  389. return this.getName() + ': ' + this.size.toString(16);
  390. };
  391. /**
  392. * Creates a dynamic code entry.
  393. *
  394. * @param {number} size Code size.
  395. * @param {string} type Code type.
  396. * @param {Profile.FunctionEntry} func Shared function entry.
  397. * @param {Profile.CodeState} state Code optimization state.
  398. * @constructor
  399. */
  400. Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
  401. CodeMap.CodeEntry.call(this, size);
  402. this.type = type;
  403. this.func = func;
  404. this.state = state;
  405. };
  406. Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];
  407. /**
  408. * Returns node name.
  409. */
  410. Profile.DynamicFuncCodeEntry.prototype.getName = function() {
  411. var name = this.func.getName();
  412. return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name;
  413. };
  414. /**
  415. * Returns raw node name (without type decoration).
  416. */
  417. Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
  418. return this.func.getName();
  419. };
  420. Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
  421. return true;
  422. };
  423. Profile.DynamicFuncCodeEntry.prototype.toString = function() {
  424. return this.getName() + ': ' + this.size.toString(16);
  425. };
  426. /**
  427. * Creates a shared function object entry.
  428. *
  429. * @param {string} name Function name.
  430. * @constructor
  431. */
  432. Profile.FunctionEntry = function(name) {
  433. CodeMap.CodeEntry.call(this, 0, name);
  434. };
  435. /**
  436. * Returns node name.
  437. */
  438. Profile.FunctionEntry.prototype.getName = function() {
  439. var name = this.name;
  440. if (name.length == 0) {
  441. name = '<anonymous>';
  442. } else if (name.charAt(0) == ' ') {
  443. // An anonymous function with location: " aaa.js:10".
  444. name = '<anonymous>' + name;
  445. }
  446. return name;
  447. };
  448. Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
  449. /**
  450. * Constructs a call graph.
  451. *
  452. * @constructor
  453. */
  454. function CallTree() {
  455. this.root_ = new CallTree.Node(
  456. CallTree.ROOT_NODE_LABEL);
  457. };
  458. /**
  459. * The label of the root node.
  460. */
  461. CallTree.ROOT_NODE_LABEL = '';
  462. /**
  463. * @private
  464. */
  465. CallTree.prototype.totalsComputed_ = false;
  466. /**
  467. * Returns the tree root.
  468. */
  469. CallTree.prototype.getRoot = function() {
  470. return this.root_;
  471. };
  472. /**
  473. * Adds the specified call path, constructing nodes as necessary.
  474. *
  475. * @param {Array<string>} path Call path.
  476. */
  477. CallTree.prototype.addPath = function(path) {
  478. if (path.length == 0) {
  479. return;
  480. }
  481. var curr = this.root_;
  482. for (var i = 0; i < path.length; ++i) {
  483. curr = curr.findOrAddChild(path[i]);
  484. }
  485. curr.selfWeight++;
  486. this.totalsComputed_ = false;
  487. };
  488. /**
  489. * Finds an immediate child of the specified parent with the specified
  490. * label, creates a child node if necessary. If a parent node isn't
  491. * specified, uses tree root.
  492. *
  493. * @param {string} label Child node label.
  494. */
  495. CallTree.prototype.findOrAddChild = function(label) {
  496. return this.root_.findOrAddChild(label);
  497. };
  498. /**
  499. * Creates a subtree by cloning and merging all subtrees rooted at nodes
  500. * with a given label. E.g. cloning the following call tree on label 'A'
  501. * will give the following result:
  502. *
  503. * <A>--<B> <B>
  504. * / /
  505. * <root> == clone on 'A' ==> <root>--<A>
  506. * \ \
  507. * <C>--<A>--<D> <D>
  508. *
  509. * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
  510. * source call tree.
  511. *
  512. * @param {string} label The label of the new root node.
  513. */
  514. CallTree.prototype.cloneSubtree = function(label) {
  515. var subTree = new CallTree();
  516. this.traverse(function(node, parent) {
  517. if (!parent && node.label != label) {
  518. return null;
  519. }
  520. var child = (parent ? parent : subTree).findOrAddChild(node.label);
  521. child.selfWeight += node.selfWeight;
  522. return child;
  523. });
  524. return subTree;
  525. };
  526. /**
  527. * Computes total weights in the call graph.
  528. */
  529. CallTree.prototype.computeTotalWeights = function() {
  530. if (this.totalsComputed_) {
  531. return;
  532. }
  533. this.root_.computeTotalWeight();
  534. this.totalsComputed_ = true;
  535. };
  536. /**
  537. * Traverses the call graph in preorder. This function can be used for
  538. * building optionally modified tree clones. This is the boilerplate code
  539. * for this scenario:
  540. *
  541. * callTree.traverse(function(node, parentClone) {
  542. * var nodeClone = cloneNode(node);
  543. * if (parentClone)
  544. * parentClone.addChild(nodeClone);
  545. * return nodeClone;
  546. * });
  547. *
  548. * @param {function(CallTree.Node, *)} f Visitor function.
  549. * The second parameter is the result of calling 'f' on the parent node.
  550. */
  551. CallTree.prototype.traverse = function(f) {
  552. var pairsToProcess = new ConsArray();
  553. pairsToProcess.concat([{node: this.root_, param: null}]);
  554. while (!pairsToProcess.atEnd()) {
  555. var pair = pairsToProcess.next();
  556. var node = pair.node;
  557. var newParam = f(node, pair.param);
  558. var morePairsToProcess = [];
  559. node.forEachChild(function (child) {
  560. morePairsToProcess.push({node: child, param: newParam}); });
  561. pairsToProcess.concat(morePairsToProcess);
  562. }
  563. };
  564. /**
  565. * Performs an indepth call graph traversal.
  566. *
  567. * @param {function(CallTree.Node)} enter A function called
  568. * prior to visiting node's children.
  569. * @param {function(CallTree.Node)} exit A function called
  570. * after visiting node's children.
  571. */
  572. CallTree.prototype.traverseInDepth = function(enter, exit) {
  573. function traverse(node) {
  574. enter(node);
  575. node.forEachChild(traverse);
  576. exit(node);
  577. }
  578. traverse(this.root_);
  579. };
  580. /**
  581. * Constructs a call graph node.
  582. *
  583. * @param {string} label Node label.
  584. * @param {CallTree.Node} opt_parent Node parent.
  585. */
  586. CallTree.Node = function(label, opt_parent) {
  587. this.label = label;
  588. this.parent = opt_parent;
  589. this.children = {};
  590. };
  591. /**
  592. * Node self weight (how many times this node was the last node in
  593. * a call path).
  594. * @type {number}
  595. */
  596. CallTree.Node.prototype.selfWeight = 0;
  597. /**
  598. * Node total weight (includes weights of all children).
  599. * @type {number}
  600. */
  601. CallTree.Node.prototype.totalWeight = 0;
  602. /**
  603. * Adds a child node.
  604. *
  605. * @param {string} label Child node label.
  606. */
  607. CallTree.Node.prototype.addChild = function(label) {
  608. var child = new CallTree.Node(label, this);
  609. this.children[label] = child;
  610. return child;
  611. };
  612. /**
  613. * Computes node's total weight.
  614. */
  615. CallTree.Node.prototype.computeTotalWeight =
  616. function() {
  617. var totalWeight = this.selfWeight;
  618. this.forEachChild(function(child) {
  619. totalWeight += child.computeTotalWeight(); });
  620. return this.totalWeight = totalWeight;
  621. };
  622. /**
  623. * Returns all node's children as an array.
  624. */
  625. CallTree.Node.prototype.exportChildren = function() {
  626. var result = [];
  627. this.forEachChild(function (node) { result.push(node); });
  628. return result;
  629. };
  630. /**
  631. * Finds an immediate child with the specified label.
  632. *
  633. * @param {string} label Child node label.
  634. */
  635. CallTree.Node.prototype.findChild = function(label) {
  636. return this.children[label] || null;
  637. };
  638. /**
  639. * Finds an immediate child with the specified label, creates a child
  640. * node if necessary.
  641. *
  642. * @param {string} label Child node label.
  643. */
  644. CallTree.Node.prototype.findOrAddChild = function(label) {
  645. return this.findChild(label) || this.addChild(label);
  646. };
  647. /**
  648. * Calls the specified function for every child.
  649. *
  650. * @param {function(CallTree.Node)} f Visitor function.
  651. */
  652. CallTree.Node.prototype.forEachChild = function(f) {
  653. for (var c in this.children) {
  654. f(this.children[c]);
  655. }
  656. };
  657. /**
  658. * Walks up from the current node up to the call tree root.
  659. *
  660. * @param {function(CallTree.Node)} f Visitor function.
  661. */
  662. CallTree.Node.prototype.walkUpToRoot = function(f) {
  663. for (var curr = this; curr != null; curr = curr.parent) {
  664. f(curr);
  665. }
  666. };
  667. /**
  668. * Tries to find a node with the specified path.
  669. *
  670. * @param {Array<string>} labels The path.
  671. * @param {function(CallTree.Node)} opt_f Visitor function.
  672. */
  673. CallTree.Node.prototype.descendToChild = function(
  674. labels, opt_f) {
  675. for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
  676. var child = curr.findChild(labels[pos]);
  677. if (opt_f) {
  678. opt_f(child, pos);
  679. }
  680. curr = child;
  681. }
  682. return curr;
  683. };