| Index: runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart
|
| diff --git a/runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart b/runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart
|
| index 9626735d85fe3643820573a6b94d3c03f006dcf5..132e43b871b5af3caa3a3be33be72df1fb451670 100644
|
| --- a/runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart
|
| +++ b/runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart
|
| @@ -8,21 +8,28 @@ import 'dart:html';
|
| import 'observatory_element.dart';
|
| import 'package:observatory/app.dart';
|
| import 'package:observatory/service.dart';
|
| -import 'package:logging/logging.dart';
|
| +import 'package:observatory/elements.dart';
|
| import 'package:polymer/polymer.dart';
|
|
|
| +class ClassSortedTable extends SortedTable {
|
| +
|
| + ClassSortedTable(columns) : super(columns);
|
| +
|
| + @override
|
| + dynamic getSortKeyFor(int row, int col) {
|
| + if (col == 0) {
|
| + // Use class name as sort key.
|
| + return rows[row].values[col].name;
|
| + }
|
| + return super.getSortKeyFor(row, col);
|
| + }
|
| +}
|
| +
|
| /// Displays an Error response.
|
| @CustomTag('heap-profile')
|
| class HeapProfileElement extends ObservatoryElement {
|
| - // Indexes into VM provided map.
|
| - static const ALLOCATED_BEFORE_GC = 0;
|
| - static const ALLOCATED_BEFORE_GC_SIZE = 1;
|
| - static const LIVE_AFTER_GC = 2;
|
| - static const LIVE_AFTER_GC_SIZE = 3;
|
| - static const ALLOCATED_SINCE_GC = 4;
|
| - static const ALLOCATED_SINCE_GC_SIZE = 5;
|
| - static const ACCUMULATED = 6;
|
| - static const ACCUMULATED_SIZE = 7;
|
| + @observable String lastServiceGC = '---';
|
| + @observable String lastAccumulatorReset = '---';
|
|
|
| // Pie chart of new space usage.
|
| var _newPieDataTable;
|
| @@ -32,149 +39,208 @@ class HeapProfileElement extends ObservatoryElement {
|
| var _oldPieDataTable;
|
| var _oldPieChart;
|
|
|
| - @observable SortedTable classTable;
|
| + @observable ClassSortedTable classTable;
|
| + var _classTableBody;
|
|
|
| @published ServiceMap profile;
|
|
|
| + @observable Isolate isolate;
|
| +
|
| HeapProfileElement.created() : super.created() {
|
| + // Create pie chart models.
|
| _newPieDataTable = new DataTable();
|
| _newPieDataTable.addColumn('string', 'Type');
|
| _newPieDataTable.addColumn('number', 'Size');
|
| _oldPieDataTable = new DataTable();
|
| _oldPieDataTable.addColumn('string', 'Type');
|
| _oldPieDataTable.addColumn('number', 'Size');
|
| +
|
| + // Create class table model.
|
| var columns = [
|
| new SortedTableColumn('Class'),
|
| - new SortedTableColumn.withFormatter('Accumulator Size (New)',
|
| + new SortedTableColumn(''), // Spacer column.
|
| + new SortedTableColumn.withFormatter('Accumulated Size (New)',
|
| Utils.formatSize),
|
| - new SortedTableColumn.withFormatter('Accumulator (New)',
|
| + new SortedTableColumn.withFormatter('Accumulated Instances',
|
| Utils.formatCommaSeparated),
|
| - new SortedTableColumn.withFormatter('Current Size (New)',
|
| + new SortedTableColumn.withFormatter('Current Size',
|
| Utils.formatSize),
|
| - new SortedTableColumn.withFormatter('Current (New)',
|
| + new SortedTableColumn.withFormatter('Current Instances',
|
| Utils.formatCommaSeparated),
|
| + new SortedTableColumn(''), // Spacer column.
|
| new SortedTableColumn.withFormatter('Accumulator Size (Old)',
|
| Utils.formatSize),
|
| - new SortedTableColumn.withFormatter('Accumulator (Old)',
|
| + new SortedTableColumn.withFormatter('Accumulator Instances',
|
| Utils.formatCommaSeparated),
|
| - new SortedTableColumn.withFormatter('Current Size (Old)',
|
| + new SortedTableColumn.withFormatter('Current Size',
|
| Utils.formatSize),
|
| - new SortedTableColumn.withFormatter('Current (Old)',
|
| + new SortedTableColumn.withFormatter('Current Instances',
|
| Utils.formatCommaSeparated)
|
| ];
|
| - classTable = new SortedTable(columns);
|
| - classTable.sortColumnIndex = 1;
|
| + classTable = new ClassSortedTable(columns);
|
| + // By default, start with accumulated new space bytes.
|
| + classTable.sortColumnIndex = 2;
|
| }
|
|
|
| - void enteredView() {
|
| - super.enteredView();
|
| + @override
|
| + void attached() {
|
| + super.attached();
|
| + // Grab the pie chart divs.
|
| _newPieChart = new Chart('PieChart',
|
| shadowRoot.querySelector('#newPieChart'));
|
| - _newPieChart.options['title'] = 'New Space';
|
| _oldPieChart = new Chart('PieChart',
|
| shadowRoot.querySelector('#oldPieChart'));
|
| - _oldPieChart.options['title'] = 'Old Space';
|
| - _draw();
|
| + _classTableBody = shadowRoot.querySelector('#classTableBody');
|
| }
|
|
|
| - void _updateChartData() {
|
| - if ((profile == null) || (profile['members'] is! List) ||
|
| - (profile['members'].length == 0)) {
|
| - return;
|
| + void _updatePieCharts() {
|
| + assert(profile != null);
|
| + _newPieDataTable.clearRows();
|
| + var isolate = profile.isolate;
|
| + _newPieDataTable.addRow(['Used', isolate.newSpace.used]);
|
| + _newPieDataTable.addRow(['Free',
|
| + isolate.newSpace.capacity - isolate.newSpace.used]);
|
| + _newPieDataTable.addRow(['External', isolate.newSpace.external]);
|
| + _oldPieDataTable.clearRows();
|
| + _oldPieDataTable.addRow(['Used', isolate.oldSpace.used]);
|
| + _oldPieDataTable.addRow(['Free',
|
| + isolate.oldSpace.capacity - isolate.oldSpace.used]);
|
| + _oldPieDataTable.addRow(['External', isolate.oldSpace.external]);
|
| + }
|
| +
|
| + void _updateClasses() {
|
| + for (ServiceMap clsAllocations in profile['members']) {
|
| + Class cls = clsAllocations['class'];
|
| + if (cls == null) {
|
| + continue;
|
| + }
|
| + cls.newSpace.update(clsAllocations['new']);
|
| + cls.oldSpace.update(clsAllocations['old']);
|
| }
|
| - assert(classTable != null);
|
| + }
|
| +
|
| + void _updateClassTable() {
|
| classTable.clearRows();
|
| - for (ServiceMap cls in profile['members']) {
|
| - if (_classHasNoAllocations(cls)) {
|
| + for (ServiceMap clsAllocations in profile['members']) {
|
| + Class cls = clsAllocations['class'];
|
| + if (cls == null) {
|
| + continue;
|
| + }
|
| + if (cls.hasNoAllocations) {
|
| // If a class has no allocations, don't display it.
|
| continue;
|
| }
|
| - var row = [cls['class'],
|
| - _combinedTableColumnValue(cls, 1),
|
| - _combinedTableColumnValue(cls, 2),
|
| - _combinedTableColumnValue(cls, 3),
|
| - _combinedTableColumnValue(cls, 4),
|
| - _combinedTableColumnValue(cls, 5),
|
| - _combinedTableColumnValue(cls, 6),
|
| - _combinedTableColumnValue(cls, 7),
|
| - _combinedTableColumnValue(cls, 8)];
|
| + var row = [cls,
|
| + '', // Spacer column.
|
| + cls.newSpace.accumulated.bytes,
|
| + cls.newSpace.accumulated.instances,
|
| + cls.newSpace.current.bytes,
|
| + cls.newSpace.current.instances,
|
| + '', // Spacer column.
|
| + cls.oldSpace.accumulated.bytes,
|
| + cls.oldSpace.accumulated.instances,
|
| + cls.oldSpace.current.bytes,
|
| + cls.oldSpace.current.instances];
|
| classTable.addRow(new SortedTableRow(row));
|
| }
|
| classTable.sort();
|
| - _newPieDataTable.clearRows();
|
| - var heap = profile['heaps']['new'];
|
| - _newPieDataTable.addRow(['Used', heap['used']]);
|
| - _newPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]);
|
| - _newPieDataTable.addRow(['External', heap['external']]);
|
| - _oldPieDataTable.clearRows();
|
| - heap = profile['heaps']['old'];
|
| - _oldPieDataTable.addRow(['Used', heap['used']]);
|
| - _oldPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]);
|
| - _oldPieDataTable.addRow(['External', heap['external']]);
|
| - _draw();
|
| }
|
|
|
| - void _draw() {
|
| - if (_newPieChart == null) {
|
| - return;
|
| - }
|
| - _newPieChart.draw(_newPieDataTable);
|
| - _oldPieChart.draw(_oldPieDataTable);
|
| + void _addClassTableDomRow() {
|
| + assert(_classTableBody != null);
|
| + var tr = new TableRowElement();
|
| +
|
| + // Add class ref.
|
| + var cell = tr.insertCell(-1);
|
| + ClassRefElement classRef = new Element.tag('class-ref');
|
| + cell.children.add(classRef);
|
| +
|
| + // Add spacer.
|
| + cell = tr.insertCell(-1);
|
| + cell.classes.add('left-border-spacer');
|
| +
|
| + // Add new space.
|
| + cell = tr.insertCell(-1);
|
| + cell = tr.insertCell(-1);
|
| + cell = tr.insertCell(-1);
|
| + cell = tr.insertCell(-1);
|
| +
|
| + // Add spacer.
|
| + cell = tr.insertCell(-1);
|
| + cell.classes.add('left-border-spacer');
|
| +
|
| + // Add old space.
|
| + cell = tr.insertCell(-1);
|
| + cell = tr.insertCell(-1);
|
| + cell = tr.insertCell(-1);
|
| + cell = tr.insertCell(-1);
|
| +
|
| + // Add row to table.
|
| + _classTableBody.children.add(tr);
|
| }
|
|
|
| - @observable void changeSort(Event e, var detail, Element target) {
|
| - if (target is TableCellElement) {
|
| - if (classTable.sortColumnIndex != target.cellIndex) {
|
| - classTable.sortColumnIndex = target.cellIndex;
|
| - classTable.sort();
|
| + void _fillClassTableDomRow(TableRowElement tr, int rowIndex) {
|
| + const SPACER_COLUMNS = const [1, 6];
|
| +
|
| + var row = classTable.rows[rowIndex];
|
| + // Add class ref.
|
| + ClassRefElement classRef = tr.children[0].children[0];
|
| + classRef.ref = row.values[0];
|
| +
|
| + for (var i = 1; i < row.values.length; i++) {
|
| + if (SPACER_COLUMNS.contains(i)) {
|
| + // Skip spacer columns.
|
| + continue;
|
| }
|
| + var cell = tr.children[i];
|
| + cell.title = row.values[i].toString();
|
| + cell.text = classTable.getFormattedValue(rowIndex, i);
|
| }
|
| }
|
|
|
| - bool _classHasNoAllocations(Map v) {
|
| - var newSpace = v['new'];
|
| - var oldSpace = v['old'];
|
| - for (var allocation in newSpace) {
|
| - if (allocation != 0) {
|
| - return false;
|
| + void _updateClassTableInDom() {
|
| + assert(_classTableBody != null);
|
| + // Resize DOM table.
|
| + if (_classTableBody.children.length > classTable.sortedRows.length) {
|
| + // Shrink the table.
|
| + var deadRows =
|
| + _classTableBody.children.length - classTable.sortedRows.length;
|
| + for (var i = 0; i < deadRows; i++) {
|
| + _classTableBody.children.removeLast();
|
| }
|
| - }
|
| - for (var allocation in oldSpace) {
|
| - if (allocation != 0) {
|
| - return false;
|
| + } else if (_classTableBody.children.length < classTable.sortedRows.length) {
|
| + // Grow table.
|
| + var newRows =
|
| + classTable.sortedRows.length - _classTableBody.children.length;
|
| + for (var i = 0; i < newRows; i++) {
|
| + _addClassTableDomRow();
|
| }
|
| }
|
| - return true;
|
| + assert(_classTableBody.children.length == classTable.sortedRows.length);
|
| + // Fill table.
|
| + for (var i = 0; i < classTable.sortedRows.length; i++) {
|
| + var rowIndex = classTable.sortedRows[i];
|
| + var tr = _classTableBody.children[i];
|
| + _fillClassTableDomRow(tr, rowIndex);
|
| + }
|
| }
|
|
|
| - dynamic _combinedTableColumnValue(Map v, int index) {
|
| - assert(index >= 0);
|
| - assert(index < 9);
|
| - switch (index) {
|
| - case 0:
|
| - return v['class']['user_name'];
|
| - case 1:
|
| - return v['new'][ACCUMULATED_SIZE];
|
| - case 2:
|
| - return v['new'][ACCUMULATED];
|
| - case 3:
|
| - return v['new'][LIVE_AFTER_GC_SIZE] +
|
| - v['new'][ALLOCATED_SINCE_GC_SIZE];
|
| - case 4:
|
| - return v['new'][LIVE_AFTER_GC] +
|
| - v['new'][ALLOCATED_SINCE_GC];
|
| - case 5:
|
| - return v['old'][ACCUMULATED_SIZE];
|
| - case 6:
|
| - return v['old'][ACCUMULATED];
|
| - case 7:
|
| - return v['old'][LIVE_AFTER_GC_SIZE] +
|
| - v['old'][ALLOCATED_SINCE_GC_SIZE];
|
| - case 8:
|
| - return v['old'][LIVE_AFTER_GC] +
|
| - v['old'][ALLOCATED_SINCE_GC];
|
| + void _drawCharts() {
|
| + _newPieChart.draw(_newPieDataTable);
|
| + _oldPieChart.draw(_oldPieDataTable);
|
| + }
|
| +
|
| + @observable void changeSort(Event e, var detail, Element target) {
|
| + if (target is TableCellElement) {
|
| + if (classTable.sortColumnIndex != target.cellIndex) {
|
| + classTable.sortColumnIndex = target.cellIndex;
|
| + classTable.sortDescending = true;
|
| + } else {
|
| + classTable.sortDescending = !classTable.sortDescending;
|
| + }
|
| + classTable.sort();
|
| + _updateClassTableInDom();
|
| }
|
| - throw new FallThroughError();
|
| }
|
|
|
| void refresh(var done) {
|
| @@ -182,77 +248,78 @@ class HeapProfileElement extends ObservatoryElement {
|
| return;
|
| }
|
| var isolate = profile.isolate;
|
| - isolate.get('/allocationprofile').then((ServiceMap response) {
|
| - assert(response['type'] == 'AllocationProfile');
|
| - profile = response;
|
| - }).catchError((e, st) {
|
| - Logger.root.info('$e $st');
|
| - }).whenComplete(done);
|
| + isolate.get('/allocationprofile').then(_update).whenComplete(done);
|
| }
|
|
|
| void refreshGC(var done) {
|
| - if (profile == null) {
|
| - return;
|
| - }
|
| - var isolate = profile.isolate;
|
| - isolate.get('/allocationprofile?gc=full').then((ServiceMap response) {
|
| - assert(response['type'] == 'AllocationProfile');
|
| - profile = response;
|
| - }).catchError((e, st) {
|
| - Logger.root.info('$e $st');
|
| - }).whenComplete(done);
|
| + if (profile == null) {
|
| + return;
|
| }
|
| + var isolate = profile.isolate;
|
| + isolate.get('/allocationprofile?gc=full').then(_update).whenComplete(done);
|
| + }
|
|
|
| void resetAccumulator(var done) {
|
| if (profile == null) {
|
| return;
|
| }
|
| var isolate = profile.isolate;
|
| - isolate.get('/allocationprofile?reset=true').then((ServiceMap response) {
|
| - assert(response['type'] == 'AllocationProfile');
|
| - profile = response;
|
| - }).catchError((e, st) {
|
| - Logger.root.info('$e $st');
|
| - }).whenComplete(done);
|
| + isolate.get('/allocationprofile?reset=true').then(_update).
|
| + whenComplete(done);
|
| + }
|
| +
|
| + void _update(ServiceMap newProfile) {
|
| + profile = newProfile;
|
| }
|
|
|
| void profileChanged(oldValue) {
|
| - try {
|
| - _updateChartData();
|
| - } catch (e, st) {
|
| - Logger.root.info('$e $st');
|
| + if (profile == null) {
|
| + return;
|
| + }
|
| + isolate = profile.isolate;
|
| + isolate.updateHeapsFromMap(profile['heaps']);
|
| + var millis = int.parse(profile['dateLastAccumulatorReset']);
|
| + if (millis != 0) {
|
| + lastAccumulatorReset =
|
| + new DateTime.fromMillisecondsSinceEpoch(millis).toString();
|
| + }
|
| + millis = int.parse(profile['dateLastServiceGC']);
|
| + if (millis != 0) {
|
| + lastServiceGC =
|
| + new DateTime.fromMillisecondsSinceEpoch(millis).toString();
|
| }
|
| - notifyPropertyChange(#formattedAverage, [], formattedAverage);
|
| - notifyPropertyChange(#formattedTotalCollectionTime, [],
|
| - formattedTotalCollectionTime);
|
| - notifyPropertyChange(#formattedCollections, [], formattedCollections);
|
| + _updatePieCharts();
|
| + _updateClasses();
|
| + _updateClassTable();
|
| + _updateClassTableInDom();
|
| + _drawCharts();
|
| + notifyPropertyChange(#formattedAverage, 0, 1);
|
| + notifyPropertyChange(#formattedTotalCollectionTime, 0, 1);
|
| + notifyPropertyChange(#formattedCollections, 0, 1);
|
| }
|
|
|
| @observable String formattedAverage(bool newSpace) {
|
| if (profile == null) {
|
| return '';
|
| }
|
| - String space = newSpace ? 'new' : 'old';
|
| - Map heap = profile['heaps'][space];
|
| - var r = ((heap['time'] * 1000.0) / heap['collections']).toStringAsFixed(2);
|
| - return '$r ms';
|
| + var heap = newSpace ? profile.isolate.newSpace : profile.isolate.oldSpace;
|
| + var avg = ((heap.totalCollectionTimeInSeconds * 1000.0) / heap.collections);
|
| + return '${avg.toStringAsFixed(2)} ms';
|
| }
|
|
|
| @observable String formattedCollections(bool newSpace) {
|
| if (profile == null) {
|
| return '';
|
| }
|
| - String space = newSpace ? 'new' : 'old';
|
| - Map heap = profile['heaps'][space];
|
| - return '${heap['collections']}';
|
| + var heap = newSpace ? profile.isolate.newSpace : profile.isolate.oldSpace;
|
| + return heap.collections.toString();
|
| }
|
|
|
| @observable String formattedTotalCollectionTime(bool newSpace) {
|
| if (profile == null) {
|
| return '';
|
| }
|
| - String space = newSpace ? 'new' : 'old';
|
| - Map heap = profile['heaps'][space];
|
| - return '${Utils.formatSeconds(heap['time'])} secs';
|
| + var heap = newSpace ? profile.isolate.newSpace : profile.isolate.oldSpace;
|
| + return '${Utils.formatSeconds(heap.totalCollectionTimeInSeconds)} secs';
|
| }
|
| }
|
|
|