| 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 d82b1f784849d9558bd629823a32c47e552efb99..892c1ce044f0c3280c0f8749def688c8b2af2223 100644
|
| --- a/runtime/observatory/lib/src/elements/cpu_profile.dart
|
| +++ b/runtime/observatory/lib/src/elements/cpu_profile.dart
|
| @@ -14,68 +14,36 @@ import 'package:observatory/cpu_profile.dart';
|
| import 'package:observatory/elements.dart';
|
| import 'package:polymer/polymer.dart';
|
|
|
| -class ProfileCodeTrieNodeTreeRow extends TableTreeRow {
|
| - final CpuProfile profile;
|
| - @reflectable final CodeTrieNode root;
|
| - @reflectable final CodeTrieNode node;
|
| - @reflectable Code get code => node.profileCode.code;
|
| -
|
| - @reflectable String tipKind = '';
|
| - @reflectable String tipParent = '';
|
| - @reflectable String tipExclusive = '';
|
| - @reflectable String tipTicks = '';
|
| - @reflectable String tipTime = '';
|
| -
|
| - ProfileCodeTrieNodeTreeRow(this.profile, this.root, this.node,
|
| - TableTree tree,
|
| - ProfileCodeTrieNodeTreeRow parent)
|
| - : super(tree, parent) {
|
| - assert(root != null);
|
| - assert(node != null);
|
| - tipTicks = '${node.count}';
|
| - var seconds = profile.approximateSecondsForCount(node.count);
|
| - tipTime = Utils.formatTimePrecise(seconds);
|
| - if (code.kind == CodeKind.Tag) {
|
| - tipKind = 'Tag (category)';
|
| - if (parent == null) {
|
| - tipParent = Utils.formatPercent(node.count, root.count);
|
| - } else {
|
| - tipParent = Utils.formatPercent(node.count, parent.node.count);
|
| - }
|
| - tipExclusive = Utils.formatPercent(node.count, root.count);
|
| - } else {
|
| - if ((code.kind == CodeKind.Collected) ||
|
| - (code.kind == CodeKind.Reused)) {
|
| - tipKind = 'Garbage Collected Code';
|
| - } else {
|
| - tipKind = '${code.kind} (Function)';
|
| - }
|
| - if (parent == null) {
|
| - tipParent = Utils.formatPercent(node.count, root.count);
|
| - } else {
|
| - tipParent = Utils.formatPercent(node.count, parent.node.count);
|
| - }
|
| - tipExclusive =
|
| - Utils.formatPercent(node.profileCode.exclusiveTicks, root.count);
|
| - }
|
| - }
|
| -
|
| - bool shouldDisplayChild(CodeTrieNode childNode, double threshold) {
|
| - return ((childNode.count / node.count) > threshold) ||
|
| - ((childNode.profileCode.exclusiveTicks / root.count) > threshold);
|
| - }
|
| +List<String> sorted(Set<String> attributes) {
|
| + var list = attributes.toList();
|
| + list.sort();
|
| + return list;
|
| +}
|
|
|
| - void _buildTooltip(DivElement memberList, Map<String, String> items) {
|
| +abstract class ProfileTreeRow<T> extends TableTreeRow {
|
| + final CpuProfile profile;
|
| + final T node;
|
| + final String selfPercent;
|
| + final String percent;
|
| + bool _infoBoxShown = false;
|
| + HtmlElement infoBox;
|
| + HtmlElement infoButton;
|
| +
|
| + ProfileTreeRow(TableTree tree, TableTreeRow parent,
|
| + this.profile, this.node, double selfPercent, double percent)
|
| + : super(tree, parent),
|
| + selfPercent = Utils.formatPercentNormalized(selfPercent),
|
| + percent = Utils.formatPercentNormalized(percent);
|
| +
|
| + static _addToMemberList(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);
|
| @@ -83,209 +51,385 @@ class ProfileCodeTrieNodeTreeRow extends TableTreeRow {
|
| });
|
| }
|
|
|
| + makeInfoBox() {
|
| + if (infoBox != null) {
|
| + return;
|
| + }
|
| + infoBox = new DivElement();
|
| + infoBox.classes.add('infoBox');
|
| + infoBox.classes.add('shadow');
|
| + infoBox.style.display = 'none';
|
| + infoBox.onClick.listen((e) => e.stopPropagation());
|
| + }
|
| +
|
| + makeInfoButton() {
|
| + infoButton = new SpanElement();
|
| + infoButton.style.marginLeft = 'auto';
|
| + infoButton.style.marginRight = '1em';
|
| + infoButton.children.add(new Element.tag('icon-info-outline'));
|
| + infoButton.onClick.listen((event) {
|
| + event.stopPropagation();
|
| + toggleInfoBox();
|
| + });
|
| + }
|
| +
|
| + static const attributes = const {
|
| + 'optimized' : const ['O', null, 'Optimized'],
|
| + 'unoptimized' : const ['U', null, 'Unoptimized'],
|
| + 'inlined' : const ['I', null, 'Inlined'],
|
| + 'dart' : const ['D', null, 'Dart'],
|
| + 'tag' : const ['T', null, 'Tag'],
|
| + 'native' : const ['N', null, 'Native'],
|
| + 'stub': const ['S', null, 'Stub'],
|
| + 'synthetic' : const ['?', null, 'Synthetic'],
|
| + };
|
| +
|
| + HtmlElement newAttributeBox(String attribute) {
|
| + List attributeDetails = attributes[attribute];
|
| + if (attributeDetails == null) {
|
| + print('could not find attribute $attribute');
|
| + return null;
|
| + }
|
| + var element = new SpanElement();
|
| + element.style.border = 'solid 2px #ECECEC';
|
| + element.style.height = '100%';
|
| + element.style.display = 'inline-block';
|
| + element.style.textAlign = 'center';
|
| + element.style.minWidth = '1.5em';
|
| + element.style.fontWeight = 'bold';
|
| + if (attributeDetails[1] != null) {
|
| + element.style.backgroundColor = attributeDetails[1];
|
| + }
|
| + element.text = attributeDetails[0];
|
| + element.title = attributeDetails[2];
|
| + return element;
|
| + }
|
| +
|
| + onHide() {
|
| + super.onHide();
|
| + infoBox = null;
|
| + infoButton = null;
|
| + }
|
| +
|
| + showInfoBox() {
|
| + if ((infoButton == null) || (infoBox == null)) {
|
| + return;
|
| + }
|
| + _infoBoxShown = true;
|
| + infoBox.style.display = 'block';
|
| + infoButton.children.clear();
|
| + infoButton.children.add(new Element.tag('icon-info'));
|
| + }
|
| +
|
| + hideInfoBox() {
|
| + _infoBoxShown = false;
|
| + if ((infoButton == null) || (infoBox == null)) {
|
| + return;
|
| + }
|
| + infoBox.style.display = 'none';
|
| + infoButton.children.clear();
|
| + infoButton.children.add(new Element.tag('icon-info-outline'));
|
| + }
|
| +
|
| + toggleInfoBox() {
|
| + if (_infoBoxShown) {
|
| + hideInfoBox();
|
| + } else {
|
| + showInfoBox();
|
| + }
|
| + }
|
| +
|
| + hideAllInfoBoxes() {
|
| + final List<ProfileTreeRow> rows = tree.rows;
|
| + for (var row in rows) {
|
| + row.hideInfoBox();
|
| + }
|
| + }
|
| +
|
| + onClick(MouseEvent e) {
|
| + e.stopPropagation();
|
| + if (e.altKey) {
|
| + bool show = !_infoBoxShown;
|
| + hideAllInfoBoxes();
|
| + if (show) {
|
| + showInfoBox();
|
| + }
|
| + return;
|
| + }
|
| + super.onClick(e);
|
| + }
|
| +
|
| + HtmlElement newCodeRef(ProfileCode code) {
|
| + var codeRef = new Element.tag('code-ref');
|
| + codeRef.ref = code.code;
|
| + return codeRef;
|
| + }
|
| +
|
| + HtmlElement newFunctionRef(ProfileFunction function) {
|
| + var ref = new Element.tag('function-ref');
|
| + ref.ref = function.function;
|
| + return ref;
|
| + }
|
| +
|
| + HtmlElement hr() {
|
| + var element = new HRElement();
|
| + return element;
|
| + }
|
| +
|
| + HtmlElement div(String text) {
|
| + var element = new DivElement();
|
| + element.text = text;
|
| + return element;
|
| + }
|
| +
|
| + HtmlElement br() {
|
| + return new BRElement();
|
| + }
|
| +
|
| + HtmlElement span(String text) {
|
| + var element = new SpanElement();
|
| + element.style.minWidth = '1em';
|
| + element.text = text;
|
| + return element;
|
| + }
|
| +}
|
| +
|
| +class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> {
|
| + CodeProfileTreeRow(TableTree tree, CodeProfileTreeRow parent,
|
| + CpuProfile profile, CodeCallTreeNode node)
|
| + : super(tree, parent, profile, node,
|
| + node.profileCode.normalizedExclusiveTicks,
|
| + node.percentage) {
|
| + // fill out attributes.
|
| + }
|
| +
|
| + bool hasChildren() => node.children.length > 0;
|
| +
|
| void onShow() {
|
| super.onShow();
|
| +
|
| if (children.length == 0) {
|
| - var threshold = profile.displayThreshold;
|
| for (var childNode in node.children) {
|
| - if (!shouldDisplayChild(childNode, threshold)) {
|
| - continue;
|
| - }
|
| - var row =
|
| - new ProfileCodeTrieNodeTreeRow(profile, root, childNode, tree, this);
|
| + var row = new CodeProfileTreeRow(tree, this, profile, childNode);
|
| children.add(row);
|
| }
|
| }
|
|
|
| - var methodCell = tableColumns[0];
|
| - // Enable expansion by clicking anywhere on the method column.
|
| - methodCell.onClick.listen(onClick);
|
| + // Fill in method column.
|
| + var methodColumn = flexColumns[0];
|
| + methodColumn.style.justifyContent = 'flex-start';
|
| + methodColumn.style.position = 'relative';
|
|
|
| - // 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);
|
| + // Percent.
|
| + var percentNode = new DivElement();
|
| + percentNode.text = percent;
|
| + percentNode.style.minWidth = '5em';
|
| + percentNode.style.textAlign = 'right';
|
| + percentNode.title = 'Self: $selfPercent';
|
| + methodColumn.children.add(percentNode);
|
|
|
| + // Gap.
|
| var gap = new SpanElement();
|
| gap.style.minWidth = '1em';
|
| - methodCell.children.add(gap);
|
| + methodColumn.children.add(gap);
|
|
|
| - var codeRef = new Element.tag('code-ref');
|
| - codeRef.ref = code;
|
| - methodCell.children.add(codeRef);
|
| -
|
| - var selfCell = tableColumns[1];
|
| - selfCell.style.position = 'relative';
|
| - selfCell.text = tipExclusive;
|
| -
|
| - 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);
|
| - }
|
| + // Code link.
|
| + var codeRef = newCodeRef(node.profileCode);
|
| + codeRef.style.alignSelf = 'center';
|
| + methodColumn.children.add(codeRef);
|
|
|
| - bool hasChildren() {
|
| - return node.children.length > 0;
|
| - }
|
| -}
|
| + gap = new SpanElement();
|
| + gap.style.minWidth = '1em';
|
| + methodColumn.children.add(gap);
|
|
|
| -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);
|
| + for (var attribute in sorted(node.attributes)) {
|
| + methodColumn.children.add(newAttributeBox(attribute));
|
| }
|
|
|
| - if (function.kind == FunctionKind.kTag) {
|
| - tipKind = 'Tag (category)';
|
| - } else if (function.kind == FunctionKind.kCollected) {
|
| - tipKind = 'Garbage Collected Code';
|
| - } else {
|
| - tipKind = '${function.kind} (Function)';
|
| + makeInfoBox();
|
| + methodColumn.children.add(infoBox);
|
| +
|
| + infoBox.children.add(span('Code '));
|
| + infoBox.children.add(newCodeRef(node.profileCode));
|
| + infoBox.children.add(span(' '));
|
| + for (var attribute in sorted(node.profileCode.attributes)) {
|
| + infoBox.children.add(newAttributeBox(attribute));
|
| }
|
| - }
|
| + infoBox.children.add(br());
|
| + infoBox.children.add(br());
|
| + var memberList = new DivElement();
|
| + memberList.classes.add('memberList');
|
| + infoBox.children.add(br());
|
| + infoBox.children.add(memberList);
|
| + ProfileTreeRow._addToMemberList(memberList, {
|
| + 'Exclusive ticks' : node.profileCode.formattedExclusiveTicks,
|
| + 'Cpu time' : node.profileCode.formattedCpuTime,
|
| + 'Inclusive ticks' : node.profileCode.formattedInclusiveTicks,
|
| + 'Call stack time' : node.profileCode.formattedOnStackTime,
|
| + });
|
|
|
| - bool hasChildren() {
|
| - return node.children.length > 0;
|
| + makeInfoButton();
|
| + methodColumn.children.add(infoButton);
|
| +
|
| + // Fill in self column.
|
| + var selfColumn = flexColumns[1];
|
| + selfColumn.style.position = 'relative';
|
| + selfColumn.style.alignItems = 'center';
|
| + selfColumn.text = selfPercent;
|
| }
|
| +}
|
|
|
| - 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);
|
| - });
|
| +class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> {
|
| + FunctionProfileTreeRow(TableTree tree, FunctionProfileTreeRow parent,
|
| + CpuProfile profile, FunctionCallTreeNode node)
|
| + : super(tree, parent, profile, node,
|
| + node.profileFunction.normalizedExclusiveTicks,
|
| + node.percentage) {
|
| + // fill out attributes.
|
| }
|
|
|
| - void onShow() {
|
| + bool hasChildren() => node.children.length > 0;
|
| +
|
| + onShow() {
|
| super.onShow();
|
| if (children.length == 0) {
|
| for (var childNode in node.children) {
|
| - var row = new ProfileFunctionTrieNodeTreeRow(profile,
|
| - root,
|
| - childNode, tree, this);
|
| + var row = new FunctionProfileTreeRow(tree, this, profile, childNode);
|
| children.add(row);
|
| }
|
| }
|
|
|
| - var selfCell = tableColumns[1];
|
| - selfCell.style.position = 'relative';
|
| - selfCell.text = tipExclusive;
|
| + var methodColumn = flexColumns[0];
|
| + methodColumn.style.justifyContent = 'flex-start';
|
|
|
| - var methodCell = tableColumns[0];
|
| - // Enable expansion by clicking anywhere on the method column.
|
| - methodCell.onClick.listen(onClick);
|
| + var codeAndFunctionColumn = new DivElement();
|
| + codeAndFunctionColumn.classes.add('flex-column');
|
| + codeAndFunctionColumn.style.justifyContent = 'center';
|
| + codeAndFunctionColumn.style.width = '100%';
|
| + methodColumn.children.add(codeAndFunctionColumn);
|
|
|
| - // Grab the flex-row Div inside the methodCell.
|
| - methodCell = methodCell.children[0];
|
| + var functionRow = new DivElement();
|
| + functionRow.classes.add('flex-row');
|
| + functionRow.style.position = 'relative';
|
| + functionRow.style.justifyContent = 'flex-start';
|
| + codeAndFunctionColumn.children.add(functionRow);
|
|
|
| // Insert the parent percentage
|
| - var parentPercent = new DivElement();
|
| - parentPercent.text = tipParent;
|
| - methodCell.children.add(parentPercent);
|
| -
|
| + var parentPercent = new SpanElement();
|
| + parentPercent.text = percent;
|
| + parentPercent.style.minWidth = '4em';
|
| + parentPercent.style.alignSelf = 'center';
|
| + parentPercent.style.textAlign = 'right';
|
| + parentPercent.title = 'Self: $selfPercent';
|
| + functionRow.children.add(parentPercent);
|
| +
|
| + // Gap.
|
| var gap = new SpanElement();
|
| gap.style.minWidth = '1em';
|
| - methodCell.children.add(gap);
|
| -
|
| - var functionAndCodeContainer = new DivElement();
|
| - methodCell.children.add(functionAndCodeContainer);
|
| + gap.text = ' ';
|
| + functionRow.children.add(gap);
|
|
|
| var functionRef = new Element.tag('function-ref');
|
| - functionRef.ref = function;
|
| - functionAndCodeContainer.children.add(functionRef);
|
| + functionRef.ref = node.profileFunction.function;
|
| + functionRef.style.alignSelf = 'center';
|
| + functionRow.children.add(functionRef);
|
|
|
| - var codeRow = new DivElement();
|
| - codeRow.style.paddingTop = '1em';
|
| - functionAndCodeContainer.children.add(codeRow);
|
| - if (!function.kind.isSynthetic()) {
|
| + gap = new SpanElement();
|
| + gap.style.minWidth = '1em';
|
| + gap.text = ' ';
|
| + functionRow.children.add(gap);
|
|
|
| + for (var attribute in sorted(node.attributes)) {
|
| + functionRow.children.add(newAttributeBox(attribute));
|
| + }
|
| +
|
| + makeInfoBox();
|
| + functionRow.children.add(infoBox);
|
| +
|
| + if (node.profileFunction.function.kind.hasDartCode()) {
|
| + infoBox.children.add(div('Hot code for current node'));
|
| + infoBox.children.add(br());
|
| 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);
|
| + infoBox.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) ';
|
| + percentageSpan.style.display = 'inline-block';
|
| + percentageSpan.text = '$percentage';
|
| + percentageSpan.style.minWidth = '5em';
|
| + percentageSpan.style.textAlign = 'right';
|
| codeRowSpan.children.add(percentageSpan);
|
| var codeRef = new Element.tag('code-ref');
|
| codeRef.ref = nodeCode.code.code;
|
| + codeRef.style.marginLeft = '1em';
|
| + codeRef.style.marginRight = 'auto';
|
| + codeRef.style.width = '100%';
|
| codeRowSpan.children.add(codeRef);
|
| }
|
| + infoBox.children.add(hr());
|
| }
|
| -
|
| - 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,
|
| + infoBox.children.add(span('Function '));
|
| + infoBox.children.add(newFunctionRef(node.profileFunction));
|
| + infoBox.children.add(span(' '));
|
| + for (var attribute in sorted(node.profileFunction.attributes)) {
|
| + infoBox.children.add(newAttributeBox(attribute));
|
| + }
|
| + var memberList = new DivElement();
|
| + memberList.classes.add('memberList');
|
| + infoBox.children.add(br());
|
| + infoBox.children.add(br());
|
| + infoBox.children.add(memberList);
|
| + infoBox.children.add(br());
|
| + ProfileTreeRow._addToMemberList(memberList, {
|
| + 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks,
|
| + 'Cpu time' : node.profileFunction.formattedCpuTime,
|
| + 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks,
|
| + 'Call stack time' : node.profileFunction.formattedOnStackTime,
|
| });
|
| - selfCell.children.add(tooltipDiv);
|
| +
|
| + if (node.profileFunction.function.kind.hasDartCode()) {
|
| + infoBox.children.add(div('Hot code containing function'));
|
| + infoBox.children.add(br());
|
| + var totalTicks = profile.sampleCount;
|
| + var codes = node.profileFunction.profileCodes;
|
| + var numCodes = codes.length;
|
| + for (var i = 0; i < numCodes; i++) {
|
| + var codeRowSpan = new DivElement();
|
| + codeRowSpan.style.paddingLeft = '1em';
|
| + infoBox.children.add(codeRowSpan);
|
| + var profileCode = codes[i];
|
| + var code = profileCode.code;
|
| + var ticks = profileCode.inclusiveTicks;
|
| + var percentage = Utils.formatPercent(ticks, totalTicks);
|
| + var percentageSpan = new SpanElement();
|
| + percentageSpan.style.display = 'inline-block';
|
| + percentageSpan.text = '$percentage';
|
| + percentageSpan.style.minWidth = '5em';
|
| + percentageSpan.style.textAlign = 'right';
|
| + percentageSpan.title = 'Inclusive ticks';
|
| + codeRowSpan.children.add(percentageSpan);
|
| + var codeRef = new Element.tag('code-ref');
|
| + codeRef.ref = code;
|
| + codeRef.style.marginLeft = '1em';
|
| + codeRef.style.marginRight = 'auto';
|
| + codeRef.style.width = '100%';
|
| + codeRowSpan.children.add(codeRef);
|
| + }
|
| + }
|
| +
|
| + makeInfoButton();
|
| + methodColumn.children.add(infoButton);
|
| +
|
| + // Fill in self column.
|
| + var selfColumn = flexColumns[1];
|
| + selfColumn.style.position = 'relative';
|
| + selfColumn.style.alignItems = 'center';
|
| + selfColumn.text = selfPercent;
|
| }
|
| }
|
|
|
| @@ -299,11 +443,18 @@ class CpuProfileElement extends ObservatoryElement {
|
| @observable String refreshTime = '';
|
| @observable String sampleRate = '';
|
| @observable String stackDepth = '';
|
| - @observable String displayCutoff = '';
|
| @observable String timeSpan = '';
|
| -
|
| + @observable String fetchTime = '';
|
| + @observable String loadTime = '';
|
| @observable String tagSelector = 'UserVM';
|
| @observable String modeSelector = 'Function';
|
| + @observable String directionSelector = 'Up';
|
| +
|
| + @observable String state = 'Requested';
|
| + @observable var exception;
|
| + @observable var stackTrace;
|
| +
|
| + final Stopwatch _sw = new Stopwatch();
|
|
|
| final CpuProfile profile = new CpuProfile();
|
|
|
| @@ -326,6 +477,10 @@ class CpuProfileElement extends ObservatoryElement {
|
| _updateView();
|
| }
|
|
|
| + void directionSelectorChanged(oldValue) {
|
| + _updateView();
|
| + }
|
| +
|
| void clear(var done) {
|
| _clearCpuProfile().whenComplete(done);
|
| }
|
| @@ -345,16 +500,55 @@ class CpuProfileElement extends ObservatoryElement {
|
| _getCpuProfile().whenComplete(done);
|
| }
|
|
|
| - Future _getCpuProfile() {
|
| + _onFetchStarted() {
|
| + _sw.reset();
|
| + _sw.start();
|
| + state = 'Requested';
|
| + }
|
| +
|
| + _onFetchFinished() {
|
| + _sw.stop();
|
| + fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds);
|
| + }
|
| +
|
| + _onLoadStarted() {
|
| + _sw.reset();
|
| + _sw.start();
|
| + state = 'Loading';
|
| + }
|
| +
|
| + _onLoadFinished() {
|
| + _sw.stop();
|
| + loadTime = formatTimeMilliseconds(_sw.elapsedMilliseconds);
|
| + state = 'Loaded';
|
| + }
|
| +
|
| + Future _getCpuProfile() async {
|
| profile.clear();
|
| + if (functionTree != null) {
|
| + functionTree.clear();
|
| + }
|
| + if (codeTree != null) {
|
| + codeTree.clear();
|
| + }
|
| if (isolate == null) {
|
| return new Future.value(null);
|
| }
|
| - return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector })
|
| - .then((ServiceMap response) {
|
| - profile.load(isolate, response);
|
| - _updateView();
|
| - });
|
| + _onFetchStarted();
|
| + var response =
|
| + await isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector });
|
| + _onFetchFinished();
|
| + _onLoadStarted();
|
| + await window.animationFrame;
|
| + try {
|
| + profile.load(isolate, response);
|
| + _onLoadFinished();
|
| + _updateView();
|
| + } catch (e, st) {
|
| + state = 'Exception';
|
| + exception = e;
|
| + stackTrace = st;
|
| + }
|
| }
|
|
|
| void _updateView() {
|
| @@ -363,69 +557,49 @@ class CpuProfileElement extends ObservatoryElement {
|
| stackDepth = profile.stackDepth.toString();
|
| sampleRate = profile.sampleRate.toStringAsFixed(0);
|
| timeSpan = formatTime(profile.timeSpan);
|
| - displayCutoff = '${(profile.displayThreshold * 100.0).toString()}%';
|
| if (functionTree != null) {
|
| functionTree.clear();
|
| }
|
| if (codeTree != null) {
|
| codeTree.clear();
|
| }
|
| + bool exclusive = directionSelector == 'Up';
|
| if (modeSelector == 'Code') {
|
| - _buildCodeTree();
|
| + _buildCodeTree(exclusive);
|
| } else {
|
| - _buildFunctionTree();
|
| + _buildFunctionTree(exclusive);
|
| }
|
| }
|
|
|
| TableTree codeTree;
|
| TableTree functionTree;
|
|
|
| - void _buildFunctionTree() {
|
| + void _buildFunctionTree(bool exclusive) {
|
| if (functionTree == null) {
|
| var tableBody = shadowRoot.querySelector('#treeBody');
|
| assert(tableBody != null);
|
| functionTree = new TableTree(tableBody, 2);
|
| }
|
| - var root = profile.functionTrieRoot;
|
| - if (root == null) {
|
| + var tree = profile.functionTrees[exclusive ? 'exclusive' : 'inclusive'];
|
| + if (tree == null) {
|
| return;
|
| }
|
| - try {
|
| - functionTree.initialize(
|
| - new ProfileFunctionTrieNodeTreeRow(profile,
|
| - root, root, functionTree, null));
|
| - } catch (e, stackTrace) {
|
| - print(e);
|
| - print(stackTrace);
|
| - Logger.root.warning('_buildFunctionTree', e, stackTrace);
|
| - }
|
| - // Check if we only have one node at the root and expand it.
|
| - if (functionTree.rows.length == 1) {
|
| - functionTree.toggle(functionTree.rows[0]);
|
| - }
|
| + var rootRow =
|
| + new FunctionProfileTreeRow(functionTree, null, profile, tree.root);
|
| + functionTree.initialize(rootRow);
|
| }
|
|
|
| - void _buildCodeTree() {
|
| + void _buildCodeTree(bool exclusive) {
|
| if (codeTree == null) {
|
| var tableBody = shadowRoot.querySelector('#treeBody');
|
| assert(tableBody != null);
|
| codeTree = new TableTree(tableBody, 2);
|
| }
|
| - var root = profile.codeTrieRoot;
|
| - if (root == null) {
|
| + var tree = profile.codeTrees[exclusive ? 'exclusive' : 'inclusive'];
|
| + if (tree == 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]);
|
| - }
|
| + var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root);
|
| + codeTree.initialize(rootRow);
|
| }
|
| }
|
|
|