OLD | NEW |
---|---|
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 } |
OLD | NEW |