• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
29/**
30 * Creates a profile object for processing profiling-related events
31 * and calculating function execution times.
32 *
33 * @constructor
34 */
35function Profile() {
36  this.codeMap_ = new CodeMap();
37  this.topDownTree_ = new CallTree();
38  this.bottomUpTree_ = new CallTree();
39  this.c_entries_ = {};
40  this.ticks_ = [];
41};
42
43
44/**
45 * Returns whether a function with the specified name must be skipped.
46 * Should be overriden by subclasses.
47 *
48 * @param {string} name Function name.
49 */
50Profile.prototype.skipThisFunction = function(name) {
51  return false;
52};
53
54
55/**
56 * Enum for profiler operations that involve looking up existing
57 * code entries.
58 *
59 * @enum {number}
60 */
61Profile.Operation = {
62  MOVE: 0,
63  DELETE: 1,
64  TICK: 2
65};
66
67
68/**
69 * Enum for code state regarding its dynamic optimization.
70 *
71 * @enum {number}
72 */
73Profile.CodeState = {
74  COMPILED: 0,
75  OPTIMIZABLE: 1,
76  OPTIMIZED: 2
77};
78
79
80/**
81 * Called whenever the specified operation has failed finding a function
82 * containing the specified address. Should be overriden by subclasses.
83 * See the Profile.Operation enum for the list of
84 * possible operations.
85 *
86 * @param {number} operation Operation.
87 * @param {number} addr Address of the unknown code.
88 * @param {number} opt_stackPos If an unknown address is encountered
89 *     during stack strace processing, specifies a position of the frame
90 *     containing the address.
91 */
92Profile.prototype.handleUnknownCode = function(
93    operation, addr, opt_stackPos) {
94};
95
96
97/**
98 * Registers a library.
99 *
100 * @param {string} name Code entry name.
101 * @param {number} startAddr Starting address.
102 * @param {number} endAddr Ending address.
103 */
104Profile.prototype.addLibrary = function(
105    name, startAddr, endAddr) {
106  var entry = new CodeMap.CodeEntry(
107      endAddr - startAddr, name, 'SHARED_LIB');
108  this.codeMap_.addLibrary(startAddr, entry);
109  return entry;
110};
111
112
113/**
114 * Registers statically compiled code entry.
115 *
116 * @param {string} name Code entry name.
117 * @param {number} startAddr Starting address.
118 * @param {number} endAddr Ending address.
119 */
120Profile.prototype.addStaticCode = function(
121    name, startAddr, endAddr) {
122  var entry = new CodeMap.CodeEntry(
123      endAddr - startAddr, name, 'CPP');
124  this.codeMap_.addStaticCode(startAddr, entry);
125  return entry;
126};
127
128
129/**
130 * Registers dynamic (JIT-compiled) code entry.
131 *
132 * @param {string} type Code entry type.
133 * @param {string} name Code entry name.
134 * @param {number} start Starting address.
135 * @param {number} size Code entry size.
136 */
137Profile.prototype.addCode = function(
138    type, name, timestamp, start, size) {
139  var entry = new Profile.DynamicCodeEntry(size, type, name);
140  this.codeMap_.addCode(start, entry);
141  return entry;
142};
143
144
145/**
146 * Registers dynamic (JIT-compiled) code entry.
147 *
148 * @param {string} type Code entry type.
149 * @param {string} name Code entry name.
150 * @param {number} start Starting address.
151 * @param {number} size Code entry size.
152 * @param {number} funcAddr Shared function object address.
153 * @param {Profile.CodeState} state Optimization state.
154 */
155Profile.prototype.addFuncCode = function(
156    type, name, timestamp, start, size, funcAddr, state) {
157  // As code and functions are in the same address space,
158  // it is safe to put them in a single code map.
159  var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
160  if (!func) {
161    func = new Profile.FunctionEntry(name);
162    this.codeMap_.addCode(funcAddr, func);
163  } else if (func.name !== name) {
164    // Function object has been overwritten with a new one.
165    func.name = name;
166  }
167  var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
168  if (entry) {
169    if (entry.size === size && entry.func === func) {
170      // Entry state has changed.
171      entry.state = state;
172    }
173  } else {
174    entry = new Profile.DynamicFuncCodeEntry(size, type, func, state);
175    this.codeMap_.addCode(start, entry);
176  }
177  return entry;
178};
179
180
181/**
182 * Reports about moving of a dynamic code entry.
183 *
184 * @param {number} from Current code entry address.
185 * @param {number} to New code entry address.
186 */
187Profile.prototype.moveCode = function(from, to) {
188  try {
189    this.codeMap_.moveCode(from, to);
190  } catch (e) {
191    this.handleUnknownCode(Profile.Operation.MOVE, from);
192  }
193};
194
195Profile.prototype.deoptCode = function(
196    timestamp, code, inliningId, scriptOffset, bailoutType,
197    sourcePositionText, deoptReasonText) {
198};
199
200/**
201 * Reports about deletion of a dynamic code entry.
202 *
203 * @param {number} start Starting address.
204 */
205Profile.prototype.deleteCode = function(start) {
206  try {
207    this.codeMap_.deleteCode(start);
208  } catch (e) {
209    this.handleUnknownCode(Profile.Operation.DELETE, start);
210  }
211};
212
213/**
214 * Adds source positions for given code.
215 */
216Profile.prototype.addSourcePositions = function(
217    start, script, startPos, endPos, sourcePositions, inliningPositions,
218    inlinedFunctions) {
219  // CLI does not need source code => ignore.
220};
221
222/**
223 * Adds script source code.
224 */
225Profile.prototype.addScriptSource = function(script, source) {
226  // CLI does not need source code => ignore.
227};
228
229/**
230 * Reports about moving of a dynamic code entry.
231 *
232 * @param {number} from Current code entry address.
233 * @param {number} to New code entry address.
234 */
235Profile.prototype.moveFunc = function(from, to) {
236  if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
237    this.codeMap_.moveCode(from, to);
238  }
239};
240
241
242/**
243 * Retrieves a code entry by an address.
244 *
245 * @param {number} addr Entry address.
246 */
247Profile.prototype.findEntry = function(addr) {
248  return this.codeMap_.findEntry(addr);
249};
250
251
252/**
253 * Records a tick event. Stack must contain a sequence of
254 * addresses starting with the program counter value.
255 *
256 * @param {Array<number>} stack Stack sample.
257 */
258Profile.prototype.recordTick = function(time_ns, vmState, stack) {
259  var processedStack = this.resolveAndFilterFuncs_(stack);
260  this.bottomUpTree_.addPath(processedStack);
261  processedStack.reverse();
262  this.topDownTree_.addPath(processedStack);
263};
264
265
266/**
267 * Translates addresses into function names and filters unneeded
268 * functions.
269 *
270 * @param {Array<number>} stack Stack sample.
271 */
272Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
273  var result = [];
274  var last_seen_c_function = '';
275  var look_for_first_c_function = false;
276  for (var i = 0; i < stack.length; ++i) {
277    var entry = this.codeMap_.findEntry(stack[i]);
278    if (entry) {
279      var name = entry.getName();
280      if (i === 0 && (entry.type === 'CPP' || entry.type === 'SHARED_LIB')) {
281        look_for_first_c_function = true;
282      }
283      if (look_for_first_c_function && entry.type === 'CPP') {
284        last_seen_c_function = name;
285      }
286      if (!this.skipThisFunction(name)) {
287        result.push(name);
288      }
289    } else {
290      this.handleUnknownCode(Profile.Operation.TICK, stack[i], i);
291      if (i === 0) result.push("UNKNOWN");
292    }
293    if (look_for_first_c_function &&
294        i > 0 &&
295        (!entry || entry.type !== 'CPP') &&
296        last_seen_c_function !== '') {
297      if (this.c_entries_[last_seen_c_function] === undefined) {
298        this.c_entries_[last_seen_c_function] = 0;
299      }
300      this.c_entries_[last_seen_c_function]++;
301      look_for_first_c_function = false;  // Found it, we're done.
302    }
303  }
304  return result;
305};
306
307
308/**
309 * Performs a BF traversal of the top down call graph.
310 *
311 * @param {function(CallTree.Node)} f Visitor function.
312 */
313Profile.prototype.traverseTopDownTree = function(f) {
314  this.topDownTree_.traverse(f);
315};
316
317
318/**
319 * Performs a BF traversal of the bottom up call graph.
320 *
321 * @param {function(CallTree.Node)} f Visitor function.
322 */
323Profile.prototype.traverseBottomUpTree = function(f) {
324  this.bottomUpTree_.traverse(f);
325};
326
327
328/**
329 * Calculates a top down profile for a node with the specified label.
330 * If no name specified, returns the whole top down calls tree.
331 *
332 * @param {string} opt_label Node label.
333 */
334Profile.prototype.getTopDownProfile = function(opt_label) {
335  return this.getTreeProfile_(this.topDownTree_, opt_label);
336};
337
338
339/**
340 * Calculates a bottom up profile for a node with the specified label.
341 * If no name specified, returns the whole bottom up calls tree.
342 *
343 * @param {string} opt_label Node label.
344 */
345Profile.prototype.getBottomUpProfile = function(opt_label) {
346  return this.getTreeProfile_(this.bottomUpTree_, opt_label);
347};
348
349
350/**
351 * Helper function for calculating a tree profile.
352 *
353 * @param {Profile.CallTree} tree Call tree.
354 * @param {string} opt_label Node label.
355 */
356Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
357  if (!opt_label) {
358    tree.computeTotalWeights();
359    return tree;
360  } else {
361    var subTree = tree.cloneSubtree(opt_label);
362    subTree.computeTotalWeights();
363    return subTree;
364  }
365};
366
367
368/**
369 * Calculates a flat profile of callees starting from a node with
370 * the specified label. If no name specified, starts from the root.
371 *
372 * @param {string} opt_label Starting node label.
373 */
374Profile.prototype.getFlatProfile = function(opt_label) {
375  var counters = new CallTree();
376  var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
377  var precs = {};
378  precs[rootLabel] = 0;
379  var root = counters.findOrAddChild(rootLabel);
380
381  this.topDownTree_.computeTotalWeights();
382  this.topDownTree_.traverseInDepth(
383    function onEnter(node) {
384      if (!(node.label in precs)) {
385        precs[node.label] = 0;
386      }
387      var nodeLabelIsRootLabel = node.label == rootLabel;
388      if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
389        if (precs[rootLabel] == 0) {
390          root.selfWeight += node.selfWeight;
391          root.totalWeight += node.totalWeight;
392        } else {
393          var rec = root.findOrAddChild(node.label);
394          rec.selfWeight += node.selfWeight;
395          if (nodeLabelIsRootLabel || precs[node.label] == 0) {
396            rec.totalWeight += node.totalWeight;
397          }
398        }
399        precs[node.label]++;
400      }
401    },
402    function onExit(node) {
403      if (node.label == rootLabel || precs[rootLabel] > 0) {
404        precs[node.label]--;
405      }
406    },
407    null);
408
409  if (!opt_label) {
410    // If we have created a flat profile for the whole program, we don't
411    // need an explicit root in it. Thus, replace the counters tree
412    // root with the node corresponding to the whole program.
413    counters.root_ = root;
414  } else {
415    // Propagate weights so percents can be calculated correctly.
416    counters.getRoot().selfWeight = root.selfWeight;
417    counters.getRoot().totalWeight = root.totalWeight;
418  }
419  return counters;
420};
421
422
423Profile.CEntryNode = function(name, ticks) {
424  this.name = name;
425  this.ticks = ticks;
426}
427
428
429Profile.prototype.getCEntryProfile = function() {
430  var result = [new Profile.CEntryNode("TOTAL", 0)];
431  var total_ticks = 0;
432  for (var f in this.c_entries_) {
433    var ticks = this.c_entries_[f];
434    total_ticks += ticks;
435    result.push(new Profile.CEntryNode(f, ticks));
436  }
437  result[0].ticks = total_ticks;  // Sorting will keep this at index 0.
438  result.sort(function(n1, n2) {
439    return n2.ticks - n1.ticks || (n2.name < n1.name ? -1 : 1)
440  });
441  return result;
442}
443
444
445/**
446 * Cleans up function entries that are not referenced by code entries.
447 */
448Profile.prototype.cleanUpFuncEntries = function() {
449  var referencedFuncEntries = [];
450  var entries = this.codeMap_.getAllDynamicEntriesWithAddresses();
451  for (var i = 0, l = entries.length; i < l; ++i) {
452    if (entries[i][1].constructor === Profile.FunctionEntry) {
453      entries[i][1].used = false;
454    }
455  }
456  for (var i = 0, l = entries.length; i < l; ++i) {
457    if ("func" in entries[i][1]) {
458      entries[i][1].func.used = true;
459    }
460  }
461  for (var i = 0, l = entries.length; i < l; ++i) {
462    if (entries[i][1].constructor === Profile.FunctionEntry &&
463        !entries[i][1].used) {
464      this.codeMap_.deleteCode(entries[i][0]);
465    }
466  }
467};
468
469
470/**
471 * Creates a dynamic code entry.
472 *
473 * @param {number} size Code size.
474 * @param {string} type Code type.
475 * @param {string} name Function name.
476 * @constructor
477 */
478Profile.DynamicCodeEntry = function(size, type, name) {
479  CodeMap.CodeEntry.call(this, size, name, type);
480};
481
482
483/**
484 * Returns node name.
485 */
486Profile.DynamicCodeEntry.prototype.getName = function() {
487  return this.type + ': ' + this.name;
488};
489
490
491/**
492 * Returns raw node name (without type decoration).
493 */
494Profile.DynamicCodeEntry.prototype.getRawName = function() {
495  return this.name;
496};
497
498
499Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
500  return false;
501};
502
503
504Profile.DynamicCodeEntry.prototype.toString = function() {
505  return this.getName() + ': ' + this.size.toString(16);
506};
507
508
509/**
510 * Creates a dynamic code entry.
511 *
512 * @param {number} size Code size.
513 * @param {string} type Code type.
514 * @param {Profile.FunctionEntry} func Shared function entry.
515 * @param {Profile.CodeState} state Code optimization state.
516 * @constructor
517 */
518Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
519  CodeMap.CodeEntry.call(this, size, '', type);
520  this.func = func;
521  this.state = state;
522};
523
524Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];
525
526/**
527 * Returns state.
528 */
529Profile.DynamicFuncCodeEntry.prototype.getState = function() {
530  return Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state];
531};
532
533/**
534 * Returns node name.
535 */
536Profile.DynamicFuncCodeEntry.prototype.getName = function() {
537  var name = this.func.getName();
538  return this.type + ': ' + this.getState() + name;
539};
540
541
542/**
543 * Returns raw node name (without type decoration).
544 */
545Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
546  return this.func.getName();
547};
548
549
550Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
551  return true;
552};
553
554
555Profile.DynamicFuncCodeEntry.prototype.toString = function() {
556  return this.getName() + ': ' + this.size.toString(16);
557};
558
559
560/**
561 * Creates a shared function object entry.
562 *
563 * @param {string} name Function name.
564 * @constructor
565 */
566Profile.FunctionEntry = function(name) {
567  CodeMap.CodeEntry.call(this, 0, name);
568};
569
570
571/**
572 * Returns node name.
573 */
574Profile.FunctionEntry.prototype.getName = function() {
575  var name = this.name;
576  if (name.length == 0) {
577    name = '<anonymous>';
578  } else if (name.charAt(0) == ' ') {
579    // An anonymous function with location: " aaa.js:10".
580    name = '<anonymous>' + name;
581  }
582  return name;
583};
584
585Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString;
586
587/**
588 * Constructs a call graph.
589 *
590 * @constructor
591 */
592function CallTree() {
593  this.root_ = new CallTree.Node(
594      CallTree.ROOT_NODE_LABEL);
595};
596
597
598/**
599 * The label of the root node.
600 */
601CallTree.ROOT_NODE_LABEL = '';
602
603
604/**
605 * @private
606 */
607CallTree.prototype.totalsComputed_ = false;
608
609
610/**
611 * Returns the tree root.
612 */
613CallTree.prototype.getRoot = function() {
614  return this.root_;
615};
616
617
618/**
619 * Adds the specified call path, constructing nodes as necessary.
620 *
621 * @param {Array<string>} path Call path.
622 */
623CallTree.prototype.addPath = function(path) {
624  if (path.length == 0) {
625    return;
626  }
627  var curr = this.root_;
628  for (var i = 0; i < path.length; ++i) {
629    curr = curr.findOrAddChild(path[i]);
630  }
631  curr.selfWeight++;
632  this.totalsComputed_ = false;
633};
634
635
636/**
637 * Finds an immediate child of the specified parent with the specified
638 * label, creates a child node if necessary. If a parent node isn't
639 * specified, uses tree root.
640 *
641 * @param {string} label Child node label.
642 */
643CallTree.prototype.findOrAddChild = function(label) {
644  return this.root_.findOrAddChild(label);
645};
646
647
648/**
649 * Creates a subtree by cloning and merging all subtrees rooted at nodes
650 * with a given label. E.g. cloning the following call tree on label 'A'
651 * will give the following result:
652 *
653 *           <A>--<B>                                     <B>
654 *          /                                            /
655 *     <root>             == clone on 'A' ==>  <root>--<A>
656 *          \                                            \
657 *           <C>--<A>--<D>                                <D>
658 *
659 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
660 * source call tree.
661 *
662 * @param {string} label The label of the new root node.
663 */
664CallTree.prototype.cloneSubtree = function(label) {
665  var subTree = new CallTree();
666  this.traverse(function(node, parent) {
667    if (!parent && node.label != label) {
668      return null;
669    }
670    var child = (parent ? parent : subTree).findOrAddChild(node.label);
671    child.selfWeight += node.selfWeight;
672    return child;
673  });
674  return subTree;
675};
676
677
678/**
679 * Computes total weights in the call graph.
680 */
681CallTree.prototype.computeTotalWeights = function() {
682  if (this.totalsComputed_) {
683    return;
684  }
685  this.root_.computeTotalWeight();
686  this.totalsComputed_ = true;
687};
688
689
690/**
691 * Traverses the call graph in preorder. This function can be used for
692 * building optionally modified tree clones. This is the boilerplate code
693 * for this scenario:
694 *
695 * callTree.traverse(function(node, parentClone) {
696 *   var nodeClone = cloneNode(node);
697 *   if (parentClone)
698 *     parentClone.addChild(nodeClone);
699 *   return nodeClone;
700 * });
701 *
702 * @param {function(CallTree.Node, *)} f Visitor function.
703 *    The second parameter is the result of calling 'f' on the parent node.
704 */
705CallTree.prototype.traverse = function(f) {
706  var pairsToProcess = new ConsArray();
707  pairsToProcess.concat([{node: this.root_, param: null}]);
708  while (!pairsToProcess.atEnd()) {
709    var pair = pairsToProcess.next();
710    var node = pair.node;
711    var newParam = f(node, pair.param);
712    var morePairsToProcess = [];
713    node.forEachChild(function (child) {
714        morePairsToProcess.push({node: child, param: newParam}); });
715    pairsToProcess.concat(morePairsToProcess);
716  }
717};
718
719
720/**
721 * Performs an indepth call graph traversal.
722 *
723 * @param {function(CallTree.Node)} enter A function called
724 *     prior to visiting node's children.
725 * @param {function(CallTree.Node)} exit A function called
726 *     after visiting node's children.
727 */
728CallTree.prototype.traverseInDepth = function(enter, exit) {
729  function traverse(node) {
730    enter(node);
731    node.forEachChild(traverse);
732    exit(node);
733  }
734  traverse(this.root_);
735};
736
737
738/**
739 * Constructs a call graph node.
740 *
741 * @param {string} label Node label.
742 * @param {CallTree.Node} opt_parent Node parent.
743 */
744CallTree.Node = function(label, opt_parent) {
745  this.label = label;
746  this.parent = opt_parent;
747  this.children = {};
748};
749
750
751/**
752 * Node self weight (how many times this node was the last node in
753 * a call path).
754 * @type {number}
755 */
756CallTree.Node.prototype.selfWeight = 0;
757
758
759/**
760 * Node total weight (includes weights of all children).
761 * @type {number}
762 */
763CallTree.Node.prototype.totalWeight = 0;
764
765
766/**
767 * Adds a child node.
768 *
769 * @param {string} label Child node label.
770 */
771CallTree.Node.prototype.addChild = function(label) {
772  var child = new CallTree.Node(label, this);
773  this.children[label] = child;
774  return child;
775};
776
777
778/**
779 * Computes node's total weight.
780 */
781CallTree.Node.prototype.computeTotalWeight =
782    function() {
783  var totalWeight = this.selfWeight;
784  this.forEachChild(function(child) {
785      totalWeight += child.computeTotalWeight(); });
786  return this.totalWeight = totalWeight;
787};
788
789
790/**
791 * Returns all node's children as an array.
792 */
793CallTree.Node.prototype.exportChildren = function() {
794  var result = [];
795  this.forEachChild(function (node) { result.push(node); });
796  return result;
797};
798
799
800/**
801 * Finds an immediate child with the specified label.
802 *
803 * @param {string} label Child node label.
804 */
805CallTree.Node.prototype.findChild = function(label) {
806  return this.children[label] || null;
807};
808
809
810/**
811 * Finds an immediate child with the specified label, creates a child
812 * node if necessary.
813 *
814 * @param {string} label Child node label.
815 */
816CallTree.Node.prototype.findOrAddChild = function(label) {
817  return this.findChild(label) || this.addChild(label);
818};
819
820
821/**
822 * Calls the specified function for every child.
823 *
824 * @param {function(CallTree.Node)} f Visitor function.
825 */
826CallTree.Node.prototype.forEachChild = function(f) {
827  for (var c in this.children) {
828    f(this.children[c]);
829  }
830};
831
832
833/**
834 * Walks up from the current node up to the call tree root.
835 *
836 * @param {function(CallTree.Node)} f Visitor function.
837 */
838CallTree.Node.prototype.walkUpToRoot = function(f) {
839  for (var curr = this; curr != null; curr = curr.parent) {
840    f(curr);
841  }
842};
843
844
845/**
846 * Tries to find a node with the specified path.
847 *
848 * @param {Array<string>} labels The path.
849 * @param {function(CallTree.Node)} opt_f Visitor function.
850 */
851CallTree.Node.prototype.descendToChild = function(
852    labels, opt_f) {
853  for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
854    var child = curr.findChild(labels[pos]);
855    if (opt_f) {
856      opt_f(child, pos);
857    }
858    curr = child;
859  }
860  return curr;
861};
862
863function JsonProfile() {
864  this.codeMap_ = new CodeMap();
865  this.codeEntries_ = [];
866  this.functionEntries_ = [];
867  this.ticks_ = [];
868  this.scripts_ = [];
869}
870
871JsonProfile.prototype.addLibrary = function(
872    name, startAddr, endAddr) {
873  var entry = new CodeMap.CodeEntry(
874      endAddr - startAddr, name, 'SHARED_LIB');
875  this.codeMap_.addLibrary(startAddr, entry);
876
877  entry.codeId = this.codeEntries_.length;
878  this.codeEntries_.push({name : entry.name, type : entry.type});
879  return entry;
880};
881
882JsonProfile.prototype.addStaticCode = function(
883    name, startAddr, endAddr) {
884  var entry = new CodeMap.CodeEntry(
885      endAddr - startAddr, name, 'CPP');
886  this.codeMap_.addStaticCode(startAddr, entry);
887
888  entry.codeId = this.codeEntries_.length;
889  this.codeEntries_.push({name : entry.name, type : entry.type});
890  return entry;
891};
892
893JsonProfile.prototype.addCode = function(
894    kind, name, timestamp, start, size) {
895  var entry = new CodeMap.CodeEntry(size, name, 'CODE');
896  this.codeMap_.addCode(start, entry);
897
898  entry.codeId = this.codeEntries_.length;
899  this.codeEntries_.push({
900    name : entry.name,
901    timestamp: timestamp,
902    type : entry.type,
903    kind : kind
904  });
905
906  return entry;
907};
908
909JsonProfile.prototype.addFuncCode = function(
910    kind, name, timestamp, start, size, funcAddr, state) {
911  // As code and functions are in the same address space,
912  // it is safe to put them in a single code map.
913  var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
914  if (!func) {
915    var func = new CodeMap.CodeEntry(0, name, 'SFI');
916    this.codeMap_.addCode(funcAddr, func);
917
918    func.funcId = this.functionEntries_.length;
919    this.functionEntries_.push({name : name, codes : []});
920  } else if (func.name !== name) {
921    // Function object has been overwritten with a new one.
922    func.name = name;
923
924    func.funcId = this.functionEntries_.length;
925    this.functionEntries_.push({name : name, codes : []});
926  }
927  // TODO(jarin): Insert the code object into the SFI's code list.
928  var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
929  if (entry) {
930    // TODO(jarin) This does not look correct, we should really
931    // update the code object (remove the old one and insert this one).
932    if (entry.size === size && entry.func === func) {
933      // Entry state has changed.
934      entry.state = state;
935    }
936  } else {
937    var entry = new CodeMap.CodeEntry(size, name, 'JS');
938    this.codeMap_.addCode(start, entry);
939
940    entry.codeId = this.codeEntries_.length;
941
942    this.functionEntries_[func.funcId].codes.push(entry.codeId);
943
944    if (state === 0) {
945      kind = "Builtin";
946    } else if (state === 1) {
947      kind = "Unopt";
948    } else if (state === 2) {
949      kind = "Opt";
950    }
951
952    this.codeEntries_.push({
953        name : entry.name,
954        type : entry.type,
955        kind : kind,
956        func : func.funcId,
957        tm : timestamp
958    });
959  }
960  return entry;
961};
962
963JsonProfile.prototype.moveCode = function(from, to) {
964  try {
965    this.codeMap_.moveCode(from, to);
966  } catch (e) {
967    printErr("Move: unknown source " + from);
968  }
969};
970
971JsonProfile.prototype.addSourcePositions = function(
972    start, script, startPos, endPos, sourcePositions, inliningPositions,
973    inlinedFunctions) {
974  var entry = this.codeMap_.findDynamicEntryByStartAddress(start);
975  if (!entry) return;
976  var codeId = entry.codeId;
977
978  // Resolve the inlined fucntions list.
979  if (inlinedFunctions.length > 0) {
980    inlinedFunctions = inlinedFunctions.substring(1).split("S");
981    for (var i = 0; i < inlinedFunctions.length; i++) {
982      var funcAddr = parseInt(inlinedFunctions[i]);
983      var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr);
984      if (!func || func.funcId === undefined) {
985        printErr("Could not find function " + inlinedFunctions[i]);
986        inlinedFunctions[i] = null;
987      } else {
988        inlinedFunctions[i] = func.funcId;
989      }
990    }
991  } else {
992    inlinedFunctions = [];
993  }
994
995  this.codeEntries_[entry.codeId].source = {
996    script : script,
997    start : startPos,
998    end : endPos,
999    positions : sourcePositions,
1000    inlined : inliningPositions,
1001    fns : inlinedFunctions
1002  };
1003};
1004
1005JsonProfile.prototype.addScriptSource = function(script, url, source) {
1006  this.scripts_[script] = {
1007    name : url,
1008    source : source
1009  };
1010};
1011
1012
1013JsonProfile.prototype.deoptCode = function(
1014    timestamp, code, inliningId, scriptOffset, bailoutType,
1015    sourcePositionText, deoptReasonText) {
1016  let entry = this.codeMap_.findDynamicEntryByStartAddress(code);
1017  if (entry) {
1018    let codeId = entry.codeId;
1019    if (!this.codeEntries_[codeId].deopt) {
1020      // Only add the deopt if there was no deopt before.
1021      // The subsequent deoptimizations should be lazy deopts for
1022      // other on-stack activations.
1023      this.codeEntries_[codeId].deopt = {
1024          tm : timestamp,
1025          inliningId : inliningId,
1026          scriptOffset : scriptOffset,
1027          posText : sourcePositionText,
1028          reason : deoptReasonText,
1029          bailoutType : bailoutType
1030      };
1031    }
1032  }
1033};
1034
1035JsonProfile.prototype.deleteCode = function(start) {
1036  try {
1037    this.codeMap_.deleteCode(start);
1038  } catch (e) {
1039    printErr("Delete: unknown address " + start);
1040  }
1041};
1042
1043JsonProfile.prototype.moveFunc = function(from, to) {
1044  if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
1045    this.codeMap_.moveCode(from, to);
1046  }
1047};
1048
1049JsonProfile.prototype.findEntry = function(addr) {
1050  return this.codeMap_.findEntry(addr);
1051};
1052
1053JsonProfile.prototype.recordTick = function(time_ns, vmState, stack) {
1054  // TODO(jarin) Resolve the frame-less case (when top of stack is
1055  // known code).
1056  var processedStack = [];
1057  for (var i = 0; i < stack.length; i++) {
1058    var resolved = this.codeMap_.findAddress(stack[i]);
1059    if (resolved) {
1060      processedStack.push(resolved.entry.codeId, resolved.offset);
1061    } else {
1062      processedStack.push(-1, stack[i]);
1063    }
1064  }
1065  this.ticks_.push({ tm : time_ns, vm : vmState, s : processedStack });
1066};
1067
1068function writeJson(s) {
1069  write(JSON.stringify(s, null, 2));
1070}
1071
1072JsonProfile.prototype.writeJson = function() {
1073  // Write out the JSON in a partially manual way to avoid creating too-large
1074  // strings in one JSON.stringify call when there are a lot of ticks.
1075  write('{\n')
1076
1077  write('  "code": ');
1078  writeJson(this.codeEntries_);
1079  write(',\n');
1080
1081  write('  "functions": ');
1082  writeJson(this.functionEntries_);
1083  write(',\n');
1084
1085  write('  "ticks": [\n');
1086  for (var i = 0; i < this.ticks_.length; i++) {
1087    write('    ');
1088    writeJson(this.ticks_[i]);
1089    if (i < this.ticks_.length - 1) {
1090      write(',\n');
1091    } else {
1092      write('\n');
1093    }
1094  }
1095  write('  ],\n');
1096
1097  write('  "scripts": ');
1098  writeJson(this.scripts_);
1099
1100  write('}\n');
1101};
1102