Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(109)

Side by Side Diff: runtime/observatory/lib/src/elements/memory_dashboard.dart

Issue 2989083002: Add memory-dashboard page to Observatory (Closed)
Patch Set: Addressed CL comments Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 import 'dart:async';
6 import 'dart:html';
7 import 'package:charted/charted.dart';
8 import "package:charted/charts/charts.dart";
9 import 'package:observatory/models.dart' as M;
10 import 'package:observatory/src/elements/class_ref.dart';
11 import 'package:observatory/src/elements/containers/virtual_collection.dart';
12 import 'package:observatory/src/elements/helpers/nav_bar.dart';
13 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
14 import 'package:observatory/src/elements/helpers/tag.dart';
15 import 'package:observatory/src/elements/nav/isolate_menu.dart';
16 import 'package:observatory/src/elements/nav/notify.dart';
17 import 'package:observatory/src/elements/nav/refresh.dart';
18 import 'package:observatory/src/elements/nav/top_menu.dart';
19 import 'package:observatory/src/elements/nav/vm_menu.dart';
20 import 'package:observatory/utils.dart';
21
22 enum _SortingField {
23 accumulatedSize,
24 accumulatedInstances,
25 currentSize,
26 currentInstances,
27 newAccumulatedSize,
28 newAccumulatedInstances,
29 newCurrentSize,
30 newCurrentInstances,
31 oldAccumulatedSize,
32 oldAccumulatedInstances,
33 oldCurrentSize,
34 oldCurrentInstances,
35 className,
36 }
37
38 enum _SortingDirection { ascending, descending }
39
40 class MemoryDashboardElement extends HtmlElement implements Renderable {
41 static const tag = const Tag<MemoryDashboardElement>('memory-dashboard',
42 dependencies: const [
43 ClassRefElement.tag,
44 NavTopMenuElement.tag,
45 NavVMMenuElement.tag,
46 NavIsolateMenuElement.tag,
47 NavRefreshElement.tag,
48 NavNotifyElement.tag,
49 VirtualCollectionElement.tag
50 ]);
51
52 RenderingScheduler<MemoryDashboardElement> _r;
53
54 Stream<RenderedEvent<MemoryDashboardElement>> get onRendered => _r.onRendered;
55
56 M.VM _vm;
57 M.IsolateRef _isolate;
58 M.EventRepository _events;
59 M.NotificationRepository _notifications;
60 M.AllocationProfileRepository _repository;
61 M.AllocationProfile _profile;
62 M.EditorRepository _editor;
63 bool _autoRefresh = false;
64 bool _isCompacted = false;
65 StreamSubscription _gcSubscription;
66 _SortingField _sortingField = _SortingField.className;
67 _SortingDirection _sortingDirection = _SortingDirection.ascending;
68
69 M.VMRef get vm => _vm;
70 M.IsolateRef get isolate => _isolate;
71 M.NotificationRepository get notifications => _notifications;
72
73 factory MemoryDashboardElement(
74 M.VM vm,
75 M.IsolateRef isolate,
76 M.EventRepository events,
77 M.NotificationRepository notifications,
78 M.AllocationProfileRepository repository,
79 M.EditorRepository editor,
80 {RenderingQueue queue}) {
81 assert(vm != null);
82 assert(isolate != null);
83 assert(events != null);
84 assert(notifications != null);
85 assert(repository != null);
86 assert(editor != null);
87 MemoryDashboardElement e = document.createElement(tag.name);
88 e._r = new RenderingScheduler(e, queue: queue);
89 e._vm = vm;
90 e._isolate = isolate;
91 e._events = events;
92 e._notifications = notifications;
93 e._repository = repository;
94 e._editor = editor;
95 return e;
96 }
97
98 MemoryDashboardElement.created() : super.created();
99
100 @override
101 attached() {
102 super.attached();
103 _r.enable();
104 _refresh();
105 _gcSubscription = _events.onGCEvent.listen((e) {
106 if (_autoRefresh && (e.isolate.id == _isolate.id)) {
107 _refresh();
108 }
109 });
110 }
111
112 @override
113 detached() {
114 super.detached();
115 _r.disable(notify: true);
116 children = [];
117 _gcSubscription.cancel();
118 }
119
120 void render() {
121 children = [
122 navBar([
123 new NavRefreshElement(
124 label: 'Download', disabled: _profile == null, queue: _r.queue)
125 ..onRefresh.listen((_) => _downloadCSV()),
126 new NavRefreshElement(label: 'Reset Accumulator', queue: _r.queue)
127 ..onRefresh.listen((_) => _refresh(reset: true)),
128 new NavRefreshElement(label: 'GC', queue: _r.queue)
129 ..onRefresh.listen((_) => _refresh(gc: true)),
130 new NavRefreshElement(queue: _r.queue)
131 ..onRefresh.listen((_) => _refresh()),
132 new DivElement()
133 ..classes = ['nav-option']
134 ..children = [
135 new CheckboxInputElement()
136 ..id = 'allocation-profile-auto-refresh'
137 ..checked = _autoRefresh
138 ..onChange.listen((_) => _autoRefresh = !_autoRefresh),
139 new LabelElement()
140 ..htmlFor = 'allocation-profile-auto-refresh'
141 ..text = 'Auto-refresh on GC'
142 ],
143 new NavNotifyElement(_notifications, queue: _r.queue)
144 ]),
145 new DivElement()
146 ..classes = ['content-centered-big']
147 ..children = [
148 new HeadingElement.h2()..text = 'Allocation Profile',
149 new HRElement()
150 ]
151 ];
152 if (_profile == null) {
153 children.addAll([
154 new DivElement()
155 ..classes = ['content-centered-big']
156 ..children = [new HeadingElement.h2()..text = 'Loading...']
157 ]);
158 } else {
159 final newChartHost = new DivElement()..classes = ['host'];
160 final newChartLegend = new DivElement()..classes = ['legend'];
161 final oldChartHost = new DivElement()..classes = ['host'];
162 final oldChartLegend = new DivElement()..classes = ['legend'];
163 children.addAll([
164 new DivElement()
165 ..classes = ['content-centered-big']
166 ..children = _isCompacted
167 ? []
168 : [
169 new DivElement()
170 ..classes = ['memberList']
171 ..children = [
172 new DivElement()
173 ..classes = ['memberItem']
174 ..children = [
175 new DivElement()
176 ..classes = ['memberName']
177 ..text = 'last forced GC at',
178 new DivElement()
179 ..classes = ['memberValue']
180 ..text = _profile.lastServiceGC == null
181 ? '---'
182 : '${_profile.lastServiceGC}',
183 ],
184 new DivElement()
185 ..classes = ['memberItem']
186 ..children = [
187 new DivElement()
188 ..classes = ['memberName']
189 ..text = 'last accumulator reset at',
190 new DivElement()
191 ..classes = ['memberValue']
192 ..text = _profile.lastAccumulatorReset == null
193 ? '---'
194 : '${_profile.lastAccumulatorReset}',
195 ]
196 ],
197 new HRElement(),
198 ],
199 new DivElement()
200 ..classes = ['content-centered-big', 'compactable']
201 ..children = [
202 new DivElement()
203 ..classes = ['heap-space', 'left']
204 ..children = _isCompacted
205 ? [
206 new HeadingElement.h2()
207 ..text = 'New Generation '
208 '(${_usedCaption(_profile.newSpace)})',
209 ]
210 : [
211 new HeadingElement.h2()..text = 'New Generation',
212 new BRElement(),
213 new DivElement()
214 ..classes = ['memberList']
215 ..children = _createSpaceMembers(_profile.newSpace),
216 new BRElement(),
217 new DivElement()
218 ..classes = ['chart']
219 ..children = [newChartLegend, newChartHost]
220 ],
221 new DivElement()
222 ..classes = ['heap-space', 'right']
223 ..children = _isCompacted
224 ? [
225 new HeadingElement.h2()
226 ..text = '(${_usedCaption(_profile.oldSpace)}) '
227 'Old Generation',
228 ]
229 : [
230 new HeadingElement.h2()..text = 'Old Generation',
231 new BRElement(),
232 new DivElement()
233 ..classes = ['memberList']
234 ..children = _createSpaceMembers(_profile.oldSpace),
235 new BRElement(),
236 new DivElement()
237 ..classes = ['chart']
238 ..children = [oldChartLegend, oldChartHost]
239 ],
240 new ButtonElement()
241 ..classes = ['compact']
242 ..text = _isCompacted ? 'expand ▼' : 'compact ▲'
243 ..onClick.listen((_) {
244 _isCompacted = !_isCompacted;
245 _r.dirty();
246 }),
247 new HRElement()
248 ],
249 new DivElement()
250 ..classes = _isCompacted ? ['collection', 'expanded'] : ['collection']
251 ..children = [
252 new VirtualCollectionElement(
253 _createCollectionLine, _updateCollectionLine,
254 createHeader: _createCollectionHeader,
255 items: _profile.members.toList()..sort(_createSorter()),
256 queue: _r.queue)
257 ]
258 ]);
259 _renderGraph(newChartHost, newChartLegend, _profile.newSpace);
260 _renderGraph(oldChartHost, oldChartLegend, _profile.oldSpace);
261 }
262 }
263
264 _createSorter() {
265 var getter;
266 switch (_sortingField) {
267 case _SortingField.accumulatedSize:
268 getter = _getAccumulatedSize;
269 break;
270 case _SortingField.accumulatedInstances:
271 getter = _getAccumulatedInstances;
272 break;
273 case _SortingField.currentSize:
274 getter = _getCurrentSize;
275 break;
276 case _SortingField.currentInstances:
277 getter = _getCurrentInstances;
278 break;
279 case _SortingField.newAccumulatedSize:
280 getter = _getNewAccumulatedSize;
281 break;
282 case _SortingField.newAccumulatedInstances:
283 getter = _getNewAccumulatedInstances;
284 break;
285 case _SortingField.newCurrentSize:
286 getter = _getNewCurrentSize;
287 break;
288 case _SortingField.newCurrentInstances:
289 getter = _getNewCurrentInstances;
290 break;
291 case _SortingField.oldAccumulatedSize:
292 getter = _getOldAccumulatedSize;
293 break;
294 case _SortingField.oldAccumulatedInstances:
295 getter = _getOldAccumulatedInstances;
296 break;
297 case _SortingField.oldCurrentSize:
298 getter = _getOldCurrentSize;
299 break;
300 case _SortingField.oldCurrentInstances:
301 getter = _getOldCurrentInstances;
302 break;
303 case _SortingField.className:
304 getter = (M.ClassHeapStats s) => s.clazz.name;
305 break;
306 }
307 switch (_sortingDirection) {
308 case _SortingDirection.ascending:
309 return (a, b) => getter(a).compareTo(getter(b));
310 case _SortingDirection.descending:
311 return (a, b) => getter(b).compareTo(getter(a));
312 }
313 }
314
315 static Element _createCollectionLine() => new DivElement()
316 ..classes = ['collection-item']
317 ..children = [
318 new SpanElement()
319 ..classes = ['bytes']
320 ..text = '0B',
321 new SpanElement()
322 ..classes = ['instances']
323 ..text = '0',
324 new SpanElement()
325 ..classes = ['bytes']
326 ..text = '0B',
327 new SpanElement()
328 ..classes = ['instances']
329 ..text = '0',
330 new SpanElement()
331 ..classes = ['bytes']
332 ..text = '0B',
333 new SpanElement()
334 ..classes = ['instances']
335 ..text = '0',
336 new SpanElement()
337 ..classes = ['bytes']
338 ..text = '0B',
339 new SpanElement()
340 ..classes = ['instances']
341 ..text = '0',
342 new SpanElement()
343 ..classes = ['bytes']
344 ..text = '0B',
345 new SpanElement()
346 ..classes = ['instances']
347 ..text = '0',
348 new SpanElement()
349 ..classes = ['bytes']
350 ..text = '0B',
351 new SpanElement()
352 ..classes = ['instances']
353 ..text = '0',
354 new SpanElement()..classes = ['name']
355 ];
356
357 List<HtmlElement> _createCollectionHeader() => [
358 new DivElement()
359 ..classes = ['collection-item']
360 ..children = [
361 new SpanElement()
362 ..classes = ['group']
363 ..text = 'Accumulated',
364 new SpanElement()
365 ..classes = ['group']
366 ..text = 'Current',
367 new SpanElement()
368 ..classes = ['group']
369 ..text = '(NEW) Accumulated',
370 new SpanElement()
371 ..classes = ['group']
372 ..text = '(NEW) Current',
373 new SpanElement()
374 ..classes = ['group']
375 ..text = '(OLD) Accumulated',
376 new SpanElement()
377 ..classes = ['group']
378 ..text = '(OLD) Current',
379 ],
380 new DivElement()
381 ..classes = ['collection-item']
382 ..children = [
383 _createHeaderButton(const ['bytes'], 'Size',
384 _SortingField.accumulatedSize, _SortingDirection.descending),
385 _createHeaderButton(
386 const ['instances'],
387 'Instances',
388 _SortingField.accumulatedInstances,
389 _SortingDirection.descending),
390 _createHeaderButton(const ['bytes'], 'Size',
391 _SortingField.currentSize, _SortingDirection.descending),
392 _createHeaderButton(const ['instances'], 'Instances',
393 _SortingField.currentInstances, _SortingDirection.descending),
394 _createHeaderButton(const ['bytes'], 'Size',
395 _SortingField.newAccumulatedSize, _SortingDirection.descending),
396 _createHeaderButton(
397 const ['instances'],
398 'Instances',
399 _SortingField.newAccumulatedInstances,
400 _SortingDirection.descending),
401 _createHeaderButton(const ['bytes'], 'Size',
402 _SortingField.newCurrentSize, _SortingDirection.descending),
403 _createHeaderButton(
404 const ['instances'],
405 'Instances',
406 _SortingField.newCurrentInstances,
407 _SortingDirection.descending),
408 _createHeaderButton(const ['bytes'], 'Size',
409 _SortingField.oldAccumulatedSize, _SortingDirection.descending),
410 _createHeaderButton(
411 const ['instances'],
412 'Instances',
413 _SortingField.oldAccumulatedInstances,
414 _SortingDirection.descending),
415 _createHeaderButton(const ['bytes'], 'Size',
416 _SortingField.oldCurrentSize, _SortingDirection.descending),
417 _createHeaderButton(
418 const ['instances'],
419 'Instances',
420 _SortingField.oldCurrentInstances,
421 _SortingDirection.descending),
422 _createHeaderButton(const ['name'], 'Class',
423 _SortingField.className, _SortingDirection.ascending)
424 ],
425 ];
426
427 ButtonElement _createHeaderButton(List<String> classes, String text,
428 _SortingField field, _SortingDirection direction) =>
429 new ButtonElement()
430 ..classes = classes
431 ..text = _sortingField != field
432 ? text
433 : _sortingDirection == _SortingDirection.ascending
434 ? '$text▼'
435 : '$text▲'
436 ..onClick.listen((_) => _setSorting(field, direction));
437
438 void _setSorting(_SortingField field, _SortingDirection defaultDirection) {
439 if (_sortingField == field) {
440 switch (_sortingDirection) {
441 case _SortingDirection.descending:
442 _sortingDirection = _SortingDirection.ascending;
443 break;
444 case _SortingDirection.ascending:
445 _sortingDirection = _SortingDirection.descending;
446 break;
447 }
448 } else {
449 _sortingDirection = defaultDirection;
450 _sortingField = field;
451 }
452 _r.dirty();
453 }
454
455 void _updateCollectionLine(Element e, M.ClassHeapStats item, index) {
456 e.children[0].text = Utils.formatSize(_getAccumulatedSize(item));
457 e.children[1].text = '${_getAccumulatedInstances(item)}';
458 e.children[2].text = Utils.formatSize(_getCurrentSize(item));
459 e.children[3].text = '${_getCurrentInstances(item)}';
460 e.children[4].text = Utils.formatSize(_getNewAccumulatedSize(item));
461 e.children[5].text = '${_getNewAccumulatedInstances(item)}';
462 e.children[6].text = Utils.formatSize(_getNewCurrentSize(item));
463 e.children[7].text = '${_getNewCurrentInstances(item)}';
464 e.children[8].text = Utils.formatSize(_getOldAccumulatedSize(item));
465 e.children[9].text = '${_getOldAccumulatedInstances(item)}';
466 e.children[10].text = Utils.formatSize(_getOldCurrentSize(item));
467 e.children[11].text = '${_getOldCurrentInstances(item)}';
468 e.children[12] = new ClassRefElement(_isolate, item.clazz, queue: _r.queue)
469 ..classes = ['name'];
470 Element.clickEvent.forTarget(e.children[12], useCapture: true).listen((e) {
471 e.preventDefault();
472 _editor.sendObject(isolate, item.clazz);
473 window.close();
474 });
475 }
476
477 static String _usedCaption(M.HeapSpace space) =>
478 '${Utils.formatSize(space.used)}'
479 ' of '
480 '${Utils.formatSize(space.capacity)}';
481
482 static List<Element> _createSpaceMembers(M.HeapSpace space) {
483 final used = _usedCaption(space);
484 final ext = '${Utils.formatSize(space.external)}';
485 final collections = '${space.collections}';
486 final avgCollectionTime =
487 '${Utils.formatDurationInMilliseconds(space.avgCollectionTime)} ms';
488 final totalCollectionTime =
489 '${Utils.formatDurationInSeconds(space.totalCollectionTime)} secs';
490 final avgCollectionPeriod =
491 '${Utils.formatDurationInMilliseconds(space.avgCollectionPeriod)} ms';
492 return [
493 new DivElement()
494 ..classes = ['memberItem']
495 ..children = [
496 new DivElement()
497 ..classes = ['memberName']
498 ..text = 'used',
499 new DivElement()
500 ..classes = ['memberValue']
501 ..text = used
502 ],
503 new DivElement()
504 ..classes = ['memberItem']
505 ..children = [
506 new DivElement()
507 ..classes = ['memberName']
508 ..text = 'external',
509 new DivElement()
510 ..classes = ['memberValue']
511 ..text = ext
512 ],
513 new DivElement()
514 ..classes = ['memberItem']
515 ..children = [
516 new DivElement()
517 ..classes = ['memberName']
518 ..text = 'collections',
519 new DivElement()
520 ..classes = ['memberValue']
521 ..text = collections
522 ],
523 new DivElement()
524 ..classes = ['memberItem']
525 ..children = [
526 new DivElement()
527 ..classes = ['memberName']
528 ..text = 'average collection time',
529 new DivElement()
530 ..classes = ['memberValue']
531 ..text = avgCollectionTime
532 ],
533 new DivElement()
534 ..classes = ['memberItem']
535 ..children = [
536 new DivElement()
537 ..classes = ['memberName']
538 ..text = 'cumulative collection time',
539 new DivElement()
540 ..classes = ['memberValue']
541 ..text = totalCollectionTime
542 ],
543 new DivElement()
544 ..classes = ['memberItem']
545 ..children = [
546 new DivElement()
547 ..classes = ['memberName']
548 ..text = 'average time between collections',
549 new DivElement()
550 ..classes = ['memberValue']
551 ..text = avgCollectionPeriod
552 ]
553 ];
554 }
555
556 static final _columns = [
557 new ChartColumnSpec(label: 'Type', type: ChartColumnSpec.TYPE_STRING),
558 new ChartColumnSpec(label: 'Size', formatter: (v) => v.toString())
559 ];
560
561 static void _renderGraph(Element host, Element legend, M.HeapSpace space) {
562 final series = [
563 new ChartSeries("Work", [1], new PieChartRenderer(sortDataByValue: false))
564 ];
565 final rect = host.getBoundingClientRect();
566 final minSize = new Rect.size(rect.width, rect.height);
567 final config = new ChartConfig(series, [0])
568 ..minimumSize = minSize
569 ..legend = new ChartLegend(legend, showValues: true);
570 final data = new ChartData(_columns, [
571 ['Used', space.used],
572 ['Free', space.capacity - space.used],
573 ['External', space.external]
574 ]);
575
576 new LayoutArea(host, data, config,
577 state: new ChartState(), autoUpdate: true)
578 ..draw();
579 }
580
581 Future _refresh({bool gc: false, bool reset: false}) async {
582 _profile = null;
583 _r.dirty();
584 _profile = await _repository.get(_isolate, gc: gc, reset: reset);
585 _r.dirty();
586 }
587
588 void _downloadCSV() {
589 assert(_profile != null);
590 final header = [
591 '"Accumulator Size"',
592 '"Accumulator Instances"',
593 '"Current Size"',
594 '"Current Instances"',
595 '"(NEW) Accumulator Size"',
596 '"(NEW) Accumulator Instances"',
597 '"(NEW) Current Size"',
598 '"(NEW) Current Instances"',
599 '"(OLD) Accumulator Size"',
600 '"(OLD) Accumulator Instances"',
601 '"(OLD) Current Size"',
602 '"(OLD) Current Instances"',
603 'Class'
604 ].join(',') +
605 '\n';
606 AnchorElement tl = document.createElement('a');
607 tl
608 ..attributes['href'] = 'data:text/plain;charset=utf-8,' +
609 Uri.encodeComponent(header +
610 (_profile.members.toList()..sort(_createSorter()))
611 .map(_csvOut)
612 .join('\n'))
613 ..attributes['download'] = 'heap-profile.csv'
614 ..click();
615 }
616
617 static _csvOut(M.ClassHeapStats s) {
618 return [
619 _getAccumulatedSize(s),
620 _getAccumulatedInstances(s),
621 _getCurrentSize(s),
622 _getCurrentInstances(s),
623 _getNewAccumulatedSize(s),
624 _getNewAccumulatedInstances(s),
625 _getNewCurrentSize(s),
626 _getNewCurrentInstances(s),
627 _getOldAccumulatedSize(s),
628 _getOldAccumulatedInstances(s),
629 _getOldCurrentSize(s),
630 _getOldCurrentInstances(s),
631 s.clazz.name
632 ].join(',');
633 }
634
635 static int _getAccumulatedSize(M.ClassHeapStats s) =>
636 s.newSpace.accumulated.bytes + s.oldSpace.accumulated.bytes;
637 static int _getAccumulatedInstances(M.ClassHeapStats s) =>
638 s.newSpace.accumulated.instances + s.oldSpace.accumulated.instances;
639 static int _getCurrentSize(M.ClassHeapStats s) =>
640 s.newSpace.current.bytes + s.oldSpace.current.bytes;
641 static int _getCurrentInstances(M.ClassHeapStats s) =>
642 s.newSpace.current.instances + s.oldSpace.current.instances;
643 static int _getNewAccumulatedSize(M.ClassHeapStats s) =>
644 s.newSpace.accumulated.bytes;
645 static int _getNewAccumulatedInstances(M.ClassHeapStats s) =>
646 s.newSpace.accumulated.instances;
647 static int _getNewCurrentSize(M.ClassHeapStats s) => s.newSpace.current.bytes;
648 static int _getNewCurrentInstances(M.ClassHeapStats s) =>
649 s.newSpace.current.instances;
650 static int _getOldAccumulatedSize(M.ClassHeapStats s) =>
651 s.oldSpace.accumulated.bytes;
652 static int _getOldAccumulatedInstances(M.ClassHeapStats s) =>
653 s.oldSpace.accumulated.instances;
654 static int _getOldCurrentSize(M.ClassHeapStats s) => s.oldSpace.current.bytes;
655 static int _getOldCurrentInstances(M.ClassHeapStats s) =>
656 s.oldSpace.current.instances;
657 }
OLDNEW
« no previous file with comments | « runtime/observatory/lib/src/elements/memory/profile.dart ('k') | runtime/observatory/lib/src/models/repositories/editor.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698