OLD | NEW |
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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 /// This Element is part of MemoryDashboardElement. | 5 /// This Element is part of MemoryDashboardElement. |
6 /// | 6 /// |
7 /// The Element is stripped down version of AllocationProfileElement where | 7 /// The Element is stripped down version of AllocationProfileElement where |
8 /// concepts like old and new space has been hidden away. | 8 /// concepts like old and new space has been hidden away. |
9 /// | 9 /// |
10 /// For each class in the system it is shown the Total number of instances | 10 /// For each class in the system it is shown the Total number of instances |
11 /// alive, the Total memory used by these instances, the number of instances | 11 /// alive, the Total memory used by these instances, the number of instances |
12 /// created since the last reset, the memory used by these instances. | 12 /// created since the last reset, the memory used by these instances. |
13 /// | 13 /// |
14 /// When a GC event is received the profile is reloaded. | 14 /// When a GC event is received the profile is reloaded. |
15 | 15 |
16 import 'dart:async'; | 16 import 'dart:async'; |
17 import 'dart:html'; | 17 import 'dart:html'; |
18 import 'package:observatory/models.dart' as M; | 18 import 'package:observatory/models.dart' as M; |
19 import 'package:observatory/src/elements/class_ref.dart'; | |
20 import 'package:observatory/src/elements/containers/virtual_collection.dart'; | |
21 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; | 19 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
22 import 'package:observatory/src/elements/helpers/tag.dart'; | 20 import 'package:observatory/src/elements/helpers/tag.dart'; |
23 import 'package:observatory/utils.dart'; | 21 import 'package:observatory/src/elements/memory/allocations.dart'; |
| 22 import 'package:observatory/src/elements/memory/snapshot.dart'; |
24 | 23 |
25 enum _SortingField { | 24 enum _Analysis { allocations, dominatorTree } |
26 accumulatedSize, | |
27 accumulatedInstances, | |
28 currentSize, | |
29 currentInstances, | |
30 className, | |
31 } | |
32 | |
33 enum _SortingDirection { ascending, descending } | |
34 | 25 |
35 class MemoryProfileElement extends HtmlElement implements Renderable { | 26 class MemoryProfileElement extends HtmlElement implements Renderable { |
36 static const tag = const Tag<MemoryProfileElement>('memory-profile', | 27 static const tag = const Tag<MemoryProfileElement>('memory-profile', |
37 dependencies: const [ClassRefElement.tag, VirtualCollectionElement.tag]); | 28 dependencies: const [ |
| 29 MemoryAllocationsElement.tag, |
| 30 MemorySnapshotElement.tag |
| 31 ]); |
38 | 32 |
39 RenderingScheduler<MemoryProfileElement> _r; | 33 RenderingScheduler<MemoryProfileElement> _r; |
40 | 34 |
41 Stream<RenderedEvent<MemoryProfileElement>> get onRendered => _r.onRendered; | 35 Stream<RenderedEvent<MemoryProfileElement>> get onRendered => _r.onRendered; |
42 | 36 |
43 M.IsolateRef _isolate; | 37 M.IsolateRef _isolate; |
44 M.EventRepository _events; | 38 M.EventRepository _events; |
45 M.AllocationProfileRepository _repository; | 39 M.AllocationProfileRepository _allocations; |
46 M.AllocationProfile _profile; | |
47 M.EditorRepository _editor; | 40 M.EditorRepository _editor; |
48 StreamSubscription _gcSubscription; | 41 M.HeapSnapshotRepository _snapshots; |
49 _SortingField _sortingField = _SortingField.accumulatedInstances; | 42 M.ObjectRepository _objects; |
50 _SortingDirection _sortingDirection = _SortingDirection.descending; | 43 |
| 44 _Analysis _analysis = _Analysis.allocations; |
51 | 45 |
52 M.IsolateRef get isolate => _isolate; | 46 M.IsolateRef get isolate => _isolate; |
53 | 47 |
54 factory MemoryProfileElement(M.IsolateRef isolate, M.EditorRepository editor, | 48 factory MemoryProfileElement( |
55 M.EventRepository events, M.AllocationProfileRepository repository, | 49 M.IsolateRef isolate, |
| 50 M.EditorRepository editor, |
| 51 M.EventRepository events, |
| 52 M.AllocationProfileRepository allocations, |
| 53 M.HeapSnapshotRepository snapshots, |
| 54 M.ObjectRepository objects, |
56 {RenderingQueue queue}) { | 55 {RenderingQueue queue}) { |
57 assert(isolate != null); | 56 assert(isolate != null); |
58 assert(events != null); | 57 assert(events != null); |
59 assert(editor != null); | 58 assert(editor != null); |
60 assert(repository != null); | 59 assert(allocations != null); |
| 60 assert(snapshots != null); |
| 61 assert(objects != null); |
61 MemoryProfileElement e = document.createElement(tag.name); | 62 MemoryProfileElement e = document.createElement(tag.name); |
62 e._r = new RenderingScheduler(e, queue: queue); | 63 e._r = new RenderingScheduler(e, queue: queue); |
63 e._isolate = isolate; | 64 e._isolate = isolate; |
64 e._editor = editor; | 65 e._editor = editor; |
65 e._events = events; | 66 e._events = events; |
66 e._repository = repository; | 67 e._allocations = allocations; |
| 68 e._snapshots = snapshots; |
| 69 e._objects = objects; |
67 return e; | 70 return e; |
68 } | 71 } |
69 | 72 |
70 MemoryProfileElement.created() : super.created(); | 73 MemoryProfileElement.created() : super.created(); |
71 | 74 |
72 @override | 75 @override |
73 attached() { | 76 attached() { |
74 super.attached(); | 77 super.attached(); |
75 _r.enable(); | 78 _r.enable(); |
76 _refresh(); | |
77 _gcSubscription = _events.onGCEvent.listen((e) { | |
78 if (e.isolate.id == _isolate.id) { | |
79 _refresh(); | |
80 } | |
81 }); | |
82 } | 79 } |
83 | 80 |
84 @override | 81 @override |
85 detached() { | 82 detached() { |
86 super.detached(); | 83 super.detached(); |
87 _r.disable(notify: true); | 84 _r.disable(notify: true); |
88 children = []; | 85 children = []; |
89 _gcSubscription.cancel(); | |
90 } | |
91 | |
92 Future reload({bool gc = false, bool reset = false}) async { | |
93 return _refresh(gc: gc, reset: reset); | |
94 } | 86 } |
95 | 87 |
96 void render() { | 88 void render() { |
97 if (_profile == null) { | 89 HtmlElement current; |
98 children = [ | 90 var reload; |
99 new DivElement() | 91 switch (_analysis) { |
100 ..classes = ['content-centered-big'] | 92 case _Analysis.allocations: |
101 ..children = [new HeadingElement.h2()..text = 'Loading...'] | 93 final MemoryAllocationsElement allocations = |
102 ]; | 94 new MemoryAllocationsElement( |
103 } else { | 95 _isolate, _editor, _events, _allocations); |
104 children = [ | 96 current = allocations; |
105 new VirtualCollectionElement( | 97 reload = ({bool gc: false}) => allocations.reload(gc: gc); |
106 _createCollectionLine, _updateCollectionLine, | |
107 createHeader: _createCollectionHeader, | |
108 items: _profile.members.toList()..sort(_createSorter()), | |
109 queue: _r.queue) | |
110 ]; | |
111 } | |
112 } | |
113 | |
114 _createSorter() { | |
115 var getter; | |
116 switch (_sortingField) { | |
117 case _SortingField.accumulatedSize: | |
118 getter = _getAccumulatedSize; | |
119 break; | 98 break; |
120 case _SortingField.accumulatedInstances: | 99 case _Analysis.dominatorTree: |
121 getter = _getAccumulatedInstances; | 100 final MemorySnapshotElement snapshot = |
122 break; | 101 new MemorySnapshotElement(_isolate, _editor, _snapshots, _objects); |
123 case _SortingField.currentSize: | 102 current = snapshot; |
124 getter = _getCurrentSize; | 103 reload = ({bool gc: false}) => snapshot.reload(gc: gc); |
125 break; | |
126 case _SortingField.currentInstances: | |
127 getter = _getCurrentInstances; | |
128 break; | |
129 case _SortingField.className: | |
130 getter = (M.ClassHeapStats s) => s.clazz.name; | |
131 break; | 104 break; |
132 } | 105 } |
133 switch (_sortingDirection) { | |
134 case _SortingDirection.ascending: | |
135 return (a, b) => getter(a).compareTo(getter(b)); | |
136 case _SortingDirection.descending: | |
137 return (a, b) => getter(b).compareTo(getter(a)); | |
138 } | |
139 } | |
140 | 106 |
141 static HtmlElement _createCollectionLine() => new DivElement() | 107 assert(current != null); |
142 ..classes = ['collection-item'] | |
143 ..children = [ | |
144 new SpanElement() | |
145 ..classes = ['bytes'] | |
146 ..text = '0B', | |
147 new SpanElement() | |
148 ..classes = ['instances'] | |
149 ..text = '0', | |
150 new SpanElement() | |
151 ..classes = ['bytes'] | |
152 ..text = '0B', | |
153 new SpanElement() | |
154 ..classes = ['instances'] | |
155 ..text = '0', | |
156 new SpanElement()..classes = ['name'] | |
157 ]; | |
158 | 108 |
159 List<HtmlElement> _createCollectionHeader() { | 109 final ButtonElement bReload = new ButtonElement(); |
160 final resetAccumulators = new ButtonElement(); | 110 final ButtonElement bGC = new ButtonElement(); |
161 return [ | 111 children = [ |
162 new DivElement() | 112 new DivElement() |
163 ..classes = ['collection-item'] | 113 ..classes = ['content-centered-big'] |
164 ..children = [ | 114 ..children = [ |
165 new SpanElement() | 115 new HeadingElement.h1() |
166 ..classes = ['group'] | |
167 ..children = [ | 116 ..children = [ |
168 new Text('Since Last '), | 117 new Text("Isolate ${_isolate.name}"), |
169 resetAccumulators | 118 bReload |
170 ..text = 'Reset↺' | 119 ..classes = ['link', 'big'] |
171 ..title = 'Reset' | 120 ..text = ' ↺ ' |
172 ..onClick.listen((_) async { | 121 ..title = 'Refresh' |
173 resetAccumulators.disabled = true; | 122 ..onClick.listen((e) async { |
174 await _refresh(reset: true); | 123 bReload.disabled = true; |
175 resetAccumulators.disabled = false; | 124 bGC.disabled = true; |
176 }) | 125 await reload(); |
| 126 bReload.disabled = false; |
| 127 bGC.disabled = false; |
| 128 }), |
| 129 bGC |
| 130 ..classes = ['link', 'big'] |
| 131 ..text = ' ♺ ' |
| 132 ..title = 'Collect Garbage' |
| 133 ..onClick.listen((e) async { |
| 134 bGC.disabled = true; |
| 135 bReload.disabled = true; |
| 136 await reload(gc: true); |
| 137 bGC.disabled = false; |
| 138 bReload.disabled = false; |
| 139 }), |
| 140 new ButtonElement() |
| 141 ..classes = ['tab_button'] |
| 142 ..text = 'Dominator Tree' |
| 143 ..disabled = _analysis == _Analysis.dominatorTree |
| 144 ..onClick.listen((_) { |
| 145 _analysis = _Analysis.dominatorTree; |
| 146 _r.dirty(); |
| 147 }), |
| 148 new ButtonElement() |
| 149 ..classes = ['tab_button'] |
| 150 ..text = 'Allocations' |
| 151 ..disabled = _analysis == _Analysis.allocations |
| 152 ..onClick.listen((_) { |
| 153 _analysis = _Analysis.allocations; |
| 154 _r.dirty(); |
| 155 }), |
177 ], | 156 ], |
178 new SpanElement() | |
179 ..classes = ['group'] | |
180 ..text = 'Current' | |
181 ], | 157 ], |
182 new DivElement() | 158 current |
183 ..classes = ['collection-item'] | |
184 ..children = [ | |
185 _createHeaderButton(const ['bytes'], 'Size', | |
186 _SortingField.accumulatedSize, _SortingDirection.descending), | |
187 _createHeaderButton(const ['instances'], 'Instances', | |
188 _SortingField.accumulatedInstances, _SortingDirection.descending), | |
189 _createHeaderButton(const ['bytes'], 'Size', | |
190 _SortingField.currentSize, _SortingDirection.descending), | |
191 _createHeaderButton(const ['instances'], 'Instances', | |
192 _SortingField.currentInstances, _SortingDirection.descending), | |
193 _createHeaderButton(const ['name'], 'Class', _SortingField.className, | |
194 _SortingDirection.ascending) | |
195 ], | |
196 ]; | 159 ]; |
197 } | 160 } |
198 | |
199 ButtonElement _createHeaderButton(List<String> classes, String text, | |
200 _SortingField field, _SortingDirection direction) => | |
201 new ButtonElement() | |
202 ..classes = classes | |
203 ..text = _sortingField != field | |
204 ? text | |
205 : _sortingDirection == _SortingDirection.ascending | |
206 ? '$text▼' | |
207 : '$text▲' | |
208 ..onClick.listen((_) => _setSorting(field, direction)); | |
209 | |
210 void _setSorting(_SortingField field, _SortingDirection defaultDirection) { | |
211 if (_sortingField == field) { | |
212 switch (_sortingDirection) { | |
213 case _SortingDirection.descending: | |
214 _sortingDirection = _SortingDirection.ascending; | |
215 break; | |
216 case _SortingDirection.ascending: | |
217 _sortingDirection = _SortingDirection.descending; | |
218 break; | |
219 } | |
220 } else { | |
221 _sortingDirection = defaultDirection; | |
222 _sortingField = field; | |
223 } | |
224 _r.dirty(); | |
225 } | |
226 | |
227 void _updateCollectionLine(Element e, M.ClassHeapStats item, index) { | |
228 e.children[0].text = Utils.formatSize(_getAccumulatedSize(item)); | |
229 e.children[1].text = '${_getAccumulatedInstances(item)}'; | |
230 e.children[2].text = Utils.formatSize(_getCurrentSize(item)); | |
231 e.children[3].text = '${_getCurrentInstances(item)}'; | |
232 e.children[4] = new ClassRefElement(_isolate, item.clazz, queue: _r.queue) | |
233 ..classes = ['name']; | |
234 Element.clickEvent.forTarget(e.children[4], useCapture: true).listen((e) { | |
235 if (_editor.canOpenClass) { | |
236 e.preventDefault(); | |
237 _editor.openClass(isolate, item.clazz); | |
238 } | |
239 }); | |
240 } | |
241 | |
242 Future _refresh({bool gc: false, bool reset: false}) async { | |
243 _profile = null; | |
244 _r.dirty(); | |
245 _profile = await _repository.get(_isolate, gc: gc, reset: reset); | |
246 _r.dirty(); | |
247 } | |
248 | |
249 static int _getAccumulatedSize(M.ClassHeapStats s) => | |
250 s.newSpace.accumulated.bytes + s.oldSpace.accumulated.bytes; | |
251 static int _getAccumulatedInstances(M.ClassHeapStats s) => | |
252 s.newSpace.accumulated.instances + s.oldSpace.accumulated.instances; | |
253 static int _getCurrentSize(M.ClassHeapStats s) => | |
254 s.newSpace.current.bytes + s.oldSpace.current.bytes; | |
255 static int _getCurrentInstances(M.ClassHeapStats s) => | |
256 s.newSpace.current.instances + s.oldSpace.current.instances; | |
257 } | 161 } |
OLD | NEW |