| Index: runtime/observatory/lib/src/cpu_profile/cpu_profile.dart
|
| diff --git a/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart b/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart
|
| index 359117128c674a4ef626cfa580dd5d2fa0209996..7be5a01c8768c4c86b15c67c768244e2bd0ca57f 100644
|
| --- a/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart
|
| +++ b/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart
|
| @@ -41,6 +41,24 @@ class CodeCallTree {
|
| _setCodePercentage(node, child);
|
| }
|
| }
|
| +
|
| + _recordCallerAndCalleesInner(CodeCallTreeNode caller,
|
| + CodeCallTreeNode callee) {
|
| + if (caller != null) {
|
| + caller.profileCode._recordCallee(callee.profileCode, callee.count);
|
| + callee.profileCode._recordCaller(caller.profileCode, callee.count);
|
| + }
|
| +
|
| + for (var child in callee.children) {
|
| + _recordCallerAndCalleesInner(callee, child);
|
| + }
|
| + }
|
| +
|
| + _recordCallerAndCallees() {
|
| + for (var child in root.children) {
|
| + _recordCallerAndCalleesInner(null, child);
|
| + }
|
| + }
|
| }
|
|
|
| class FunctionCallTreeNodeCode {
|
| @@ -129,6 +147,124 @@ class FunctionCallTreeNode {
|
| }
|
| }
|
|
|
| +/// Predicate filter function. Returns true if path from root to [node] and all
|
| +/// of [node]'s children should be added to the filtered tree.
|
| +typedef bool FunctionCallTreeNodeFilter(FunctionCallTreeNode node);
|
| +
|
| +/// Build a filter version of a FunctionCallTree.
|
| +class _FilteredFunctionCallTreeBuilder {
|
| + /// The filter.
|
| + final FunctionCallTreeNodeFilter filter;
|
| + /// The unfiltered tree.
|
| + final FunctionCallTree _unfilteredTree;
|
| + /// The filtered tree (construct by [build]).
|
| + final FunctionCallTree filtered;
|
| + final List _currentPath = [];
|
| +
|
| + /// Construct a filtered tree builder using [filter] and [tree].
|
| + _FilteredFunctionCallTreeBuilder(this.filter, FunctionCallTree tree)
|
| + : _unfilteredTree = tree,
|
| + filtered =
|
| + new FunctionCallTree(
|
| + tree.inclusive,
|
| + new FunctionCallTreeNode(
|
| + tree.root.profileFunction,
|
| + tree.root.count));
|
| +
|
| + /// Build the filtered tree.
|
| + build() {
|
| + assert(filtered != null);
|
| + assert(filter != null);
|
| + assert(_unfilteredTree != null);
|
| + _descend(_unfilteredTree.root);
|
| + }
|
| +
|
| + FunctionCallTreeNode _findFunctionInChildren(FunctionCallTreeNode current,
|
| + FunctionCallTreeNode needle) {
|
| + for (var child in current.children) {
|
| + if (child.profileFunction == needle.profileFunction) {
|
| + return child;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + /// Add all nodes in [_currentPath].
|
| + FunctionCallTreeNode _addCurrentPath() {
|
| + FunctionCallTreeNode current = filtered.root;
|
| + // Tree root is always the first element of the current path.
|
| + assert(_unfilteredTree.root == _currentPath[0]);
|
| + // Assert that unfiltered tree's root and filtered tree's root are different.
|
| + assert(_unfilteredTree.root != current);
|
| + for (var i = 1; i < _currentPath.length; i++) {
|
| + // toAdd is from the unfiltered tree.
|
| + var toAdd = _currentPath[i];
|
| + // See if we already have a node for toAdd in the filtered tree.
|
| + var child = _findFunctionInChildren(current, toAdd);
|
| + if (child == null) {
|
| + // New node.
|
| + child = new FunctionCallTreeNode(toAdd.profileFunction, toAdd.count);
|
| + current.children.add(child);
|
| + }
|
| + current = child;
|
| + assert(current.count == toAdd.count);
|
| + }
|
| + return current;
|
| + }
|
| +
|
| + /// Starting at [current] append [next] and all of [next]'s sub-trees
|
| + _appendTree(FunctionCallTreeNode current, FunctionCallTreeNode next) {
|
| + if (next == null) {
|
| + return;
|
| + }
|
| + var child = _findFunctionInChildren(current, next);
|
| + if (child == null) {
|
| + child = new FunctionCallTreeNode(next.profileFunction, next.count);
|
| + current.children.add(child);
|
| + }
|
| + current = child;
|
| + for (var nextChild in next.children) {
|
| + _appendTree(current, nextChild);
|
| + }
|
| + }
|
| +
|
| + /// Add path from root to [child], [child], and all of [child]'s sub-trees
|
| + /// to filtered tree.
|
| + _addTree(FunctionCallTreeNode child) {
|
| + var current = _addCurrentPath();
|
| + _appendTree(current, child);
|
| + }
|
| +
|
| + /// Descend further into the tree. [current] is from the unfiltered tree.
|
| + _descend(FunctionCallTreeNode current) {
|
| + if (current == null) {
|
| + return;
|
| + }
|
| + _currentPath.add(current);
|
| +
|
| + if (filter(current)) {
|
| + // Filter matched.
|
| + if (current.children.length == 0) {
|
| + // Have no children. Add this path.
|
| + _addTree(null);
|
| + } else {
|
| + // Add all child trees.
|
| + for (var child in current.children) {
|
| + _addTree(child);
|
| + }
|
| + }
|
| + } else {
|
| + // Did not match, descend to each child.
|
| + for (var child in current.children) {
|
| + _descend(child);
|
| + }
|
| + }
|
| +
|
| + var last = _currentPath.removeLast();
|
| + assert(current == last);
|
| + }
|
| +}
|
| +
|
| class FunctionCallTree {
|
| final bool inclusive;
|
| final FunctionCallTreeNode root;
|
| @@ -136,6 +272,13 @@ class FunctionCallTree {
|
| _setFunctionPercentage(null, root);
|
| }
|
|
|
| + FunctionCallTree filtered(FunctionCallTreeNodeFilter filter) {
|
| + var treeFilter = new _FilteredFunctionCallTreeBuilder(filter, this);
|
| + treeFilter.build();
|
| + _setFunctionPercentage(null, treeFilter.filtered.root);
|
| + return treeFilter.filtered;
|
| + }
|
| +
|
| void _setFunctionPercentage(FunctionCallTreeNode parent,
|
| FunctionCallTreeNode node) {
|
| assert(node != null);
|
| @@ -154,6 +297,23 @@ class FunctionCallTree {
|
| _setFunctionPercentage(node, child);
|
| }
|
| }
|
| +
|
| + _markFunctionCallsInner(FunctionCallTreeNode caller,
|
| + FunctionCallTreeNode callee) {
|
| + if (caller != null) {
|
| + caller.profileFunction._recordCallee(callee.profileFunction, callee.count);
|
| + callee.profileFunction._recordCaller(caller.profileFunction, callee.count);
|
| + }
|
| + for (var child in callee.children) {
|
| + _markFunctionCallsInner(callee, child);
|
| + }
|
| + }
|
| +
|
| + _markFunctionCalls() {
|
| + for (var child in root.children) {
|
| + _markFunctionCallsInner(null, child);
|
| + }
|
| + }
|
| }
|
|
|
| class CodeTick {
|
| @@ -186,6 +346,8 @@ class ProfileCode {
|
| String formattedCpuTime = '';
|
| String formattedOnStackTime = '';
|
| final Set<String> attributes = new Set<String>();
|
| + final Map<ProfileCode, int> callers = new Map<ProfileCode, int>();
|
| + final Map<ProfileCode, int> callees = new Map<ProfileCode, int>();
|
|
|
| void _processTicks(List<String> profileTicks) {
|
| assert(profileTicks != null);
|
| @@ -262,6 +424,22 @@ class ProfileCode {
|
| '${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} '
|
| '($exclusiveTicks)';
|
| }
|
| +
|
| + _recordCaller(ProfileCode caller, int count) {
|
| + var r = callers[caller];
|
| + if (r == null) {
|
| + r = 0;
|
| + }
|
| + callers[caller] = r + count;
|
| + }
|
| +
|
| + _recordCallee(ProfileCode callee, int count) {
|
| + var r = callees[callee];
|
| + if (r == null) {
|
| + r = 0;
|
| + }
|
| + callees[callee] = r + count;
|
| + }
|
| }
|
|
|
| class ProfileFunction {
|
| @@ -269,6 +447,9 @@ class ProfileFunction {
|
| final ServiceFunction function;
|
| // List of compiled code objects containing this function.
|
| final List<ProfileCode> profileCodes = new List<ProfileCode>();
|
| + final Map<ProfileFunction, int> callers = new Map<ProfileFunction, int>();
|
| + final Map<ProfileFunction, int> callees = new Map<ProfileFunction, int>();
|
| +
|
| // Absolute ticks:
|
| int exclusiveTicks = 0;
|
| int inclusiveTicks = 0;
|
| @@ -356,6 +537,7 @@ class ProfileFunction {
|
| }
|
|
|
| ProfileFunction.fromMap(this.profile, this.function, Map data) {
|
| + function.profile = this;
|
| for (var codeIndex in data['codes']) {
|
| var profileCode = profile.codes[codeIndex];
|
| profileCodes.add(profileCode);
|
| @@ -397,6 +579,22 @@ class ProfileFunction {
|
| '${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} '
|
| '($exclusiveTicks)';
|
| }
|
| +
|
| + _recordCaller(ProfileFunction caller, int count) {
|
| + var r = callers[caller];
|
| + if (r == null) {
|
| + r = 0;
|
| + }
|
| + callers[caller] = r + count;
|
| + }
|
| +
|
| + _recordCallee(ProfileFunction callee, int count) {
|
| + var r = callees[callee];
|
| + if (r == null) {
|
| + r = 0;
|
| + }
|
| + callees[callee] = r + count;
|
| + }
|
| }
|
|
|
|
|
| @@ -416,7 +614,9 @@ class CpuProfile {
|
|
|
| final Map<String, List> tries = <String, List>{};
|
| final List<ProfileCode> codes = new List<ProfileCode>();
|
| + bool _builtCodeCalls = false;
|
| final List<ProfileFunction> functions = new List<ProfileFunction>();
|
| + bool _builtFunctionCalls = false;
|
|
|
| CodeCallTree loadCodeTree(String name) {
|
| if (name == 'inclusive') {
|
| @@ -434,7 +634,25 @@ class CpuProfile {
|
| }
|
| }
|
|
|
| - void clear() {
|
| + buildCodeCallerAndCallees() {
|
| + if (_builtCodeCalls) {
|
| + return;
|
| + }
|
| + _builtCodeCalls = true;
|
| + var tree = loadCodeTree('inclusive');
|
| + tree._recordCallerAndCallees();
|
| + }
|
| +
|
| + buildFunctionCallerAndCallees() {
|
| + if (_builtFunctionCalls) {
|
| + return;
|
| + }
|
| + _builtFunctionCalls = true;
|
| + var tree = loadFunctionTree('inclusive');
|
| + tree._markFunctionCalls();
|
| + }
|
| +
|
| + clear() {
|
| sampleCount = 0;
|
| samplePeriod = 0;
|
| sampleRate = 0.0;
|
| @@ -443,9 +661,11 @@ class CpuProfile {
|
| codes.clear();
|
| functions.clear();
|
| tries.clear();
|
| + _builtCodeCalls = false;
|
| + _builtFunctionCalls = false;
|
| }
|
|
|
| - void load(Isolate isolate, ServiceMap profile) {
|
| + load(Isolate isolate, ServiceMap profile) {
|
| clear();
|
| if ((isolate == null) || (profile == null)) {
|
| return;
|
|
|