OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// This Element is part of MemoryDashboardElement. |
| 6 /// |
| 7 /// The Element is stripped down version of AllocationProfileElement where |
| 8 /// concepts like old and new space has been hidden away. |
| 9 /// |
| 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 |
| 12 /// created since the last reset, the memory used by these instances. |
| 13 /// |
| 14 /// When a GC event is received the profile is reloaded. |
| 15 |
| 16 import 'dart:async'; |
| 17 import 'dart:html'; |
| 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'; |
| 22 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 23 import 'package:observatory/utils.dart'; |
| 24 |
| 25 enum _SortingField { |
| 26 accumulatedSize, |
| 27 accumulatedInstances, |
| 28 currentSize, |
| 29 currentInstances, |
| 30 className, |
| 31 } |
| 32 |
| 33 enum _SortingDirection { ascending, descending } |
| 34 |
| 35 class MemoryProfileElement extends HtmlElement implements Renderable { |
| 36 static const tag = const Tag<MemoryProfileElement>('memory-profile', |
| 37 dependencies: const [ClassRefElement.tag, VirtualCollectionElement.tag]); |
| 38 |
| 39 RenderingScheduler<MemoryProfileElement> _r; |
| 40 |
| 41 Stream<RenderedEvent<MemoryProfileElement>> get onRendered => _r.onRendered; |
| 42 |
| 43 M.IsolateRef _isolate; |
| 44 M.EventRepository _events; |
| 45 M.AllocationProfileRepository _repository; |
| 46 M.AllocationProfile _profile; |
| 47 M.EditorRepository _editor; |
| 48 StreamSubscription _gcSubscription; |
| 49 _SortingField _sortingField = _SortingField.accumulatedInstances; |
| 50 _SortingDirection _sortingDirection = _SortingDirection.descending; |
| 51 |
| 52 M.IsolateRef get isolate => _isolate; |
| 53 |
| 54 factory MemoryProfileElement(M.IsolateRef isolate, M.EditorRepository editor, |
| 55 M.EventRepository events, M.AllocationProfileRepository repository, |
| 56 {RenderingQueue queue}) { |
| 57 assert(isolate != null); |
| 58 assert(events != null); |
| 59 assert(editor != null); |
| 60 assert(repository != null); |
| 61 MemoryProfileElement e = document.createElement(tag.name); |
| 62 e._r = new RenderingScheduler(e, queue: queue); |
| 63 e._isolate = isolate; |
| 64 e._editor = editor; |
| 65 e._events = events; |
| 66 e._repository = repository; |
| 67 return e; |
| 68 } |
| 69 |
| 70 MemoryProfileElement.created() : super.created(); |
| 71 |
| 72 @override |
| 73 attached() { |
| 74 super.attached(); |
| 75 _r.enable(); |
| 76 _refresh(); |
| 77 _gcSubscription = _events.onGCEvent.listen((e) { |
| 78 if (e.isolate.id == _isolate.id) { |
| 79 _refresh(); |
| 80 } |
| 81 }); |
| 82 } |
| 83 |
| 84 @override |
| 85 detached() { |
| 86 super.detached(); |
| 87 _r.disable(notify: true); |
| 88 children = []; |
| 89 _gcSubscription.cancel(); |
| 90 } |
| 91 |
| 92 Future reload({bool gc = false, bool reset = false}) async { |
| 93 return _refresh(gc: gc, reset: reset); |
| 94 } |
| 95 |
| 96 void render() { |
| 97 if (_profile == null) { |
| 98 children = [ |
| 99 new DivElement() |
| 100 ..classes = ['content-centered-big'] |
| 101 ..children = [new HeadingElement.h2()..text = 'Loading...'] |
| 102 ]; |
| 103 } else { |
| 104 children = [ |
| 105 new VirtualCollectionElement( |
| 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; |
| 120 case _SortingField.accumulatedInstances: |
| 121 getter = _getAccumulatedInstances; |
| 122 break; |
| 123 case _SortingField.currentSize: |
| 124 getter = _getCurrentSize; |
| 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; |
| 132 } |
| 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 |
| 141 static HtmlElement _createCollectionLine() => new DivElement() |
| 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 |
| 159 List<HtmlElement> _createCollectionHeader() { |
| 160 final resetAccumulators = new ButtonElement(); |
| 161 return [ |
| 162 new DivElement() |
| 163 ..classes = ['collection-item'] |
| 164 ..children = [ |
| 165 new SpanElement() |
| 166 ..classes = ['group'] |
| 167 ..children = [ |
| 168 new Text('Since Last '), |
| 169 resetAccumulators |
| 170 ..text = 'Reset↺' |
| 171 ..title = 'Reset' |
| 172 ..onClick.listen((_) async { |
| 173 resetAccumulators.disabled = true; |
| 174 await _refresh(reset: true); |
| 175 resetAccumulators.disabled = false; |
| 176 }) |
| 177 ], |
| 178 new SpanElement() |
| 179 ..classes = ['group'] |
| 180 ..text = 'Current' |
| 181 ], |
| 182 new DivElement() |
| 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 ]; |
| 197 } |
| 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 } |
OLD | NEW |