Index: runtime/observatory/lib/src/elements/cpu_profile_table.dart |
diff --git a/runtime/observatory/lib/src/elements/cpu_profile_table.dart b/runtime/observatory/lib/src/elements/cpu_profile_table.dart |
index 2a76e17bc9275de1f03424cbbb7fbba50c566724..88edbc6b2cb20a2994662bbc656a456d301ccd0a 100644 |
--- a/runtime/observatory/lib/src/elements/cpu_profile_table.dart |
+++ b/runtime/observatory/lib/src/elements/cpu_profile_table.dart |
@@ -6,924 +6,446 @@ library cpu_profile_table_element; |
import 'dart:async'; |
import 'dart:html'; |
-import 'observatory_element.dart'; |
-import 'sample_buffer_control.dart'; |
-import 'stack_trace_tree_config.dart'; |
-import 'cpu_profile/virtual_tree.dart'; |
-import 'package:observatory/service.dart'; |
-import 'package:observatory/app.dart'; |
-import 'package:observatory/cpu_profile.dart'; |
-import 'package:observatory/elements.dart'; |
import 'package:observatory/models.dart' as M; |
-import 'package:observatory/repositories.dart'; |
-import 'package:polymer/polymer.dart'; |
- |
-List<String> sorted(Set<String> attributes) { |
- var list = attributes.toList(); |
- list.sort(); |
- return list; |
+import 'package:observatory/src/elements/containers/virtual_collection.dart'; |
+import 'package:observatory/src/elements/cpu_profile/virtual_tree.dart'; |
+import 'package:observatory/src/elements/function_ref.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'; |
+import 'package:observatory/utils.dart'; |
+ |
+enum _Table { |
+ functions, |
+ caller, |
+ callee |
} |
-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; |
- } |
+enum _SortingField { |
+ exclusive, |
+ inclusive, |
+ caller, |
+ callee, |
+ method |
} |
-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; |
- } |
+enum _SortingDirection { |
+ ascending, |
+ descending |
} |
-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; |
- } |
-} |
+class CpuProfileTableElement extends HtmlElement implements Renderable { |
+ static const tag = const Tag<CpuProfileTableElement>('cpu-profile-table', |
+ dependencies: const [ |
+ FunctionRefElement.tag, |
+ NavBarElement.tag, |
+ NavTopMenuElement.tag, |
+ NavVMMenuElement.tag, |
+ NavIsolateMenuElement.tag, |
+ NavMenuElement.tag, |
+ NavRefreshElement.tag, |
+ NavNotifyElement.tag, |
+ SampleBufferControlElement.tag, |
+ StackTraceTreeConfigElement.tag, |
+ CpuProfileVirtualTreeElement.tag, |
+ VirtualCollectionElement.tag |
+ ]); |
+ |
+ RenderingScheduler<CpuProfileTableElement> _r; |
+ |
+ Stream<RenderedEvent<CpuProfileTableElement>> 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; |
+ final _sortingField = <_Table, _SortingField>{ |
+ _Table.functions : _SortingField.exclusive, |
+ _Table.caller : _SortingField.caller, |
+ _Table.callee : _SortingField.callee, |
+ }; |
+ final _sortingDirection = <_Table, _SortingDirection>{ |
+ _Table.functions : _SortingDirection.descending, |
+ _Table.caller : _SortingDirection.descending, |
+ _Table.callee : _SortingDirection.descending, |
+ }; |
+ String _filter = ''; |
+ |
+ |
+ M.IsolateRef get isolate => _isolate; |
+ M.NotificationRepository get notifications => _notifications; |
+ M.IsolateSampleProfileRepository get profiles => _profiles; |
+ M.VMRef get vm => _vm; |
+ |
+ factory CpuProfileTableElement(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); |
+ CpuProfileTableElement 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; |
+ } |
+ |
+ CpuProfileTableElement.created() : super.created(); |
-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(); |
- _updateTask.queue(); |
- _resizeSubscription = window.onResize.listen((_) => _updateSize()); |
- _updateSize(); |
+ _r.enable(); |
+ _request(); |
} |
+ @override |
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() async { |
- _clearView(); |
- if (isolate == null) { |
- return; |
- } |
- final stream = _repository.get(isolate, M.SampleProfileTag.none); |
- var progress = (await stream.first).progress; |
- shadowRoot.querySelector('#sampleBufferControl').children = [ |
- sampleBufferControlElement = |
- new SampleBufferControlElement(progress, stream, showTag: false, |
- selectedTag: M.SampleProfileTag.none, queue: app.queue) |
+ _r.disable(notify: true); |
+ children = []; |
+ } |
+ |
+ void render() { |
+ 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 (table)', |
+ 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 (M.isSampleProcessRunning(progress.status)) { |
- progress = (await stream.last).progress; |
- } |
- if (progress.status == M.SampleProfileLoadingStatus.loaded) { |
- _profile = progress.profile; |
- shadowRoot.querySelector('#stackTraceTreeConfig').children = [ |
- stackTraceTreeConfigElement = |
- new StackTraceTreeConfigElement(showMode: false, |
- showDirection: false, mode: ProfileTreeMode.function, |
- direction: M.ProfileTreeDirection.exclusive, queue: app.queue) |
- ..onModeChange.listen((e) { |
- cpuProfileTreeElement.mode = e.element.mode; |
- _renderTask.queue(); |
- }) |
- ..onDirectionChange.listen((e) { |
- cpuProfileTreeElement.direction = e.element.direction; |
- _renderTask.queue(); |
- }) |
- ..onFilterChange.listen((e) =>_renderTask.queue()) |
- ]; |
- shadowRoot.querySelector('#cpuProfileTree').children = [ |
- cpuProfileTreeElement = |
- new CpuProfileVirtualTreeElement(isolate, _profile, |
- queue: app.queue) |
- ]; |
- } |
- _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) { |
+ if (_progress == null) { |
+ children = content; |
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; |
- } |
+ content.add(new SampleBufferControlElement(_progress, _progressStream, |
+ showTag: false, queue: _r.queue)); |
+ if (_progress.status == M.SampleProfileLoadingStatus.loaded) { |
+ content.add(new BRElement()); |
+ content.addAll(_createTables()); |
+ content.add(new BRElement()); |
+ content.addAll(_createTree()); |
} |
- return null; |
+ children = content; |
} |
- _focusOnFunction(ServiceFunction function) { |
- if (focusedFunction == function) { |
- // Do nothing. |
- return; |
- } |
- |
- _clearFocusedFunction(); |
+ M.ProfileFunction _selected; |
+ VirtualCollectionElement _functions; |
+ VirtualCollectionElement _callers; |
+ VirtualCollectionElement _callees; |
- 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 |
- } |
+ List<Element> _createTables() { |
+ _functions = _functions ?? new VirtualCollectionElement( |
+ _createFunction, |
+ _updateFunction, |
+ createHeader: _createFunctionHeader, |
+ queue: _r.queue |
); |
+ _functions.items = _progress.profile.functions.toList() |
+ ..sort(_createSorter(_Table.functions)); |
+ _functions.takeIntoView(_selected); |
+ _callers = _callers ?? new VirtualCollectionElement( |
+ _createCaller, |
+ _updateCaller, |
+ createHeader: _createCallerHeader, |
+ queue: _r.queue |
+ ); |
+ _callees = _callees ?? new VirtualCollectionElement( |
+ _createCallee, |
+ _updateCallee, |
+ createHeader: _createCalleeHeader, |
+ queue: _r.queue |
+ ); |
+ if (_selected != null) { |
+ _callers.items = _selected.callers.keys.toList() |
+ ..sort(_createSorter(_Table.caller)); |
+ _callees.items = _selected.callees.keys.toList() |
+ ..sort(_createSorter(_Table.callee)); |
+ } else { |
+ _callers.items = const []; |
+ _callees.items = const []; |
+ } |
+ return [ |
+ new DivElement()..classes = ['profile-trees'] |
+ ..children = [ |
+ new DivElement()..classes = ['profile-trees-all'] |
+ ..children = [_functions], |
+ new DivElement()..classes = ['profile-trees-current'] |
+ ..children = [ |
+ new DivElement()..classes = ['profile-trees-caller'] |
+ ..children = [_callers], |
+ new DivElement()..classes = ['profile-trees-selected'] |
+ ..children = _selected == null |
+ ? [new SpanElement()..text = 'No element selected'] |
+ : [new FunctionRefElement(_isolate, _selected.function, |
+ queue : _r.queue)], |
+ new DivElement()..classes = ['profile-trees-callee'] |
+ ..children = [_callees] |
+ ] |
+ ] |
+ ]; |
} |
- _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, |
+ Element _createFunction() { |
+ final element = new DivElement() |
+ ..classes = const ['function-item'] |
+ ..children = [ |
+ new SpanElement()..classes = const ['exclusive'] |
+ ..text = '0%', |
+ new SpanElement()..classes = const ['inclusive'] |
+ ..text = '0%', |
+ new SpanElement()..classes = const ['name'] |
]; |
- model.addRow(new SortedTableRow(row)); |
+ element.onClick.listen((_) { |
+ _selected = _functions.getItemFromElement(element); |
+ _r.dirty(); |
}); |
- 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); |
+ return element; |
} |
- _buildCalleesTable(ServiceFunction function) { |
- var calls = (function != null) ? function.profile.callees : null; |
- var table = $['callees-table']; |
- _buildCallTable(calls, profileCalleesTable); |
- _renderCallTable(table, profileCalleesTable, _onCalleesClick); |
- } |
+ void _updateFunction(Element e, M.ProfileFunction item, int index) { |
+ if (item == _selected) { |
+ e.classes = const ['function-item', 'selected']; |
+ } else { |
+ e.classes = const ['function-item']; |
+ } |
+ e.children[0].text = Utils.formatPercentNormalized(_getExclusiveT(item)); |
+ e.children[1].text = Utils.formatPercentNormalized(_getInclusiveT(item)); |
+ e.children[2] = new FunctionRefElement(_isolate, item.function, |
+ queue: _r.queue)..classes = const ['name']; |
+ } |
+ |
+ Element _createFunctionHeader() => |
+ new DivElement() |
+ ..classes = const ['function-item'] |
+ ..children = [ |
+ _createHeaderButton(const ['exclusive'], 'Execution(%)', |
+ _Table.functions, |
+ _SortingField.exclusive, |
+ _SortingDirection.descending), |
+ _createHeaderButton(const ['inclusive'], 'Stack(%)', |
+ _Table.functions, |
+ _SortingField.inclusive, |
+ _SortingDirection.descending), |
+ _createHeaderButton(const ['name'], 'Method', |
+ _Table.functions, |
+ _SortingField.method, |
+ _SortingDirection.descending), |
+ ]; |
- _changeSort(Element target, NameSortedTable table) { |
- if (target is TableCellElement) { |
- if (table.sortColumnIndex != target.cellIndex) { |
- table.sortColumnIndex = target.cellIndex; |
- table.sortDescending = true; |
+ void _setSorting(_Table table, |
+ _SortingField field, |
+ _SortingDirection defaultDirection) { |
+ if (_sortingField[table] == field) { |
+ switch (_sortingDirection[table]) { |
+ case _SortingDirection.descending: |
+ _sortingDirection[table] = _SortingDirection.ascending; |
+ break; |
+ case _SortingDirection.ascending: |
+ _sortingDirection[table] = _SortingDirection.descending; |
+ break; |
+ } |
} else { |
- table.sortDescending = !profileTable.sortDescending; |
+ _sortingDirection[table] = defaultDirection; |
+ _sortingField[table] = field; |
} |
- table.sort(); |
+ _r.dirty(); |
} |
- } |
- 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); |
+ Element _createCallee() { |
+ final element = new DivElement() |
+ ..classes = const ['function-item'] |
+ ..children = [ |
+ new SpanElement()..classes = const ['inclusive'] |
+ ..text = '0%', |
+ new SpanElement()..classes = const ['name'] |
+ ]; |
+ element.onClick.listen((_) { |
+ _selected = _callees.getItemFromElement(element); |
+ _r.dirty(); |
+ }); |
+ return element; |
} |
- changeSortCallees(Event e, var detail, Element target) { |
- _changeSort(target, profileCalleesTable); |
- _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); |
- } |
+ void _updateCallee(Element e, item, int index) { |
+ e.children[0].text = Utils.formatPercentNormalized(_getCalleeT(item)); |
+ e.children[1] = new FunctionRefElement(_isolate, item.function, |
+ queue: _r.queue)..classes = const ['name']; |
+ } |
+ |
+ Element _createCalleeHeader() => |
+ new DivElement() |
+ ..classes = const ['function-item'] |
+ ..children = [ |
+ _createHeaderButton(const ['inclusive'], 'Callees(%)', |
+ _Table.callee, |
+ _SortingField.callee, |
+ _SortingDirection.descending), |
+ _createHeaderButton(const ['name'], 'Method', |
+ _Table.callee, |
+ _SortingField.method, |
+ _SortingDirection.descending), |
+ ]; |
- ////// |
- /// |
- /// Function tree. |
- /// |
- TableTree functionTree; |
- _updateFunctionTreeView() { |
- if (cpuProfileTreeElement == null) { |
- return; |
- } |
- cpuProfileTreeElement.filter = (FunctionCallTreeNode node) { |
- return node.profileFunction.function == focusedFunction; |
- }; |
+ Element _createCaller() { |
+ final element = new DivElement() |
+ ..classes = const ['function-item'] |
+ ..children = [ |
+ new SpanElement()..classes = const ['inclusive'] |
+ ..text = '0%', |
+ new SpanElement()..classes = const ['name'] |
+ ]; |
+ element.onClick.listen((_) { |
+ _selected = _callers.getItemFromElement(element); |
+ _r.dirty(); |
+ }); |
+ return element; |
} |
- @published Isolate isolate; |
- @observable NameSortedTable profileTable; |
- @observable NameSortedTable profileCallersTable; |
- @observable NameSortedTable profileCalleesTable; |
- @observable ServiceFunction focusedFunction; |
- @observable int focusedRow; |
- |
+ void _updateCaller(Element e, item, int index) { |
+ e.children[0].text = Utils.formatPercentNormalized(_getCallerT(item)); |
+ e.children[1] = new FunctionRefElement(_isolate, item.function, |
+ queue: _r.queue)..classes = const ['name']; |
+ } |
+ |
+ Element _createCallerHeader() => |
+ new DivElement() |
+ ..classes = const ['function-item'] |
+ ..children = [ |
+ _createHeaderButton(const ['inclusive'], 'Callers(%)', |
+ _Table.caller, |
+ _SortingField.caller, |
+ _SortingDirection.descending), |
+ _createHeaderButton(const ['name'], 'Method', |
+ _Table.caller, |
+ _SortingField.method, |
+ _SortingDirection.descending), |
+ ]; |
- StreamSubscription _resizeSubscription; |
- Task _updateTask; |
- Task _renderTask; |
+ ButtonElement _createHeaderButton(List<String> classes, |
+ String text, |
+ _Table table, |
+ _SortingField field, |
+ _SortingDirection direction) => |
+ new ButtonElement()..classes = classes |
+ ..text = _sortingField[table] != field ? text : |
+ _sortingDirection[table] == _SortingDirection.ascending |
+ ? '$textâ–¼' : '$textâ–²' |
+ ..onClick.listen((_) => _setSorting(table, field, direction)); |
+ |
+ List<Element> _createTree() { |
+ CpuProfileVirtualTreeElement tree; |
+ return [ |
+ new StackTraceTreeConfigElement(showMode: false, |
+ showDirection: false, mode: ProfileTreeMode.function, |
+ direction: M.ProfileTreeDirection.exclusive, filter: _filter, |
+ queue: _r.queue) |
+ ..onFilterChange.listen((e) { |
+ _filter = e.element.filter.trim(); |
+ tree.filters = _filter.isNotEmpty |
+ ? [_filterTree, (node) { return node.name.contains(_filter); }] |
+ : [_filterTree]; |
+ }), |
+ new BRElement(), |
+ tree = new CpuProfileVirtualTreeElement(_isolate, _progress.profile, |
+ mode: ProfileTreeMode.function, |
+ direction: M.ProfileTreeDirection.exclusive, |
+ queue: _r.queue) |
+ ..filters = _filter.isNotEmpty |
+ ? [_filterTree, (node) { return node.name.contains(_filter); }] |
+ : [_filterTree] |
+ ]; |
+ } |
- IsolateSampleProfileRepository _repository = |
- new IsolateSampleProfileRepository(); |
- CpuProfile _profile; |
- SampleBufferControlElement sampleBufferControlElement; |
- StackTraceTreeConfigElement stackTraceTreeConfigElement; |
- CpuProfileVirtualTreeElement cpuProfileTreeElement; |
+ bool _filterTree(M.FunctionCallTreeNode node) => |
+ node.profileFunction == _selected; |
+ |
+ Future _request({bool clear: false, bool forceFetch: false}) async { |
+ _progress = null; |
+ _progressStream = _profiles.get(isolate, M.SampleProfileTag.none, |
+ 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; |
+ } |
+ |
+ _createSorter(_Table table) { |
+ var getter; |
+ switch (_sortingField[table]) { |
+ case _SortingField.exclusive: |
+ getter = _getExclusiveT; |
+ break; |
+ case _SortingField.inclusive: |
+ getter = _getInclusiveT; |
+ break; |
+ case _SortingField.callee: |
+ getter = _getCalleeT; |
+ break; |
+ case _SortingField.caller: |
+ getter = _getCallerT; |
+ break; |
+ case _SortingField.method: |
+ getter = (M.ProfileFunction s) => |
+ M.getFunctionFullName(s.function); |
+ break; |
+ } |
+ switch (_sortingDirection[table]) { |
+ case _SortingDirection.ascending: |
+ return (a, b) => getter(a).compareTo(getter(b)); |
+ case _SortingDirection.descending: |
+ return (a, b) => getter(b).compareTo(getter(a)); |
+ } |
+ } |
+ |
+ static double _getExclusiveT(M.ProfileFunction f) => |
+ f.normalizedExclusiveTicks; |
+ static double _getInclusiveT(M.ProfileFunction f) => |
+ f.normalizedExclusiveTicks; |
+ double _getCalleeT(M.ProfileFunction f) => |
+ _selected.callees[f] / _selected.callees.values.reduce((a, b) => a + b); |
+ double _getCallerT(M.ProfileFunction f) => |
+ _selected.callers[f] / _selected.callers.values.reduce((a, b) => a + b); |
} |