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 // 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 Loading... | |
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 } |
OLD | NEW |