Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(538)

Unified Diff: runtime/observatory/lib/src/elements/cpu_profile.dart

Issue 1013563002: CPU profile displayed in three tables with a tree (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 b4406ac4ea2b9e176bceb51c89224799183fe8fb..479e6c3914c559edc979fbb8c4ee23d4fd463dd0 100644
--- a/runtime/observatory/lib/src/elements/cpu_profile.dart
+++ b/runtime/observatory/lib/src/elements/cpu_profile.dart
@@ -224,7 +224,7 @@ class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> {
percentNode.text = percent;
percentNode.style.minWidth = '5em';
percentNode.style.textAlign = 'right';
- percentNode.title = 'Self: $selfPercent';
+ percentNode.title = 'Executing: $selfPercent';
methodColumn.children.add(percentNode);
// Gap.
@@ -319,7 +319,7 @@ class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> {
parentPercent.style.minWidth = '4em';
parentPercent.style.alignSelf = 'center';
parentPercent.style.textAlign = 'right';
- parentPercent.title = 'Self: $selfPercent';
+ parentPercent.title = 'Executing: $selfPercent';
functionRow.children.add(parentPercent);
// Gap.
@@ -346,7 +346,7 @@ class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> {
functionRow.children.add(infoBox);
if (node.profileFunction.function.kind.hasDartCode()) {
- infoBox.children.add(div('Hot code for current node'));
+ infoBox.children.add(div('Code for current node'));
infoBox.children.add(br());
var totalTicks = node.totalCodesTicks;
var numCodes = node.codes.length;
@@ -392,7 +392,7 @@ class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> {
});
if (node.profileFunction.function.kind.hasDartCode()) {
- infoBox.children.add(div('Hot code containing function'));
+ infoBox.children.add(div('Code containing function'));
infoBox.children.add(br());
var totalTicks = profile.sampleCount;
var codes = node.profileFunction.profileCodes;
@@ -510,10 +510,11 @@ class CpuProfileElement extends ObservatoryElement {
fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds);
}
- _onLoadStarted() {
+ Future _onLoadStarted() {
_sw.reset();
_sw.start();
state = 'Loading';
+ return window.animationFrame;
}
_onLoadFinished() {
@@ -537,9 +538,9 @@ class CpuProfileElement extends ObservatoryElement {
}
_onFetchStarted();
return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector })
- .then((response) {
+ .then((response) async {
_onFetchFinished();
- _onLoadStarted();
+ await _onLoadStarted();
try {
profile.load(isolate, response);
_onLoadFinished();
@@ -610,3 +611,516 @@ class CpuProfileElement extends ObservatoryElement {
codeTree.initialize(rootRow);
}
}
+
+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 {
+ final Stopwatch _sw = new Stopwatch();
+ final CpuProfile profile = new CpuProfile();
+ StreamSubscription _resizeSubscription;
+ @observable NameSortedTable profileTable;
+ @observable NameSortedTable profileCallersTable;
+ @observable NameSortedTable profileCalleesTable;
+ @observable ServiceFunction focusedFunction;
+ @observable int focusedRow;
+ @observable String fetchTime = '';
+ @observable String loadTime = '';
+ @observable String state = 'Requested';
+ @observable var exception;
+ @observable var stackTrace;
+ @observable Isolate isolate;
+ @observable String sampleCount = '';
+ @observable String refreshTime = '';
+ @observable String sampleRate = '';
+ @observable String stackDepth = '';
+ @observable String timeSpan = '';
+ @observable String directionSelector = 'Up';
+
+ CpuProfileTableElement.created() : super.created() {
+ 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();
+ _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() {
+ _getCpuProfile().whenComplete(checkParameters);
+ }
+
+ checkParameters() {
+ var functionId = app.locationManager.uri.queryParameters['functionId'];
+ if (functionId == null) {
+ _focusOnFunction(null);
+ return;
+ }
+ if (isolate == null) {
+ return;
+ }
+ isolate.getObject(functionId).then((func) => _focusOnFunction(func));
+ }
+
+ void directionSelectorChanged(oldValue) {
+ _updateFunctionTreeView();
+ }
+
+ void refresh(var done) {
+ _getCpuProfile().whenComplete(done);
+ }
+
+ void clear(var done) {
+ _clearCpuProfile().whenComplete(done);
+ }
+
+ _onFetchStarted() {
+ _sw.reset();
+ _sw.start();
+ state = 'Requested';
+ }
+
+ _onFetchFinished() {
+ _sw.stop();
+ fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds);
+ }
+
+ _onLoadStarted() {
+ _sw.reset();
+ _sw.start();
+ state = 'Loading';
+ }
+
+ _onLoadFinished() {
+ _sw.stop();
+ loadTime = formatTimeMilliseconds(_sw.elapsedMilliseconds);
+ state = 'Loaded';
+ }
+
+ Future _clearCpuProfile() {
+ profile.clear();
+ _clearView();
+ if (isolate == null) {
+ return new Future.value(null);
+ }
+ return isolate.invokeRpc('clearCpuProfile', { })
+ .then((ServiceMap response) {
+ _updateView();
+ });
+ }
+
+ Future _getCpuProfile() {
+ profile.clear();
+ _clearView();
+ if (isolate == null) {
+ return new Future.value(null);
+ }
+ _onFetchStarted();
+ return isolate.invokeRpc('getCpuProfile', { 'tags': 'None' })
+ .then((response) {
+ _onFetchFinished();
+ _onLoadStarted();
+ try {
+ profile.load(isolate, response);
+ profile.buildFunctionCallerAndCallees();
+ _onLoadFinished();
+ _updateView();
+ } catch (e, st) {
+ state = 'Exception';
+ exception = e;
+ stackTrace = st;
+ }
+ }).catchError((e, st) {
+ state = 'Exception';
+ exception = e;
+ stackTrace = st;
+ });
+ }
+
+ _clearView() {
+ profileTable.clearRows();
+ _renderTable();
+ }
+
+ _updateView() {
+ sampleCount = profile.sampleCount.toString();
+ refreshTime = new DateTime.now().toString();
+ stackDepth = profile.stackDepth.toString();
+ sampleRate = profile.sampleRate.toStringAsFixed(0);
+ timeSpan = formatTime(profile.timeSpan);
+ _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');
+ }
+
+ _clearFocusedFunction() {
+ TableSectionElement tableBody = $['profile-table'];
+ // Clear current focus.
+ if (focusedRow != null) {
+ tableBody.children[focusedRow].classes.remove('focused');
+ }
+ focusedRow = null;
+ focusedFunction = 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.goParameter(
+ {
+ 'functionId': function.id
+ }
+ );
+ }
+
+ _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() {
+ if (functionTree != null) {
+ functionTree.clear();
+ functionTree = null;
+ }
+ _buildFunctionTree();
+ }
+
+ void _buildFunctionTree() {
+ if (functionTree == null) {
+ var tableBody = shadowRoot.querySelector('#treeBody');
+ assert(tableBody != null);
+ functionTree = new TableTree(tableBody, 2);
+ }
+ if (focusedFunction == null) {
+ return;
+ }
+ bool exclusive = directionSelector == 'Up';
+ var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive');
+ if (tree == null) {
+ return;
+ }
+ var filter = (FunctionCallTreeNode node) {
+ return node.profileFunction.function == focusedFunction;
+ };
+ tree = tree.filtered(filter);
+ var rootRow =
+ new FunctionProfileTreeRow(functionTree, null, profile, tree.root);
+ functionTree.initialize(rootRow);
+ }
+}
« no previous file with comments | « runtime/observatory/lib/src/elements/context_view.html ('k') | runtime/observatory/lib/src/elements/cpu_profile.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698