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 99744f6029952257f37e8723dac377569ce17cba..ead56dd2e1dd7b3312c35f93b9e21d5e68176263 100644 |
--- a/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart |
+++ b/runtime/observatory/lib/src/cpu_profile/cpu_profile.dart |
@@ -4,27 +4,155 @@ |
part of cpu_profiler; |
-class CodeTrieNode { |
+class CodeCallTreeNode { |
final ProfileCode profileCode; |
final int count; |
- final children = new List<CodeTrieNode>(); |
- CodeTrieNode(this.profileCode, this.count); |
+ double get percentage => _percentage; |
+ double _percentage = 0.0; |
+ final children = new List<CodeCallTreeNode>(); |
+ final Set<String> attributes = new Set<String>(); |
+ CodeCallTreeNode(this.profileCode, this.count) { |
+ attributes.addAll(profileCode.attributes); |
+ } |
+} |
+ |
+class CodeCallTree { |
+ final bool inclusive; |
+ final CodeCallTreeNode root; |
+ CodeCallTree(this.inclusive, this.root) { |
+ _setCodePercentage(null, root); |
+ } |
+ |
+ _setCodePercentage(CodeCallTreeNode parent, CodeCallTreeNode node) { |
+ assert(node != null); |
+ var parentPercentage = 1.0; |
+ var parentCount = node.count; |
+ if (parent != null) { |
+ parentPercentage = parent._percentage; |
+ parentCount = parent.count; |
+ } |
+ if (inclusive) { |
+ node._percentage = parentPercentage * (node.count / parentCount); |
+ } else { |
+ node._percentage = (node.count / parentCount); |
+ } |
+ for (var child in node.children) { |
+ _setCodePercentage(node, child); |
+ } |
+ } |
} |
-class FunctionTrieNodeCode { |
+class FunctionCallTreeNodeCode { |
final ProfileCode code; |
final int ticks; |
- FunctionTrieNodeCode(this.code, this.ticks); |
+ FunctionCallTreeNodeCode(this.code, this.ticks); |
} |
-class FunctionTrieNode { |
+class FunctionCallTreeNode { |
final ProfileFunction profileFunction; |
final int count; |
- final children = new List<FunctionTrieNode>(); |
- final codes = new List<FunctionTrieNodeCode>(); |
+ double get percentage => _percentage; |
+ double _percentage = 0.0; |
+ final children = new List<FunctionCallTreeNode>(); |
+ final Set<String> attributes = new Set<String>(); |
+ final codes = new List<FunctionCallTreeNodeCode>(); |
int _totalCodeTicks = 0; |
int get totalCodesTicks => _totalCodeTicks; |
- FunctionTrieNode(this.profileFunction, this.count); |
+ |
+ // Does this function have an optimized version of itself? |
+ bool hasOptimizedCode() { |
+ for (var nodeCode in codes) { |
+ var profileCode = nodeCode.code; |
+ if (!profileCode.code.isDartCode) { |
+ continue; |
+ } |
+ if (profileCode.code.function != profileFunction.function) { |
+ continue; |
+ } |
+ if (profileCode.code.isOptimized) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Does this function have an unoptimized version of itself? |
+ bool hasUnoptimizedCode() { |
+ for (var nodeCode in codes) { |
+ var profileCode = nodeCode.code; |
+ if (!profileCode.code.isDartCode) { |
+ continue; |
+ } |
+ if (profileCode.code.kind == CodeKind.Stub) { |
+ continue; |
+ } |
+ if (!profileCode.code.isOptimized) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Has this function been inlined in another function? |
+ bool isInlined() { |
+ for (var nodeCode in codes) { |
+ var profileCode = nodeCode.code; |
+ if (!profileCode.code.isDartCode) { |
+ continue; |
+ } |
+ if (profileCode.code.kind == CodeKind.Stub) { |
+ continue; |
+ } |
+ // If the code's function isn't this function. |
+ if (profileCode.code.function != profileFunction.function) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ setCodeAttributes() { |
+ if (hasOptimizedCode()) { |
+ attributes.add('optimized'); |
+ } |
+ if (hasUnoptimizedCode()) { |
+ attributes.add('unoptimized'); |
+ } |
+ if (isInlined()) { |
+ attributes.add('inlined'); |
+ } |
+ } |
+ |
+ FunctionCallTreeNode(this.profileFunction, this.count) { |
+ profileFunction._addKindBasedAttributes(attributes); |
+ } |
+} |
+ |
+class FunctionCallTree { |
+ final bool inclusive; |
+ final FunctionCallTreeNode root; |
+ FunctionCallTree(this.inclusive, this.root) { |
+ _setFunctionPercentage(null, root); |
+ } |
+ |
+ void _setFunctionPercentage(FunctionCallTreeNode parent, |
+ FunctionCallTreeNode node) { |
+ assert(node != null); |
+ var parentPercentage = 1.0; |
+ var parentCount = node.count; |
+ if (parent != null) { |
+ parentPercentage = parent._percentage; |
+ parentCount = parent.count; |
+ } |
+ if (inclusive) { |
+ node._percentage = parentPercentage * (node.count / parentCount); |
+ } else { |
+ node._percentage = (node.count / parentCount); |
+ } |
+ for (var child in node.children) { |
+ _setFunctionPercentage(node, child); |
+ } |
+ } |
} |
class CodeTick { |
@@ -47,10 +175,16 @@ class ProfileCode { |
final Code code; |
int exclusiveTicks; |
int inclusiveTicks; |
+ double normalizedExclusiveTicks = 0.0; |
+ double normalizedInclusiveTicks = 0.0; |
final addressTicks = new Map<int, CodeTick>(); |
final intervalTicks = new Map<int, InlineIntervalTick>(); |
String formattedInclusiveTicks = ''; |
String formattedExclusiveTicks = ''; |
+ String formattedExclusivePercent = ''; |
+ String formattedCpuTime = ''; |
+ String formattedOnStackTime = ''; |
+ final Set<String> attributes = new Set<String>(); |
void _processTicks(List<String> profileTicks) { |
assert(profileTicks != null); |
@@ -82,13 +216,43 @@ class ProfileCode { |
code.profile = this; |
+ if (code.isDartCode) { |
+ if (code.isOptimized) { |
+ attributes.add('optimized'); |
+ } else { |
+ attributes.add('unoptimized'); |
+ } |
+ } |
+ if (code.isDartCode) { |
+ attributes.add('dart'); |
+ } else if (code.kind == CodeKind.Tag) { |
+ attributes.add('tag'); |
+ } else if (code.kind == CodeKind.Native) { |
+ attributes.add('native'); |
+ } |
inclusiveTicks = int.parse(data['inclusiveTicks']); |
exclusiveTicks = int.parse(data['exclusiveTicks']); |
+ |
+ normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount; |
+ |
+ normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount; |
+ |
var ticks = data['ticks']; |
if (ticks != null) { |
_processTicks(ticks); |
} |
+ formattedExclusivePercent = |
+ Utils.formatPercent(exclusiveTicks, profile.sampleCount); |
+ |
+ formattedCpuTime = |
+ Utils.formatTimeMilliseconds( |
+ profile.approximateMillisecondsForCount(exclusiveTicks)); |
+ |
+ formattedOnStackTime = |
+ Utils.formatTimeMilliseconds( |
+ profile.approximateMillisecondsForCount(inclusiveTicks)); |
+ |
formattedInclusiveTicks = |
'${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} ' |
'($inclusiveTicks)'; |
@@ -104,14 +268,20 @@ class ProfileFunction { |
final ServiceFunction function; |
// List of compiled code objects containing this function. |
final List<ProfileCode> profileCodes = new List<ProfileCode>(); |
- |
// Absolute ticks: |
int exclusiveTicks = 0; |
int inclusiveTicks = 0; |
// Global percentages: |
- double globalExclusiveTicks = 0.0; |
- double globalInclusiveTicks = 0.0; |
+ double normalizedExclusiveTicks = 0.0; |
+ double normalizedInclusiveTicks = 0.0; |
+ |
+ String formattedInclusiveTicks = ''; |
+ String formattedExclusiveTicks = ''; |
+ String formattedExclusivePercent = ''; |
+ String formattedCpuTime = ''; |
+ String formattedOnStackTime = ''; |
+ final Set<String> attributes = new Set<String>(); |
int _sortCodes(ProfileCode a, ProfileCode b) { |
if (a.code.isOptimized == b.code.isOptimized) { |
@@ -123,6 +293,67 @@ class ProfileFunction { |
return 1; |
} |
+ // Does this function have an optimized version of itself? |
+ bool hasOptimizedCode() { |
+ for (var profileCode in profileCodes) { |
+ if (profileCode.code.function != function) { |
+ continue; |
+ } |
+ if (profileCode.code.isOptimized) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Does this function have an unoptimized version of itself? |
+ bool hasUnoptimizedCode() { |
+ for (var profileCode in profileCodes) { |
+ if (profileCode.code.kind == CodeKind.Stub) { |
+ continue; |
+ } |
+ if (!profileCode.code.isDartCode) { |
+ continue; |
+ } |
+ if (!profileCode.code.isOptimized) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Has this function been inlined in another function? |
+ bool isInlined() { |
+ for (var profileCode in profileCodes) { |
+ if (profileCode.code.kind == CodeKind.Stub) { |
+ continue; |
+ } |
+ if (!profileCode.code.isDartCode) { |
+ continue; |
+ } |
+ // If the code's function isn't this function. |
+ if (profileCode.code.function != function) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ void _addKindBasedAttributes(Set<String> attribs) { |
+ if (function.kind == FunctionKind.kTag) { |
+ attribs.add('tag'); |
+ } else if (function.kind == FunctionKind.kStub) { |
+ attribs.add('dart'); |
+ attribs.add('stub'); |
+ } else if (function.kind == FunctionKind.kNative) { |
+ attribs.add('native'); |
+ } else if (function.kind.isSynthetic()) { |
+ attribs.add('synthetic'); |
+ } else { |
+ attribs.add('dart'); |
+ } |
+ } |
+ |
ProfileFunction.fromMap(this.profile, this.function, Map data) { |
for (var codeIndex in data['codes']) { |
var profileCode = profile.codes[codeIndex]; |
@@ -130,11 +361,40 @@ class ProfileFunction { |
} |
profileCodes.sort(_sortCodes); |
+ if (hasOptimizedCode()) { |
+ attributes.add('optimized'); |
+ } |
+ if (hasUnoptimizedCode()) { |
+ attributes.add('unoptimized'); |
+ } |
+ if (isInlined()) { |
+ attributes.add('inlined'); |
+ } |
+ _addKindBasedAttributes(attributes); |
exclusiveTicks = int.parse(data['exclusiveTicks']); |
inclusiveTicks = int.parse(data['inclusiveTicks']); |
- globalExclusiveTicks = exclusiveTicks / profile.sampleCount; |
- globalInclusiveTicks = inclusiveTicks / profile.sampleCount; |
+ normalizedExclusiveTicks = exclusiveTicks / profile.sampleCount; |
+ normalizedInclusiveTicks = inclusiveTicks / profile.sampleCount; |
+ |
+ formattedExclusivePercent = |
+ Utils.formatPercent(exclusiveTicks, profile.sampleCount); |
+ |
+ formattedCpuTime = |
+ Utils.formatTimeMilliseconds( |
+ profile.approximateMillisecondsForCount(exclusiveTicks)); |
+ |
+ formattedOnStackTime = |
+ Utils.formatTimeMilliseconds( |
+ profile.approximateMillisecondsForCount(inclusiveTicks)); |
+ |
+ formattedInclusiveTicks = |
+ '${Utils.formatPercent(inclusiveTicks, profile.sampleCount)} ' |
+ '($inclusiveTicks)'; |
+ |
+ formattedExclusiveTicks = |
+ '${Utils.formatPercent(exclusiveTicks, profile.sampleCount)} ' |
+ '($exclusiveTicks)'; |
} |
} |
@@ -153,8 +413,10 @@ class CpuProfile { |
double timeSpan = 0.0; |
- CodeTrieNode codeTrieRoot; |
- FunctionTrieNode functionTrieRoot; |
+ final Map<String, CodeCallTree> codeTrees = |
+ <String, CodeCallTree>{}; |
+ final Map<String, FunctionCallTree> functionTrees = |
+ <String, FunctionCallTree>{}; |
final List<ProfileCode> codes = new List<ProfileCode>(); |
final List<ProfileFunction> functions = new List<ProfileFunction>(); |
@@ -165,8 +427,8 @@ class CpuProfile { |
sampleRate = 0.0; |
stackDepth = 0; |
timeSpan = 0.0; |
- codeTrieRoot = null; |
- functionTrieRoot = null; |
+ codeTrees.clear(); |
+ functionTrees.clear(); |
codes.clear(); |
functions.clear(); |
} |
@@ -202,15 +464,27 @@ class CpuProfile { |
new ProfileFunction.fromMap(this, function, profileFunction)); |
} |
- // Process code trie. |
+ // Process code trees. |
var exclusiveCodeTrie = profile['exclusiveCodeTrie']; |
- assert(exclusiveCodeTrie != null); |
- codeTrieRoot = _processCodeTrie(exclusiveCodeTrie); |
+ if (exclusiveCodeTrie != null) { |
+ codeTrees['exclusive'] = _loadCodeTree(false, exclusiveCodeTrie); |
+ } |
+ var inclusiveCodeTrie = profile['inclusiveCodeTrie']; |
+ if (inclusiveCodeTrie != null) { |
+ codeTrees['inclusive'] = _loadCodeTree(true, inclusiveCodeTrie); |
+ } |
- // Process function trie. |
+ // Process function trees. |
var exclusiveFunctionTrie = profile['exclusiveFunctionTrie']; |
- assert(exclusiveFunctionTrie != null); |
- functionTrieRoot = _processFunctionTrie(exclusiveFunctionTrie); |
+ if (exclusiveFunctionTrie != null) { |
+ functionTrees['exclusive'] = |
+ _loadFunctionTree(false, exclusiveFunctionTrie); |
+ } |
+ var inclusiveFunctionTrie = profile['inclusiveFunctionTrie']; |
+ if (inclusiveFunctionTrie != null) { |
+ functionTrees['inclusive'] = |
+ _loadFunctionTree(true, inclusiveFunctionTrie); |
+ } |
} |
// Data shared across calls to _read*TrieNode. |
@@ -225,7 +499,7 @@ class CpuProfile { |
// [2] child node count |
// Reading the trie is done by recursively reading the tree depth-first |
// pre-order. |
- CodeTrieNode _processCodeTrie(List<int> data) { |
+ CodeCallTree _loadCodeTree(bool inclusive, List<int> data) { |
// Setup state shared across calls to _readTrieNode. |
_trieDataCursor = 0; |
_trieData = data; |
@@ -237,10 +511,11 @@ class CpuProfile { |
return null; |
} |
// Read the tree, returns the root node. |
- return _readCodeTrieNode(); |
+ var root = _readCodeTrieNode(); |
+ return new CodeCallTree(inclusive, root); |
} |
- CodeTrieNode _readCodeTrieNode() { |
+ CodeCallTreeNode _readCodeTrieNode() { |
// Read index into code table. |
var index = _trieData[_trieDataCursor++]; |
// Lookup code object. |
@@ -248,7 +523,7 @@ class CpuProfile { |
// Frame counter. |
var count = _trieData[_trieDataCursor++]; |
// Create node. |
- var node = new CodeTrieNode(code, count); |
+ var node = new CodeCallTreeNode(code, count); |
// Number of children. |
var children = _trieData[_trieDataCursor++]; |
// Recursively read child nodes. |
@@ -259,7 +534,7 @@ class CpuProfile { |
return node; |
} |
- FunctionTrieNode _processFunctionTrie(List<int> data) { |
+ FunctionCallTree _loadFunctionTree(bool inclusive, List<int> data) { |
// Setup state shared across calls to _readTrieNode. |
_trieDataCursor = 0; |
_trieData = data; |
@@ -271,10 +546,11 @@ class CpuProfile { |
return null; |
} |
// Read the tree, returns the root node. |
- return _readFunctionTrieNode(); |
+ var root = _readFunctionTrieNode(); |
+ return new FunctionCallTree(inclusive, root); |
} |
- FunctionTrieNode _readFunctionTrieNode() { |
+ FunctionCallTreeNode _readFunctionTrieNode() { |
// Read index into function table. |
var index = _trieData[_trieDataCursor++]; |
// Lookup function object. |
@@ -282,7 +558,7 @@ class CpuProfile { |
// Frame counter. |
var count = _trieData[_trieDataCursor++]; |
// Create node. |
- var node = new FunctionTrieNode(function, count); |
+ var node = new FunctionCallTreeNode(function, count); |
// Number of code index / count pairs. |
var codeCount = _trieData[_trieDataCursor++]; |
var totalCodeTicks = 0; |
@@ -291,8 +567,9 @@ class CpuProfile { |
var code = codes[codeIndex]; |
var codeTicks = _trieData[_trieDataCursor++]; |
totalCodeTicks += codeTicks; |
- var nodeCode = new FunctionTrieNodeCode(code, codeTicks); |
+ var nodeCode = new FunctionCallTreeNodeCode(code, codeTicks); |
node.codes.add(nodeCode); |
+ node.setCodeAttributes(); |
} |
node._totalCodeTicks = totalCodeTicks; |
// Number of children. |
@@ -305,6 +582,11 @@ class CpuProfile { |
return node; |
} |
+ int approximateMillisecondsForCount(count) { |
+ var MICROSECONDS_PER_MILLISECOND = 1000.0; |
+ return (count * samplePeriod) ~/ MICROSECONDS_PER_MILLISECOND; |
+ } |
+ |
double approximateSecondsForCount(count) { |
var MICROSECONDS_PER_SECOND = 1000000.0; |
return (count * samplePeriod) / MICROSECONDS_PER_SECOND; |