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