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