blob: d2b53223c042edcdb17063c625372264fd924fa4 [file] [log] [blame]
ager@chromium.org65dad4b2009-04-23 08:48:43 +00001// 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/**
48 * Returns whether a function with the specified name must be skipped.
49 * Should be overriden by subclasses.
50 *
51 * @param {string} name Function name.
52 */
53devtools.profiler.Profile.prototype.skipThisFunction = function(name) {
54 return false;
55};
56
57
58/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000059 * Enum for profiler operations that involve looking up existing
60 * code entries.
61 *
62 * @enum {number}
63 */
64devtools.profiler.Profile.Operation = {
65 MOVE: 0,
66 DELETE: 1,
67 TICK: 2
68};
69
70
71/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +000072 * Called whenever the specified operation has failed finding a function
73 * containing the specified address. Should be overriden by subclasses.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000074 * See the devtools.profiler.Profile.Operation enum for the list of
75 * possible operations.
ager@chromium.org65dad4b2009-04-23 08:48:43 +000076 *
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000077 * @param {number} operation Operation.
ager@chromium.org65dad4b2009-04-23 08:48:43 +000078 * @param {number} addr Address of the unknown code.
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000079 * @param {number} opt_stackPos If an unknown address is encountered
80 * during stack strace processing, specifies a position of the frame
81 * containing the address.
ager@chromium.org65dad4b2009-04-23 08:48:43 +000082 */
83devtools.profiler.Profile.prototype.handleUnknownCode = function(
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000084 operation, addr, opt_stackPos) {
ager@chromium.org65dad4b2009-04-23 08:48:43 +000085};
86
87
88/**
89 * Registers static (library) code entry.
90 *
91 * @param {string} name Code entry name.
92 * @param {number} startAddr Starting address.
93 * @param {number} endAddr Ending address.
94 */
95devtools.profiler.Profile.prototype.addStaticCode = function(
96 name, startAddr, endAddr) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +000097 var entry = new devtools.profiler.CodeMap.CodeEntry(
98 endAddr - startAddr, name);
99 this.codeMap_.addStaticCode(startAddr, entry);
100 return entry;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000101};
102
103
104/**
105 * Registers dynamic (JIT-compiled) code entry.
106 *
107 * @param {string} type Code entry type.
108 * @param {string} name Code entry name.
109 * @param {number} start Starting address.
110 * @param {number} size Code entry size.
111 */
112devtools.profiler.Profile.prototype.addCode = function(
113 type, name, start, size) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000114 var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name);
115 this.codeMap_.addCode(start, entry);
116 return entry;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000117};
118
119
120/**
121 * Reports about moving of a dynamic code entry.
122 *
123 * @param {number} from Current code entry address.
124 * @param {number} to New code entry address.
125 */
126devtools.profiler.Profile.prototype.moveCode = function(from, to) {
127 try {
128 this.codeMap_.moveCode(from, to);
129 } catch (e) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000130 this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000131 }
132};
133
134
135/**
136 * Reports about deletion of a dynamic code entry.
137 *
138 * @param {number} start Starting address.
139 */
140devtools.profiler.Profile.prototype.deleteCode = function(start) {
141 try {
142 this.codeMap_.deleteCode(start);
143 } catch (e) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000144 this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000145 }
146};
147
148
149/**
150 * Records a tick event. Stack must contain a sequence of
151 * addresses starting with the program counter value.
152 *
153 * @param {Array<number>} stack Stack sample.
154 */
155devtools.profiler.Profile.prototype.recordTick = function(stack) {
156 var processedStack = this.resolveAndFilterFuncs_(stack);
157 this.bottomUpTree_.addPath(processedStack);
158 processedStack.reverse();
159 this.topDownTree_.addPath(processedStack);
160};
161
162
163/**
164 * Translates addresses into function names and filters unneeded
165 * functions.
166 *
167 * @param {Array<number>} stack Stack sample.
168 */
169devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) {
170 var result = [];
171 for (var i = 0; i < stack.length; ++i) {
172 var entry = this.codeMap_.findEntry(stack[i]);
173 if (entry) {
174 var name = entry.getName();
175 if (!this.skipThisFunction(name)) {
176 result.push(name);
177 }
178 } else {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000179 this.handleUnknownCode(
180 devtools.profiler.Profile.Operation.TICK, stack[i], i);
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000181 }
182 }
183 return result;
184};
185
186
187/**
188 * Returns the root of the top down call graph.
189 */
190devtools.profiler.Profile.prototype.getTopDownTreeRoot = function() {
191 this.topDownTree_.computeTotalWeights();
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000192 return this.topDownTree_.getRoot();
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000193};
194
195
196/**
197 * Returns the root of the bottom up call graph.
198 */
199devtools.profiler.Profile.prototype.getBottomUpTreeRoot = function() {
200 this.bottomUpTree_.computeTotalWeights();
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000201 return this.bottomUpTree_.getRoot();
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000202};
203
204
205/**
206 * Traverses the top down call graph in preorder.
207 *
208 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
209 */
210devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) {
211 this.topDownTree_.traverse(f);
212};
213
214
215/**
216 * Traverses the bottom up call graph in preorder.
217 *
218 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
219 */
220devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) {
221 this.bottomUpTree_.traverse(f);
222};
223
224
225/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000226 * Calculates a top down profile starting from the specified node.
227 *
228 * @param {devtools.profiler.CallTree.Node} opt_root Starting node.
229 */
230devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_root) {
231 if (!opt_root) {
232 this.topDownTree_.computeTotalWeights();
233 return this.topDownTree_;
234 } else {
235 throw Error('not implemented');
236 }
237};
238
239
240/**
241 * Calculates a bottom up profile starting from the specified node.
242 *
243 * @param {devtools.profiler.CallTree.Node} opt_root Starting node.
244 */
245devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_root) {
246 if (!opt_root) {
247 this.bottomUpTree_.computeTotalWeights();
248 return this.bottomUpTree_;
249 } else {
250 throw Error('not implemented');
251 }
252};
253
254
255/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000256 * Calculates a flat profile of callees starting from the specified node.
257 *
258 * @param {devtools.profiler.CallTree.Node} opt_root Starting node.
259 */
260devtools.profiler.Profile.prototype.getFlatProfile = function(opt_root) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000261 var counters = new devtools.profiler.CallTree();
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000262 var precs = {};
263 this.topDownTree_.computeTotalWeights();
264 this.topDownTree_.traverseInDepth(
265 function onEnter(node) {
266 if (!(node.label in precs)) {
267 precs[node.label] = 0;
268 }
269 var rec = counters.findOrAddChild(node.label);
270 rec.selfWeight += node.selfWeight;
271 if (precs[node.label] == 0) {
272 rec.totalWeight += node.totalWeight;
273 }
274 precs[node.label]++;
275 },
276 function onExit(node) {
277 precs[node.label]--;
278 },
279 opt_root);
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000280 return counters;
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000281};
282
283
284/**
285 * Creates a dynamic code entry.
286 *
287 * @param {number} size Code size.
288 * @param {string} type Code type.
289 * @param {string} name Function name.
290 * @constructor
291 */
292devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) {
293 devtools.profiler.CodeMap.CodeEntry.call(this, size, name);
294 this.type = type;
295};
296
297
298/**
299 * Returns node name.
300 */
301devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() {
302 var name = this.name;
303 if (name.length == 0) {
304 name = '<anonymous>';
305 } else if (name.charAt(0) == ' ') {
306 // An anonymous function with location: " aaa.js:10".
307 name = '<anonymous>' + name;
308 }
309 return this.type + ': ' + name;
310};
311
312
313/**
314 * Constructs a call graph.
315 *
316 * @constructor
317 */
318devtools.profiler.CallTree = function() {
319 this.root_ = new devtools.profiler.CallTree.Node('');
320};
321
322
323/**
324 * @private
325 */
326devtools.profiler.CallTree.prototype.totalsComputed_ = false;
327
328
329/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000330 * Returns the tree root.
331 */
332devtools.profiler.CallTree.prototype.getRoot = function() {
333 return this.root_;
334};
335
336
337/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000338 * Adds the specified call path, constructing nodes as necessary.
339 *
340 * @param {Array<string>} path Call path.
341 */
342devtools.profiler.CallTree.prototype.addPath = function(path) {
343 if (path.length == 0) {
344 return;
345 }
346 var curr = this.root_;
347 for (var i = 0; i < path.length; ++i) {
348 curr = curr.findOrAddChild(path[i]);
349 }
350 curr.selfWeight++;
351 this.totalsComputed_ = false;
352};
353
354
355/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000356 * Finds an immediate child of the specified parent with the specified
357 * label, creates a child node if necessary. If a parent node isn't
358 * specified, uses tree root.
359 *
360 * @param {string} label Child node label.
361 */
362devtools.profiler.CallTree.prototype.findOrAddChild = function(
363 label, opt_parent) {
364 var parent = opt_parent || this.root_;
365 return parent.findOrAddChild(label);
366};
367
368
369/**
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000370 * Computes total weights in the call graph.
371 */
372devtools.profiler.CallTree.prototype.computeTotalWeights = function() {
373 if (this.totalsComputed_) {
374 return;
375 }
376 this.root_.computeTotalWeight();
377 this.totalsComputed_ = true;
378};
379
380
381/**
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000382 * Traverses the call graph in preorder. This function can be used for
383 * building optionally modified tree clones. This is the boilerplate code
384 * for this scenario:
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000385 *
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000386 * callTree.traverse(function(node, parentClone) {
387 * var nodeClone = cloneNode(node);
388 * if (parentClone)
389 * parentClone.addChild(nodeClone);
390 * return nodeClone;
391 * });
392 *
393 * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function.
394 * The second parameter is the result of calling 'f' on the parent node.
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000395 * @param {devtools.profiler.CallTree.Node} opt_start Starting node.
396 */
397devtools.profiler.CallTree.prototype.traverse = function(f, opt_start) {
ager@chromium.org3a37e9b2009-04-27 09:26:21 +0000398 var pairsToProcess = [{node: opt_start || this.root_, param: null}];
399 while (pairsToProcess.length > 0) {
400 var pair = pairsToProcess.shift();
401 var node = pair.node;
402 var newParam = f(node, pair.param);
403 node.forEachChild(
404 function (child) { pairsToProcess.push({node: child, param: newParam}); }
405 );
ager@chromium.org65dad4b2009-04-23 08:48:43 +0000406 }
407};
408
409
410/**
411 * Performs an indepth call graph traversal.
412 *
413 * @param {function(devtools.profiler.CallTree.Node)} enter A function called
414 * prior to visiting node's children.
415 * @param {function(devtools.profiler.CallTree.Node)} exit A function called
416 * after visiting node's children.
417 * @param {devtools.profiler.CallTree.Node} opt_start Starting node.
418 */
419devtools.profiler.CallTree.prototype.traverseInDepth = function(
420 enter, exit, opt_start) {
421 function traverse(node) {
422 enter(node);
423 node.forEachChild(traverse);
424 exit(node);
425 }
426 traverse(opt_start || this.root_);
427};
428
429
430/**
431 * Constructs a call graph node.
432 *
433 * @param {string} label Node label.
434 * @param {devtools.profiler.CallTree.Node} opt_parent Node parent.
435 */
436devtools.profiler.CallTree.Node = function(label, opt_parent) {
437 this.label = label;
438 this.parent = opt_parent;
439 this.children = {};
440};
441
442
443/**
444 * Node self weight (how many times this node was the last node in
445 * a call path).
446 * @type {number}
447 */
448devtools.profiler.CallTree.Node.prototype.selfWeight = 0;
449
450
451/**
452 * Node total weight (includes weights of all children).
453 * @type {number}
454 */
455devtools.profiler.CallTree.Node.prototype.totalWeight = 0;
456
457
458/**
459 * Adds a child node.
460 *
461 * @param {string} label Child node label.
462 */
463devtools.profiler.CallTree.Node.prototype.addChild = function(label) {
464 var child = new devtools.profiler.CallTree.Node(label, this);
465 this.children[label] = child;
466 return child;
467};
468
469
470/**
471 * Computes node's total weight.
472 */
473devtools.profiler.CallTree.Node.prototype.computeTotalWeight =
474 function() {
475 var totalWeight = this.selfWeight;
476 this.forEachChild(function(child) {
477 totalWeight += child.computeTotalWeight(); });
478 return this.totalWeight = totalWeight;
479};
480
481
482/**
483 * Returns all node's children as an array.
484 */
485devtools.profiler.CallTree.Node.prototype.exportChildren = function() {
486 var result = [];
487 this.forEachChild(function (node) { result.push(node); });
488 return result;
489};
490
491
492/**
493 * Finds an immediate child with the specified label.
494 *
495 * @param {string} label Child node label.
496 */
497devtools.profiler.CallTree.Node.prototype.findChild = function(label) {
498 return this.children[label] || null;
499};
500
501
502/**
503 * Finds an immediate child with the specified label, creates a child
504 * node if necessary.
505 *
506 * @param {string} label Child node label.
507 */
508devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(
509 label) {
510 return this.findChild(label) || this.addChild(label);
511};
512
513
514/**
515 * Calls the specified function for every child.
516 *
517 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
518 */
519devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) {
520 for (var c in this.children) {
521 f(this.children[c]);
522 }
523};
524
525
526/**
527 * Walks up from the current node up to the call tree root.
528 *
529 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function.
530 */
531devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) {
532 for (var curr = this; curr != null; curr = curr.parent) {
533 f(curr);
534 }
535};
536
537
538/**
539 * Tries to find a node with the specified path.
540 *
541 * @param {Array<string>} labels The path.
542 * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function.
543 */
544devtools.profiler.CallTree.Node.prototype.descendToChild = function(
545 labels, opt_f) {
546 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) {
547 var child = curr.findChild(labels[pos]);
548 if (opt_f) {
549 opt_f(child, pos);
550 }
551 curr = child;
552 }
553 return curr;
554};