blob: 8a69f906c5b24e31e8a6e8ea324f7392c4555b8e [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
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';
List<String> sorted(Set<String> attributes) {
var list = attributes.toList();
list.sort();
return list;
}
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.text = k;
var value = new DivElement();
value.classes.add('memberValue');
value.text = v;
item.children.add(name);
item.children.add(value);
memberList.children.add(item);
});
}
makeInfoBox() {
if (infoBox != null) {
return;
}
infoBox = new DivElement();
infoBox.classes.add('infoBox');
infoBox.classes.add('shadow');
infoBox.style.display = 'none';
listeners.add(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'));
listeners.add(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) {
for (var childNode in node.children) {
var row = new CodeProfileTreeRow(tree, this, profile, childNode);
children.add(row);
}
}
// Fill in method column.
var methodColumn = flexColumns[0];
methodColumn.style.justifyContent = 'flex-start';
methodColumn.style.position = 'relative';
// 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';
methodColumn.children.add(gap);
// Code link.
var codeRef = newCodeRef(node.profileCode);
codeRef.style.alignSelf = 'center';
methodColumn.children.add(codeRef);
gap = new SpanElement();
gap.style.minWidth = '1em';
methodColumn.children.add(gap);
for (var attribute in sorted(node.attributes)) {
methodColumn.children.add(newAttributeBox(attribute));
}
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,
});
makeInfoButton();
methodColumn.children.add(infoButton);
// Fill in self column.
var selfColumn = flexColumns[1];
selfColumn.style.position = 'relative';
selfColumn.style.alignItems = 'center';
selfColumn.text = selfPercent;
}
}
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.
}
bool hasChildren() => node.children.length > 0;
onShow() {
super.onShow();
if (children.length == 0) {
for (var childNode in node.children) {
var row = new FunctionProfileTreeRow(tree, this, profile, childNode);
children.add(row);
}
}
var methodColumn = flexColumns[0];
methodColumn.style.justifyContent = 'flex-start';
var codeAndFunctionColumn = new DivElement();
codeAndFunctionColumn.classes.add('flex-column');
codeAndFunctionColumn.style.justifyContent = 'center';
codeAndFunctionColumn.style.width = '100%';
methodColumn.children.add(codeAndFunctionColumn);
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 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';
gap.text = ' ';
functionRow.children.add(gap);
var functionRef = new Element.tag('function-ref');
functionRef.ref = node.profileFunction.function;
functionRef.style.alignSelf = 'center';
functionRow.children.add(functionRef);
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;
for (var i = 0; i < numCodes; i++) {
var codeRowSpan = new DivElement();
codeRowSpan.style.paddingLeft = '1em';
infoBox.children.add(codeRowSpan);
var nodeCode = node.codes[i];
var ticks = nodeCode.ticks;
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';
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());
}
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,
});
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;
}
}
/// Displays a CpuProfile
@CustomTag('cpu-profile')
class CpuProfileElement extends ObservatoryElement {
static const MICROSECONDS_PER_SECOND = 1000000.0;
@published Isolate isolate;
@observable String sampleCount = '';
@observable String refreshTime = '';
@observable String sampleRate = '';
@observable String stackDepth = '';
@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();
CpuProfileElement.created() : super.created();
@override
void attached() {
super.attached();
}
void isolateChanged(oldValue) {
_getCpuProfile();
}
void tagSelectorChanged(oldValue) {
_getCpuProfile();
}
void modeSelectorChanged(oldValue) {
_updateView();
}
void directionSelectorChanged(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) {
_getCpuProfile().whenComplete(done);
}
_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() {
profile.clear();
if (functionTree != null) {
functionTree.clear();
functionTree = null;
}
if (codeTree != null) {
codeTree.clear();
codeTree = null;
}
if (isolate == null) {
return new Future.value(null);
}
_onFetchStarted();
return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector })
.then((response) {
_onFetchFinished();
_onLoadStarted();
try {
profile.load(isolate, response);
_onLoadFinished();
_updateView();
} catch (e, st) {
state = 'Exception';
exception = e;
stackTrace = st;
}
}).catchError((e, st) {
state = 'Exception';
exception = e;
stackTrace = st;
});
}
void _updateView() {
sampleCount = profile.sampleCount.toString();
refreshTime = new DateTime.now().toString();
stackDepth = profile.stackDepth.toString();
sampleRate = profile.sampleRate.toStringAsFixed(0);
timeSpan = formatTime(profile.timeSpan);
bool exclusive = directionSelector == 'Up';
if (functionTree != null) {
functionTree.clear();
functionTree = null;
}
if (codeTree != null) {
codeTree.clear();
codeTree = null;
}
if (modeSelector == 'Code') {
_buildCodeTree(exclusive);
} else {
_buildFunctionTree(exclusive);
}
}
TableTree codeTree;
TableTree functionTree;
void _buildFunctionTree(bool exclusive) {
if (functionTree == null) {
var tableBody = shadowRoot.querySelector('#treeBody');
assert(tableBody != null);
functionTree = new TableTree(tableBody, 2);
}
var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive');
if (tree == null) {
return;
}
var rootRow =
new FunctionProfileTreeRow(functionTree, null, profile, tree.root);
functionTree.initialize(rootRow);
}
void _buildCodeTree(bool exclusive) {
if (codeTree == null) {
var tableBody = shadowRoot.querySelector('#treeBody');
assert(tableBody != null);
codeTree = new TableTree(tableBody, 2);
}
var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive');
if (tree == null) {
return;
}
var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root);
codeTree.initialize(rootRow);
}
}