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