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

Side by Side Diff: runtime/bin/vmservice/client/lib/src/elements/heap_profile.dart

Issue 342513004: Visual refresh of allocation profile page (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 6 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library heap_profile_element; 5 library heap_profile_element;
6 6
7 import 'dart:html'; 7 import 'dart:html';
8 import 'observatory_element.dart'; 8 import 'observatory_element.dart';
9 import 'package:observatory/app.dart'; 9 import 'package:observatory/app.dart';
10 import 'package:observatory/service.dart'; 10 import 'package:observatory/service.dart';
11 import 'package:logging/logging.dart'; 11 import 'package:observatory/elements.dart';
12 import 'package:polymer/polymer.dart'; 12 import 'package:polymer/polymer.dart';
13 13
14 class ClassSortedTable extends SortedTable {
15
16 ClassSortedTable(columns) : super(columns);
17
18 @override
19 dynamic getSortKeyFor(int row, int col) {
20 if (col == 0) {
21 // Use class name as sort key.
22 return rows[row].values[col].name;
23 }
24 return super.getSortKeyFor(row, col);
25 }
26 }
27
14 /// Displays an Error response. 28 /// Displays an Error response.
15 @CustomTag('heap-profile') 29 @CustomTag('heap-profile')
16 class HeapProfileElement extends ObservatoryElement { 30 class HeapProfileElement extends ObservatoryElement {
17 // Indexes into VM provided map. 31 // Indexes into VM provided map.
18 static const ALLOCATED_BEFORE_GC = 0; 32 static const ALLOCATED_BEFORE_GC = 0;
19 static const ALLOCATED_BEFORE_GC_SIZE = 1; 33 static const ALLOCATED_BEFORE_GC_SIZE = 1;
20 static const LIVE_AFTER_GC = 2; 34 static const LIVE_AFTER_GC = 2;
21 static const LIVE_AFTER_GC_SIZE = 3; 35 static const LIVE_AFTER_GC_SIZE = 3;
22 static const ALLOCATED_SINCE_GC = 4; 36 static const ALLOCATED_SINCE_GC = 4;
23 static const ALLOCATED_SINCE_GC_SIZE = 5; 37 static const ALLOCATED_SINCE_GC_SIZE = 5;
24 static const ACCUMULATED = 6; 38 static const ACCUMULATED = 6;
25 static const ACCUMULATED_SIZE = 7; 39 static const ACCUMULATED_SIZE = 7;
26 40
41 @observable String lastForcedGC = '---';
42 @observable String lastAccumulatorReset = '---';
43
27 // Pie chart of new space usage. 44 // Pie chart of new space usage.
28 var _newPieDataTable; 45 var _newPieDataTable;
29 var _newPieChart; 46 var _newPieChart;
30 47
31 // Pie chart of old space usage. 48 // Pie chart of old space usage.
32 var _oldPieDataTable; 49 var _oldPieDataTable;
33 var _oldPieChart; 50 var _oldPieChart;
34 51
35 @observable SortedTable classTable; 52 @observable ClassSortedTable classTable;
53 var _classTableBody;
36 54
37 @published ServiceMap profile; 55 @published ServiceMap profile;
38 56
57 @observable Isolate isolate;
58
59 void _trace(Stopwatch sw, String label) {
60 print('$label took ${sw.elapsedMicroseconds} us.');
61 sw.reset();
62 }
63
64 void _traceMillis(Stopwatch sw, String label) {
65 print('$label took ${sw.elapsedMilliseconds} ms.');
66 sw.reset();
67 }
68
39 HeapProfileElement.created() : super.created() { 69 HeapProfileElement.created() : super.created() {
70 // Create pie chart models.
40 _newPieDataTable = new DataTable(); 71 _newPieDataTable = new DataTable();
41 _newPieDataTable.addColumn('string', 'Type'); 72 _newPieDataTable.addColumn('string', 'Type');
42 _newPieDataTable.addColumn('number', 'Size'); 73 _newPieDataTable.addColumn('number', 'Size');
43 _oldPieDataTable = new DataTable(); 74 _oldPieDataTable = new DataTable();
44 _oldPieDataTable.addColumn('string', 'Type'); 75 _oldPieDataTable.addColumn('string', 'Type');
45 _oldPieDataTable.addColumn('number', 'Size'); 76 _oldPieDataTable.addColumn('number', 'Size');
77
78 // Create class table model.
46 var columns = [ 79 var columns = [
47 new SortedTableColumn('Class'), 80 new SortedTableColumn('Class'),
48 new SortedTableColumn.withFormatter('Accumulator Size (New)', 81 new SortedTableColumn(''),
koda 2014/06/17 21:04:06 Why this empty column?
Cutch 2014/06/18 14:35:03 Spacer column, added comment.
82 new SortedTableColumn.withFormatter('Accumulated Size (New)',
49 Utils.formatSize), 83 Utils.formatSize),
50 new SortedTableColumn.withFormatter('Accumulator (New)', 84 new SortedTableColumn.withFormatter('Accumulated Instances',
51 Utils.formatCommaSeparated), 85 Utils.formatCommaSeparated),
52 new SortedTableColumn.withFormatter('Current Size (New)', 86 new SortedTableColumn.withFormatter('Current Size',
53 Utils.formatSize), 87 Utils.formatSize),
54 new SortedTableColumn.withFormatter('Current (New)', 88 new SortedTableColumn.withFormatter('Current Instances',
55 Utils.formatCommaSeparated), 89 Utils.formatCommaSeparated),
90 new SortedTableColumn(''),
56 new SortedTableColumn.withFormatter('Accumulator Size (Old)', 91 new SortedTableColumn.withFormatter('Accumulator Size (Old)',
57 Utils.formatSize), 92 Utils.formatSize),
58 new SortedTableColumn.withFormatter('Accumulator (Old)', 93 new SortedTableColumn.withFormatter('Accumulator Instances',
59 Utils.formatCommaSeparated), 94 Utils.formatCommaSeparated),
60 new SortedTableColumn.withFormatter('Current Size (Old)', 95 new SortedTableColumn.withFormatter('Current Size',
61 Utils.formatSize), 96 Utils.formatSize),
62 new SortedTableColumn.withFormatter('Current (Old)', 97 new SortedTableColumn.withFormatter('Current Instances',
63 Utils.formatCommaSeparated) 98 Utils.formatCommaSeparated)
64 ]; 99 ];
65 classTable = new SortedTable(columns); 100 classTable = new ClassSortedTable(columns);
66 classTable.sortColumnIndex = 1; 101 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.
67 } 102 }
68 103
69 void enteredView() { 104 @override
70 super.enteredView(); 105 void attached() {
106 super.attached();
107 // Grab the pie chart divs.
71 _newPieChart = new Chart('PieChart', 108 _newPieChart = new Chart('PieChart',
72 shadowRoot.querySelector('#newPieChart')); 109 shadowRoot.querySelector('#newPieChart'));
73 _newPieChart.options['title'] = 'New Space';
74 _oldPieChart = new Chart('PieChart', 110 _oldPieChart = new Chart('PieChart',
75 shadowRoot.querySelector('#oldPieChart')); 111 shadowRoot.querySelector('#oldPieChart'));
76 _oldPieChart.options['title'] = 'Old Space'; 112 _classTableBody = shadowRoot.querySelector('#classTableBody');
77 _draw();
78 } 113 }
79 114
80 void _updateChartData() { 115 void _updatePieCharts() {
81 if ((profile == null) || (profile['members'] is! List) || 116 assert(profile != null);
82 (profile['members'].length == 0)) {
83 return;
84 }
85 assert(classTable != null);
86 classTable.clearRows();
87 for (ServiceMap cls in profile['members']) {
88 if (_classHasNoAllocations(cls)) {
89 // If a class has no allocations, don't display it.
90 continue;
91 }
92 var row = [cls['class'],
93 _combinedTableColumnValue(cls, 1),
94 _combinedTableColumnValue(cls, 2),
95 _combinedTableColumnValue(cls, 3),
96 _combinedTableColumnValue(cls, 4),
97 _combinedTableColumnValue(cls, 5),
98 _combinedTableColumnValue(cls, 6),
99 _combinedTableColumnValue(cls, 7),
100 _combinedTableColumnValue(cls, 8)];
101 classTable.addRow(new SortedTableRow(row));
102 }
103 classTable.sort();
104 _newPieDataTable.clearRows(); 117 _newPieDataTable.clearRows();
105 var heap = profile['heaps']['new']; 118 var heap = profile['heaps']['new'];
106 _newPieDataTable.addRow(['Used', heap['used']]); 119 _newPieDataTable.addRow(['Used', heap['used']]);
107 _newPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]); 120 _newPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]);
108 _newPieDataTable.addRow(['External', heap['external']]); 121 _newPieDataTable.addRow(['External', heap['external']]);
109 _oldPieDataTable.clearRows(); 122 _oldPieDataTable.clearRows();
110 heap = profile['heaps']['old']; 123 heap = profile['heaps']['old'];
111 _oldPieDataTable.addRow(['Used', heap['used']]); 124 _oldPieDataTable.addRow(['Used', heap['used']]);
112 _oldPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]); 125 _oldPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]);
113 _oldPieDataTable.addRow(['External', heap['external']]); 126 _oldPieDataTable.addRow(['External', heap['external']]);
114 _draw();
115 } 127 }
116 128
117 void _draw() { 129 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.
118 if (_newPieChart == null) { 130 cls.accumulatedNewSpace.instances = newSpace[ACCUMULATED];
119 return; 131 cls.accumulatedNewSpace.bytes = newSpace[ACCUMULATED_SIZE];
132 cls.currentNewSpace.instances =
133 newSpace[LIVE_AFTER_GC] + newSpace[ALLOCATED_SINCE_GC];
134 cls.currentNewSpace.bytes =
135 newSpace[LIVE_AFTER_GC_SIZE] + newSpace[ALLOCATED_SINCE_GC_SIZE];
136
137 cls.accumulatedOldSpace.instances = oldSpace[ACCUMULATED];
138 cls.accumulatedOldSpace.bytes = oldSpace[ACCUMULATED_SIZE];
139 cls.currentOldSpace.instances =
140 oldSpace[LIVE_AFTER_GC] + oldSpace[ALLOCATED_SINCE_GC];
141 cls.currentOldSpace.bytes =
142 oldSpace[LIVE_AFTER_GC_SIZE] + oldSpace[ALLOCATED_SINCE_GC_SIZE];
143 }
144
145 void _updateClasses() {
146 for (ServiceMap clsAllocations in profile['members']) {
147 Class cls = clsAllocations['class'];
148 if (cls == null) {
149 continue;
150 }
151 _updateClassStats(cls, clsAllocations['new'], clsAllocations['old']);
120 } 152 }
153 }
154
155 void _updateClassTable() {
156 classTable.clearRows();
157 for (ServiceMap clsAllocations in profile['members']) {
158 Class cls = clsAllocations['class'];
159 if (cls == null) {
160 continue;
161 }
162 if (cls.hasNoAllocations) {
163 // If a class has no allocations, don't display it.
164 continue;
165 }
166 var row = [cls,
167 '',
168 cls.accumulatedNewSpace.bytes,
169 cls.accumulatedNewSpace.instances,
170 cls.currentNewSpace.bytes,
171 cls.currentNewSpace.instances,
172 '',
173 cls.accumulatedOldSpace.bytes,
174 cls.accumulatedOldSpace.instances,
175 cls.currentOldSpace.bytes,
176 cls.currentOldSpace.instances];
177 classTable.addRow(new SortedTableRow(row));
178 }
179 classTable.sort();
180 }
181
182 void _updateClassTableInDom() {
183 assert(_classTableBody != null);
184 _classTableBody.children.clear();
185 for (var rowIndex in classTable.sortedRows) {
186 var row = classTable.rows[rowIndex];
187 var tr = new TableRowElement();
188
189 // Add class ref.
190 var cell = tr.insertCell(-1);
191 ClassRefElement classRef = new Element.tag('class-ref');
192 classRef.ref = row.values[0];
193 cell.children.add(classRef);
194
195 // Add spacer.
196 cell = tr.insertCell(-1);
197 cell.classes.add('left-border-spacer');
198
199 // Add new space.
200 cell = tr.insertCell(-1);
201 cell.title = row.values[2].toString();
202 cell.innerHtml = classTable.getFormattedValue(rowIndex, 2);
203 cell = tr.insertCell(-1);
204 cell.title = row.values[3].toString();
205 cell.innerHtml = classTable.getFormattedValue(rowIndex, 3);
206 cell = tr.insertCell(-1);
207 cell.title = row.values[4].toString();
208 cell.innerHtml = classTable.getFormattedValue(rowIndex, 4);
209 cell = tr.insertCell(-1);
210 cell.title = row.values[5].toString();
211 cell.innerHtml = classTable.getFormattedValue(rowIndex, 5);
212
213 // Add spacer.
214 cell = tr.insertCell(-1);
215 cell.classes.add('left-border-spacer');
216
217 // Add old space.
218 cell = tr.insertCell(-1);
219 cell.title = row.values[7].toString();
220 cell.innerHtml = classTable.getFormattedValue(rowIndex, 7);
221 cell = tr.insertCell(-1);
222 cell.title = row.values[8].toString();
223 cell.innerHtml = classTable.getFormattedValue(rowIndex, 8);
224 cell = tr.insertCell(-1);
225 cell.title = row.values[9].toString();
226 cell.innerHtml = classTable.getFormattedValue(rowIndex, 9);
227 cell = tr.insertCell(-1);
228 cell.title = row.values[10].toString();
229 cell.innerHtml = classTable.getFormattedValue(rowIndex, 10);
230
231 // Add row to table.
232 _classTableBody.children.add(tr);
233 }
234 }
235
236 void _drawCharts() {
121 _newPieChart.draw(_newPieDataTable); 237 _newPieChart.draw(_newPieDataTable);
122 _oldPieChart.draw(_oldPieDataTable); 238 _oldPieChart.draw(_oldPieDataTable);
123 } 239 }
124 240
125 @observable void changeSort(Event e, var detail, Element target) { 241 @observable void changeSort(Event e, var detail, Element target) {
126 if (target is TableCellElement) { 242 if (target is TableCellElement) {
127 if (classTable.sortColumnIndex != target.cellIndex) { 243 if (classTable.sortColumnIndex != target.cellIndex) {
128 classTable.sortColumnIndex = target.cellIndex; 244 classTable.sortColumnIndex = target.cellIndex;
129 classTable.sort(); 245 classTable.sortDescending = true;
246 } else {
247 classTable.sortDescending = !classTable.sortDescending;
130 } 248 }
249 classTable.sort();
250 Stopwatch sw = new Stopwatch()..start();
251 _updateClassTableInDom();
252 _traceMillis(sw, 'Inserting class table into DOM');
131 } 253 }
132 } 254 }
133 255
134 bool _classHasNoAllocations(Map v) {
135 var newSpace = v['new'];
136 var oldSpace = v['old'];
137 for (var allocation in newSpace) {
138 if (allocation != 0) {
139 return false;
140 }
141 }
142 for (var allocation in oldSpace) {
143 if (allocation != 0) {
144 return false;
145 }
146 }
147 return true;
148 }
149
150 dynamic _combinedTableColumnValue(Map v, int index) {
151 assert(index >= 0);
152 assert(index < 9);
153 switch (index) {
154 case 0:
155 return v['class']['user_name'];
156 case 1:
157 return v['new'][ACCUMULATED_SIZE];
158 case 2:
159 return v['new'][ACCUMULATED];
160 case 3:
161 return v['new'][LIVE_AFTER_GC_SIZE] +
162 v['new'][ALLOCATED_SINCE_GC_SIZE];
163 case 4:
164 return v['new'][LIVE_AFTER_GC] +
165 v['new'][ALLOCATED_SINCE_GC];
166 case 5:
167 return v['old'][ACCUMULATED_SIZE];
168 case 6:
169 return v['old'][ACCUMULATED];
170 case 7:
171 return v['old'][LIVE_AFTER_GC_SIZE] +
172 v['old'][ALLOCATED_SINCE_GC_SIZE];
173 case 8:
174 return v['old'][LIVE_AFTER_GC] +
175 v['old'][ALLOCATED_SINCE_GC];
176 }
177 throw new FallThroughError();
178 }
179
180 void refresh(var done) { 256 void refresh(var done) {
181 if (profile == null) { 257 if (profile == null) {
182 return; 258 return;
183 } 259 }
184 var isolate = profile.isolate; 260 var isolate = profile.isolate;
185 isolate.get('/allocationprofile').then((ServiceMap response) { 261 isolate.get('/allocationprofile').then(_update).whenComplete(done);
186 assert(response['type'] == 'AllocationProfile');
187 profile = response;
188 }).catchError((e, st) {
189 Logger.root.info('$e $st');
190 }).whenComplete(done);
191 } 262 }
192 263
193 void refreshGC(var done) { 264 void refreshGC(var done) {
194 if (profile == null) { 265 if (profile == null) {
195 return; 266 return;
196 }
197 var isolate = profile.isolate;
198 isolate.get('/allocationprofile?gc=full').then((ServiceMap response) {
199 assert(response['type'] == 'AllocationProfile');
200 profile = response;
201 }).catchError((e, st) {
202 Logger.root.info('$e $st');
203 }).whenComplete(done);
204 } 267 }
268 var isolate = profile.isolate;
269 isolate.get('/allocationprofile?gc=full').then(_update).whenComplete(done);
270 }
205 271
206 void resetAccumulator(var done) { 272 void resetAccumulator(var done) {
207 if (profile == null) { 273 if (profile == null) {
208 return; 274 return;
209 } 275 }
210 var isolate = profile.isolate; 276 var isolate = profile.isolate;
211 isolate.get('/allocationprofile?reset=true').then((ServiceMap response) { 277 isolate.get('/allocationprofile?reset=true').then(_update).
212 assert(response['type'] == 'AllocationProfile'); 278 whenComplete(done);
213 profile = response; 279 }
214 }).catchError((e, st) { 280
215 Logger.root.info('$e $st'); 281 void _update(ServiceMap newProfile) {
216 }).whenComplete(done); 282 profile = newProfile;
217 } 283 }
218 284
219 void profileChanged(oldValue) { 285 void profileChanged(oldValue) {
220 try { 286 if (profile == null) {
221 _updateChartData(); 287 return;
222 } catch (e, st) {
223 Logger.root.info('$e $st');
224 } 288 }
225 notifyPropertyChange(#formattedAverage, [], formattedAverage); 289 Stopwatch sw = new Stopwatch()..start();
226 notifyPropertyChange(#formattedTotalCollectionTime, [], 290 isolate = profile.isolate;
227 formattedTotalCollectionTime); 291 isolate.updateHeapsFromMap(profile['heaps']);
228 notifyPropertyChange(#formattedCollections, [], formattedCollections); 292 var millis = int.parse(profile['dateLastAccumulatorReset']);
293 if (millis != 0) {
294 lastAccumulatorReset =
295 new DateTime.fromMillisecondsSinceEpoch(millis).toString();
296 }
297 millis = int.parse(profile['dateLastGC']);
298 if (millis != 0) {
299 lastForcedGC =
300 new DateTime.fromMillisecondsSinceEpoch(millis).toString();
301 }
302 _trace(sw, 'Updating heaps');
303 _updatePieCharts();
304 _trace(sw, 'Updating pie charts');
305 _updateClasses();
306 _trace(sw, 'Updating classes');
307 _updateClassTable();
308 _trace(sw, 'Updating class table');
309 _updateClassTableInDom();
310 _traceMillis(sw, 'Inserting class table into DOM');
311 _drawCharts();
312 _traceMillis(sw, 'Drawing pie charts');
313 notifyPropertyChange(#formattedAverage, 0, 1);
314 notifyPropertyChange(#formattedTotalCollectionTime, 0, 1);
315 notifyPropertyChange(#formattedCollections, 0, 1);
229 } 316 }
230 317
231 @observable String formattedAverage(bool newSpace) { 318 @observable String formattedAverage(bool newSpace) {
232 if (profile == null) { 319 if (profile == null) {
233 return ''; 320 return '';
234 } 321 }
235 String space = newSpace ? 'new' : 'old'; 322 String space = newSpace ? 'new' : 'old';
236 Map heap = profile['heaps'][space]; 323 Map heap = profile['heaps'][space];
237 var r = ((heap['time'] * 1000.0) / heap['collections']).toStringAsFixed(2); 324 var r = ((heap['time'] * 1000.0) / heap['collections']).toStringAsFixed(2);
238 return '$r ms'; 325 return '$r ms';
(...skipping 10 matching lines...) Expand all
249 336
250 @observable String formattedTotalCollectionTime(bool newSpace) { 337 @observable String formattedTotalCollectionTime(bool newSpace) {
251 if (profile == null) { 338 if (profile == null) {
252 return ''; 339 return '';
253 } 340 }
254 String space = newSpace ? 'new' : 'old'; 341 String space = newSpace ? 'new' : 'old';
255 Map heap = profile['heaps'][space]; 342 Map heap = profile['heaps'][space];
256 return '${Utils.formatSeconds(heap['time'])} secs'; 343 return '${Utils.formatSeconds(heap['time'])} secs';
257 } 344 }
258 } 345 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698