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

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 @observable String lastServiceGC = '---';
18 static const ALLOCATED_BEFORE_GC = 0; 32 @observable String lastAccumulatorReset = '---';
19 static const ALLOCATED_BEFORE_GC_SIZE = 1;
20 static const LIVE_AFTER_GC = 2;
21 static const LIVE_AFTER_GC_SIZE = 3;
22 static const ALLOCATED_SINCE_GC = 4;
23 static const ALLOCATED_SINCE_GC_SIZE = 5;
24 static const ACCUMULATED = 6;
25 static const ACCUMULATED_SIZE = 7;
26 33
27 // Pie chart of new space usage. 34 // Pie chart of new space usage.
28 var _newPieDataTable; 35 var _newPieDataTable;
29 var _newPieChart; 36 var _newPieChart;
30 37
31 // Pie chart of old space usage. 38 // Pie chart of old space usage.
32 var _oldPieDataTable; 39 var _oldPieDataTable;
33 var _oldPieChart; 40 var _oldPieChart;
34 41
35 @observable SortedTable classTable; 42 @observable ClassSortedTable classTable;
43 var _classTableBody;
36 44
37 @published ServiceMap profile; 45 @published ServiceMap profile;
38 46
47 @observable Isolate isolate;
48
49 void _trace(Stopwatch sw, String label) {
50 print('$label took ${sw.elapsedMicroseconds} us.');
51 sw.reset();
52 }
53
54 void _traceMillis(Stopwatch sw, String label) {
55 print('$label took ${sw.elapsedMilliseconds} ms.');
56 sw.reset();
57 }
58
39 HeapProfileElement.created() : super.created() { 59 HeapProfileElement.created() : super.created() {
60 // Create pie chart models.
40 _newPieDataTable = new DataTable(); 61 _newPieDataTable = new DataTable();
41 _newPieDataTable.addColumn('string', 'Type'); 62 _newPieDataTable.addColumn('string', 'Type');
42 _newPieDataTable.addColumn('number', 'Size'); 63 _newPieDataTable.addColumn('number', 'Size');
43 _oldPieDataTable = new DataTable(); 64 _oldPieDataTable = new DataTable();
44 _oldPieDataTable.addColumn('string', 'Type'); 65 _oldPieDataTable.addColumn('string', 'Type');
45 _oldPieDataTable.addColumn('number', 'Size'); 66 _oldPieDataTable.addColumn('number', 'Size');
67
68 // Create class table model.
46 var columns = [ 69 var columns = [
47 new SortedTableColumn('Class'), 70 new SortedTableColumn('Class'),
48 new SortedTableColumn.withFormatter('Accumulator Size (New)', 71 new SortedTableColumn(''), // Spacer column.
49 Utils.formatSize), 72 new SortedTableColumn.withFormatter('Accumulated Size (New)',
50 new SortedTableColumn.withFormatter('Accumulator (New)', 73 Utils.formatSize),
74 new SortedTableColumn.withFormatter('Accumulated Instances',
51 Utils.formatCommaSeparated), 75 Utils.formatCommaSeparated),
52 new SortedTableColumn.withFormatter('Current Size (New)', 76 new SortedTableColumn.withFormatter('Current Size',
53 Utils.formatSize), 77 Utils.formatSize),
54 new SortedTableColumn.withFormatter('Current (New)', 78 new SortedTableColumn.withFormatter('Current Instances',
55 Utils.formatCommaSeparated), 79 Utils.formatCommaSeparated),
80 new SortedTableColumn(''), // Spacer column.
56 new SortedTableColumn.withFormatter('Accumulator Size (Old)', 81 new SortedTableColumn.withFormatter('Accumulator Size (Old)',
57 Utils.formatSize), 82 Utils.formatSize),
58 new SortedTableColumn.withFormatter('Accumulator (Old)', 83 new SortedTableColumn.withFormatter('Accumulator Instances',
59 Utils.formatCommaSeparated), 84 Utils.formatCommaSeparated),
60 new SortedTableColumn.withFormatter('Current Size (Old)', 85 new SortedTableColumn.withFormatter('Current Size',
61 Utils.formatSize), 86 Utils.formatSize),
62 new SortedTableColumn.withFormatter('Current (Old)', 87 new SortedTableColumn.withFormatter('Current Instances',
63 Utils.formatCommaSeparated) 88 Utils.formatCommaSeparated)
64 ]; 89 ];
65 classTable = new SortedTable(columns); 90 classTable = new ClassSortedTable(columns);
66 classTable.sortColumnIndex = 1; 91 // By default, start with accumulated new space bytes.
67 } 92 classTable.sortColumnIndex = 2;
68 93 }
69 void enteredView() { 94
70 super.enteredView(); 95 @override
96 void attached() {
97 super.attached();
98 // Grab the pie chart divs.
71 _newPieChart = new Chart('PieChart', 99 _newPieChart = new Chart('PieChart',
72 shadowRoot.querySelector('#newPieChart')); 100 shadowRoot.querySelector('#newPieChart'));
73 _newPieChart.options['title'] = 'New Space';
74 _oldPieChart = new Chart('PieChart', 101 _oldPieChart = new Chart('PieChart',
75 shadowRoot.querySelector('#oldPieChart')); 102 shadowRoot.querySelector('#oldPieChart'));
76 _oldPieChart.options['title'] = 'Old Space'; 103 _classTableBody = shadowRoot.querySelector('#classTableBody');
77 _draw(); 104 }
78 } 105
79 106 void _updatePieCharts() {
80 void _updateChartData() { 107 assert(profile != null);
81 if ((profile == null) || (profile['members'] is! List) || 108 _newPieDataTable.clearRows();
82 (profile['members'].length == 0)) { 109 var isolate = profile.isolate;
83 return; 110 _newPieDataTable.addRow(['Used', isolate.newSpace.used]);
84 } 111 _newPieDataTable.addRow(['Free',
85 assert(classTable != null); 112 isolate.newSpace.capacity - isolate.newSpace.used]);
113 _newPieDataTable.addRow(['External', isolate.newSpace.external]);
114 _oldPieDataTable.clearRows();
115 _oldPieDataTable.addRow(['Used', isolate.oldSpace.used]);
116 _oldPieDataTable.addRow(['Free',
117 isolate.oldSpace.capacity - isolate.oldSpace.used]);
118 _oldPieDataTable.addRow(['External', isolate.oldSpace.external]);
119 }
120
121 void _updateClasses() {
122 for (ServiceMap clsAllocations in profile['members']) {
123 Class cls = clsAllocations['class'];
124 if (cls == null) {
125 continue;
126 }
127 cls.newSpace.update(clsAllocations['new']);
128 cls.oldSpace.update(clsAllocations['old']);
129 }
130 }
131
132 void _updateClassTable() {
86 classTable.clearRows(); 133 classTable.clearRows();
87 for (ServiceMap cls in profile['members']) { 134 for (ServiceMap clsAllocations in profile['members']) {
88 if (_classHasNoAllocations(cls)) { 135 Class cls = clsAllocations['class'];
136 if (cls == null) {
137 continue;
138 }
139 if (cls.hasNoAllocations) {
89 // If a class has no allocations, don't display it. 140 // If a class has no allocations, don't display it.
90 continue; 141 continue;
91 } 142 }
92 var row = [cls['class'], 143 var row = [cls,
93 _combinedTableColumnValue(cls, 1), 144 '', // Spacer column.
94 _combinedTableColumnValue(cls, 2), 145 cls.newSpace.accumulated.bytes,
95 _combinedTableColumnValue(cls, 3), 146 cls.newSpace.accumulated.instances,
96 _combinedTableColumnValue(cls, 4), 147 cls.newSpace.current.bytes,
97 _combinedTableColumnValue(cls, 5), 148 cls.newSpace.current.instances,
98 _combinedTableColumnValue(cls, 6), 149 '', // Spacer column.
99 _combinedTableColumnValue(cls, 7), 150 cls.oldSpace.accumulated.bytes,
100 _combinedTableColumnValue(cls, 8)]; 151 cls.oldSpace.accumulated.instances,
152 cls.oldSpace.current.bytes,
153 cls.oldSpace.current.instances];
101 classTable.addRow(new SortedTableRow(row)); 154 classTable.addRow(new SortedTableRow(row));
102 } 155 }
103 classTable.sort(); 156 classTable.sort();
104 _newPieDataTable.clearRows(); 157 }
105 var heap = profile['heaps']['new']; 158
106 _newPieDataTable.addRow(['Used', heap['used']]); 159 void _addClassTableDomRow() {
107 _newPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]); 160 assert(_classTableBody != null);
108 _newPieDataTable.addRow(['External', heap['external']]); 161 var tr = new TableRowElement();
109 _oldPieDataTable.clearRows(); 162
110 heap = profile['heaps']['old']; 163 // Add class ref.
111 _oldPieDataTable.addRow(['Used', heap['used']]); 164 var cell = tr.insertCell(-1);
112 _oldPieDataTable.addRow(['Free', heap['capacity'] - heap['used']]); 165 ClassRefElement classRef = new Element.tag('class-ref');
113 _oldPieDataTable.addRow(['External', heap['external']]); 166 cell.children.add(classRef);
114 _draw(); 167
115 } 168 // Add spacer.
116 169 cell = tr.insertCell(-1);
117 void _draw() { 170 cell.classes.add('left-border-spacer');
118 if (_newPieChart == null) { 171
119 return; 172 // Add new space.
120 } 173 cell = tr.insertCell(-1);
174 cell = tr.insertCell(-1);
175 cell = tr.insertCell(-1);
176 cell = tr.insertCell(-1);
177
178 // Add spacer.
179 cell = tr.insertCell(-1);
180 cell.classes.add('left-border-spacer');
181
182 // Add old space.
183 cell = tr.insertCell(-1);
184 cell = tr.insertCell(-1);
185 cell = tr.insertCell(-1);
186 cell = tr.insertCell(-1);
187
188 // Add row to table.
189 _classTableBody.children.add(tr);
190 }
191
192 void _fillClassTableDomRow(TableRowElement tr, int rowIndex) {
193 var row = classTable.rows[rowIndex];
194 // Add class ref.
195 ClassRefElement classRef = tr.children[0].children[0];
196 classRef.ref = row.values[0];
197
198 var cell = tr.children[2];
199 cell.title = row.values[2].toString();
200 cell.text = classTable.getFormattedValue(rowIndex, 2);
201 cell = tr.children[3];
202 cell.title = row.values[3].toString();
203 cell.text = classTable.getFormattedValue(rowIndex, 3);
204 cell = tr.children[4];
205 cell.title = row.values[4].toString();
206 cell.text = classTable.getFormattedValue(rowIndex, 4);
207 cell = tr.children[5];
208 cell.title = row.values[5].toString();
209 cell.text = classTable.getFormattedValue(rowIndex, 5);
210
211 cell = tr.children[7];
212 cell.title = row.values[7].toString();
213 cell.text = classTable.getFormattedValue(rowIndex, 7);
214 cell = tr.children[8];
215 cell.title = row.values[8].toString();
216 cell.text = classTable.getFormattedValue(rowIndex, 8);
217
218 cell = tr.children[9];
219 cell.title = row.values[9].toString();
220 cell.text = classTable.getFormattedValue(rowIndex, 9);
221
222 cell = tr.children[10];
223 cell.title = row.values[10].toString();
224 cell.text = classTable.getFormattedValue(rowIndex, 10);
koda 2014/06/18 17:04:02 Consider having a constant SPACER_COLUMNS = [1, 6]
Cutch 2014/06/18 17:13:03 Done.
225 }
226
227 void _updateClassTableInDom() {
228 assert(_classTableBody != null);
229 // Resize DOM table.
230 if (_classTableBody.children.length > classTable.sortedRows.length) {
231 // Shrink the table.
232 var deadRows =
233 _classTableBody.children.length - classTable.sortedRows.length;
234 for (var i = 0; i < deadRows; i++) {
235 _classTableBody.children.removeLast();
236 }
237 } else if (_classTableBody.children.length < classTable.sortedRows.length) {
238 // Grow table.
239 var newRows =
240 classTable.sortedRows.length - _classTableBody.children.length;
241 for (var i = 0; i < newRows; i++) {
242 _addClassTableDomRow();
243 }
244 }
245 assert(_classTableBody.children.length == classTable.sortedRows.length);
246 // Fill table.
247 for (var i = 0; i < classTable.sortedRows.length; i++) {
248 var rowIndex = classTable.sortedRows[i];
249 var tr = _classTableBody.children[i];
250 _fillClassTableDomRow(tr, rowIndex);
251 }
252 }
253
254 void _drawCharts() {
121 _newPieChart.draw(_newPieDataTable); 255 _newPieChart.draw(_newPieDataTable);
122 _oldPieChart.draw(_oldPieDataTable); 256 _oldPieChart.draw(_oldPieDataTable);
123 } 257 }
124 258
125 @observable void changeSort(Event e, var detail, Element target) { 259 @observable void changeSort(Event e, var detail, Element target) {
126 if (target is TableCellElement) { 260 if (target is TableCellElement) {
127 if (classTable.sortColumnIndex != target.cellIndex) { 261 if (classTable.sortColumnIndex != target.cellIndex) {
128 classTable.sortColumnIndex = target.cellIndex; 262 classTable.sortColumnIndex = target.cellIndex;
129 classTable.sort(); 263 classTable.sortDescending = true;
264 } else {
265 classTable.sortDescending = !classTable.sortDescending;
130 } 266 }
267 classTable.sort();
268 Stopwatch sw = new Stopwatch()..start();
269 _updateClassTableInDom();
270 _traceMillis(sw, 'Inserting class table into DOM');
131 } 271 }
132 } 272 }
133 273
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) { 274 void refresh(var done) {
181 if (profile == null) { 275 if (profile == null) {
182 return; 276 return;
183 } 277 }
184 var isolate = profile.isolate; 278 var isolate = profile.isolate;
185 isolate.get('/allocationprofile').then((ServiceMap response) { 279 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 } 280 }
192 281
193 void refreshGC(var done) { 282 void refreshGC(var done) {
194 if (profile == null) { 283 if (profile == null) {
195 return; 284 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 } 285 }
286 var isolate = profile.isolate;
287 isolate.get('/allocationprofile?gc=full').then(_update).whenComplete(done);
288 }
205 289
206 void resetAccumulator(var done) { 290 void resetAccumulator(var done) {
207 if (profile == null) { 291 if (profile == null) {
208 return; 292 return;
209 } 293 }
210 var isolate = profile.isolate; 294 var isolate = profile.isolate;
211 isolate.get('/allocationprofile?reset=true').then((ServiceMap response) { 295 isolate.get('/allocationprofile?reset=true').then(_update).
212 assert(response['type'] == 'AllocationProfile'); 296 whenComplete(done);
213 profile = response; 297 }
214 }).catchError((e, st) { 298
215 Logger.root.info('$e $st'); 299 void _update(ServiceMap newProfile) {
216 }).whenComplete(done); 300 profile = newProfile;
217 } 301 }
218 302
219 void profileChanged(oldValue) { 303 void profileChanged(oldValue) {
220 try { 304 if (profile == null) {
221 _updateChartData(); 305 return;
222 } catch (e, st) {
223 Logger.root.info('$e $st');
224 } 306 }
225 notifyPropertyChange(#formattedAverage, [], formattedAverage); 307 Stopwatch sw = new Stopwatch()..start();
226 notifyPropertyChange(#formattedTotalCollectionTime, [], 308 isolate = profile.isolate;
227 formattedTotalCollectionTime); 309 isolate.updateHeapsFromMap(profile['heaps']);
228 notifyPropertyChange(#formattedCollections, [], formattedCollections); 310 var millis = int.parse(profile['dateLastAccumulatorReset']);
311 if (millis != 0) {
312 lastAccumulatorReset =
313 new DateTime.fromMillisecondsSinceEpoch(millis).toString();
314 }
315 millis = int.parse(profile['dateLastServiceGC']);
316 if (millis != 0) {
317 lastServiceGC =
318 new DateTime.fromMillisecondsSinceEpoch(millis).toString();
319 }
320 _trace(sw, 'Updating heaps');
321 _updatePieCharts();
322 _trace(sw, 'Updating pie charts');
323 _updateClasses();
324 _trace(sw, 'Updating classes');
325 _updateClassTable();
326 _trace(sw, 'Updating class table');
327 _updateClassTableInDom();
328 _traceMillis(sw, 'Inserting class table into DOM');
329 _drawCharts();
330 _traceMillis(sw, 'Drawing pie charts');
331 notifyPropertyChange(#formattedAverage, 0, 1);
332 notifyPropertyChange(#formattedTotalCollectionTime, 0, 1);
333 notifyPropertyChange(#formattedCollections, 0, 1);
229 } 334 }
230 335
231 @observable String formattedAverage(bool newSpace) { 336 @observable String formattedAverage(bool newSpace) {
232 if (profile == null) { 337 if (profile == null) {
233 return ''; 338 return '';
234 } 339 }
235 String space = newSpace ? 'new' : 'old'; 340 var heap = newSpace ? profile.isolate.newSpace : profile.isolate.oldSpace;
236 Map heap = profile['heaps'][space]; 341 var avg = ((heap.totalCollectionTimeInSeconds * 1000.0) / heap.collections);
237 var r = ((heap['time'] * 1000.0) / heap['collections']).toStringAsFixed(2); 342 return '${avg.toStringAsFixed(2)} ms';
238 return '$r ms';
239 } 343 }
240 344
241 @observable String formattedCollections(bool newSpace) { 345 @observable String formattedCollections(bool newSpace) {
242 if (profile == null) { 346 if (profile == null) {
243 return ''; 347 return '';
244 } 348 }
245 String space = newSpace ? 'new' : 'old'; 349 var heap = newSpace ? profile.isolate.newSpace : profile.isolate.oldSpace;
246 Map heap = profile['heaps'][space]; 350 return heap.collections.toString();
247 return '${heap['collections']}';
248 } 351 }
249 352
250 @observable String formattedTotalCollectionTime(bool newSpace) { 353 @observable String formattedTotalCollectionTime(bool newSpace) {
251 if (profile == null) { 354 if (profile == null) {
252 return ''; 355 return '';
253 } 356 }
254 String space = newSpace ? 'new' : 'old'; 357 var heap = newSpace ? profile.isolate.newSpace : profile.isolate.oldSpace;
255 Map heap = profile['heaps'][space]; 358 return '${Utils.formatSeconds(heap.totalCollectionTimeInSeconds)} secs';
256 return '${Utils.formatSeconds(heap['time'])} secs';
257 } 359 }
258 } 360 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698