| 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 82f479d00e370e27122b69741a7412bd50061b6b..0b24b07481f76ea90bf9023d4ca9eb81692f6ee6 100644
|
| --- a/runtime/observatory/lib/src/elements/cpu_profile.dart
|
| +++ b/runtime/observatory/lib/src/elements/cpu_profile.dart
|
| @@ -6,1571 +6,168 @@ library cpu_profile_element;
|
|
|
| import 'dart:async';
|
| import 'dart:html';
|
| -import 'observatory_element.dart';
|
| import 'package:observatory/models.dart' as M;
|
| -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';
|
| +import 'package:observatory/src/elements/cpu_profile/virtual_tree.dart';
|
| +import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
|
| +import 'package:observatory/src/elements/helpers/tag.dart';
|
| +import 'package:observatory/src/elements/helpers/uris.dart';
|
| +import 'package:observatory/src/elements/nav/bar.dart';
|
| +import 'package:observatory/src/elements/nav/isolate_menu.dart';
|
| +import 'package:observatory/src/elements/nav/menu.dart';
|
| +import 'package:observatory/src/elements/nav/notify.dart';
|
| +import 'package:observatory/src/elements/nav/refresh.dart';
|
| +import 'package:observatory/src/elements/nav/top_menu.dart';
|
| +import 'package:observatory/src/elements/nav/vm_menu.dart';
|
| +import 'package:observatory/src/elements/sample_buffer_control.dart';
|
| +import 'package:observatory/src/elements/stack_trace_tree_config.dart';
|
| +
|
| +class CpuProfileElement extends HtmlElement implements Renderable {
|
| + static const tag = const Tag<CpuProfileElement>('cpu-profile',
|
| + dependencies: const [
|
| + NavBarElement.tag,
|
| + NavTopMenuElement.tag,
|
| + NavVMMenuElement.tag,
|
| + NavIsolateMenuElement.tag,
|
| + NavMenuElement.tag,
|
| + NavMenuElement.tag,
|
| + NavRefreshElement.tag,
|
| + NavNotifyElement.tag,
|
| + SampleBufferControlElement.tag,
|
| + StackTraceTreeConfigElement.tag,
|
| + CpuProfileVirtualTreeElement.tag,
|
| + ]);
|
| +
|
| + RenderingScheduler<CpuProfileElement> _r;
|
| +
|
| + Stream<RenderedEvent<CpuProfileElement>> get onRendered => _r.onRendered;
|
| +
|
| + M.VM _vm;
|
| + M.IsolateRef _isolate;
|
| + M.EventRepository _events;
|
| + M.NotificationRepository _notifications;
|
| + M.IsolateSampleProfileRepository _profiles;
|
| + Stream<M.SampleProfileLoadingProgressEvent> _progressStream;
|
| + M.SampleProfileLoadingProgress _progress;
|
| + M.SampleProfileTag _tag = M.SampleProfileTag.none;
|
| + ProfileTreeMode _mode = ProfileTreeMode.function;
|
| + M.ProfileTreeDirection _direction = M.ProfileTreeDirection.exclusive;
|
| + String _filter = '';
|
| +
|
| +
|
| + M.IsolateRef get isolate => _isolate;
|
| + M.NotificationRepository get notifications => _notifications;
|
| + M.IsolateSampleProfileRepository get profiles => _profiles;
|
| + M.VMRef get vm => _vm;
|
| +
|
| + factory CpuProfileElement(M.VM vm, M.IsolateRef isolate,
|
| + M.EventRepository events,
|
| + M.NotificationRepository notifications,
|
| + M.IsolateSampleProfileRepository profiles,
|
| + {RenderingQueue queue}) {
|
| + assert(vm != null);
|
| + assert(isolate != null);
|
| + assert(events != null);
|
| + assert(notifications != null);
|
| + assert(profiles != null);
|
| + CpuProfileElement e = document.createElement(tag.name);
|
| + e._r = new RenderingScheduler(e, queue: queue);
|
| + e._vm = vm;
|
| + e._isolate = isolate;
|
| + e._events = events;
|
| + e._notifications = notifications;
|
| + e._profiles = profiles;
|
| + return e;
|
| + }
|
| +
|
| + CpuProfileElement.created() : super.created();
|
|
|
| -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'],
|
| - 'intrinsic' : const ['It', null, 'Intrinsic'],
|
| - 'ffi' : const ['F', null, 'FFI'],
|
| - '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 = 'Executing: $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 = 'Executing: $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 (M.hasDartCode(node.profileFunction.function.kind)) {
|
| - infoBox.children.add(div('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 (M.hasDartCode(node.profileFunction.function.kind)) {
|
| - infoBox.children.add(div('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;
|
| - }
|
| -}
|
| -
|
| -@CustomTag('sample-buffer-control')
|
| -class SampleBufferControlElement extends ObservatoryElement {
|
| - SampleBufferControlElement.created() : super.created() {
|
| - _stopWatch.start();
|
| - }
|
| -
|
| - Future<CpuProfile> reload(Isolate isolate) async {
|
| - profile.clear();
|
| - if (isolate == null) {
|
| - _update(profile);
|
| - // Notify listener.
|
| - onSampleBufferUpdate(profile);
|
| - return profile;
|
| - }
|
| - profileVM = isolate.vm.profileVM;
|
| - if (tagSelector == null) {
|
| - // Set default value.
|
| - tagSelector = profileVM ? 'UserVM' : 'None';
|
| - }
|
| - await _changeState(kFetchingState);
|
| - try {
|
| - var response;
|
| - if (allocationProfileClass != null) {
|
| - response =
|
| - await allocationProfileClass.getAllocationSamples(tagSelector);
|
| - } else {
|
| - var params = { 'tags': tagSelector };
|
| - response = await isolate.invokeRpc('_getCpuProfile', params);
|
| - }
|
| - await _changeState(kLoadingState);
|
| - profile.load(isolate, response);
|
| - profile.buildFunctionCallerAndCallees();
|
| - _update(profile);
|
| - await _changeState(kLoadedState);
|
| - // Notify listener.
|
| - onSampleBufferUpdate(profile);
|
| - return profile;
|
| - } catch (e, st) {
|
| - if (e is ServerRpcException) {
|
| - ServerRpcException se = e;
|
| - if (se.code == ServerRpcException.kFeatureDisabled) {
|
| - await _changeState(kDisabledState);
|
| - return profile;
|
| - }
|
| - }
|
| - await _changeState(kExceptionState, e, st);
|
| - rethrow;
|
| - }
|
| - }
|
| -
|
| - Future _changeState(String newState, [exception, stackTrace]) {
|
| - if ((newState == kDisabledState) ||
|
| - (newState == kFetchingState) ||
|
| - (newState == kExceptionState)) {
|
| - loadTime = '';
|
| - fetchTime = '';
|
| - } else if (newState == kLoadingState) {
|
| - fetchTime = formatTimeMilliseconds(_stopWatch.elapsedMilliseconds);
|
| - loadTime = '';
|
| - } else if (newState == kLoadedState) {
|
| - loadTime = formatTimeMilliseconds(_stopWatch.elapsedMilliseconds);
|
| - }
|
| - state = newState;
|
| - this.exception = exception;
|
| - this.stackTrace = stackTrace;
|
| - _stopWatch.reset();
|
| - return window.animationFrame;
|
| - }
|
| -
|
| - _update(CpuProfile sampleBuffer) {
|
| - sampleCount = profile.sampleCount.toString();
|
| - refreshTime = new DateTime.now().toString();
|
| - stackDepth = profile.stackDepth.toString();
|
| - sampleRate = profile.sampleRate.toStringAsFixed(0);
|
| - if (profile.sampleCount == 0) {
|
| - timeSpan = '0s';
|
| - } else {
|
| - timeSpan = formatTime(profile.timeSpan);
|
| - }
|
| - }
|
| -
|
| - void tagSelectorChanged(oldValue) {
|
| - reload(profile.isolate);
|
| - }
|
| -
|
| - Function onSampleBufferUpdate;
|
| - @observable bool showTagSelector = true;
|
| - @observable bool profileVM = false;
|
| - @observable String sampleCount = '';
|
| - @observable String refreshTime = '';
|
| - @observable String sampleRate = '';
|
| - @observable String stackDepth = '';
|
| - @observable String timeSpan = '';
|
| - @observable String fetchTime = '';
|
| - @observable String loadTime = '';
|
| - @observable String tagSelector;
|
| - @observable String state = kFetchingState;
|
| - @observable var exception;
|
| - @observable var stackTrace;
|
| -
|
| - static const kDisabledState = 'kDisabled';
|
| - static const kExceptionState = 'Exception';
|
| - static const kFetchingState = 'kFetching';
|
| - static const kLoadedState = 'kLoaded';
|
| - static const kLoadingState = 'kLoading';
|
| - static const kNotLoadedState = 'kNotLoaded';
|
| -
|
| - Isolate isolate;
|
| - Class allocationProfileClass;
|
| -
|
| - final CpuProfile profile = new CpuProfile();
|
| - final Stopwatch _stopWatch = new Stopwatch();
|
| -}
|
| -
|
| -@CustomTag('stack-trace-tree-config')
|
| -class StackTraceTreeConfigElement extends ObservatoryElement {
|
| - StackTraceTreeConfigElement.created() : super.created();
|
| -
|
| - attached() {
|
| - super.attached();
|
| - var filterElement = shadowRoot.querySelector('#filterInput');
|
| - keyDownSubscription = filterElement.onKeyDown.listen(_onKeyDown);
|
| - blurSubscription = filterElement.onBlur.listen(_onBlur);
|
| - }
|
| -
|
| - detached() {
|
| - super.detached();
|
| - keyDownSubscription?.cancel();
|
| - blurSubscription?.cancel();
|
| - }
|
| -
|
| - void _onKeyDown(KeyboardEvent keyEvent) {
|
| - if (keyEvent.keyCode == 13) {
|
| - // On enter, update the filter string.
|
| - filterString =
|
| - (shadowRoot.querySelector('#filterInput') as InputElement).value;
|
| - if (onTreeConfigChange == null) {
|
| - return;
|
| - }
|
| - onTreeConfigChange(modeSelector, directionSelector, filterString);
|
| - }
|
| - }
|
| -
|
| - void _onBlur(Event event) {
|
| - // Input box has lost focus, update the display to match the active
|
| - // filter string.
|
| - (shadowRoot.querySelector('#filterInput') as InputElement).value =
|
| - filterString;
|
| - }
|
| -
|
| - void modeSelectorChanged(oldValue) {
|
| - if (onTreeConfigChange == null) {
|
| - return;
|
| - }
|
| - onTreeConfigChange(modeSelector, directionSelector, filterString);
|
| - }
|
| -
|
| - void directionSelectorChanged(oldValue) {
|
| - if (onTreeConfigChange == null) {
|
| - return;
|
| - }
|
| - onTreeConfigChange(modeSelector, directionSelector, filterString);
|
| - }
|
| -
|
| - Function onTreeConfigChange;
|
| - StreamSubscription keyDownSubscription;
|
| - StreamSubscription blurSubscription;
|
| - @observable bool show = true;
|
| - @observable bool showModeSelector = true;
|
| - @observable bool showDirectionSelector = true;
|
| - @observable bool showFilter = true;
|
| - @observable String modeSelector = 'Function';
|
| - @observable String directionSelector = 'Up';
|
| - @observable String filterString;
|
| -}
|
| -
|
| -class FunctionCallTreeNodeRow extends VirtualTreeRow {
|
| - final CpuProfile profile;
|
| - final FunctionCallTreeNode node;
|
| - String selfPercent;
|
| - String totalPercent;
|
| - String percent;
|
| -
|
| - static const kHotThreshold = 0.05; // 5%.
|
| - static const kMediumThreshold = 0.02; // 2%.
|
| -
|
| - double _percent(bool self) {
|
| - if (self) {
|
| - return node.profileFunction.normalizedExclusiveTicks;
|
| - } else {
|
| - return node.profileFunction.normalizedInclusiveTicks;
|
| - }
|
| - }
|
| -
|
| - bool isHot(bool self) => _percent(self) > kHotThreshold;
|
| - bool isMedium(bool self) => _percent(self) > kMediumThreshold;
|
| -
|
| - String rowClass(bool self) {
|
| - if (isHot(self)) {
|
| - return 'hotProfile';
|
| - } else if (isMedium(self)) {
|
| - return 'mediumProfile';
|
| - } else {
|
| - return 'coldProfile';
|
| - }
|
| - }
|
| -
|
| - FunctionCallTreeNodeRow(VirtualTree tree,
|
| - int depth,
|
| - this.profile,
|
| - FunctionCallTreeNode node)
|
| - : node = node,
|
| - super(tree, depth) {
|
| - if ((node.profileFunction.function.kind == M.FunctionKind.tag) &&
|
| - (node.profileFunction.normalizedExclusiveTicks == 0) &&
|
| - (node.profileFunction.normalizedInclusiveTicks == 0)) {
|
| - selfPercent = '';
|
| - totalPercent = '';
|
| - } else {
|
| - selfPercent = Utils.formatPercentNormalized(
|
| - node.profileFunction.normalizedExclusiveTicks);
|
| - totalPercent = Utils.formatPercentNormalized(
|
| - node.profileFunction.normalizedInclusiveTicks);
|
| - }
|
| - percent = Utils.formatPercentNormalized(node.percentage);
|
| - }
|
| -
|
| - void onRender(DivElement rowDiv) {
|
| - rowDiv.children.add(makeGap(ems:0.1));
|
| - rowDiv.children.add(
|
| - makeText(totalPercent,
|
| - toolTip: 'global % on stack'));
|
| - rowDiv.children.add(makeGap());
|
| - rowDiv.children.add(
|
| - makeText(selfPercent,
|
| - toolTip: 'global % executing'));
|
| - rowDiv.children.add(makeGap());
|
| - rowDiv.children.add(makeIndenter(depth, colored: false));
|
| - rowDiv.children.add(makeColorBar(depth));
|
| - rowDiv.children.add(makeGap(ems: 1.0));
|
| - rowDiv.children.add(makeExpander());
|
| - rowDiv.children.add(
|
| - makeText(percent, toolTip: 'tree node %', flexBasis: null));
|
| - rowDiv.children.add(makeGap(ems: 0.5));
|
| - functionRef.ref = node.profileFunction.function;
|
| - rowDiv.children.add(functionRef);
|
| - }
|
| -
|
| - var functionRef = new Element.tag('function-ref');
|
| -
|
| - int get childCount => node.children.length;
|
| -
|
| - void onShow() {
|
| - if (children.length > 0) {
|
| - return;
|
| - }
|
| - for (var childNode in node.children) {
|
| - var row = new FunctionCallTreeNodeRow(tree, depth + 1, profile, childNode);
|
| - children.add(row);
|
| - }
|
| - }
|
| -}
|
| -
|
| -
|
| -class CodeCallTreeNodeRow extends VirtualTreeRow {
|
| - final CpuProfile profile;
|
| - final CodeCallTreeNode node;
|
| - String selfPercent;
|
| - String totalPercent;
|
| - String percent;
|
| -
|
| - static const kHotThreshold = 0.05; // 5%.
|
| - static const kMediumThreshold = 0.02; // 2%.
|
| -
|
| - double _percent(bool self) {
|
| - if (self) {
|
| - return node.profileCode.normalizedExclusiveTicks;
|
| - } else {
|
| - return node.profileCode.normalizedInclusiveTicks;
|
| - }
|
| - }
|
| -
|
| - bool isHot(bool self) => _percent(self) > kHotThreshold;
|
| - bool isMedium(bool self) => _percent(self) > kMediumThreshold;
|
| -
|
| - String rowClass(bool self) {
|
| - if (isHot(self)) {
|
| - return 'hotProfile';
|
| - } else if (isMedium(self)) {
|
| - return 'mediumProfile';
|
| - } else {
|
| - return 'coldProfile';
|
| - }
|
| - }
|
| -
|
| - CodeCallTreeNodeRow(VirtualTree tree,
|
| - int depth,
|
| - this.profile,
|
| - CodeCallTreeNode node)
|
| - : node = node,
|
| - super(tree, depth) {
|
| - if ((node.profileCode.code.kind == M.CodeKind.tag) &&
|
| - (node.profileCode.normalizedExclusiveTicks == 0) &&
|
| - (node.profileCode.normalizedInclusiveTicks == 0)) {
|
| - selfPercent = '';
|
| - totalPercent = '';
|
| - } else {
|
| - selfPercent = Utils.formatPercentNormalized(
|
| - node.profileCode.normalizedExclusiveTicks);
|
| - totalPercent = Utils.formatPercentNormalized(
|
| - node.profileCode.normalizedInclusiveTicks);
|
| - }
|
| - percent = Utils.formatPercentNormalized(node.percentage);
|
| - }
|
| -
|
| - void onRender(DivElement rowDiv) {
|
| - rowDiv.children.add(makeGap(ems:0.1));
|
| - rowDiv.children.add(
|
| - makeText(totalPercent,
|
| - toolTip: 'global % on stack'));
|
| - rowDiv.children.add(makeGap());
|
| - rowDiv.children.add(
|
| - makeText(selfPercent,
|
| - toolTip: 'global % executing'));
|
| - rowDiv.children.add(makeGap());
|
| - rowDiv.children.add(makeIndenter(depth, colored: false));
|
| - rowDiv.children.add(makeColorBar(depth));
|
| - rowDiv.children.add(makeGap(ems: 1.0));
|
| - rowDiv.children.add(makeExpander());
|
| - rowDiv.children.add(
|
| - makeText(percent, toolTip: 'tree node %', flexBasis: null));
|
| - rowDiv.children.add(makeGap(ems: 0.5));
|
| - codeRef.ref = node.profileCode.code;
|
| - rowDiv.children.add(codeRef);
|
| - }
|
| -
|
| - var codeRef = new Element.tag('code-ref');
|
| -
|
| - int get childCount => node.children.length;
|
| -
|
| - void onShow() {
|
| - if (children.length > 0) {
|
| - return;
|
| - }
|
| - for (var childNode in node.children) {
|
| - var row = new CodeCallTreeNodeRow(tree, depth + 1, profile, childNode);
|
| - children.add(row);
|
| - }
|
| - }
|
| -}
|
| -
|
| -/// Displays a CpuProfile
|
| -@CustomTag('cpu-profile')
|
| -class CpuProfileElement extends ObservatoryElement {
|
| - CpuProfileElement.created() : super.created() {
|
| - _updateTask = new Task(update);
|
| - _renderTask = new Task(render);
|
| - }
|
| -
|
| - attached() {
|
| - super.attached();
|
| - sampleBufferControlElement =
|
| - shadowRoot.querySelector('#sampleBufferControl');
|
| - assert(sampleBufferControlElement != null);
|
| - sampleBufferControlElement.onSampleBufferUpdate = onSampleBufferChange;
|
| - stackTraceTreeConfigElement =
|
| - shadowRoot.querySelector('#stackTraceTreeConfig');
|
| - assert(stackTraceTreeConfigElement != null);
|
| - stackTraceTreeConfigElement.onTreeConfigChange = onTreeConfigChange;
|
| - cpuProfileVirtualTreeElement = shadowRoot.querySelector('#cpuProfileVirtualTree');
|
| - assert(cpuProfileVirtualTreeElement != null);
|
| - cpuProfileVirtualTreeElement.profile = sampleBufferControlElement.profile;
|
| - _resizeSubscription = window.onResize.listen((_) => _updateSize());
|
| - _updateTask.queue();
|
| - _updateSize();
|
| - }
|
| -
|
| - detached() {
|
| - super.detached();
|
| - if (_resizeSubscription != null) {
|
| - _resizeSubscription.cancel();
|
| - }
|
| - }
|
| -
|
| - _updateSize() {
|
| - var applySize = (e) {
|
| - Rectangle rect = e.getBoundingClientRect();
|
| - final totalHeight = window.innerHeight;
|
| - final bottomMargin = 200;
|
| - final mainHeight = totalHeight - bottomMargin;
|
| - e.style.setProperty('height', '${mainHeight}px');
|
| - };
|
| - HtmlElement e2 = $['cpuProfileVirtualTree'];
|
| - applySize(e2);
|
| - }
|
| -
|
| - isolateChanged(oldValue) {
|
| - _updateTask.queue();
|
| - }
|
| -
|
| - update() {
|
| - if (sampleBufferControlElement != null) {
|
| - sampleBufferControlElement.reload(isolate);
|
| - }
|
| - }
|
| -
|
| - onSampleBufferChange(CpuProfile sampleBuffer) {
|
| - _renderTask.queue();
|
| - }
|
| -
|
| - onTreeConfigChange(String modeSelector,
|
| - String directionSelector,
|
| - String filterString) {
|
| - ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
|
| - if (directionSelector != 'Up') {
|
| - direction = ProfileTreeDirection.Inclusive;
|
| - }
|
| - ProfileTreeMode mode = ProfileTreeMode.Function;
|
| - if (modeSelector == 'Code') {
|
| - mode = ProfileTreeMode.Code;
|
| - }
|
| - // Clear the filter.
|
| - cpuProfileVirtualTreeElement.filter = null;
|
| - if (filterString != null) {
|
| - filterString = filterString.trim();
|
| - if (filterString.isNotEmpty) {
|
| - cpuProfileVirtualTreeElement.filter = (CallTreeNode node) {
|
| - return node.name.contains(filterString);
|
| - };
|
| - }
|
| - }
|
| - cpuProfileVirtualTreeElement.direction = direction;
|
| - cpuProfileVirtualTreeElement.mode = mode;
|
| - _renderTask.queue();
|
| - }
|
| -
|
| - Future clearCpuProfile() async {
|
| - await isolate.invokeRpc('_clearCpuProfile', { });
|
| - _updateTask.queue();
|
| - return new Future.value(null);
|
| - }
|
| -
|
| - Future refresh() {
|
| - _updateTask.queue();
|
| - return new Future.value(null);
|
| - }
|
| -
|
| - render() {
|
| - cpuProfileVirtualTreeElement.render();
|
| - }
|
| -
|
| - @published Isolate isolate;
|
| -
|
| - StreamSubscription _resizeSubscription;
|
| - Task _updateTask;
|
| - Task _renderTask;
|
| - SampleBufferControlElement sampleBufferControlElement;
|
| - StackTraceTreeConfigElement stackTraceTreeConfigElement;
|
| - CpuProfileVirtualTreeElement cpuProfileVirtualTreeElement;
|
| -}
|
| -
|
| -class NameSortedTable extends SortedTable {
|
| - NameSortedTable(columns) : super(columns);
|
| @override
|
| - dynamic getSortKeyFor(int row, int col) {
|
| - if (col == FUNCTION_COLUMN) {
|
| - // Use name as sort key.
|
| - return rows[row].values[col].name;
|
| - }
|
| - return super.getSortKeyFor(row, col);
|
| - }
|
| -
|
| - SortedTableRow rowFromIndex(int tableIndex) {
|
| - final modelIndex = sortedRows[tableIndex];
|
| - return rows[modelIndex];
|
| - }
|
| -
|
| - static const FUNCTION_SPACER_COLUMNS = const [];
|
| - static const FUNCTION_COLUMN = 2;
|
| - TableRowElement _makeFunctionRow() {
|
| - var tr = new TableRowElement();
|
| - var cell;
|
| -
|
| - // Add percentage.
|
| - cell = tr.insertCell(-1);
|
| - cell = tr.insertCell(-1);
|
| -
|
| - // Add function ref.
|
| - cell = tr.insertCell(-1);
|
| - var functionRef = new Element.tag('function-ref');
|
| - cell.children.add(functionRef);
|
| -
|
| - return tr;
|
| - }
|
| -
|
| - static const CALL_SPACER_COLUMNS = const [];
|
| - static const CALL_FUNCTION_COLUMN = 1;
|
| - TableRowElement _makeCallRow() {
|
| - var tr = new TableRowElement();
|
| - var cell;
|
| -
|
| - // Add percentage.
|
| - cell = tr.insertCell(-1);
|
| - // Add function ref.
|
| - cell = tr.insertCell(-1);
|
| - var functionRef = new Element.tag('function-ref');
|
| - cell.children.add(functionRef);
|
| - return tr;
|
| - }
|
| -
|
| - _updateRow(TableRowElement tr,
|
| - int rowIndex,
|
| - List spacerColumns,
|
| - int refColumn) {
|
| - var row = rows[rowIndex];
|
| - // Set reference
|
| - var ref = tr.children[refColumn].children[0];
|
| - ref.ref = row.values[refColumn];
|
| -
|
| - for (var i = 0; i < row.values.length; i++) {
|
| - if (spacerColumns.contains(i) || (i == refColumn)) {
|
| - // Skip spacer columns.
|
| - continue;
|
| - }
|
| - var cell = tr.children[i];
|
| - cell.title = row.values[i].toString();
|
| - cell.text = getFormattedValue(rowIndex, i);
|
| - }
|
| - }
|
| -
|
| - _updateTableView(HtmlElement table,
|
| - HtmlElement makeEmptyRow(),
|
| - void onRowClick(TableRowElement tr),
|
| - List spacerColumns,
|
| - int refColumn) {
|
| - assert(table != null);
|
| -
|
| - // Resize DOM table.
|
| - if (table.children.length > sortedRows.length) {
|
| - // Shrink the table.
|
| - var deadRows = table.children.length - sortedRows.length;
|
| - for (var i = 0; i < deadRows; i++) {
|
| - table.children.removeLast();
|
| - }
|
| - } else if (table.children.length < sortedRows.length) {
|
| - // Grow table.
|
| - var newRows = sortedRows.length - table.children.length;
|
| - for (var i = 0; i < newRows; i++) {
|
| - var row = makeEmptyRow();
|
| - row.onClick.listen((e) {
|
| - e.stopPropagation();
|
| - e.preventDefault();
|
| - onRowClick(row);
|
| - });
|
| - table.children.add(row);
|
| - }
|
| - }
|
| -
|
| - assert(table.children.length == sortedRows.length);
|
| -
|
| - // Fill table.
|
| - for (var i = 0; i < sortedRows.length; i++) {
|
| - var rowIndex = sortedRows[i];
|
| - var tr = table.children[i];
|
| - _updateRow(tr, rowIndex, spacerColumns, refColumn);
|
| - }
|
| - }
|
| -}
|
| -
|
| -@CustomTag('cpu-profile-table')
|
| -class CpuProfileTableElement extends ObservatoryElement {
|
| - CpuProfileTableElement.created() : super.created() {
|
| - _updateTask = new Task(update);
|
| - _renderTask = new Task(render);
|
| - var columns = [
|
| - new SortedTableColumn.withFormatter('Executing (%)',
|
| - Utils.formatPercentNormalized),
|
| - new SortedTableColumn.withFormatter('In stack (%)',
|
| - Utils.formatPercentNormalized),
|
| - new SortedTableColumn('Method'),
|
| - ];
|
| - profileTable = new NameSortedTable(columns);
|
| - profileTable.sortColumnIndex = 0;
|
| -
|
| - columns = [
|
| - new SortedTableColumn.withFormatter('Callees (%)',
|
| - Utils.formatPercentNormalized),
|
| - new SortedTableColumn('Method')
|
| - ];
|
| - profileCalleesTable = new NameSortedTable(columns);
|
| - profileCalleesTable.sortColumnIndex = 0;
|
| -
|
| - columns = [
|
| - new SortedTableColumn.withFormatter('Callers (%)',
|
| - Utils.formatPercentNormalized),
|
| - new SortedTableColumn('Method')
|
| - ];
|
| - profileCallersTable = new NameSortedTable(columns);
|
| - profileCallersTable.sortColumnIndex = 0;
|
| - }
|
| -
|
| - attached() {
|
| - super.attached();
|
| - sampleBufferControlElement =
|
| - shadowRoot.querySelector('#sampleBufferControl');
|
| - assert(sampleBufferControlElement != null);
|
| - sampleBufferControlElement.onSampleBufferUpdate = onSampleBufferChange;
|
| - // Disable the tag selector- we always want no tags.
|
| - sampleBufferControlElement.tagSelector = 'None';
|
| - sampleBufferControlElement.showTagSelector = false;
|
| - stackTraceTreeConfigElement =
|
| - shadowRoot.querySelector('#stackTraceTreeConfig');
|
| - assert(stackTraceTreeConfigElement != null);
|
| - stackTraceTreeConfigElement.onTreeConfigChange = onTreeConfigChange;
|
| - stackTraceTreeConfigElement.modeSelector = 'Function';
|
| - stackTraceTreeConfigElement.showModeSelector = false;
|
| - stackTraceTreeConfigElement.directionSelector = 'Down';
|
| - stackTraceTreeConfigElement.showDirectionSelector = false;
|
| - cpuProfileTreeElement = shadowRoot.querySelector('#cpuProfileTree');
|
| - assert(cpuProfileTreeElement != null);
|
| - cpuProfileTreeElement.profile = sampleBufferControlElement.profile;
|
| - _updateTask.queue();
|
| - _resizeSubscription = window.onResize.listen((_) => _updateSize());
|
| - _updateSize();
|
| - }
|
| -
|
| - detached() {
|
| - super.detached();
|
| - if (_resizeSubscription != null) {
|
| - _resizeSubscription.cancel();
|
| - }
|
| - }
|
| -
|
| - _updateSize() {
|
| - HtmlElement e = $['main'];
|
| - final totalHeight = window.innerHeight;
|
| - final top = e.offset.top;
|
| - final bottomMargin = 32;
|
| - final mainHeight = totalHeight - top - bottomMargin;
|
| - e.style.setProperty('height', '${mainHeight}px');
|
| - }
|
| -
|
| - isolateChanged(oldValue) {
|
| - _updateTask.queue();
|
| - }
|
| -
|
| - update() {
|
| - _clearView();
|
| - if (sampleBufferControlElement != null) {
|
| - sampleBufferControlElement.reload(isolate).whenComplete(checkParameters);
|
| - }
|
| - }
|
| -
|
| - onSampleBufferChange(CpuProfile sampleBuffer) {
|
| - _renderTask.queue();
|
| - }
|
| -
|
| - onTreeConfigChange(String modeSelector,
|
| - String directionSelector,
|
| - String filterString) {
|
| - ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
|
| - if (directionSelector != 'Up') {
|
| - direction = ProfileTreeDirection.Inclusive;
|
| - }
|
| - ProfileTreeMode mode = ProfileTreeMode.Function;
|
| - if (modeSelector == 'Code') {
|
| - mode = ProfileTreeMode.Code;
|
| - }
|
| - cpuProfileTreeElement.direction = direction;
|
| - cpuProfileTreeElement.mode = mode;
|
| - _renderTask.queue();
|
| - }
|
| -
|
| - Future clearCpuProfile() async {
|
| - await isolate.invokeRpc('_clearCpuProfile', { });
|
| - _updateTask.queue();
|
| - return new Future.value(null);
|
| - }
|
| -
|
| - Future refresh() {
|
| - _updateTask.queue();
|
| - return new Future.value(null);
|
| - }
|
| -
|
| - render() {
|
| - _updateView();
|
| - }
|
| -
|
| - checkParameters() {
|
| - if (isolate == null) {
|
| - return;
|
| - }
|
| - var functionId = app.locationManager.uri.queryParameters['functionId'];
|
| - var functionName =
|
| - app.locationManager.uri.queryParameters['functionName'];
|
| - if (functionId == '') {
|
| - // Fallback to searching by name.
|
| - _focusOnFunction(_findFunction(functionName));
|
| - } else {
|
| - if (functionId == null) {
|
| - _focusOnFunction(null);
|
| - return;
|
| - }
|
| - isolate.getObject(functionId).then((func) => _focusOnFunction(func));
|
| - }
|
| - }
|
| -
|
| - _clearView() {
|
| - profileTable.clearRows();
|
| - _renderTable();
|
| - }
|
| -
|
| - _updateView() {
|
| - _buildFunctionTable();
|
| - _renderTable();
|
| - _updateFunctionTreeView();
|
| - }
|
| -
|
| - int _findFunctionRow(ServiceFunction function) {
|
| - for (var i = 0; i < profileTable.sortedRows.length; i++) {
|
| - var rowIndex = profileTable.sortedRows[i];
|
| - var row = profileTable.rows[rowIndex];
|
| - if (row.values[NameSortedTable.FUNCTION_COLUMN] == function) {
|
| - return i;
|
| - }
|
| - }
|
| - return -1;
|
| - }
|
| -
|
| - _scrollToFunction(ServiceFunction function) {
|
| - TableSectionElement tableBody = $['profile-table'];
|
| - var row = _findFunctionRow(function);
|
| - if (row == -1) {
|
| - return;
|
| - }
|
| - tableBody.children[row].classes.remove('shake');
|
| - // trigger reflow.
|
| - tableBody.children[row].offsetHeight;
|
| - tableBody.children[row].scrollIntoView(ScrollAlignment.CENTER);
|
| - tableBody.children[row].classes.add('shake');
|
| - // Focus on clicked function.
|
| - _focusOnFunction(function);
|
| - }
|
| -
|
| - _clearFocusedFunction() {
|
| - TableSectionElement tableBody = $['profile-table'];
|
| - // Clear current focus.
|
| - if (focusedRow != null) {
|
| - tableBody.children[focusedRow].classes.remove('focused');
|
| - }
|
| - focusedRow = null;
|
| - focusedFunction = null;
|
| - }
|
| -
|
| - ServiceFunction _findFunction(String functionName) {
|
| - for (var func in profile.functions) {
|
| - if (func.function.name == functionName) {
|
| - return func.function;
|
| - }
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - _focusOnFunction(ServiceFunction function) {
|
| - if (focusedFunction == function) {
|
| - // Do nothing.
|
| - return;
|
| - }
|
| -
|
| - _clearFocusedFunction();
|
| -
|
| - if (function == null) {
|
| - _updateFunctionTreeView();
|
| - _clearCallTables();
|
| - return;
|
| - }
|
| -
|
| - var row = _findFunctionRow(function);
|
| - if (row == -1) {
|
| - _updateFunctionTreeView();
|
| - _clearCallTables();
|
| - return;
|
| - }
|
| -
|
| - focusedRow = row;
|
| - focusedFunction = function;
|
| -
|
| - TableSectionElement tableBody = $['profile-table'];
|
| - tableBody.children[focusedRow].classes.add('focused');
|
| - _updateFunctionTreeView();
|
| - _buildCallersTable(focusedFunction);
|
| - _buildCalleesTable(focusedFunction);
|
| - }
|
| -
|
| - _onRowClick(TableRowElement tr) {
|
| - var tableBody = $['profile-table'];
|
| - var row = profileTable.rowFromIndex(tableBody.children.indexOf(tr));
|
| - var function = row.values[NameSortedTable.FUNCTION_COLUMN];
|
| - app.locationManager.goReplacingParameters(
|
| - {
|
| - 'functionId': function.id,
|
| - 'functionName': function.vmName
|
| - }
|
| - );
|
| - }
|
| -
|
| - _renderTable() {
|
| - profileTable._updateTableView($['profile-table'],
|
| - profileTable._makeFunctionRow,
|
| - _onRowClick,
|
| - NameSortedTable.FUNCTION_SPACER_COLUMNS,
|
| - NameSortedTable.FUNCTION_COLUMN);
|
| - }
|
| -
|
| - _buildFunctionTable() {
|
| - for (var func in profile.functions) {
|
| - if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) {
|
| - // Skip.
|
| - continue;
|
| - }
|
| - var row = [
|
| - func.normalizedExclusiveTicks,
|
| - func.normalizedInclusiveTicks,
|
| - func.function,
|
| - ];
|
| - profileTable.addRow(new SortedTableRow(row));
|
| - }
|
| - profileTable.sort();
|
| - }
|
| -
|
| - _renderCallTable(TableSectionElement view,
|
| - NameSortedTable model,
|
| - void onRowClick(TableRowElement tr)) {
|
| - model._updateTableView(view,
|
| - model._makeCallRow,
|
| - onRowClick,
|
| - NameSortedTable.CALL_SPACER_COLUMNS,
|
| - NameSortedTable.CALL_FUNCTION_COLUMN);
|
| - }
|
| -
|
| - _buildCallTable(Map<ProfileFunction, int> calls,
|
| - NameSortedTable model) {
|
| - model.clearRows();
|
| - if (calls == null) {
|
| - return;
|
| - }
|
| - var sum = 0;
|
| - calls.values.forEach((i) => sum += i);
|
| - calls.forEach((func, count) {
|
| - var row = [
|
| - count / sum,
|
| - func.function,
|
| - ];
|
| - model.addRow(new SortedTableRow(row));
|
| - });
|
| - model.sort();
|
| - }
|
| -
|
| - _clearCallTables() {
|
| - _buildCallersTable(null);
|
| - _buildCalleesTable(null);
|
| - }
|
| -
|
| - _onCallersClick(TableRowElement tr) {
|
| - var table = $['callers-table'];
|
| - final row = profileCallersTable.rowFromIndex(table.children.indexOf(tr));
|
| - var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN];
|
| - _scrollToFunction(function);
|
| - }
|
| -
|
| - _buildCallersTable(ServiceFunction function) {
|
| - var calls = (function != null) ? function.profile.callers : null;
|
| - var table = $['callers-table'];
|
| - _buildCallTable(calls, profileCallersTable);
|
| - _renderCallTable(table, profileCallersTable, _onCallersClick);
|
| - }
|
| -
|
| - _onCalleesClick(TableRowElement tr) {
|
| - var table = $['callees-table'];
|
| - final row = profileCalleesTable.rowFromIndex(table.children.indexOf(tr));
|
| - var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN];
|
| - _scrollToFunction(function);
|
| - }
|
| -
|
| - _buildCalleesTable(ServiceFunction function) {
|
| - var calls = (function != null) ? function.profile.callees : null;
|
| - var table = $['callees-table'];
|
| - _buildCallTable(calls, profileCalleesTable);
|
| - _renderCallTable(table, profileCalleesTable, _onCalleesClick);
|
| - }
|
| -
|
| - _changeSort(Element target, NameSortedTable table) {
|
| - if (target is TableCellElement) {
|
| - if (table.sortColumnIndex != target.cellIndex) {
|
| - table.sortColumnIndex = target.cellIndex;
|
| - table.sortDescending = true;
|
| - } else {
|
| - table.sortDescending = !profileTable.sortDescending;
|
| - }
|
| - table.sort();
|
| - }
|
| - }
|
| -
|
| - changeSortProfile(Event e, var detail, Element target) {
|
| - _changeSort(target, profileTable);
|
| - _renderTable();
|
| - }
|
| -
|
| - changeSortCallers(Event e, var detail, Element target) {
|
| - _changeSort(target, profileCallersTable);
|
| - _renderCallTable($['callers-table'], profileCallersTable, _onCallersClick);
|
| - }
|
| -
|
| - changeSortCallees(Event e, var detail, Element target) {
|
| - _changeSort(target, profileCalleesTable);
|
| - _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick);
|
| - }
|
| -
|
| - //////
|
| - ///
|
| - /// Function tree.
|
| - ///
|
| - TableTree functionTree;
|
| - _updateFunctionTreeView() {
|
| - cpuProfileTreeElement.filter = (FunctionCallTreeNode node) {
|
| - return node.profileFunction.function == focusedFunction;
|
| - };
|
| - cpuProfileTreeElement.render();
|
| - }
|
| -
|
| - @published Isolate isolate;
|
| - @observable NameSortedTable profileTable;
|
| - @observable NameSortedTable profileCallersTable;
|
| - @observable NameSortedTable profileCalleesTable;
|
| - @observable ServiceFunction focusedFunction;
|
| - @observable int focusedRow;
|
| -
|
| -
|
| - StreamSubscription _resizeSubscription;
|
| - Task _updateTask;
|
| - Task _renderTask;
|
| -
|
| - CpuProfile get profile => sampleBufferControlElement.profile;
|
| - SampleBufferControlElement sampleBufferControlElement;
|
| - StackTraceTreeConfigElement stackTraceTreeConfigElement;
|
| - CpuProfileVirtualTreeElement cpuProfileTreeElement;
|
| -}
|
| -
|
| -enum ProfileTreeDirection {
|
| - Exclusive,
|
| - Inclusive
|
| -}
|
| -
|
| -enum ProfileTreeMode {
|
| - Code,
|
| - Function,
|
| -}
|
| -
|
| -@CustomTag('cpu-profile-virtual-tree')
|
| -class CpuProfileVirtualTreeElement extends ObservatoryElement {
|
| - ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
|
| - ProfileTreeMode mode = ProfileTreeMode.Function;
|
| - CpuProfile profile;
|
| - VirtualTree virtualTree;
|
| - CallTreeNodeFilter filter;
|
| - StreamSubscription _resizeSubscription;
|
| - @observable bool show = true;
|
| -
|
| - CpuProfileVirtualTreeElement.created() : super.created();
|
| -
|
| attached() {
|
| super.attached();
|
| - _resizeSubscription = window.onResize.listen((_) => _updateSize());
|
| + _r.enable();
|
| + _request();
|
| }
|
|
|
| + @override
|
| detached() {
|
| - super.detached();
|
| - _resizeSubscription?.cancel();
|
| - }
|
| -
|
| - void render() {
|
| - _updateView();
|
| + super.detached(); _r.disable(notify: true);
|
| + children = [];
|
| }
|
|
|
| - showChanged(oldValue) {
|
| - if (show) {
|
| - virtualTree?.root?.style?.display = 'block';
|
| - } else {
|
| - virtualTree?.root?.style?.display = 'none';
|
| - }
|
| - }
|
| -
|
| - void _updateView() {
|
| - _updateSize();
|
| - virtualTree?.clear();
|
| - virtualTree?.uninstall();
|
| - virtualTree = null;
|
| - bool exclusive = direction == ProfileTreeDirection.Exclusive;
|
| - if (mode == ProfileTreeMode.Code) {
|
| - _buildCodeTree(exclusive);
|
| - } else {
|
| - assert(mode == ProfileTreeMode.Function);
|
| - _buildFunctionTree(exclusive);
|
| - }
|
| - virtualTree?.refresh();
|
| - }
|
| -
|
| - void _updateSize() {
|
| - var treeBody = shadowRoot.querySelector('#tree');
|
| - assert(treeBody != null);
|
| - int windowHeight = window.innerHeight - 32;
|
| - treeBody.style.height = '${windowHeight}px';
|
| - }
|
| -
|
| - void _buildFunctionTree(bool exclusive) {
|
| - var treeBody = shadowRoot.querySelector('#tree');
|
| - assert(treeBody != null);
|
| - virtualTree = new VirtualTree(32, treeBody);
|
| - if (profile == null) {
|
| - return;
|
| - }
|
| - var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive');
|
| - if (tree == null) {
|
| - return;
|
| - }
|
| - if (filter != null) {
|
| - tree = tree.filtered(filter);
|
| - }
|
| - for (var child in tree.root.children) {
|
| - virtualTree.rows.add(
|
| - new FunctionCallTreeNodeRow(virtualTree, 0, profile, child));
|
| - }
|
| - if (virtualTree.rows.length == 1) {
|
| - virtualTree.rows[0].expanded = true;
|
| - }
|
| - }
|
| -
|
| - void _buildCodeTree(bool exclusive) {
|
| - var treeBody = shadowRoot.querySelector('#tree');
|
| - assert(treeBody != null);
|
| - virtualTree = new VirtualTree(32, treeBody);
|
| - if (profile == null) {
|
| - return;
|
| - }
|
| - var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive');
|
| - if (tree == null) {
|
| - return;
|
| - }
|
| - if (filter != null) {
|
| - tree = tree.filtered(filter);
|
| - }
|
| - for (var child in tree.root.children) {
|
| - virtualTree.rows.add(
|
| - new CodeCallTreeNodeRow(virtualTree, 0, profile, child));
|
| - }
|
| - if (virtualTree.rows.length == 1) {
|
| - virtualTree.rows[0].expanded = true;
|
| - }
|
| - }
|
| -}
|
| -
|
| -@CustomTag('cpu-profile-tree')
|
| -class CpuProfileTreeElement extends ObservatoryElement {
|
| - ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
|
| - ProfileTreeMode mode = ProfileTreeMode.Function;
|
| - CpuProfile profile;
|
| - TableTree codeTree;
|
| - TableTree functionTree;
|
| - CallTreeNodeFilter functionFilter;
|
| - @observable bool show = true;
|
| -
|
| - CpuProfileTreeElement.created() : super.created();
|
| -
|
| void render() {
|
| - _updateView();
|
| - }
|
| -
|
| - showChanged(oldValue) {
|
| - var treeTable = shadowRoot.querySelector('#treeTable');
|
| - assert(treeTable != null);
|
| - treeTable.style.display = show ? 'table' : 'none';
|
| - }
|
| -
|
| - void _updateView() {
|
| - if (functionTree != null) {
|
| - functionTree.clear();
|
| - functionTree = null;
|
| - }
|
| - if (codeTree != null) {
|
| - codeTree.clear();
|
| - codeTree = null;
|
| - }
|
| - bool exclusive = direction == ProfileTreeDirection.Exclusive;
|
| - if (mode == ProfileTreeMode.Code) {
|
| - _buildCodeTree(exclusive);
|
| - } else {
|
| - assert(mode == ProfileTreeMode.Function);
|
| - _buildFunctionTree(exclusive);
|
| - }
|
| - }
|
| -
|
| - void _buildFunctionTree(bool exclusive) {
|
| - if (functionTree == null) {
|
| - var tableBody = shadowRoot.querySelector('#treeBody');
|
| - assert(tableBody != null);
|
| - functionTree = new TableTree(tableBody, 2);
|
| - }
|
| - if (profile == null) {
|
| - return;
|
| - }
|
| - var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive');
|
| - if (tree == null) {
|
| - return;
|
| - }
|
| - if (functionFilter != null) {
|
| - tree = tree.filtered(functionFilter);
|
| - }
|
| - 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);
|
| - }
|
| - if (profile == null) {
|
| - return;
|
| - }
|
| - var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive');
|
| - if (tree == null) {
|
| + var content = [
|
| + new NavBarElement(queue: _r.queue)
|
| + ..children = [
|
| + new NavTopMenuElement(queue: _r.queue),
|
| + new NavVMMenuElement(_vm, _events, queue: _r.queue),
|
| + new NavIsolateMenuElement(_isolate, _events, queue: _r.queue),
|
| + new NavMenuElement('cpu profile', link: Uris.profiler(_isolate),
|
| + last: true, queue: _r.queue),
|
| + new NavRefreshElement(queue: _r.queue)
|
| + ..onRefresh.listen(_refresh),
|
| + new NavRefreshElement(label: 'Clear', queue: _r.queue)
|
| + ..onRefresh.listen(_clearCpuProfile),
|
| + new NavNotifyElement(_notifications, queue: _r.queue)
|
| + ],
|
| + ];
|
| + if (_progress == null) {
|
| + children = content;
|
| return;
|
| }
|
| - var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root);
|
| - codeTree.initialize(rootRow);
|
| + content.add(new SampleBufferControlElement(_progress, _progressStream,
|
| + selectedTag: _tag, queue: _r.queue)
|
| + ..onTagChange.listen((e) {
|
| + _tag = e.element.selectedTag;
|
| + _request();
|
| + }));
|
| + if (_progress.status == M.SampleProfileLoadingStatus.loaded) {
|
| + CpuProfileVirtualTreeElement tree;
|
| + content.addAll([
|
| + new BRElement(),
|
| + new StackTraceTreeConfigElement(mode: _mode, direction: _direction,
|
| + filter: _filter, queue: _r.queue)
|
| + ..onModeChange.listen((e) {
|
| + _mode = tree.mode = e.element.mode;
|
| + })
|
| + ..onFilterChange.listen((e) {
|
| + _filter = e.element.filter.trim();
|
| + tree.filter = _filter.isNotEmpty
|
| + ? (node) { return node.name.contains(_filter); }
|
| + : null;
|
| + })
|
| + ..onDirectionChange.listen((e) {
|
| + _direction = tree.direction = e.element.direction;
|
| + }),
|
| + new BRElement(),
|
| + tree = new CpuProfileVirtualTreeElement(_isolate, _progress.profile,
|
| + queue: _r.queue)
|
| + ]);
|
| + }
|
| + children = content;
|
| + }
|
| +
|
| + Future _request({bool clear: false, bool forceFetch: false}) async {
|
| + _progress = null;
|
| + _progressStream = _profiles.get(isolate, _tag, clear: clear,
|
| + forceFetch: forceFetch);
|
| + _r.dirty();
|
| + _progress = (await _progressStream.first).progress;
|
| + _r.dirty();
|
| + if (M.isSampleProcessRunning(_progress.status)) {
|
| + _progress = (await _progressStream.last).progress;
|
| + _r.dirty();
|
| + }
|
| + }
|
| +
|
| + Future _clearCpuProfile(RefreshEvent e) async {
|
| + e.element.disabled = true;
|
| + await _request(clear: true);
|
| + e.element.disabled = false;
|
| + }
|
| +
|
| + Future _refresh(e) async {
|
| + e.element.disabled = true;
|
| + await _request(forceFetch: true);
|
| + e.element.disabled = false;
|
| }
|
| }
|
|
|