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