OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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_snapshot_element; | 5 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
6 // for details. All rights reserved. Use of this source code is governed by a | |
7 // BSD-style license that can be found in the LICENSE file. | |
6 | 8 |
7 import 'dart:async'; | 9 import 'dart:async'; |
8 import 'dart:html'; | 10 import 'dart:html'; |
9 import 'class_ref_wrapper.dart'; | 11 import 'dart:math' as Math; |
10 import 'observatory_element.dart'; | 12 import 'package:observatory/models.dart' as M; |
11 import 'package:observatory/app.dart'; | 13 import 'package:observatory/src/elements/class_ref.dart'; |
12 import 'package:observatory/service.dart'; | 14 import 'package:observatory/src/elements/containers/virtual_tree.dart'; |
13 import 'package:observatory/elements.dart'; | 15 import 'package:observatory/src/elements/helpers/any_ref.dart'; |
14 import 'package:observatory/object_graph.dart'; | 16 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
15 import 'package:polymer/polymer.dart'; | 17 import 'package:observatory/src/elements/helpers/tag.dart'; |
16 import 'package:logging/logging.dart'; | 18 import 'package:observatory/src/elements/helpers/uris.dart'; |
17 | 19 import 'package:observatory/src/elements/nav/bar.dart'; |
18 class DominatorTreeRow extends TableTreeRow { | 20 import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
19 final ObjectVertex vertex; | 21 import 'package:observatory/src/elements/nav/menu.dart'; |
20 final HeapSnapshot snapshot; | 22 import 'package:observatory/src/elements/nav/notify.dart'; |
21 | 23 import 'package:observatory/src/elements/nav/refresh.dart'; |
22 var _domTreeChildren; | 24 import 'package:observatory/src/elements/nav/top_menu.dart'; |
23 get domTreeChildren { | 25 import 'package:observatory/src/elements/nav/vm_menu.dart'; |
24 if (_domTreeChildren == null) { | 26 import 'package:observatory/utils.dart'; |
25 _domTreeChildren = vertex.dominatorTreeChildren(); | 27 |
26 } | 28 enum HeapSnapshotTreeMode { |
27 return _domTreeChildren; | 29 dominatorTree, |
28 } | 30 groupByClass |
29 | 31 } |
30 DominatorTreeRow(TableTree tree, | 32 |
31 TableTreeRow parent, | 33 class HeapSnapshotElement extends HtmlElement implements Renderable { |
32 this.vertex, | 34 static const tag = const Tag<HeapSnapshotElement>('heap-snapshot', |
33 this.snapshot) | 35 dependencies: const [ |
34 : super(tree, parent) { | 36 ClassRefElement.tag, |
35 } | 37 NavBarElement.tag, |
36 | 38 NavTopMenuElement.tag, |
37 bool hasChildren() { | 39 NavVMMenuElement.tag, |
38 return domTreeChildren.length > 0; | 40 NavIsolateMenuElement.tag, |
41 NavMenuElement.tag, | |
42 NavRefreshElement.tag, | |
43 NavNotifyElement.tag, | |
44 VirtualTreeElement.tag, | |
45 ]); | |
46 | |
47 RenderingScheduler<HeapSnapshotElement> _r; | |
48 | |
49 Stream<RenderedEvent<HeapSnapshotElement>> get onRendered => _r.onRendered; | |
50 | |
51 M.VM _vm; | |
52 M.IsolateRef _isolate; | |
53 M.EventRepository _events; | |
54 M.NotificationRepository _notifications; | |
55 M.HeapSnapshotRepository _snapshots; | |
56 M.InstanceRepository _instances; | |
57 M.HeapSnapshot _snapshot; | |
58 Stream<M.HeapSnapshotLoadingProgressEvent> _progressStream; | |
59 M.HeapSnapshotLoadingProgress _progress; | |
60 HeapSnapshotTreeMode _mode = HeapSnapshotTreeMode.dominatorTree; | |
61 | |
62 | |
63 M.IsolateRef get isolate => _isolate; | |
64 M.NotificationRepository get notifications => _notifications; | |
65 M.HeapSnapshotRepository get profiles => _snapshots; | |
66 M.VMRef get vm => _vm; | |
67 | |
68 factory HeapSnapshotElement(M.VM vm, M.IsolateRef isolate, | |
69 M.EventRepository events, | |
70 M.NotificationRepository notifications, | |
71 M.HeapSnapshotRepository snapshots, | |
72 M.InstanceRepository instances, | |
73 {RenderingQueue queue}) { | |
74 assert(vm != null); | |
75 assert(isolate != null); | |
76 assert(events != null); | |
77 assert(notifications != null); | |
78 assert(snapshots != null); | |
79 assert(instances != null); | |
80 HeapSnapshotElement e = document.createElement(tag.name); | |
81 e._r = new RenderingScheduler(e, queue: queue); | |
82 e._vm = vm; | |
83 e._isolate = isolate; | |
84 e._events = events; | |
85 e._notifications = notifications; | |
86 e._snapshots = snapshots; | |
87 e._instances = instances; | |
88 return e; | |
89 } | |
90 | |
91 HeapSnapshotElement.created() : super.created(); | |
92 | |
93 @override | |
94 attached() { | |
95 super.attached(); | |
96 _r.enable(); | |
97 _refresh(); | |
98 } | |
99 | |
100 @override | |
101 detached() { | |
102 super.detached(); | |
103 _r.disable(notify: true); | |
104 children = []; | |
105 } | |
106 | |
107 void render() { | |
108 var content = [ | |
109 new NavBarElement(queue: _r.queue) | |
110 ..children = [ | |
111 new NavTopMenuElement(queue: _r.queue), | |
112 new NavVMMenuElement(_vm, _events, queue: _r.queue), | |
113 new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), | |
114 new NavMenuElement('heap snapshot', link: Uris.profiler(_isolate), | |
115 last: true, queue: _r.queue), | |
116 new NavRefreshElement(queue: _r.queue) | |
117 ..disabled = M.isHeapSnapshotProgressRunning(_progress?.status) | |
118 ..onRefresh.listen((e) { | |
119 _refresh(); | |
120 }), | |
121 new NavNotifyElement(_notifications, queue: _r.queue) | |
122 ], | |
123 ]; | |
124 if (_progress == null) { | |
125 children = content; | |
126 return; | |
127 } | |
128 switch (_progress.status) { | |
rmacnak
2016/08/23 16:57:09
Because computing the dominator tree can take quit
cbernaschina
2016/08/23 17:28:28
Done.
| |
129 case M.HeapSnapshotLoadingStatus.fetching : | |
130 content.addAll(_createStatusMessage('Fetching profile from VM...')); | |
rmacnak
2016/08/23 16:57:09
profile -> snapshot
cbernaschina
2016/08/23 17:28:28
Done.
| |
131 break; | |
132 case M.HeapSnapshotLoadingStatus.loading : | |
133 content.addAll(_createStatusMessage('Loading profile...', | |
134 progress: _progress.progress)); | |
135 break; | |
136 case M.HeapSnapshotLoadingStatus.disabled : | |
137 content.addAll(_createDisabledMessage()); | |
138 break; | |
139 case M.HeapSnapshotLoadingStatus.loaded: | |
140 content.addAll(_createReport()); | |
141 break; | |
142 } | |
143 children = content; | |
144 } | |
145 | |
146 Future _refresh() async { | |
147 _progress = null; | |
148 _progressStream = _snapshots.get(isolate); | |
149 _r.dirty(); | |
150 _progressStream.listen((_) => _r.dirty()); | |
151 _progress = (await _progressStream.first).progress; | |
152 _r.dirty(); | |
153 if (M.isHeapSnapshotProgressRunning(_progress.status)) { | |
154 _progress = (await _progressStream.last).progress; | |
155 _snapshot = _progress.snapshot; | |
156 _r.dirty(); | |
157 } | |
158 } | |
159 | |
160 static List<Element> _createStatusMessage(String message, | |
161 {double progress: 0.0}) { | |
162 return [ | |
163 new DivElement()..classes = ['content-centered-big'] | |
164 ..children = [ | |
165 new DivElement()..classes = ['statusBox', 'shadow', 'center'] | |
166 ..children = [ | |
167 new DivElement()..classes = ['statusMessage'] | |
168 ..text = message, | |
169 new DivElement()..style.background = '#0489c3' | |
170 ..style.width = '$progress%' | |
171 ..style.height = '15px' | |
172 ..style.borderRadius = '4px' | |
173 ] | |
174 ] | |
175 ]; | |
176 } | |
177 | |
178 static List<Element> _createDisabledMessage() { | |
rmacnak
2016/08/23 16:57:09
The profile flag has no effect on the heap snapsho
cbernaschina
2016/08/23 17:28:28
Done
| |
179 return [ | |
180 new DivElement()..classes = ['content-centered-big'] | |
181 ..children = [ | |
182 new DivElement()..classes = ['statusBox' 'shadow' 'center'] | |
183 ..children = [ | |
184 new DivElement() | |
185 ..children = [ | |
186 new HeadingElement.h1() | |
187 ..text = 'Profiling is disabled', | |
188 new BRElement(), | |
189 new DivElement() | |
190 ..innerHtml = 'Perhaps the <b>profile</b> ' | |
191 'flag has been disabled for this VM.', | |
192 new BRElement(), | |
193 new SpanElement()..text = 'See all', | |
194 new AnchorElement(href: Uris.flags())..text = 'vm flags' | |
195 ] | |
196 ] | |
197 ] | |
198 ]; | |
199 } | |
200 | |
201 VirtualTreeElement _tree; | |
202 | |
203 List<Element> _createReport() { | |
204 var report = [ | |
205 new DivElement()..classes = ['content-centered-big'] | |
206 ..children = [ | |
207 new DivElement()..classes = ['memberList'] | |
208 ..children = [ | |
209 new DivElement()..classes = ['memberItem'] | |
210 ..children = [ | |
211 new DivElement()..classes = ['memberName'] | |
212 ..text = 'Refreshed ', | |
213 new DivElement()..classes = ['memberName'] | |
214 ..text = Utils.formatDateTime(_snapshot.timestamp) | |
215 ], | |
216 new DivElement()..classes = ['memberItem'] | |
217 ..children = [ | |
218 new DivElement()..classes = ['memberName'] | |
219 ..text = 'Objects ', | |
220 new DivElement()..classes = ['memberName'] | |
221 ..text = '${_snapshot.objects}' | |
222 ], | |
223 new DivElement()..classes = ['memberItem'] | |
224 ..children = [ | |
225 new DivElement()..classes = ['memberName'] | |
226 ..text = 'References ', | |
227 new DivElement()..classes = ['memberName'] | |
228 ..text = '${_snapshot.references}' | |
229 ], | |
230 new DivElement()..classes = ['memberItem'] | |
231 ..children = [ | |
232 new DivElement()..classes = ['memberName'] | |
233 ..text = 'Size ', | |
234 new DivElement()..classes = ['memberName'] | |
235 ..text = Utils.formatSize(_snapshot.size) | |
236 ], | |
237 new DivElement()..classes = ['memberItem'] | |
238 ..children = [ | |
239 new DivElement()..classes = ['memberName'] | |
240 ..text = 'Analysis ', | |
241 new DivElement()..classes = ['memberName'] | |
242 ..children = _createModeSelect() | |
243 ] | |
244 ] | |
245 ], | |
246 ]; | |
247 switch (_mode) { | |
248 case HeapSnapshotTreeMode.dominatorTree: | |
249 _tree = new VirtualTreeElement(_createDominator, _updateDominator, | |
250 _getChildrenDominator, | |
251 items: _getChildrenDominator(_snapshot.dominatorTree), | |
252 queue: _r.queue); | |
253 _tree.expand(_snapshot.dominatorTree); | |
254 final text = 'In a heap dominator tree, an object X is a parent of ' | |
255 'object Y if every path from the root to Y goes through ' | |
256 'X. This allows you to find "choke points" that are ' | |
257 'holding onto a lot memory. If an object becomes garbage, ' | |
rmacnak
2016/08/23 16:57:09
a lot of memory
cbernaschina
2016/08/23 17:28:28
Done.
| |
258 'all its children in the dominator tree become garbage as ' | |
259 'well. The retained size of an object is the sum of the ' | |
260 'retained sizes of its children in the dominator tree ' | |
261 'plus its own shallow size, and is the amount of memory ' | |
262 'that would be freed if the object became garbage.'; | |
263 report.addAll([ | |
264 new DivElement()..classes = ['content-centered-big', 'explanation'] | |
265 ..text = text | |
266 ..title = text, | |
267 _tree | |
268 ]); | |
269 break; | |
270 case HeapSnapshotTreeMode.groupByClass: | |
271 final items = _snapshot.classReferences.toList(); | |
272 items.sort((a, b) => b.shallowSize - a.shallowSize); | |
273 _tree = new VirtualTreeElement(_createGroup, _updateGroup, | |
274 _getChildrenGroup, items: items, queue: _r.queue); | |
275 _tree.expand(_snapshot.dominatorTree); | |
276 report.add(_tree); | |
277 break; | |
278 default: | |
279 break; | |
280 } | |
281 return report; | |
282 } | |
283 | |
284 static Element _createDominator(toggle) { | |
285 return new DivElement() | |
286 ..classes = const ['tree-item'] | |
287 ..children = [ | |
288 new SpanElement()..classes = const ['size'] | |
289 ..title = 'retained size', | |
290 new SpanElement()..classes = const ['lines'], | |
291 new ButtonElement()..classes = const ['expander'] | |
292 ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), | |
293 new SpanElement()..classes = const ['percentage'] | |
294 ..title = 'percentage of heap being retained', | |
295 new SpanElement()..classes = const ['name'] | |
296 ]; | |
297 } | |
298 | |
299 static Element _createGroup(toggle) { | |
300 return new DivElement() | |
301 ..classes = const ['tree-item'] | |
302 ..children = [ | |
303 new SpanElement()..classes = const ['size'] | |
304 ..title = 'shallow size', | |
305 new SpanElement()..classes = const ['lines'], | |
306 new ButtonElement()..classes = const ['expander'] | |
307 ..onClick.listen((_) => toggle(autoToggleSingleChildNodes: true)), | |
308 new SpanElement()..classes = const ['count'] | |
309 ..title = 'shallow size', | |
310 new SpanElement()..classes = const ['name'] | |
311 ]; | |
39 } | 312 } |
40 | 313 |
41 static const int kMaxChildren = 100; | 314 static const int kMaxChildren = 100; |
42 static const int kMinRetainedSize = 4096; | 315 static const int kMinRetainedSize = 4096; |
43 | 316 |
44 void onShow() { | 317 static _getChildrenDominator(M.HeapSnapshotDominatorNode node) { |
45 super.onShow(); | 318 final list = node.children.toList(); |
46 if (children.length == 0) { | 319 list.sort((a, b) => b.retainedSize - a.retainedSize); |
47 domTreeChildren.sort((a, b) => b.retainedSize - a.retainedSize); | 320 return list.where((child) => child.retainedSize >= kMinRetainedSize) |
48 int includedChildren = 0; | 321 .take(kMaxChildren); |
49 for (var childVertex in domTreeChildren) { | 322 } |
50 if (childVertex.retainedSize >= kMinRetainedSize) { | 323 |
51 if (++includedChildren <= kMaxChildren) { | 324 static _getChildrenGroup(item) { |
52 var row = new DominatorTreeRow(tree, this, childVertex, snapshot); | 325 if (item is M.HeapSnapshotClassReferences) { |
53 children.add(row); | 326 if (item.inbounds.isNotEmpty || item.outbounds.isNotEmpty) { |
54 } | 327 return [item.inbounds, item.outbounds]; |
55 } | |
56 } | 328 } |
57 } | 329 } else if (item is Iterable) { |
58 | 330 return item.toList()..sort((a, b) => b.shallowSize - a.shallowSize); |
59 var firstColumn = flexColumns[0]; | 331 } |
60 firstColumn.style.justifyContent = 'flex-start'; | 332 return const []; |
61 firstColumn.style.position = 'relative'; | 333 } |
62 firstColumn.style.alignItems = 'center'; | 334 |
63 firstColumn.style.setProperty('overflow-x', 'hidden'); | 335 void _updateDominator(HtmlElement element, M.HeapSnapshotDominatorNode node, |
64 | 336 int depth) { |
65 var percentRetained = vertex.retainedSize / snapshot.graph.size; | 337 element.children[0].text = Utils.formatSize(node.shallowSize); |
rmacnak
2016/08/23 17:01:02
retainedSize
cbernaschina
2016/08/23 17:28:28
Done.
| |
66 var percentNode = new SpanElement(); | 338 _updateLines(element.children[1].children, depth); |
67 percentNode.text = Utils.formatPercentNormalized(percentRetained); | 339 if (_getChildrenDominator(node).isNotEmpty) { |
68 percentNode.style.minWidth = '5em'; | 340 element.children[2].text = _tree.isExpanded(node) ? 'â–¼' : 'â–º'; |
69 percentNode.style.textAlign = 'right'; | 341 } else { |
70 percentNode.title = "Percent of heap being retained"; | 342 element.children[2].text = ''; |
71 percentNode.style.display = 'inline-block'; | 343 } |
72 firstColumn.children.add(percentNode); | 344 element.children[3].text = Utils.formatPercentNormalized( |
73 | 345 node.retainedSize * 1.0 / _snapshot.size); |
74 var gap = new SpanElement(); | 346 final wrapper = new SpanElement()..classes = const ['name'] |
75 gap.style.minWidth = '1em'; | 347 ..text = 'Loading...'; |
76 gap.style.display = 'inline-block'; | 348 element.children[4] = wrapper; |
77 firstColumn.children.add(gap); | 349 node.object.then((object) { |
78 | 350 wrapper..text = '' |
79 AnyServiceRefElement objectRef = new Element.tag("any-service-ref"); | 351 ..children = [anyRef(_isolate, object, _instances, queue: _r.queue)]; |
80 snapshot.isolate.getObjectByAddress(vertex.address).then((obj) { | |
81 objectRef.ref = obj; | |
82 }); | 352 }); |
83 objectRef.style.alignSelf = 'center'; | 353 } |
84 firstColumn.children.add(objectRef); | 354 |
85 | 355 void _updateGroup(HtmlElement element, item, int depth) { |
86 var secondColumn = flexColumns[1]; | 356 _updateLines(element.children[1].children, depth); |
87 secondColumn.style.justifyContent = 'flex-end'; | 357 if (item is M.HeapSnapshotClassReferences) { |
88 secondColumn.style.position = 'relative'; | 358 element.children[0].text = Utils.formatSize(item.shallowSize); |
89 secondColumn.style.alignItems = 'center'; | 359 element.children[2].text = _tree.isExpanded(item) ? 'â–¼' : 'â–º'; |
90 secondColumn.style.paddingRight = '0.5em'; | 360 element.children[3].text = '${item.instances} instances of '; |
91 secondColumn.text = Utils.formatSize(vertex.retainedSize); | 361 element.children[4] = new ClassRefElement(_isolate, item.clazz, |
362 queue: _r.queue)..classes = const ['name']; | |
363 } else if (item is Iterable) { | |
364 element.children[0].text = ''; | |
365 if (item.isNotEmpty) { | |
366 element.children[2].text = _tree.isExpanded(item) ? 'â–¼' : 'â–º'; | |
367 } else { | |
368 element.children[2].text = ''; | |
369 } | |
370 element.children[3].text = ''; | |
371 if (item is Iterable<M.HeapSnapshotClassInbound>) { | |
372 element.children[4] = new SpanElement()..classes = const ['name'] | |
373 ..text = '${item.length} Ingoing references'; | |
374 } else { | |
375 element.children[4] = new SpanElement()..classes = const ['name'] | |
376 ..text = '${item.length} Outgoing references'; | |
377 } | |
378 } else { | |
379 element.children[0].text = ''; | |
380 element.children[2].text = ''; | |
381 element.children[3].text = ''; | |
382 element.children[4] = new SpanElement()..classes = const ['name']; | |
383 if (item is M.HeapSnapshotClassInbound){ | |
384 element.children[3].text = | |
385 '${item.count} references from instances of '; | |
386 element.children[4].children = [ | |
387 new ClassRefElement(_isolate, item.source, | |
388 queue: _r.queue) | |
389 ]; | |
390 } else if (item is M.HeapSnapshotClassOutbound){ | |
391 element.children[3]..text = '${item.count} references to instances of '; | |
392 element.children[4].children = [ | |
393 new ClassRefElement(_isolate, item.target, | |
394 queue: _r.queue) | |
395 ]; | |
396 } | |
397 } | |
398 } | |
399 | |
400 static _updateLines(List<Element> lines, int n) { | |
401 n = Math.max(0, n); | |
402 while (lines.length > n) { | |
403 lines.removeLast(); | |
404 } | |
405 while (lines.length < n) { | |
406 lines.add(new SpanElement()); | |
407 } | |
408 } | |
409 | |
410 static String modeToString(HeapSnapshotTreeMode mode) { | |
411 switch (mode) { | |
412 case HeapSnapshotTreeMode.dominatorTree: return 'Dominator tree'; | |
413 case HeapSnapshotTreeMode.groupByClass: return 'Group by class'; | |
414 } | |
415 throw new Exception('Unknown ProfileTreeMode'); | |
416 } | |
417 | |
418 List<Element> _createModeSelect() { | |
419 var s; | |
420 return [ | |
421 s = new SelectElement()..classes = ['analysis-select'] | |
422 ..value = modeToString(_mode) | |
423 ..children = HeapSnapshotTreeMode.values.map((mode) { | |
424 return new OptionElement(value: modeToString(mode), | |
425 selected: _mode == mode) | |
426 ..text = modeToString(mode); | |
427 }).toList(growable: false) | |
428 ..onChange.listen((_) { | |
429 _mode = HeapSnapshotTreeMode.values[s.selectedIndex]; | |
430 _r.dirty(); | |
431 }) | |
432 ]; | |
92 } | 433 } |
93 } | 434 } |
94 | |
95 | |
96 class MergedVerticesRow extends TableTreeRow { | |
97 final Isolate isolate; | |
98 final List<MergedVertex> mergedVertices; | |
99 | |
100 MergedVerticesRow(TableTree tree, | |
101 TableTreeRow parent, | |
102 this.isolate, | |
103 this.mergedVertices) | |
104 : super(tree, parent) { | |
105 } | |
106 | |
107 bool hasChildren() { | |
108 return mergedVertices.length > 0; | |
109 } | |
110 | |
111 void onShow() { | |
112 super.onShow(); | |
113 | |
114 if (children.length == 0) { | |
115 mergedVertices.sort((a, b) => b.shallowSize - a.shallowSize); | |
116 for (var mergedVertex in mergedVertices) { | |
117 if (mergedVertex.instances > 0) { | |
118 var row = new MergedVertexRow(tree, this, isolate, mergedVertex); | |
119 children.add(row); | |
120 } | |
121 } | |
122 } | |
123 } | |
124 } | |
125 | |
126 class MergedVertexRow extends TableTreeRow { | |
127 final Isolate isolate; | |
128 final MergedVertex vertex; | |
129 | |
130 MergedVertexRow(TableTree tree, | |
131 TableTreeRow parent, | |
132 this.isolate, | |
133 this.vertex) | |
134 : super(tree, parent) { | |
135 } | |
136 | |
137 bool hasChildren() { | |
138 return vertex.outgoingEdges.length > 0 || | |
139 vertex.incomingEdges.length > 0; | |
140 } | |
141 | |
142 void onShow() { | |
143 super.onShow(); | |
144 if (children.length == 0) { | |
145 children.add(new MergedEdgesRow(tree, this, isolate, vertex, true)); | |
146 children.add(new MergedEdgesRow(tree, this, isolate, vertex, false)); | |
147 } | |
148 | |
149 | |
150 var firstColumn = flexColumns[0]; | |
151 firstColumn.style.justifyContent = 'flex-start'; | |
152 firstColumn.style.position = 'relative'; | |
153 firstColumn.style.alignItems = 'center'; | |
154 | |
155 var percentNode = new SpanElement(); | |
156 percentNode.text = "${vertex.instances} instances of"; | |
157 percentNode.style.minWidth = '5em'; | |
158 percentNode.style.textAlign = 'right'; | |
159 firstColumn.children.add(percentNode); | |
160 | |
161 var gap = new SpanElement(); | |
162 gap.style.minWidth = '1em'; | |
163 gap.style.display = 'inline-block'; | |
164 firstColumn.children.add(gap); | |
165 | |
166 ClassRefElementWrapper classRef = new Element.tag("class-ref"); | |
167 classRef.ref = isolate.getClassByCid(vertex.cid); | |
168 classRef.style.alignSelf = 'center'; | |
169 firstColumn.children.add(classRef); | |
170 | |
171 var secondColumn = flexColumns[1]; | |
172 secondColumn.style.justifyContent = 'flex-end'; | |
173 secondColumn.style.position = 'relative'; | |
174 secondColumn.style.alignItems = 'center'; | |
175 secondColumn.style.paddingRight = '0.5em'; | |
176 secondColumn.text = Utils.formatSize(vertex.shallowSize); | |
177 } | |
178 } | |
179 | |
180 class MergedEdgesRow extends TableTreeRow { | |
181 final Isolate isolate; | |
182 final MergedVertex vertex; | |
183 final bool outgoing; | |
184 | |
185 MergedEdgesRow(TableTree tree, | |
186 TableTreeRow parent, | |
187 this.isolate, | |
188 this.vertex, | |
189 this.outgoing) | |
190 : super(tree, parent) { | |
191 } | |
192 | |
193 bool hasChildren() { | |
194 return outgoing | |
195 ? vertex.outgoingEdges.length > 0 | |
196 : vertex.incomingEdges.length > 0; | |
197 } | |
198 | |
199 void onShow() { | |
200 super.onShow(); | |
201 if (children.length == 0) { | |
202 if (outgoing) { | |
203 var outgoingEdges = vertex.outgoingEdges.values.toList(); | |
204 outgoingEdges.sort((a, b) => b.shallowSize - a.shallowSize); | |
205 for (var edge in outgoingEdges) { | |
206 if (edge.count > 0) { | |
207 var row = new MergedEdgeRow(tree, this, isolate, edge, true); | |
208 children.add(row); | |
209 } | |
210 } | |
211 } else { | |
212 vertex.incomingEdges.sort((a, b) => b.shallowSize - a.shallowSize); | |
213 for (var edge in vertex.incomingEdges) { | |
214 if (edge.count > 0) { | |
215 var row = new MergedEdgeRow(tree, this, isolate, edge, false); | |
216 children.add(row); | |
217 } | |
218 } | |
219 } | |
220 } | |
221 | |
222 var count = 0; | |
223 var shallowSize = 0; | |
224 var edges = outgoing ? vertex.outgoingEdges.values : vertex.incomingEdges; | |
225 for (var edge in edges) { | |
226 count += edge.count; | |
227 shallowSize += edge.shallowSize; | |
228 } | |
229 | |
230 var firstColumn = flexColumns[0]; | |
231 firstColumn.style.justifyContent = 'flex-start'; | |
232 firstColumn.style.position = 'relative'; | |
233 firstColumn.style.alignItems = 'center'; | |
234 | |
235 var countNode = new SpanElement(); | |
236 countNode.text = "$count"; | |
237 countNode.style.minWidth = '5em'; | |
238 countNode.style.textAlign = 'right'; | |
239 firstColumn.children.add(countNode); | |
240 | |
241 var gap = new SpanElement(); | |
242 gap.style.minWidth = '1em'; | |
243 gap.style.display = 'inline-block'; | |
244 firstColumn.children.add(gap); | |
245 | |
246 var labelNode = new SpanElement(); | |
247 labelNode.text = outgoing ? "Outgoing references" : "Incoming references"; | |
248 firstColumn.children.add(labelNode); | |
249 | |
250 var secondColumn = flexColumns[1]; | |
251 secondColumn.style.justifyContent = 'flex-end'; | |
252 secondColumn.style.position = 'relative'; | |
253 secondColumn.style.alignItems = 'center'; | |
254 secondColumn.style.paddingRight = '0.5em'; | |
255 secondColumn.text = Utils.formatSize(shallowSize); | |
256 } | |
257 } | |
258 | |
259 class MergedEdgeRow extends TableTreeRow { | |
260 final Isolate isolate; | |
261 final MergedEdge edge; | |
262 final bool outgoing; | |
263 | |
264 MergedEdgeRow(TableTree tree, | |
265 TableTreeRow parent, | |
266 this.isolate, | |
267 this.edge, | |
268 this.outgoing) | |
269 : super(tree, parent) { | |
270 } | |
271 | |
272 bool hasChildren() => false; | |
273 | |
274 void onShow() { | |
275 super.onShow(); | |
276 | |
277 var firstColumn = flexColumns[0]; | |
278 firstColumn.style.justifyContent = 'flex-start'; | |
279 firstColumn.style.position = 'relative'; | |
280 firstColumn.style.alignItems = 'center'; | |
281 | |
282 var percentNode = new SpanElement(); | |
283 var preposition = outgoing ? "to" : "from"; | |
284 percentNode.text = "${edge.count} references $preposition instances of"; | |
285 percentNode.style.minWidth = '5em'; | |
286 percentNode.style.textAlign = 'right'; | |
287 firstColumn.children.add(percentNode); | |
288 | |
289 var gap = new SpanElement(); | |
290 gap.style.minWidth = '1em'; | |
291 gap.style.display = 'inline-block'; | |
292 firstColumn.children.add(gap); | |
293 | |
294 MergedVertex v = outgoing ? edge.target : edge.source; | |
295 if (v.cid == 0) { | |
296 var rootName = new SpanElement(); | |
297 rootName.text = '<root>'; | |
298 firstColumn.children.add(rootName); | |
299 } else { | |
300 ClassRefElementWrapper classRef = new Element.tag("class-ref"); | |
301 classRef.ref = isolate.getClassByCid(v.cid); | |
302 classRef.style.alignSelf = 'center'; | |
303 firstColumn.children.add(classRef); | |
304 } | |
305 | |
306 var secondColumn = flexColumns[1]; | |
307 secondColumn.style.justifyContent = 'flex-end'; | |
308 secondColumn.style.position = 'relative'; | |
309 secondColumn.style.alignItems = 'center'; | |
310 secondColumn.style.paddingRight = '0.5em'; | |
311 secondColumn.text = Utils.formatSize(edge.shallowSize); | |
312 } | |
313 } | |
314 | |
315 | |
316 class MergedEdge { | |
317 final MergedVertex source; | |
318 final MergedVertex target; | |
319 int count = 0; | |
320 int shallowSize = 0; | |
321 int retainedSize = 0; | |
322 | |
323 MergedEdge(this.source, this.target); | |
324 } | |
325 | |
326 class MergedVertex { | |
327 final int cid; | |
328 int instances = 0; | |
329 int shallowSize = 0; | |
330 int retainedSize = 0; | |
331 | |
332 List<MergedEdge> incomingEdges = new List<MergedEdge>(); | |
333 Map<int, MergedEdge> outgoingEdges = new Map<int, MergedEdge>(); | |
334 | |
335 MergedVertex(this.cid); | |
336 } | |
337 | |
338 | |
339 Future<List<MergedVertex>> buildMergedVertices(ObjectGraph graph) async { | |
340 Logger.root.info("Start merge vertices"); | |
341 | |
342 var cidToMergedVertex = {}; | |
343 | |
344 for (var vertex in graph.vertices) { | |
345 var cid = vertex.vmCid; | |
346 MergedVertex source = cidToMergedVertex[cid]; | |
347 if (source == null) { | |
348 cidToMergedVertex[cid] = source = new MergedVertex(cid); | |
349 } | |
350 | |
351 source.instances++; | |
352 source.shallowSize += (vertex.shallowSize == null ? 0 : vertex.shallowSize); | |
353 | |
354 for (var vertex2 in vertex.successors) { | |
355 var cid2 = vertex2.vmCid; | |
356 MergedEdge edge = source.outgoingEdges[cid2]; | |
357 if (edge == null) { | |
358 MergedVertex target = cidToMergedVertex[cid2]; | |
359 if (target == null) { | |
360 cidToMergedVertex[cid2] = target = new MergedVertex(cid2); | |
361 } | |
362 edge = new MergedEdge(source, target); | |
363 source.outgoingEdges[cid2] = edge; | |
364 target.incomingEdges.add(edge); | |
365 } | |
366 edge.count++; | |
367 // An over-estimate if there are multiple references to the same object. | |
368 edge.shallowSize += vertex2.shallowSize == null ? 0 : vertex2.shallowSize; | |
369 } | |
370 } | |
371 | |
372 Logger.root.info("End merge vertices"); | |
373 | |
374 return cidToMergedVertex.values.toList(); | |
375 } | |
376 | |
377 @CustomTag('heap-snapshot') | |
378 class HeapSnapshotElement extends ObservatoryElement { | |
379 @published Isolate isolate; | |
380 @observable HeapSnapshot snapshot; | |
381 | |
382 @published String state = 'Requested'; | |
383 @published String analysisSelector = 'DominatorTree'; | |
384 | |
385 HeapSnapshotElement.created() : super.created(); | |
386 | |
387 void analysisSelectorChanged(oldValue) { | |
388 _update(); | |
389 } | |
390 | |
391 void isolateChanged(oldValue) { | |
392 if (isolate == null) return; | |
393 | |
394 if (isolate.latestSnapshot == null) { | |
395 _getHeapSnapshot(); | |
396 } else { | |
397 snapshot = isolate.latestSnapshot; | |
398 state = 'Loaded'; | |
399 _update(); | |
400 } | |
401 } | |
402 | |
403 Future refresh() { | |
404 return _getHeapSnapshot(); | |
405 } | |
406 | |
407 Future _getHeapSnapshot() { | |
408 var completer = new Completer(); | |
409 state = "Requesting heap snapshot..."; | |
410 isolate.getClassRefs(); | |
411 | |
412 bool collectGarbage = | |
413 app.locationManager.getBoolParameter('collectGarbage', true); | |
414 | |
415 var stopwatch = new Stopwatch()..start(); | |
416 isolate.fetchHeapSnapshot(collectGarbage).listen((event) { | |
417 if (event is String) { | |
418 print("${stopwatch.elapsedMilliseconds} $event"); | |
419 state = event; | |
420 } else if (event is HeapSnapshot) { | |
421 snapshot = event; | |
422 state = 'Loaded'; | |
423 completer.complete(snapshot); | |
424 _update(); | |
425 } else { | |
426 throw "Unexpected event $event"; | |
427 } | |
428 }); | |
429 return completer.future; | |
430 } | |
431 | |
432 void _update() { | |
433 if (snapshot == null) { | |
434 return; | |
435 } | |
436 | |
437 switch(analysisSelector) { | |
438 case 'DominatorTree': | |
439 _buildDominatorTree(); | |
440 break; | |
441 case 'MergeByClass': | |
442 _buildMergedVertices(); | |
443 break; | |
444 } | |
445 } | |
446 | |
447 void _buildDominatorTree() { | |
448 var tableBody = shadowRoot.querySelector('#treeBody'); | |
449 var tree = new TableTree(tableBody, 2); | |
450 var rootRow = | |
451 new DominatorTreeRow(tree, null, snapshot.graph.root, snapshot); | |
452 tree.initialize(rootRow); | |
453 return; | |
454 } | |
455 | |
456 void _buildMergedVertices() { | |
457 state = 'Grouping...'; | |
458 var tableBody = shadowRoot.querySelector('#treeBody'); | |
459 var tree = new TableTree(tableBody, 2); | |
460 tableBody.children.clear(); | |
461 | |
462 new Future.delayed(const Duration(milliseconds: 500), () { | |
463 buildMergedVertices(snapshot.graph).then((vertices) { | |
464 state = 'Loaded'; | |
465 var rootRow = new MergedVerticesRow(tree, null, isolate, vertices); | |
466 tree.initialize(rootRow); | |
467 }); | |
468 }); | |
469 } | |
470 } | |
OLD | NEW |