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

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

Issue 2989083002: Add memory-dashboard page to Observatory (Closed)
Patch Set: Upgrade to latest virtual-collection 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) 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
siva 2017/08/03 18:14:51 Ditto comment about adding a comment blob here.
cbernaschina 2017/08/03 22:32:53 Done.
5 import 'dart:async';
6 import 'dart:html';
7 import 'package:observatory/models.dart' as M;
8 import 'package:charted/charted.dart';
9 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
10 import 'package:observatory/src/elements/helpers/tag.dart';
11 import 'package:observatory/utils.dart';
12
13 class IsolateSelectedEvent {
14 final M.Isolate isolate;
15
16 const IsolateSelectedEvent([this.isolate]);
17 }
18
19 class DataPoint {}
siva 2017/08/03 18:14:51 What is this empty class for?
cbernaschina 2017/08/03 22:32:53 Done.
20
21 class MemoryGraphElement extends HtmlElement implements Renderable {
22 static const tag = const Tag<MemoryGraphElement>('memory-graph');
23
24 RenderingScheduler<MemoryGraphElement> _r;
25
26 final StreamController<IsolateSelectedEvent> _onIsolateSelected =
27 new StreamController<IsolateSelectedEvent>();
28
29 Stream<RenderedEvent<MemoryGraphElement>> get onRendered => _r.onRendered;
30 Stream<IsolateSelectedEvent> get onIsolateSelected =>
31 _onIsolateSelected.stream;
32
33 M.VM _vm;
34 M.IsolateRepository _isolates;
35 M.EventRepository _events;
36 StreamSubscription _onGCSubscription;
37 StreamSubscription _onConnectionClosedSubscription;
38 Timer _onTimer;
39
40 M.VM get vm => _vm;
41
42 factory MemoryGraphElement(
43 M.VM vm, M.IsolateRepository isolates, M.EventRepository events,
44 {RenderingQueue queue}) {
45 assert(vm != null);
46 assert(isolates != null);
47 assert(events != null);
48 MemoryGraphElement e = document.createElement(tag.name);
49 e._r = new RenderingScheduler(e, queue: queue);
50 e._vm = vm;
51 e._isolates = isolates;
52 e._events = events;
53 return e;
54 }
55
56 MemoryGraphElement.created() : super.created() {
57 final now = new DateTime.now();
58 var sample = now.subtract(_window);
59 while (sample.isBefore(now)) {
60 _ts.add(sample);
61 _vmSamples.add(0);
62 _isolateUsedSamples.add([]);
63 _isolateFreeSamples.add([]);
64 sample = sample.add(_period);
65 }
66 _ts.add(now);
67 _vmSamples.add(0);
68 _isolateUsedSamples.add([]);
69 _isolateFreeSamples.add([]);
70 }
71
72 static const Duration _period = const Duration(seconds: 2);
73 static const Duration _window = const Duration(minutes: 2);
74
75 @override
76 attached() {
77 super.attached();
78 _r.enable();
79 _onGCSubscription =
80 _events.onGCEvent.listen((e) => _refresh(gcIsolate: e.isolate));
81 _onConnectionClosedSubscription =
82 _events.onConnectionClosed.listen((_) => _onTimer.cancel());
83 _onTimer = new Timer.periodic(_period, (_) => _refresh());
84 _refresh();
85 }
86
87 @override
88 detached() {
89 super.detached();
90 _r.disable(notify: true);
91 children = [];
92 _onGCSubscription.cancel();
93 _onConnectionClosedSubscription.cancel();
94 _onTimer.cancel();
95 }
96
97 final List<DateTime> _ts = <DateTime>[];
98 final List<int> _vmSamples = <int>[];
99 final List<M.IsolateRef> _seenIsolates = <M.IsolateRef>[];
100 final List<List<int>> _isolateUsedSamples = <List<int>>[];
101 final List<List<int>> _isolateFreeSamples = <List<int>>[];
102 final Map<String, int> _isolateIndex = <String, int>{};
103 final Map<String, String> _isolateName = <String, String>{};
104
105 var _selected;
106 var _previewed;
107 var _hovered;
108
109 void render() {
110 if (_previewed != null || _hovered != null) return;
111
112 final now = new DateTime.now();
113 final nativeComponents = 1;
114 final legend = new DivElement();
115 final host = new DivElement();
116 final theme = new MemoryChartTheme(1);
117 children = [theme.style, legend, host];
118 final rect = host.getBoundingClientRect();
119
120 final series =
121 new List<int>.generate(_isolateIndex.length * 2 + 1, (i) => i + 1);
122 // The stacked line chart sorts from top to bottom
123 final columns = [
124 new ChartColumnSpec(
125 formatter: _formatTimeAxis, type: ChartColumnSpec.TYPE_NUMBER),
126 new ChartColumnSpec(label: 'Native', formatter: Utils.formatSize)
127 ]..addAll(_isolateName.keys.expand((id) => [
128 new ChartColumnSpec(formatter: Utils.formatSize),
129 new ChartColumnSpec(label: _label(id), formatter: Utils.formatSize)
130 ]));
131 // The stacked line chart sorts from top to bottom
132 final rows = new List.generate(_ts.length, (sampleIndex) {
133 final free = _isolateFreeSamples[sampleIndex];
134 final used = _isolateUsedSamples[sampleIndex];
135 return [
136 _ts[sampleIndex].difference(now).inMicroseconds,
137 _vmSamples[sampleIndex]
138 ]..addAll(_isolateIndex.keys.expand((key) {
139 final isolateIndex = _isolateIndex[key];
140 return [free[isolateIndex], used[isolateIndex]];
141 }));
142 });
143
144 final sMemory =
145 new ChartSeries('Memory', series, new StackedLineChartRenderer());
146 final config = new ChartConfig([sMemory], [0])
147 ..legend = new ChartLegend(legend);
148 config.minimumSize = new Rect(rect.width, rect.height);
149 final data = new ChartData(columns, rows);
150 final state = new ChartState(isMultiSelect: true)
151 ..changes.listen(_handleEvent);
152 final area = new CartesianArea(host, data, config, state: state)
153 ..theme = theme;
154 area.addChartBehavior(new Hovercard(builder: (int column, int row) {
155 if (column == 1) {
156 return _formatNativeOvercard(row);
157 }
158 return _formatIsolateOvercard(_seenIsolates[column - 2].id, row);
159 }));
160 area.draw();
161
162 if (_selected != null) {
163 state.select(_selected);
164 if (_selected > 1) {
165 state.select(_selected + 1);
166 }
167 }
168 }
169
170 String _formatTimeAxis(num ms) =>
171 Utils.formatDuration(new Duration(microseconds: ms.toInt()),
172 precision: DurationComponent.Seconds);
173
174 bool _running = false;
175
176 Future _refresh({M.IsolateRef gcIsolate}) async {
177 if (_running) return;
178 _running = true;
179 final now = new DateTime.now();
180 final start = now.subtract(_window);
181 // The Service classes order isolates from the older to the newer
182 final isolates =
183 (await Future.wait(_vm.isolates.map(_isolates.get))).reversed.toList();
184 while (_ts.first.isBefore(start)) {
185 _ts.removeAt(0);
186 _vmSamples.removeAt(0);
187 _isolateUsedSamples.removeAt(0);
188 _isolateFreeSamples.removeAt(0);
189 }
190
191 if (_isolateIndex.length == 0) {
192 _selected = isolates.length * 2;
193 _onIsolateSelected.add(new IsolateSelectedEvent(isolates.last));
194 }
195
196 isolates
197 .where((isolate) => !_isolateIndex.containsKey(isolate.id))
198 .forEach((isolate) {
199 _isolateIndex[isolate.id] = _isolateIndex.length;
200 _seenIsolates.addAll([isolate, isolate]);
201 });
202
203 if (_isolateIndex.length != _isolateName.length) {
204 final extra =
205 new List.filled(_isolateIndex.length - _isolateName.length, 0);
206 _isolateUsedSamples.forEach((sample) => sample.addAll(extra));
207 _isolateFreeSamples.forEach((sample) => sample.addAll(extra));
208 }
209
210 final length = _isolateIndex.length;
211
212 if (gcIsolate != null) {
213 // After GC we add an extra point to show the drop in a clear way
214 final List<int> isolateUsedSample = new List<int>.filled(length, 0);
215 final List<int> isolateFreeSample = new List<int>.filled(length, 0);
216 isolates.forEach((M.Isolate isolate) {
217 _isolateName[isolate.id] = isolate.name;
218 final index = _isolateIndex[isolate.id];
219 if (isolate.id == gcIsolate) {
220 isolateUsedSample[index] =
221 _isolateUsedSamples.last[index] + _isolateFreeSamples.last[index];
222 isolateFreeSample[index] = 0;
223 } else {
224 isolateUsedSample[index] = _used(isolate);
225 isolateFreeSample[index] = _free(isolate);
226 }
227 });
228 _isolateUsedSamples.add(isolateUsedSample);
229 _isolateFreeSamples.add(isolateFreeSample);
230
231 _vmSamples.add(vm.heapAllocatedMemoryUsage);
232
233 _ts.add(now);
234 }
235 final List<int> isolateUsedSample = new List<int>.filled(length, 0);
236 final List<int> isolateFreeSample = new List<int>.filled(length, 0);
237 isolates.forEach((M.Isolate isolate) {
238 _isolateName[isolate.id] = isolate.name;
239 final index = _isolateIndex[isolate.id];
240 isolateUsedSample[index] = _used(isolate);
241 isolateFreeSample[index] = _free(isolate);
242 });
243 _isolateUsedSamples.add(isolateUsedSample);
244 _isolateFreeSamples.add(isolateFreeSample);
245
246 _vmSamples.add(vm.heapAllocatedMemoryUsage);
247
248 _ts.add(now);
249 _r.dirty();
250 _running = false;
251 }
252
253 void _handleEvent(records) => records.forEach((record) {
254 if (record is ChartSelectionChangeRecord) {
255 var selected = record.add;
256 if (selected == null) {
257 if (selected != _selected) {
258 _onIsolateSelected.add(const IsolateSelectedEvent());
259 _r.dirty();
260 }
261 } else {
262 if (selected == 1) {
263 if (selected != _selected) {
264 _onIsolateSelected.add(const IsolateSelectedEvent());
265 _r.dirty();
266 }
267 } else {
268 selected -= selected % 2;
269 if (selected != _selected) {
270 _onIsolateSelected
271 .add(new IsolateSelectedEvent(_seenIsolates[selected - 2]));
272 _r.dirty();
273 }
274 }
275 }
276 _selected = selected;
277 _previewed = null;
278 _hovered = null;
279 } else if (record is ChartPreviewChangeRecord) {
280 _previewed = record.previewed;
281 } else if (record is ChartHoverChangeRecord) {
282 _hovered = record.hovered;
283 }
284 });
285
286 int _used(M.Isolate i) => i.newSpace.used + i.oldSpace.used;
287 int _capacity(M.Isolate i) => i.newSpace.capacity + i.oldSpace.capacity;
288 int _free(M.Isolate i) => _capacity(i) - _used(i);
289
290 String _label(String isolateId) {
291 final index = _isolateIndex[isolateId];
292 final name = _isolateName[isolateId];
293 final free = _isolateFreeSamples.last[index];
294 final used = _isolateUsedSamples.last[index];
295 final usedStr = Utils.formatSize(used);
296 final capacity = free + used;
297 final capacityStr = Utils.formatSize(capacity);
298 return '${name} ($usedStr / $capacityStr)';
299 }
300
301 Element _formatNativeOvercard(int row) => new DivElement()
302 ..children = [
303 new DivElement()
304 ..classes = ['hovercard-title']
305 ..text = 'Native',
306 new DivElement()
307 ..classes = ['hovercard-measure', 'hovercard-multi']
308 ..children = [
309 new DivElement()
310 ..classes = ['hovercard-measure-label']
311 ..text = 'Heap',
312 new DivElement()
313 ..classes = ['hovercard-measure-value']
314 ..text = Utils.formatSize(_vmSamples[row]),
315 ]
316 ];
317
318 Element _formatIsolateOvercard(String isolateId, int row) {
319 final index = _isolateIndex[isolateId];
320 final free = _isolateFreeSamples[row][index];
321 final used = _isolateUsedSamples[row][index];
322 final capacity = free + used;
323 return new DivElement()
324 ..children = [
325 new DivElement()
326 ..classes = ['hovercard-title']
327 ..text = _isolateName[isolateId],
328 new DivElement()
329 ..classes = ['hovercard-measure', 'hovercard-multi']
330 ..children = [
331 new DivElement()
332 ..classes = ['hovercard-measure-label']
333 ..text = 'Heap Capacity',
334 new DivElement()
335 ..classes = ['hovercard-measure-value']
336 ..text = Utils.formatSize(capacity),
337 ],
338 new DivElement()
339 ..classes = ['hovercard-measure', 'hovercard-multi']
340 ..children = [
341 new DivElement()
342 ..classes = ['hovercard-measure-label']
343 ..text = 'Free Heap',
344 new DivElement()
345 ..classes = ['hovercard-measure-value']
346 ..text = Utils.formatSize(free),
347 ],
348 new DivElement()
349 ..classes = ['hovercard-measure', 'hovercard-multi']
350 ..children = [
351 new DivElement()
352 ..classes = ['hovercard-measure-label']
353 ..text = 'Used Heap',
354 new DivElement()
355 ..classes = ['hovercard-measure-value']
356 ..text = Utils.formatSize(used),
357 ]
358 ];
359 }
360 }
361
362 class MemoryChartTheme extends QuantumChartTheme {
363 final int _offset;
364
365 MemoryChartTheme(int offset) : _offset = offset {
366 assert(offset != null);
367 assert(offset >= 0);
368 }
369
370 @override
371 String getColorForKey(key, [int state = 0]) {
372 key -= 1;
373 if (key > _offset) {
374 key = _offset + (key - _offset) ~/ 2;
375 }
376 key += 1;
377 return super.getColorForKey(key, state);
378 }
379
380 @override
381 String getFilterForState(int state) => state & ChartState.COL_PREVIEW != 0 ||
382 state & ChartState.VAL_HOVERED != 0 ||
383 state & ChartState.COL_SELECTED != 0 ||
384 state & ChartState.VAL_HIGHLIGHTED != 0
385 ? 'url(#drop-shadow)'
386 : '';
387
388 @override
389 String get filters =>
390 '<defs>' +
391 super.filters +
392 '''
393 <filter id="stroke-grid" primitiveUnits="userSpaceOnUse">
394 <feFlood in="SourceGraphic" x="0" y="0" width="4" height="4"
395 flood-color="black" flood-opacity="0.2" result='Black'/>
396 <feFlood in="SourceGraphic" x="1" y="1" width="3" height="3"
397 flood-color="black" flood-opacity="0.8" result='White'/>
398 <feComposite in="Black" in2="White" operator="xor" x="0" y="0" width="4" hei ght="4"/>
399 <feTile x="0" y="0" width="100%" height="100%" />
400 <feComposite in2="SourceAlpha" result="Pattern" operator="in" x="0" y="0" wi dth="100%" height="100%"/>
401 <feComposite in="SourceGraphic" in2="Pattern" operator="atop" x="0" y="0" wi dth="100%" height="100%"/>
402 </filter>
403 </defs>
404 ''';
405
406 StyleElement get style => new StyleElement()
407 ..text = '''
408 memory-graph svg .stacked-line-rdr-line:nth-child(2n+${_offset+1})
409 path:nth-child(1) {
410 filter: url(#stroke-grid);
411 }''';
412 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698