| 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]);
|
| + }
|
| }
|
| }
|
|
|