• 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 = new Profile.DynamicFuncCodeEntry(size, type, func, state);
166  this.codeMap_.addCode(start, entry);
167  return entry;
168};
169
170
171/**
172 * Reports about moving of a dynamic code entry.
173 *
174 * @param {number} from Current code entry address.
175 * @param {number} to New code entry address.
176 */
177Profile.prototype.moveCode = function(from, to) {
178  try {
179    this.codeMap_.moveCode(from, to);
180  } catch (e) {
181    this.handleUnknownCode(Profile.Operation.MOVE, from);
182  }
183};
184
185
186/**
187 * Reports about deletion of a dynamic code entry.
188 *
189 * @param {number} start Starting address.
190 */
191Profile.prototype.deleteCode = function(start) {
192  try {
193    this.codeMap_.deleteCode(start);
194  } catch (e) {
195    this.handleUnknownCode(Profile.Operation.DELETE, start);
196  }
197};
198
199
200/**
201 * Reports about moving of a dynamic code entry.
202 *
203 * @param {number} from Current code entry address.
204 * @param {number} to New code entry address.
205 */
206Profile.prototype.moveFunc = function(from, to) {
207  if (this.codeMap_.findDynamicEntryByStartAddress(from)) {
208    this.codeMap_.moveCode(from, to);
209  }
210};
211
212
213/**
214 * Retrieves a code entry by an address.
215 *
216 * @param {number} addr Entry address.
217 */
218Profile.prototype.findEntry = function(addr) {
219  return this.codeMap_.findEntry(addr);
220};
221
222
223/**
224 * Records a tick event. Stack must contain a sequence of
225 * addresses starting with the program counter value.
226 *
227 * @param {Array<number>} stack Stack sample.
228 */
229Profile.prototype.recordTick = function(stack) {
230  var processedStack = this.resolveAndFilterFuncs_(stack);
231  this.bottomUpTree_.addPath(processedStack);
232  processedStack.reverse();
233  this.topDownTree_.addPath(processedStack);
234};
235
236
237/**
238 * Translates addresses into function names and filters unneeded
239 * functions.
240 *
241 * @param {Array<number>} stack Stack sample.
242 */
243Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
244  var result = [];
245  for (var i = 0; i < stack.length; ++i) {
246    var entry = this.codeMap_.findEntry(stack[i]);
247    if (entry) {
248      var name = entry.getName();
249      if (!this.skipThisFunction(name)) {
250        result.push(name);
251      }
252    } else {
253      this.handleUnknownCode(
254          Profile.Operation.TICK, stack[i], i);
255    }
256  }
257  return result;
258};
259
260
261/**
262 * Performs a BF traversal of the top down call graph.
263 *
264 * @param {function(CallTree.Node)} f Visitor function.
265 */
266Profile.prototype.traverseTopDownTree = function(f) {
267  this.topDownTree_.traverse(f);
268};
269
270
271/**
272 * Performs a BF traversal of the bottom up call graph.
273 *
274 * @param {function(CallTree.Node)} f Visitor function.
275 */
276Profile.prototype.traverseBottomUpTree = function(f) {
277  this.bottomUpTree_.traverse(f);
278};
279
280
281/**
282 * Calculates a top down profile for a node with the specified label.
283 * If no name specified, returns the whole top down calls tree.
284 *
285 * @param {string} opt_label Node label.
286 */
287Profile.prototype.getTopDownProfile = function(opt_label) {
288  return this.getTreeProfile_(this.topDownTree_, opt_label);
289};
290
291
292/**
293 * Calculates a bottom up profile for a node with the specified label.
294 * If no name specified, returns the whole bottom up calls tree.
295 *
296 * @param {string} opt_label Node label.
297 */
298Profile.prototype.getBottomUpProfile = function(opt_label) {
299  return this.getTreeProfile_(this.bottomUpTree_, opt_label);
300};
301
302
303/**
304 * Helper function for calculating a tree profile.
305 *
306 * @param {Profile.CallTree} tree Call tree.
307 * @param {string} opt_label Node label.
308 */
309Profile.prototype.getTreeProfile_ = function(tree, opt_label) {
310  if (!opt_label) {
311    tree.computeTotalWeights();
312    return tree;
313  } else {
314    var subTree = tree.cloneSubtree(opt_label);
315    subTree.computeTotalWeights();
316    return subTree;
317  }
318};
319
320
321/**
322 * Calculates a flat profile of callees starting from a node with
323 * the specified label. If no name specified, starts from the root.
324 *
325 * @param {string} opt_label Starting node label.
326 */
327Profile.prototype.getFlatProfile = function(opt_label) {
328  var counters = new CallTree();
329  var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL;
330  var precs = {};
331  precs[rootLabel] = 0;
332  var root = counters.findOrAddChild(rootLabel);
333
334  this.topDownTree_.computeTotalWeights();
335  this.topDownTree_.traverseInDepth(
336    function onEnter(node) {
337      if (!(node.label in precs)) {
338        precs[node.label] = 0;
339      }
340      var nodeLabelIsRootLabel = node.label == rootLabel;
341      if (nodeLabelIsRootLabel || precs[rootLabel] > 0) {
342        if (precs[rootLabel] == 0) {
343          root.selfWeight += node.selfWeight;
344          root.totalWeight += node.totalWeight;
345        } else {
346          var rec = root.findOrAddChild(node.label);
347          rec.selfWeight += node.selfWeight;
348          if (nodeLabelIsRootLabel || precs[node.label] == 0) {
349            rec.totalWeight += node.totalWeight;
350          }
351        }
352        precs[node.label]++;
353      }
354    },
355    function onExit(node) {
356      if (node.label == rootLabel || precs[rootLabel] > 0) {
357        precs[node.label]--;
358      }
359    },
360    null);
361
362  if (!opt_label) {
363    // If we have created a flat profile for the whole program, we don't
364    // need an explicit root in it. Thus, replace the counters tree
365    // root with the node corresponding to the whole program.
366    counters.root_ = root;
367  } else {
368    // Propagate weights so percents can be calculated correctly.
369    counters.getRoot().selfWeight = root.selfWeight;
370    counters.getRoot().totalWeight = root.totalWeight;
371  }
372  return counters;
373};
374
375
376/**
377 * Creates a dynamic code entry.
378 *
379 * @param {number} size Code size.
380 * @param {string} type Code type.
381 * @param {string} name Function name.
382 * @constructor
383 */
384Profile.DynamicCodeEntry = function(size, type, name) {
385  CodeMap.CodeEntry.call(this, size, name);
386  this.type = type;
387};
388
389
390/**
391 * Returns node name.
392 */
393Profile.DynamicCodeEntry.prototype.getName = function() {
394  return this.type + ': ' + this.name;
395};
396
397
398/**
399 * Returns raw node name (without type decoration).
400 */
401Profile.DynamicCodeEntry.prototype.getRawName = function() {
402  return this.name;
403};
404
405
406Profile.DynamicCodeEntry.prototype.isJSFunction = function() {
407  return false;
408};
409
410
411/**
412 * Creates a dynamic code entry.
413 *
414 * @param {number} size Code size.
415 * @param {string} type Code type.
416 * @param {Profile.FunctionEntry} func Shared function entry.
417 * @param {Profile.CodeState} state Code optimization state.
418 * @constructor
419 */
420Profile.DynamicFuncCodeEntry = function(size, type, func, state) {
421  CodeMap.CodeEntry.call(this, size);
422  this.type = type;
423  this.func = func;
424  this.state = state;
425};
426
427Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"];
428
429/**
430 * Returns node name.
431 */
432Profile.DynamicFuncCodeEntry.prototype.getName = function() {
433  var name = this.func.getName();
434  return this.type + ': ' + Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + name;
435};
436
437
438/**
439 * Returns raw node name (without type decoration).
440 */
441Profile.DynamicFuncCodeEntry.prototype.getRawName = function() {
442  return this.func.getName();
443};
444
445
446Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() {
447  return true;
448};
449
450
451/**
452 * Creates a shared function object entry.
453 *
454 * @param {string} name Function name.
455 * @constructor
456 */
457Profile.FunctionEntry = function(name) {
458  CodeMap.CodeEntry.call(this, 0, name);
459};
460
461
462/**
463 * Returns node name.
464 */
465Profile.FunctionEntry.prototype.getName = function() {
466  var name = this.name;
467  if (name.length == 0) {
468    name = '<anonymous>';
469  } else if (name.charAt(0) == ' ') {
470    // An anonymous function with location: " aaa.js:10".
471    name = '<anonymous>' + name;
472  }
473  return name;
474};
475
476
477/**
478 * Constructs a call graph.
479 *
480 * @constructor
481 */
482function CallTree() {
483  this.root_ = new CallTree.Node(
484      CallTree.ROOT_NODE_LABEL);
485};
486
487
488/**
489 * The label of the root node.
490 */
491CallTree.ROOT_NODE_LABEL = '';
492
493
494/**
495 * @private
496 */
497CallTree.prototype.totalsComputed_ = false;
498
499
500/**
501 * Returns the tree root.
502 */
503CallTree.prototype.getRoot = function() {
504  return this.root_;
505};
506
507
508/**
509 * Adds the specified call path, constructing nodes as necessary.
510 *
511 * @param {Array<string>} path Call path.
512 */
513CallTree.prototype.addPath = function(path) {
514  if (path.length == 0) {
515    return;
516  }
517  var curr = this.root_;
518  for (var i = 0; i < path.length; ++i) {
519    curr = curr.findOrAddChild(path[i]);
520  }
521  curr.selfWeight++;
522  this.totalsComputed_ = false;
523};
524
525
526/**
527 * Finds an immediate child of the specified parent with the specified
528 * label, creates a child node if necessary. If a parent node isn't
529 * specified, uses tree root.
530 *
531 * @param {string} label Child node label.
532 */
533CallTree.prototype.findOrAddChild = function(label) {
534  return this.root_.findOrAddChild(label);
535};
536
537
538/**
539 * Creates a subtree by cloning and merging all subtrees rooted at nodes
540 * with a given label. E.g. cloning the following call tree on label 'A'
541 * will give the following result:
542 *
543 *           <A>--<B>                                     <B>
544 *          /                                            /
545 *     <root>             == clone on 'A' ==>  <root>--<A>
546 *          \                                            \
547 *           <C>--<A>--<D>                                <D>
548 *
549 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the
550 * source call tree.
551 *
552 * @param {string} label The label of the new root node.
553 */
554CallTree.prototype.cloneSubtree = function(label) {
555  var subTree = new CallTree();
556  this.traverse(function(node, parent) {
557    if (!parent && node.label != label) {
558      return null;
559    }
560    var child = (parent ? parent : subTree).findOrAddChild(node.label);
561    child.selfWeight += node.selfWeight;
562    return child;
563  });
564  return subTree;
565};
566
567
568/**
569 * Computes total weights in the call graph.
570 */
571CallTree.prototype.computeTotalWeights = function() {
572  if (this.totalsComputed_) {
573    return;
574  }
575  this.root_.computeTotalWeight();
576  this.totalsComputed_ = true;
577};
578
579
580/**
581 * Traverses the call graph in preorder. This function can be used for
582 * building optionally modified tree clones. This is the boilerplate code
583 * for this scenario:
584 *
585 * callTree.traverse(function(node, parentClone) {
586 *   var nodeClone = cloneNode(node);
587 *   if (parentClone)
588 *     parentClone.addChild(nodeClone);
589 *   return nodeClone;
590 * });
591 *
592 * @param {function(CallTree.Node, *)} f Visitor function.
593 *    The second parameter is the result of calling 'f' on the parent node.
594 */
595CallTree.prototype.traverse = function(f) {
596  var pairsToProcess = new ConsArray();
597  pairsToProcess.concat([{node: this.root_, param: null}]);
598  while (!pairsToProcess.atEnd()) {
599    var pair = pairsToProcess.next();
600    var node = pair.node;
601    var newParam = f(node, pair.param);
602    var morePairsToProcess = [];
603    node.forEachChild(function (child) {
604        morePairsToProcess.push({node: child, param: newParam}); });
605    pairsToProcess.concat(morePairsToProcess);
606  }
607};
608
609
610/**
611 * Performs an indepth call graph traversal.
612 *
613 * @param {function(CallTree.Node)} enter A function called
614 *     prior to visiting node's children.
615 * @param {function(CallTree.Node)} exit A function called
616 *     after visiting node's children.
617 */
618CallTree.prototype.traverseInDepth = function(enter, exit) {
619  function traverse(node) {
620    enter(node);
621    node.forEachChild(traverse);
622    exit(node);
623  }
624  traverse(this.root_);
625};
626
627
628/**
629 * Constructs a call graph node.
630 *
631 * @param {string} label Node label.
632 * @param {CallTree.Node} opt_parent Node parent.
633 */
634CallTree.Node = function(label, opt_parent) {
635  this.label = label;
636  this.parent = opt_parent;
637  this.children = {};
638};
639
640
641/**
642 * Node self weight (how many times this node was the last node in
643 * a call path).
644 * @type {number}
645 */
646CallTree.Node.prototype.selfWeight = 0;
647
648
649/**
650 * Node total weight (includes weights of all children).
651 * @type {number}
652 */
653CallTree.Node.prototype.totalWeight = 0;
654
655
656/**
657 * Adds a child node.
658 *
659 * @param {string} label Child node label.
660 */
661CallTree.Node.prototype.addChild = function(label) {
662  var child = new CallTree.Node(label, this);
663  this.children[label] = child;
664  return child;
665};
666
667
668/**
669 * Computes node's total weight.
670 */
671CallTree.Node.prototype.computeTotalWeight =
672    function() {
673  var totalWeight = this.selfWeight;
674  this.forEachChild(function(child) {
675      totalWeight += child.computeTotalWeight(); });
676  return this.totalWeight = totalWeight;
677};
678
679
680/**
681 * Returns all node's children as an array.
682 */
683CallTree.Node.prototype.exportChildren = function() {
684  var result = [];
685  this.forEachChild(function (node) { result.push(node); });
686  return result;
687};
688
689
690/**
691 * Finds an immediate child with the specified label.
692 *
693 * @param {string} label Child node label.
694 */
695CallTree.Node.prototype.findChild = function(label) {
696  return this.children[label] || null;
697};
698
699
700/**
701 * Finds an immediate child with the specified label, creates a child
702 * node if necessary.
703 *
704 * @param {string} label Child node label.
705 */
706CallTree.Node.prototype.findOrAddChild = function(label) {
707  return this.findChild(label) || this.addChild(label);
708};
709
710
711/**
712 * Calls the specified function for every child.
713 *
714 * @param {function(CallTree.Node)} f Visitor function.
715 */
716CallTree.Node.prototype.forEachChild = function(f) {
717  for (var c in this.children) {
718    f(this.children[c]);
719  }
720};
721
722
723/**
724 * Walks up from the current node up to the call tree root.
725 *
726 * @param {function(CallTree.Node)} f Visitor function.
727 */
728CallTree.Node.prototype.walkUpToRoot = function(f) {
729  for (var curr = this; curr != null; curr = curr.parent) {
730    f(curr);
731  }
732};
733
734
735/**
736 * Tries to find a node with the specified path.
737 *
738 * @param {Array<string>} labels The path.
739 * @param {function(CallTree.Node)} opt_f Visitor function.
740 */
741CallTree.Node.prototype.descendToChild = function(
742    labels, opt_f) {
743  for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
744    var child = curr.findChild(labels[pos]);
745    if (opt_f) {
746      opt_f(child, pos);
747    }
748    curr = child;
749  }
750  return curr;
751};
752