Index: runtime/observatory/lib/src/elements/cpu_profile.dart |
diff --git a/runtime/observatory/lib/src/elements/cpu_profile.dart b/runtime/observatory/lib/src/elements/cpu_profile.dart |
index 154f80fee98b6c44be366ac5369b42f60c81f37c..d82b1f784849d9558bd629823a32c47e552efb99 100644 |
--- a/runtime/observatory/lib/src/elements/cpu_profile.dart |
+++ b/runtime/observatory/lib/src/elements/cpu_profile.dart |
@@ -4,19 +4,21 @@ |
library cpu_profile_element; |
+import 'dart:async'; |
import 'dart:html'; |
import 'observatory_element.dart'; |
import 'package:logging/logging.dart'; |
import 'package:observatory/service.dart'; |
import 'package:observatory/app.dart'; |
+import 'package:observatory/cpu_profile.dart'; |
import 'package:observatory/elements.dart'; |
import 'package:polymer/polymer.dart'; |
class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
- final ServiceMap profile; |
+ final CpuProfile profile; |
@reflectable final CodeTrieNode root; |
@reflectable final CodeTrieNode node; |
- @reflectable Code get code => node.code; |
+ @reflectable Code get code => node.profileCode.code; |
@reflectable String tipKind = ''; |
@reflectable String tipParent = ''; |
@@ -27,13 +29,11 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
ProfileCodeTrieNodeTreeRow(this.profile, this.root, this.node, |
TableTree tree, |
ProfileCodeTrieNodeTreeRow parent) |
- : super(tree, parent) { |
+ : super(tree, parent) { |
assert(root != null); |
assert(node != null); |
tipTicks = '${node.count}'; |
- var period = profile['period']; |
- var MICROSECONDS_PER_SECOND = 1000000.0; |
- var seconds = (period * node.count) / MICROSECONDS_PER_SECOND; // seconds |
+ var seconds = profile.approximateSecondsForCount(node.count); |
tipTime = Utils.formatTimePrecise(seconds); |
if (code.kind == CodeKind.Tag) { |
tipKind = 'Tag (category)'; |
@@ -45,7 +45,7 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
tipExclusive = Utils.formatPercent(node.count, root.count); |
} else { |
if ((code.kind == CodeKind.Collected) || |
- (code.kind == CodeKind.Reused)) { |
+ (code.kind == CodeKind.Reused)) { |
tipKind = 'Garbage Collected Code'; |
} else { |
tipKind = '${code.kind} (Function)'; |
@@ -55,13 +55,14 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
} else { |
tipParent = Utils.formatPercent(node.count, parent.node.count); |
} |
- tipExclusive = Utils.formatPercent(node.code.exclusiveTicks, root.count); |
+ tipExclusive = |
+ Utils.formatPercent(node.profileCode.exclusiveTicks, root.count); |
} |
} |
bool shouldDisplayChild(CodeTrieNode childNode, double threshold) { |
return ((childNode.count / node.count) > threshold) || |
- ((childNode.code.exclusiveTicks / root.count) > threshold); |
+ ((childNode.profileCode.exclusiveTicks / root.count) > threshold); |
} |
void _buildTooltip(DivElement memberList, Map<String, String> items) { |
@@ -85,29 +86,33 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
void onShow() { |
super.onShow(); |
if (children.length == 0) { |
- var threshold = profile['threshold']; |
+ var threshold = profile.displayThreshold; |
for (var childNode in node.children) { |
if (!shouldDisplayChild(childNode, threshold)) { |
continue; |
} |
var row = |
- new ProfileCodeTrieNodeTreeRow(profile, root, childNode, tree, this); |
+ new ProfileCodeTrieNodeTreeRow(profile, root, childNode, tree, this); |
children.add(row); |
} |
} |
- var row = tr; |
var methodCell = tableColumns[0]; |
// Enable expansion by clicking anywhere on the method column. |
methodCell.onClick.listen(onClick); |
+ // Grab the flex-row Div inside the methodCell. |
+ methodCell = methodCell.children[0]; |
+ |
// Insert the parent percentage |
var parentPercent = new DivElement(); |
- parentPercent.style.position = 'relative'; |
- parentPercent.style.display = 'inline'; |
parentPercent.text = tipParent; |
methodCell.children.add(parentPercent); |
+ var gap = new SpanElement(); |
+ gap.style.minWidth = '1em'; |
+ methodCell.children.add(gap); |
+ |
var codeRef = new Element.tag('code-ref'); |
codeRef.ref = code; |
methodCell.children.add(codeRef); |
@@ -123,10 +128,10 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
memberListDiv.classes.add('memberList'); |
tooltipDiv.children.add(memberListDiv); |
_buildTooltip(memberListDiv, { |
- 'Kind' : tipKind, |
- 'Percent of Parent' : tipParent, |
- 'Sample Count' : tipTicks, |
- 'Approximate Execution Time': tipTime, |
+ 'Kind' : tipKind, |
+ 'Percent of Parent' : tipParent, |
+ 'Sample Count' : tipTicks, |
+ 'Approximate Execution Time': tipTime, |
}); |
selfCell.children.add(tooltipDiv); |
} |
@@ -136,106 +141,291 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
} |
} |
+class ProfileFunctionTrieNodeTreeRow extends TableTreeRow { |
+ final CpuProfile profile; |
+ @reflectable final FunctionTrieNode root; |
+ @reflectable final FunctionTrieNode node; |
+ ProfileFunction get profileFunction => node.profileFunction; |
+ @reflectable ServiceFunction get function => node.profileFunction.function; |
+ @reflectable String tipKind = ''; |
+ @reflectable String tipParent = ''; |
+ @reflectable String tipExclusive = ''; |
+ @reflectable String tipTime = ''; |
+ @reflectable String tipTicks = ''; |
+ |
+ String tipOptimized = ''; |
+ |
+ ProfileFunctionTrieNodeTreeRow(this.profile, this.root, this.node, |
+ TableTree tree, |
+ ProfileFunctionTrieNodeTreeRow parent) |
+ : super(tree, parent) { |
+ assert(root != null); |
+ assert(node != null); |
+ tipTicks = '${node.count}'; |
+ var seconds = profile.approximateSecondsForCount(node.count); |
+ tipTime = Utils.formatTimePrecise(seconds); |
+ if (parent == null) { |
+ tipParent = Utils.formatPercent(node.count, root.count); |
+ } else { |
+ tipParent = Utils.formatPercent(node.count, parent.node.count); |
+ } |
+ if (function.kind == FunctionKind.kTag) { |
+ tipExclusive = Utils.formatPercent(node.count, root.count); |
+ } else { |
+ tipExclusive = |
+ Utils.formatPercent(node.profileFunction.exclusiveTicks, root.count); |
+ } |
+ |
+ if (function.kind == FunctionKind.kTag) { |
+ tipKind = 'Tag (category)'; |
+ } else if (function.kind == FunctionKind.kCollected) { |
+ tipKind = 'Garbage Collected Code'; |
+ } else { |
+ tipKind = '${function.kind} (Function)'; |
+ } |
+ } |
+ |
+ bool hasChildren() { |
+ return node.children.length > 0; |
+ } |
+ |
+ void _buildTooltip(DivElement memberList, Map<String, String> items) { |
+ items.forEach((k, v) { |
+ var item = new DivElement(); |
+ item.classes.add('memberItem'); |
+ var name = new DivElement(); |
+ name.classes.add('memberName'); |
+ name.classes.add('white'); |
+ name.text = k; |
+ var value = new DivElement(); |
+ value.classes.add('memberValue'); |
+ value.classes.add('white'); |
+ value.text = v; |
+ item.children.add(name); |
+ item.children.add(value); |
+ memberList.children.add(item); |
+ }); |
+ } |
+ |
+ void onShow() { |
+ super.onShow(); |
+ if (children.length == 0) { |
+ for (var childNode in node.children) { |
+ var row = new ProfileFunctionTrieNodeTreeRow(profile, |
+ root, |
+ childNode, tree, this); |
+ children.add(row); |
+ } |
+ } |
+ |
+ var selfCell = tableColumns[1]; |
+ selfCell.style.position = 'relative'; |
+ selfCell.text = tipExclusive; |
+ |
+ var methodCell = tableColumns[0]; |
+ // Enable expansion by clicking anywhere on the method column. |
+ methodCell.onClick.listen(onClick); |
+ |
+ // Grab the flex-row Div inside the methodCell. |
+ methodCell = methodCell.children[0]; |
+ |
+ // Insert the parent percentage |
+ var parentPercent = new DivElement(); |
+ parentPercent.text = tipParent; |
+ methodCell.children.add(parentPercent); |
+ |
+ var gap = new SpanElement(); |
+ gap.style.minWidth = '1em'; |
+ methodCell.children.add(gap); |
+ |
+ var functionAndCodeContainer = new DivElement(); |
+ methodCell.children.add(functionAndCodeContainer); |
+ |
+ var functionRef = new Element.tag('function-ref'); |
+ functionRef.ref = function; |
+ functionAndCodeContainer.children.add(functionRef); |
+ |
+ var codeRow = new DivElement(); |
+ codeRow.style.paddingTop = '1em'; |
+ functionAndCodeContainer.children.add(codeRow); |
+ if (!function.kind.isSynthetic()) { |
+ |
+ var totalTicks = node.totalCodesTicks; |
+ var numCodes = node.codes.length; |
+ var label = new SpanElement(); |
+ label.text = 'Compiled into:\n'; |
+ codeRow.children.add(label); |
+ var curlyBlock = new Element.tag('curly-block'); |
+ codeRow.children.add(curlyBlock); |
+ for (var i = 0; i < numCodes; i++) { |
+ var codeRowSpan = new DivElement(); |
+ codeRowSpan.style.paddingLeft = '1em'; |
+ curlyBlock.children.add(codeRowSpan); |
+ var nodeCode = node.codes[i]; |
+ var ticks = nodeCode.ticks; |
+ var percentage = Utils.formatPercent(ticks, totalTicks); |
+ var percentageSpan = new SpanElement(); |
+ percentageSpan.text = '($percentage) '; |
+ codeRowSpan.children.add(percentageSpan); |
+ var codeRef = new Element.tag('code-ref'); |
+ codeRef.ref = nodeCode.code.code; |
+ codeRowSpan.children.add(codeRef); |
+ } |
+ } |
+ |
+ var tooltipDiv = new DivElement(); |
+ tooltipDiv.classes.add('tooltip'); |
+ |
+ var memberListDiv = new DivElement(); |
+ memberListDiv.classes.add('memberList'); |
+ tooltipDiv.children.add(memberListDiv); |
+ _buildTooltip(memberListDiv, { |
+ 'Kind' : tipKind, |
+ 'Percent of Parent' : tipParent, |
+ 'Sample Count' : tipTicks, |
+ 'Approximate Execution Time': tipTime, |
+ }); |
+ selfCell.children.add(tooltipDiv); |
+ } |
+} |
+ |
/// Displays a CpuProfile |
@CustomTag('cpu-profile') |
class CpuProfileElement extends ObservatoryElement { |
- CpuProfileElement.created() : super.created(); |
- @published Isolate isolate; |
+ static const MICROSECONDS_PER_SECOND = 1000000.0; |
- @observable ServiceMap profile; |
- @observable bool hideTagsChecked; |
+ @published Isolate isolate; |
@observable String sampleCount = ''; |
@observable String refreshTime = ''; |
@observable String sampleRate = ''; |
- @observable String sampleDepth = ''; |
+ @observable String stackDepth = ''; |
@observable String displayCutoff = ''; |
@observable String timeSpan = ''; |
- @reflectable double displayThreshold = 0.0002; // 0.02%. |
@observable String tagSelector = 'UserVM'; |
+ @observable String modeSelector = 'Function'; |
- final _id = '#tableTree'; |
- TableTree tree; |
+ final CpuProfile profile = new CpuProfile(); |
- static const MICROSECONDS_PER_SECOND = 1000000.0; |
- |
- void isolateChanged(oldValue) { |
- if (isolate == null) { |
- profile = null; |
- return; |
- } |
- isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) |
- .then((ServiceObject obj) { |
- print(obj); |
- // Assert we got back the a profile. |
- assert(obj.type == 'CpuProfile'); |
- profile = obj; |
- _update(); |
- }); |
- } |
+ CpuProfileElement.created() : super.created(); |
@override |
void attached() { |
super.attached(); |
- var tableBody = shadowRoot.querySelector('#tableTreeBody'); |
- assert(tableBody != null); |
- tree = new TableTree(tableBody, 2); |
- _update(); |
+ } |
+ |
+ void isolateChanged(oldValue) { |
+ _getCpuProfile(); |
} |
void tagSelectorChanged(oldValue) { |
- isolateChanged(null); |
+ _getCpuProfile(); |
+ } |
+ |
+ void modeSelectorChanged(oldValue) { |
+ _updateView(); |
+ } |
+ |
+ void clear(var done) { |
+ _clearCpuProfile().whenComplete(done); |
+ } |
+ |
+ Future _clearCpuProfile() { |
+ profile.clear(); |
+ if (isolate == null) { |
+ return new Future.value(null); |
+ } |
+ return isolate.invokeRpc('clearCpuProfile', { }) |
+ .then((ServiceMap response) { |
+ _updateView(); |
+ }); |
} |
void refresh(var done) { |
- isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) |
- .then((ServiceObject obj) { |
- // Assert we got back the a profile. |
- assert(obj.type == 'CpuProfile'); |
- profile = obj; |
- _update(); |
- }).whenComplete(done); |
+ _getCpuProfile().whenComplete(done); |
} |
- void _update() { |
- if (profile == null) { |
- return; |
+ Future _getCpuProfile() { |
+ profile.clear(); |
+ if (isolate == null) { |
+ return new Future.value(null); |
+ } |
+ return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) |
+ .then((ServiceMap response) { |
+ profile.load(isolate, response); |
+ _updateView(); |
+ }); |
+ } |
+ |
+ void _updateView() { |
+ sampleCount = profile.sampleCount.toString(); |
+ refreshTime = new DateTime.now().toString(); |
+ stackDepth = profile.stackDepth.toString(); |
+ sampleRate = profile.sampleRate.toStringAsFixed(0); |
+ timeSpan = formatTime(profile.timeSpan); |
+ displayCutoff = '${(profile.displayThreshold * 100.0).toString()}%'; |
+ if (functionTree != null) { |
+ functionTree.clear(); |
} |
- var totalSamples = profile['samples']; |
- var now = new DateTime.now(); |
- sampleCount = totalSamples.toString(); |
- refreshTime = now.toString(); |
- sampleDepth = profile['depth'].toString(); |
- var period = profile['period']; |
- sampleRate = (MICROSECONDS_PER_SECOND / period).toStringAsFixed(0); |
- timeSpan = formatTime(profile['timeSpan']); |
- displayCutoff = '${(displayThreshold * 100.0).toString()}%'; |
- profile.isolate.processProfile(profile); |
- profile['threshold'] = displayThreshold; |
- _buildTree(); |
- } |
- |
- void _buildStackTree() { |
- var root = profile.isolate.profileTrieRoot; |
+ if (codeTree != null) { |
+ codeTree.clear(); |
+ } |
+ if (modeSelector == 'Code') { |
+ _buildCodeTree(); |
+ } else { |
+ _buildFunctionTree(); |
+ } |
+ } |
+ |
+ TableTree codeTree; |
+ TableTree functionTree; |
+ |
+ void _buildFunctionTree() { |
+ if (functionTree == null) { |
+ var tableBody = shadowRoot.querySelector('#treeBody'); |
+ assert(tableBody != null); |
+ functionTree = new TableTree(tableBody, 2); |
+ } |
+ var root = profile.functionTrieRoot; |
if (root == null) { |
return; |
} |
try { |
- tree.initialize( |
- new ProfileCodeTrieNodeTreeRow(profile, root, root, tree, null)); |
+ functionTree.initialize( |
+ new ProfileFunctionTrieNodeTreeRow(profile, |
+ root, root, functionTree, null)); |
} catch (e, stackTrace) { |
print(e); |
print(stackTrace); |
- Logger.root.warning('_buildStackTree', e, stackTrace); |
+ Logger.root.warning('_buildFunctionTree', e, stackTrace); |
} |
// Check if we only have one node at the root and expand it. |
- if (tree.rows.length == 1) { |
- tree.toggle(tree.rows[0]); |
+ if (functionTree.rows.length == 1) { |
+ functionTree.toggle(functionTree.rows[0]); |
} |
- notifyPropertyChange(#tree, null, tree); |
} |
- void _buildTree() { |
- _buildStackTree(); |
+ void _buildCodeTree() { |
+ if (codeTree == null) { |
+ var tableBody = shadowRoot.querySelector('#treeBody'); |
+ assert(tableBody != null); |
+ codeTree = new TableTree(tableBody, 2); |
+ } |
+ var root = profile.codeTrieRoot; |
+ if (root == null) { |
+ return; |
+ } |
+ try { |
+ codeTree.initialize( |
+ new ProfileCodeTrieNodeTreeRow(profile, root, root, codeTree, null)); |
+ } catch (e, stackTrace) { |
+ print(e); |
+ print(stackTrace); |
+ Logger.root.warning('_buildCodeTree', e, stackTrace); |
+ } |
+ // Check if we only have one node at the root and expand it. |
+ if (codeTree.rows.length == 1) { |
+ codeTree.toggle(codeTree.rows[0]); |
+ } |
} |
} |