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..972b4dd72b43a8ec9b40b82e00fb1633352f6f01 100644 |
--- a/runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart |
+++ b/runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart |
@@ -8,9 +8,23 @@ 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 { |
@@ -24,6 +38,9 @@ class HeapProfileElement extends ObservatoryElement { |
static const ACCUMULATED = 6; |
static const ACCUMULATED_SIZE = 7; |
+ @observable String lastForcedGC = '---'; |
+ @observable String lastAccumulatorReset = '---'; |
+ |
// Pie chart of new space usage. |
var _newPieDataTable; |
var _newPieChart; |
@@ -32,75 +49,71 @@ class HeapProfileElement extends ObservatoryElement { |
var _oldPieDataTable; |
var _oldPieChart; |
- @observable SortedTable classTable; |
+ @observable ClassSortedTable classTable; |
+ var _classTableBody; |
@published ServiceMap profile; |
+ @observable Isolate isolate; |
+ |
+ void _trace(Stopwatch sw, String label) { |
+ print('$label took ${sw.elapsedMicroseconds} us.'); |
+ sw.reset(); |
+ } |
+ |
+ void _traceMillis(Stopwatch sw, String label) { |
+ print('$label took ${sw.elapsedMilliseconds} ms.'); |
+ sw.reset(); |
+ } |
+ |
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(''), |
koda
2014/06/17 21:04:06
Why this empty column?
Cutch
2014/06/18 14:35:03
Spacer column, added comment.
|
+ 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(''), |
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); |
+ classTable.sortColumnIndex = 2; |
koda
2014/06/17 21:04:06
Remind the reader which column this is.
Cutch
2014/06/18 14:35:03
Done.
|
} |
- 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; |
- } |
- assert(classTable != null); |
- classTable.clearRows(); |
- for (ServiceMap cls in profile['members']) { |
- if (_classHasNoAllocations(cls)) { |
- // 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)]; |
- classTable.addRow(new SortedTableRow(row)); |
- } |
- classTable.sort(); |
+ void _updatePieCharts() { |
+ assert(profile != null); |
_newPieDataTable.clearRows(); |
var heap = profile['heaps']['new']; |
_newPieDataTable.addRow(['Used', heap['used']]); |
@@ -111,70 +124,133 @@ class HeapProfileElement extends ObservatoryElement { |
_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 _updateClassStats(Class cls, List newSpace, List oldSpace) { |
koda
2014/06/17 21:04:06
Any way to share more code between new and old? It
Cutch
2014/06/18 14:35:03
Done.
|
+ cls.accumulatedNewSpace.instances = newSpace[ACCUMULATED]; |
+ cls.accumulatedNewSpace.bytes = newSpace[ACCUMULATED_SIZE]; |
+ cls.currentNewSpace.instances = |
+ newSpace[LIVE_AFTER_GC] + newSpace[ALLOCATED_SINCE_GC]; |
+ cls.currentNewSpace.bytes = |
+ newSpace[LIVE_AFTER_GC_SIZE] + newSpace[ALLOCATED_SINCE_GC_SIZE]; |
+ |
+ cls.accumulatedOldSpace.instances = oldSpace[ACCUMULATED]; |
+ cls.accumulatedOldSpace.bytes = oldSpace[ACCUMULATED_SIZE]; |
+ cls.currentOldSpace.instances = |
+ oldSpace[LIVE_AFTER_GC] + oldSpace[ALLOCATED_SINCE_GC]; |
+ cls.currentOldSpace.bytes = |
+ oldSpace[LIVE_AFTER_GC_SIZE] + oldSpace[ALLOCATED_SINCE_GC_SIZE]; |
} |
- @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 _updateClasses() { |
+ for (ServiceMap clsAllocations in profile['members']) { |
+ Class cls = clsAllocations['class']; |
+ if (cls == null) { |
+ continue; |
} |
+ _updateClassStats(cls, clsAllocations['new'], clsAllocations['old']); |
} |
} |
- bool _classHasNoAllocations(Map v) { |
- var newSpace = v['new']; |
- var oldSpace = v['old']; |
- for (var allocation in newSpace) { |
- if (allocation != 0) { |
- return false; |
+ void _updateClassTable() { |
+ classTable.clearRows(); |
+ for (ServiceMap clsAllocations in profile['members']) { |
+ Class cls = clsAllocations['class']; |
+ if (cls == null) { |
+ continue; |
} |
- } |
- for (var allocation in oldSpace) { |
- if (allocation != 0) { |
- return false; |
+ if (cls.hasNoAllocations) { |
+ // If a class has no allocations, don't display it. |
+ continue; |
} |
+ var row = [cls, |
+ '', |
+ cls.accumulatedNewSpace.bytes, |
+ cls.accumulatedNewSpace.instances, |
+ cls.currentNewSpace.bytes, |
+ cls.currentNewSpace.instances, |
+ '', |
+ cls.accumulatedOldSpace.bytes, |
+ cls.accumulatedOldSpace.instances, |
+ cls.currentOldSpace.bytes, |
+ cls.currentOldSpace.instances]; |
+ classTable.addRow(new SortedTableRow(row)); |
} |
- return true; |
+ classTable.sort(); |
+ } |
+ |
+ void _updateClassTableInDom() { |
+ assert(_classTableBody != null); |
+ _classTableBody.children.clear(); |
+ for (var rowIndex in classTable.sortedRows) { |
+ var row = classTable.rows[rowIndex]; |
+ var tr = new TableRowElement(); |
+ |
+ // Add class ref. |
+ var cell = tr.insertCell(-1); |
+ ClassRefElement classRef = new Element.tag('class-ref'); |
+ classRef.ref = row.values[0]; |
+ cell.children.add(classRef); |
+ |
+ // Add spacer. |
+ cell = tr.insertCell(-1); |
+ cell.classes.add('left-border-spacer'); |
+ |
+ // Add new space. |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[2].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 2); |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[3].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 3); |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[4].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 4); |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[5].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 5); |
+ |
+ // Add spacer. |
+ cell = tr.insertCell(-1); |
+ cell.classes.add('left-border-spacer'); |
+ |
+ // Add old space. |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[7].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 7); |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[8].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 8); |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[9].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 9); |
+ cell = tr.insertCell(-1); |
+ cell.title = row.values[10].toString(); |
+ cell.innerHtml = classTable.getFormattedValue(rowIndex, 10); |
+ |
+ // Add row to table. |
+ _classTableBody.children.add(tr); |
+ } |
+ } |
+ |
+ void _drawCharts() { |
+ _newPieChart.draw(_newPieDataTable); |
+ _oldPieChart.draw(_oldPieDataTable); |
} |
- 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]; |
+ @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(); |
+ Stopwatch sw = new Stopwatch()..start(); |
+ _updateClassTableInDom(); |
+ _traceMillis(sw, 'Inserting class table into DOM'); |
} |
- throw new FallThroughError(); |
} |
void refresh(var done) { |
@@ -182,50 +258,61 @@ 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; |
+ } |
+ Stopwatch sw = new Stopwatch()..start(); |
+ 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['dateLastGC']); |
+ if (millis != 0) { |
+ lastForcedGC = |
+ new DateTime.fromMillisecondsSinceEpoch(millis).toString(); |
} |
- notifyPropertyChange(#formattedAverage, [], formattedAverage); |
- notifyPropertyChange(#formattedTotalCollectionTime, [], |
- formattedTotalCollectionTime); |
- notifyPropertyChange(#formattedCollections, [], formattedCollections); |
+ _trace(sw, 'Updating heaps'); |
+ _updatePieCharts(); |
+ _trace(sw, 'Updating pie charts'); |
+ _updateClasses(); |
+ _trace(sw, 'Updating classes'); |
+ _updateClassTable(); |
+ _trace(sw, 'Updating class table'); |
+ _updateClassTableInDom(); |
+ _traceMillis(sw, 'Inserting class table into DOM'); |
+ _drawCharts(); |
+ _traceMillis(sw, 'Drawing pie charts'); |
+ notifyPropertyChange(#formattedAverage, 0, 1); |
+ notifyPropertyChange(#formattedTotalCollectionTime, 0, 1); |
+ notifyPropertyChange(#formattedCollections, 0, 1); |
} |
@observable String formattedAverage(bool newSpace) { |