blob: bbb57f6f0e25151eff684a3d13bf6de3088dfdbf [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/base/base.html">
<script>
'use strict';
tr.exportTo('tr.ui.analysis', function() {
/**
* Z-function: Given a list (or a string) of length n, for each index i from
* 1 to n - 1, find the length z[i] of the longest substring starting at
* position i which is also a prefix of the list. This function returns the
* list of maximum lengths z.
*
* Mathematically, for each i from 1 to n - 1, z[i] is the maximum value such
* that [list[0], ..., list[i - 1]] = [list[i], ..., list[i + z[i] - 1]].
* z[0] is defined to be zero for convenience.
*
* Example:
*
* Input (list): ['A', 'B', 'A', 'C', 'A', 'B', 'A']
* Output (z): [ 0 , 0 , 1 , 0 , 3 , 0 , 1 ]
*
* Unlike the brute-force approach (which is O(n^2) in the worst case), the
* complexity of this implementation is linear in the size of the list, i.e.
* O(n).
*
* Source: http://e-maxx-eng.github.io/string/z-function.html
*/
function zFunction(list) {
var n = list.length;
if (n === 0)
return [];
var z = new Array(n);
z[0] = 0;
for (var i = 1, left = 0, right = 0; i < n; ++i) {
var maxLength;
if (i <= right)
maxLength = Math.min(right - i + 1, z[i - left]);
else
maxLength = 0;
while (i + maxLength < n && list[maxLength] === list[i + maxLength])
++maxLength;
if (i + maxLength - 1 > right) {
left = i;
right = i + maxLength - 1;
}
z[i] = maxLength;
}
return z;
}
/**
* Node of a stack frame hierarchy representation for tree (top down) and
* heavy (bottom up) views.
*
* To build a top-down view of a set of stack traces, you simply create a
* root node and than add the stack traces to it:
*
* var topDownRoot = new StackFrameTreeNode('root', undefined);
* topDownRoot.addStackTrace(trace1, size1);
* topDownRoot.addStackTrace(trace2, size2);
* ...
* topDownRoot.addStackTrace(traceN, sizeN);
*
* The corresponding bottom-up view is constructed indirectly by converting
* the top-down view:
*
* var bottomUpRoot = topDownRoot.convertToBottomUpView();
*
* @{constructor}
*/
function StackFrameTreeNode(title, opt_frame) {
this.title = title;
this.frame = opt_frame;
this.parent = undefined;
this.children = [];
this.childMap = new Map();
this.total = 0;
this.self = 0;
}
StackFrameTreeNode.prototype = {
/** Duck type <tr-ui-b-table> rows. */
get subRows() {
return this.children;
},
/**
* Returns the list of titles of this node and all its ancestors (including
* the root). The title of this node is first in the list.
*
* Note that this method does not use
* tr.model.StackFrame.prototype.getUserFriendlyStackTrace because some
* nodes don't have frames (e.g. the root).
*/
get stackTraceTitles() {
var titles = [];
for (var currentNode = this; currentNode !== undefined;
currentNode = currentNode.parent) {
titles.push(currentNode.title);
}
return titles;
},
getOrCreateChild: function(title, opt_frame) {
var childNode = this.childMap.get(title);
if (childNode !== undefined)
return childNode;
childNode = new StackFrameTreeNode(title, opt_frame);
childNode.parent = this;
this.children.push(childNode);
this.childMap.set(title, childNode);
return childNode;
},
/**
* Add a stack trace to the stack frame tree. The first element of the
* trace is expected to be the leaf stack frame (so that
* tr.model.StackFrame.prototype.stackTrace could be used as the parameter
* without any modification).
*
* For example, the following code snippet:
*
* var frameA = new StackFrame(undefined, tr.b.GUID.allocate(), 'A');
* var frameB = new StackFrame(frameA, tr.b.GUID.allocate(), 'B');
* var frameC = new StackFrame(frameB, tr.b.GUID.allocate(), 'C');
* root.addStackTrace(frameC.stackTrace, 42)
*
* will add the path root -> A -> B -> C to the tree (if necessary), add 42
* to the total value of all the nodes on the path (root, A, B, C), and add
* 42 to the self value of the leaf node (C).
*
* Important: No stack traces should be added to a bottom-up view (once it
* has been converted). Doing so will not update the structure and values
* of the view correctly!
*/
addStackTrace: function(trace, value, opt_traceContainsRootFrame) {
var currentNode = this;
var startIndex = trace.length - (opt_traceContainsRootFrame ? 2 : 1);
for (var i = startIndex; i >= 0; i--) {
currentNode.total += value;
var stackFrame = trace[i];
currentNode =
currentNode.getOrCreateChild(stackFrame.title, stackFrame);
}
currentNode.total += value;
currentNode.self += value;
},
/**
* Converts this stack frame tree from top-down view to the corresponding
* bottom-up view. This method returns the root of the tree representing
* the bottom-up view.
*
* Note that there is no connection between the two representations after
* the conversion (modifying the structure and/or values of one of them
* will not affect the other one).
*/
convertToBottomUpView: function() {
var bottomUpViewRoot = new StackFrameTreeNode(this.title, this.frame);
bottomUpViewRoot.total = this.total;
bottomUpViewRoot.self = this.self;
this.addChildrenToBottomUpViewRecursively_(bottomUpViewRoot);
return bottomUpViewRoot;
},
addChildrenToBottomUpViewRecursively_: function(bottomUpViewRoot) {
this.children.forEach(function(child) {
child.addToBottomUpViewRecursively_(bottomUpViewRoot);
});
},
/**
* Add this node and all its children to the provided bottom-up view.
*
* This code was inspired by
* third_party/WebKit/Source/devtools/front_end/profiler/CPUProfileBottomUpDataGrid.js
* in the Chromium tree.
*/
addToBottomUpViewRecursively_: function(bottomUpViewRoot) {
// Determine the length of the suffix of the trace associated with this
// node whose total should *not* be added to the corresponding bottom-up
// view node. This is to avoid double-counting recursive calls. Note that
// this does not affect self size.
//
// For example, if this node corresponds to the leaf stack frame (B) of
// root -> A -> B -> C -> A -> B, then the length of the suffix will be
// 2. This means that the total size of this node will only be added to
// nodes marked with * in the resulting bottom-up representation
// root -> B -> A -> C* -> B* -> A*. The reason for this is that the
// total would already have been included when the root -> A -> B prefix
// of the trace was added to the bottom-up view (when the great
// grandparent (A) of this node was visited by this recursive method).
var remainingRecursiveSuffixLength =
this.calculateRecursiveSuffixLength_();
// Construct the bottom-up view counterpart of this top-down view node.
//
// For example, if this node corresponds to the leaf stack frame (C) of
// the stack trace root -> A -> B -> C, the bottom-up view will be
// updated with root -> C -> B -> A.
var bottomUpParentNode = bottomUpViewRoot;
for (var topDownNode = this;
topDownNode.parent !== undefined /* don't include the root node */;
topDownNode = topDownNode.parent) {
var bottomUpChildNode = bottomUpParentNode.getOrCreateChild(
topDownNode.title, topDownNode.frame);
bottomUpChildNode.self += this.self;
if (remainingRecursiveSuffixLength > 0)
remainingRecursiveSuffixLength--;
else
bottomUpChildNode.total += this.total;
bottomUpParentNode = bottomUpChildNode;
}
this.addChildrenToBottomUpViewRecursively_(bottomUpViewRoot);
},
/**
* Determine the length of the longest suffix of the stack trace associated
* with this node which is repeated in the trace.
*
* For example, if this node corresponds to the leaf stack frame (C) of the
* stack trace root -> A -> B -> C -> A -> B -> B -> C, then this method
* will return 2 because the suffix B -> C is repeated in the trace.
*/
calculateRecursiveSuffixLength_: function() {
var maxLengths = zFunction(this.stackTraceTitles);
var recursiveSuffixLength = 0;
for (var i = 0; i < maxLengths.length; i++)
recursiveSuffixLength = Math.max(recursiveSuffixLength, maxLengths[i]);
return recursiveSuffixLength;
}
};
return {
StackFrameTreeNode: StackFrameTreeNode,
zFunction: zFunction // Exported for testing.
};
});
</script>