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

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

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

Powered by Google App Engine
This is Rietveld 408576698