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

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

Issue 2204563003: Converted Observatory cpu-profile element (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Removed tmp files Created 4 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
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, 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 library cpu_profile_element; 5 library cpu_profile_element;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:html'; 8 import 'dart:html';
9 import 'observatory_element.dart';
10 import 'package:observatory/models.dart' as M; 9 import 'package:observatory/models.dart' as M;
11 import 'package:observatory/service.dart'; 10 import 'package:observatory/src/elements/cpu_profile/virtual_tree.dart';
12 import 'package:observatory/app.dart'; 11 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
13 import 'package:observatory/cpu_profile.dart'; 12 import 'package:observatory/src/elements/helpers/tag.dart';
14 import 'package:observatory/elements.dart'; 13 import 'package:observatory/src/elements/helpers/uris.dart';
15 import 'package:polymer/polymer.dart'; 14 import 'package:observatory/src/elements/nav/bar.dart';
15 import 'package:observatory/src/elements/nav/isolate_menu.dart';
16 import 'package:observatory/src/elements/nav/menu.dart';
17 import 'package:observatory/src/elements/nav/notify.dart';
18 import 'package:observatory/src/elements/nav/refresh.dart';
19 import 'package:observatory/src/elements/nav/top_menu.dart';
20 import 'package:observatory/src/elements/nav/vm_menu.dart';
21 import 'package:observatory/src/elements/sample_buffer_control.dart';
22 import 'package:observatory/src/elements/stack_trace_tree_config.dart';
16 23
17 List<String> sorted(Set<String> attributes) { 24 class CpuProfileElement extends HtmlElement implements Renderable {
18 var list = attributes.toList(); 25 static const tag =
19 list.sort(); 26 const Tag<CpuProfileElement>('cpu-profile');
20 return list;
21 }
22 27
23 abstract class ProfileTreeRow<T> extends TableTreeRow { 28 RenderingScheduler<CpuProfileElement> _r;
24 final CpuProfile profile;
25 final T node;
26 final String selfPercent;
27 final String percent;
28 bool _infoBoxShown = false;
29 HtmlElement infoBox;
30 HtmlElement infoButton;
31 29
32 ProfileTreeRow(TableTree tree, TableTreeRow parent, 30 Stream<RenderedEvent<CpuProfileElement>> get onRendered =>
33 this.profile, this.node, double selfPercent, double percent) 31 _r.onRendered;
Cutch 2016/08/02 15:52:20 weird indentation
cbernaschina 2016/08/02 17:40:24 Done.
34 : super(tree, parent),
35 selfPercent = Utils.formatPercentNormalized(selfPercent),
36 percent = Utils.formatPercentNormalized(percent);
37 32
38 static _addToMemberList(DivElement memberList, Map<String, String> items) { 33 M.IsolateRef _isolate;
39 items.forEach((k, v) { 34 Stream<M.IsolateUpdateEvent> _isolateUpdates;
40 var item = new DivElement(); 35 M.NotificationRepository _notifications;
41 item.classes.add('memberItem'); 36 M.VMRef _vm;
42 var name = new DivElement(); 37 Stream<M.VMUpdateEvent> _vmUpdates;
43 name.classes.add('memberName'); 38 M.IsolateSampleProfileRepository _profiles;
44 name.text = k; 39 M.SampleProfileLoadingProgress _progress;
45 var value = new DivElement(); 40 M.SampleProfileTag _tag = M.SampleProfileTag.None;
46 value.classes.add('memberValue'); 41
47 value.text = v; 42 M.IsolateRef get isolate => _isolate;
48 item.children.add(name); 43 M.NotificationRepository get notifications => _notifications;
49 item.children.add(value); 44 M.IsolateSampleProfileRepository get profiles => _profiles;
50 memberList.children.add(item); 45 M.VMRef get vm => _vm;
51 }); 46
47 factory CpuProfileElement(M.VMRef vm, Stream<M.VMUpdateEvent> vmUpdates,
48 M.IsolateRef isolate, Stream<M.IsolateUpdateEvent> isolateUpdates,
49 M.IsolateSampleProfileRepository profiles,
50 M.NotificationRepository notifications,
51 {RenderingQueue queue}) {
52 assert(isolate != null);
53 assert(isolateUpdates != null);
54 assert(notifications != null);
55 assert(profiles != null);
56 assert(vm != null);
57 assert(vmUpdates != null);
58 CpuProfileElement e = document.createElement(tag.name);
59 e._r = new RenderingScheduler(e, queue: queue);
60 e._isolate = isolate;
61 e._isolateUpdates = isolateUpdates;
62 e._notifications = notifications;
63 e._profiles = profiles;
64 e._vm = vm;
65 e._vmUpdates = vmUpdates;
66 return e;
52 } 67 }
53 68
54 makeInfoBox() { 69 CpuProfileElement.created() : super.created();
55 if (infoBox != null) { 70
56 return; 71 @override
57 } 72 attached() {
58 infoBox = new DivElement(); 73 super.attached();
59 infoBox.classes.add('infoBox'); 74 _r.enable();
60 infoBox.classes.add('shadow'); 75 _request();
61 infoBox.style.display = 'none';
62 listeners.add(infoBox.onClick.listen((e) => e.stopPropagation()));
63 } 76 }
64 77
65 makeInfoButton() { 78 @override
66 infoButton = new SpanElement();
67 infoButton.style.marginLeft = 'auto';
68 infoButton.style.marginRight = '1em';
69 infoButton.children.add(new Element.tag('icon-info-outline'));
70 listeners.add(infoButton.onClick.listen((event) {
71 event.stopPropagation();
72 toggleInfoBox();
73 }));
74 }
75
76 static const attributes = const {
77 'optimized' : const ['O', null, 'Optimized'],
78 'unoptimized' : const ['U', null, 'Unoptimized'],
79 'inlined' : const ['I', null, 'Inlined'],
80 'intrinsic' : const ['It', null, 'Intrinsic'],
81 'ffi' : const ['F', null, 'FFI'],
82 'dart' : const ['D', null, 'Dart'],
83 'tag' : const ['T', null, 'Tag'],
84 'native' : const ['N', null, 'Native'],
85 'stub': const ['S', null, 'Stub'],
86 'synthetic' : const ['?', null, 'Synthetic'],
87 };
88
89 HtmlElement newAttributeBox(String attribute) {
90 List attributeDetails = attributes[attribute];
91 if (attributeDetails == null) {
92 print('could not find attribute $attribute');
93 return null;
94 }
95 var element = new SpanElement();
96 element.style.border = 'solid 2px #ECECEC';
97 element.style.height = '100%';
98 element.style.display = 'inline-block';
99 element.style.textAlign = 'center';
100 element.style.minWidth = '1.5em';
101 element.style.fontWeight = 'bold';
102 if (attributeDetails[1] != null) {
103 element.style.backgroundColor = attributeDetails[1];
104 }
105 element.text = attributeDetails[0];
106 element.title = attributeDetails[2];
107 return element;
108 }
109
110 onHide() {
111 super.onHide();
112 infoBox = null;
113 infoButton = null;
114 }
115
116 showInfoBox() {
117 if ((infoButton == null) || (infoBox == null)) {
118 return;
119 }
120 _infoBoxShown = true;
121 infoBox.style.display = 'block';
122 infoButton.children.clear();
123 infoButton.children.add(new Element.tag('icon-info'));
124 }
125
126 hideInfoBox() {
127 _infoBoxShown = false;
128 if ((infoButton == null) || (infoBox == null)) {
129 return;
130 }
131 infoBox.style.display = 'none';
132 infoButton.children.clear();
133 infoButton.children.add(new Element.tag('icon-info-outline'));
134 }
135
136 toggleInfoBox() {
137 if (_infoBoxShown) {
138 hideInfoBox();
139 } else {
140 showInfoBox();
141 }
142 }
143
144 hideAllInfoBoxes() {
145 final List<ProfileTreeRow> rows = tree.rows;
146 for (var row in rows) {
147 row.hideInfoBox();
148 }
149 }
150
151 onClick(MouseEvent e) {
152 e.stopPropagation();
153 if (e.altKey) {
154 bool show = !_infoBoxShown;
155 hideAllInfoBoxes();
156 if (show) {
157 showInfoBox();
158 }
159 return;
160 }
161 super.onClick(e);
162 }
163
164 HtmlElement newCodeRef(ProfileCode code) {
165 var codeRef = new Element.tag('code-ref');
166 codeRef.ref = code.code;
167 return codeRef;
168 }
169
170 HtmlElement newFunctionRef(ProfileFunction function) {
171 var ref = new Element.tag('function-ref');
172 ref.ref = function.function;
173 return ref;
174 }
175
176 HtmlElement hr() {
177 var element = new HRElement();
178 return element;
179 }
180
181 HtmlElement div(String text) {
182 var element = new DivElement();
183 element.text = text;
184 return element;
185 }
186
187 HtmlElement br() {
188 return new BRElement();
189 }
190
191 HtmlElement span(String text) {
192 var element = new SpanElement();
193 element.style.minWidth = '1em';
194 element.text = text;
195 return element;
196 }
197 }
198
199 class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> {
200 CodeProfileTreeRow(TableTree tree, CodeProfileTreeRow parent,
201 CpuProfile profile, CodeCallTreeNode node)
202 : super(tree, parent, profile, node,
203 node.profileCode.normalizedExclusiveTicks,
204 node.percentage) {
205 // fill out attributes.
206 }
207
208 bool hasChildren() => node.children.length > 0;
209
210 void onShow() {
211 super.onShow();
212
213 if (children.length == 0) {
214 for (var childNode in node.children) {
215 var row = new CodeProfileTreeRow(tree, this, profile, childNode);
216 children.add(row);
217 }
218 }
219
220 // Fill in method column.
221 var methodColumn = flexColumns[0];
222 methodColumn.style.justifyContent = 'flex-start';
223 methodColumn.style.position = 'relative';
224
225 // Percent.
226 var percentNode = new DivElement();
227 percentNode.text = percent;
228 percentNode.style.minWidth = '5em';
229 percentNode.style.textAlign = 'right';
230 percentNode.title = 'Executing: $selfPercent';
231 methodColumn.children.add(percentNode);
232
233 // Gap.
234 var gap = new SpanElement();
235 gap.style.minWidth = '1em';
236 methodColumn.children.add(gap);
237
238 // Code link.
239 var codeRef = newCodeRef(node.profileCode);
240 codeRef.style.alignSelf = 'center';
241 methodColumn.children.add(codeRef);
242
243 gap = new SpanElement();
244 gap.style.minWidth = '1em';
245 methodColumn.children.add(gap);
246
247 for (var attribute in sorted(node.attributes)) {
248 methodColumn.children.add(newAttributeBox(attribute));
249 }
250
251 makeInfoBox();
252 methodColumn.children.add(infoBox);
253
254 infoBox.children.add(span('Code '));
255 infoBox.children.add(newCodeRef(node.profileCode));
256 infoBox.children.add(span(' '));
257 for (var attribute in sorted(node.profileCode.attributes)) {
258 infoBox.children.add(newAttributeBox(attribute));
259 }
260 infoBox.children.add(br());
261 infoBox.children.add(br());
262 var memberList = new DivElement();
263 memberList.classes.add('memberList');
264 infoBox.children.add(br());
265 infoBox.children.add(memberList);
266 ProfileTreeRow._addToMemberList(memberList, {
267 'Exclusive ticks' : node.profileCode.formattedExclusiveTicks,
268 'Cpu time' : node.profileCode.formattedCpuTime,
269 'Inclusive ticks' : node.profileCode.formattedInclusiveTicks,
270 'Call stack time' : node.profileCode.formattedOnStackTime,
271 });
272
273 makeInfoButton();
274 methodColumn.children.add(infoButton);
275
276 // Fill in self column.
277 var selfColumn = flexColumns[1];
278 selfColumn.style.position = 'relative';
279 selfColumn.style.alignItems = 'center';
280 selfColumn.text = selfPercent;
281 }
282 }
283
284 class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> {
285 FunctionProfileTreeRow(TableTree tree, FunctionProfileTreeRow parent,
286 CpuProfile profile, FunctionCallTreeNode node)
287 : super(tree, parent, profile, node,
288 node.profileFunction.normalizedExclusiveTicks,
289 node.percentage) {
290 // fill out attributes.
291 }
292
293 bool hasChildren() => node.children.length > 0;
294
295 onShow() {
296 super.onShow();
297 if (children.length == 0) {
298 for (var childNode in node.children) {
299 var row = new FunctionProfileTreeRow(tree, this, profile, childNode);
300 children.add(row);
301 }
302 }
303
304 var methodColumn = flexColumns[0];
305 methodColumn.style.justifyContent = 'flex-start';
306
307 var codeAndFunctionColumn = new DivElement();
308 codeAndFunctionColumn.classes.add('flex-column');
309 codeAndFunctionColumn.style.justifyContent = 'center';
310 codeAndFunctionColumn.style.width = '100%';
311 methodColumn.children.add(codeAndFunctionColumn);
312
313 var functionRow = new DivElement();
314 functionRow.classes.add('flex-row');
315 functionRow.style.position = 'relative';
316 functionRow.style.justifyContent = 'flex-start';
317 codeAndFunctionColumn.children.add(functionRow);
318
319 // Insert the parent percentage
320 var parentPercent = new SpanElement();
321 parentPercent.text = percent;
322 parentPercent.style.minWidth = '4em';
323 parentPercent.style.alignSelf = 'center';
324 parentPercent.style.textAlign = 'right';
325 parentPercent.title = 'Executing: $selfPercent';
326 functionRow.children.add(parentPercent);
327
328 // Gap.
329 var gap = new SpanElement();
330 gap.style.minWidth = '1em';
331 gap.text = ' ';
332 functionRow.children.add(gap);
333
334 var functionRef = new Element.tag('function-ref');
335 functionRef.ref = node.profileFunction.function;
336 functionRef.style.alignSelf = 'center';
337 functionRow.children.add(functionRef);
338
339 gap = new SpanElement();
340 gap.style.minWidth = '1em';
341 gap.text = ' ';
342 functionRow.children.add(gap);
343
344 for (var attribute in sorted(node.attributes)) {
345 functionRow.children.add(newAttributeBox(attribute));
346 }
347
348 makeInfoBox();
349 functionRow.children.add(infoBox);
350
351 if (M.hasDartCode(node.profileFunction.function.kind)) {
352 infoBox.children.add(div('Code for current node'));
353 infoBox.children.add(br());
354 var totalTicks = node.totalCodesTicks;
355 var numCodes = node.codes.length;
356 for (var i = 0; i < numCodes; i++) {
357 var codeRowSpan = new DivElement();
358 codeRowSpan.style.paddingLeft = '1em';
359 infoBox.children.add(codeRowSpan);
360 var nodeCode = node.codes[i];
361 var ticks = nodeCode.ticks;
362 var percentage = Utils.formatPercent(ticks, totalTicks);
363 var percentageSpan = new SpanElement();
364 percentageSpan.style.display = 'inline-block';
365 percentageSpan.text = '$percentage';
366 percentageSpan.style.minWidth = '5em';
367 percentageSpan.style.textAlign = 'right';
368 codeRowSpan.children.add(percentageSpan);
369 var codeRef = new Element.tag('code-ref');
370 codeRef.ref = nodeCode.code.code;
371 codeRef.style.marginLeft = '1em';
372 codeRef.style.marginRight = 'auto';
373 codeRef.style.width = '100%';
374 codeRowSpan.children.add(codeRef);
375 }
376 infoBox.children.add(hr());
377 }
378 infoBox.children.add(span('Function '));
379 infoBox.children.add(newFunctionRef(node.profileFunction));
380 infoBox.children.add(span(' '));
381 for (var attribute in sorted(node.profileFunction.attributes)) {
382 infoBox.children.add(newAttributeBox(attribute));
383 }
384 var memberList = new DivElement();
385 memberList.classes.add('memberList');
386 infoBox.children.add(br());
387 infoBox.children.add(br());
388 infoBox.children.add(memberList);
389 infoBox.children.add(br());
390 ProfileTreeRow._addToMemberList(memberList, {
391 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks,
392 'Cpu time' : node.profileFunction.formattedCpuTime,
393 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks,
394 'Call stack time' : node.profileFunction.formattedOnStackTime,
395 });
396
397 if (M.hasDartCode(node.profileFunction.function.kind)) {
398 infoBox.children.add(div('Code containing function'));
399 infoBox.children.add(br());
400 var totalTicks = profile.sampleCount;
401 var codes = node.profileFunction.profileCodes;
402 var numCodes = codes.length;
403 for (var i = 0; i < numCodes; i++) {
404 var codeRowSpan = new DivElement();
405 codeRowSpan.style.paddingLeft = '1em';
406 infoBox.children.add(codeRowSpan);
407 var profileCode = codes[i];
408 var code = profileCode.code;
409 var ticks = profileCode.inclusiveTicks;
410 var percentage = Utils.formatPercent(ticks, totalTicks);
411 var percentageSpan = new SpanElement();
412 percentageSpan.style.display = 'inline-block';
413 percentageSpan.text = '$percentage';
414 percentageSpan.style.minWidth = '5em';
415 percentageSpan.style.textAlign = 'right';
416 percentageSpan.title = 'Inclusive ticks';
417 codeRowSpan.children.add(percentageSpan);
418 var codeRef = new Element.tag('code-ref');
419 codeRef.ref = code;
420 codeRef.style.marginLeft = '1em';
421 codeRef.style.marginRight = 'auto';
422 codeRef.style.width = '100%';
423 codeRowSpan.children.add(codeRef);
424 }
425 }
426
427 makeInfoButton();
428 methodColumn.children.add(infoButton);
429
430 // Fill in self column.
431 var selfColumn = flexColumns[1];
432 selfColumn.style.position = 'relative';
433 selfColumn.style.alignItems = 'center';
434 selfColumn.text = selfPercent;
435 }
436 }
437
438 @CustomTag('sample-buffer-control')
439 class SampleBufferControlElement extends ObservatoryElement {
440 SampleBufferControlElement.created() : super.created() {
441 _stopWatch.start();
442 }
443
444 Future<CpuProfile> reload(Isolate isolate) async {
445 profile.clear();
446 if (isolate == null) {
447 _update(profile);
448 // Notify listener.
449 onSampleBufferUpdate(profile);
450 return profile;
451 }
452 profileVM = isolate.vm.profileVM;
453 if (tagSelector == null) {
454 // Set default value.
455 tagSelector = profileVM ? 'UserVM' : 'None';
456 }
457 await _changeState(kFetchingState);
458 try {
459 var response;
460 if (allocationProfileClass != null) {
461 response =
462 await allocationProfileClass.getAllocationSamples(tagSelector);
463 } else {
464 var params = { 'tags': tagSelector };
465 response = await isolate.invokeRpc('_getCpuProfile', params);
466 }
467 await _changeState(kLoadingState);
468 profile.load(isolate, response);
469 profile.buildFunctionCallerAndCallees();
470 _update(profile);
471 await _changeState(kLoadedState);
472 // Notify listener.
473 onSampleBufferUpdate(profile);
474 return profile;
475 } catch (e, st) {
476 if (e is ServerRpcException) {
477 ServerRpcException se = e;
478 if (se.code == ServerRpcException.kFeatureDisabled) {
479 await _changeState(kDisabledState);
480 return profile;
481 }
482 }
483 await _changeState(kExceptionState, e, st);
484 rethrow;
485 }
486 }
487
488 Future _changeState(String newState, [exception, stackTrace]) {
489 if ((newState == kDisabledState) ||
490 (newState == kFetchingState) ||
491 (newState == kExceptionState)) {
492 loadTime = '';
493 fetchTime = '';
494 } else if (newState == kLoadingState) {
495 fetchTime = formatTimeMilliseconds(_stopWatch.elapsedMilliseconds);
496 loadTime = '';
497 } else if (newState == kLoadedState) {
498 loadTime = formatTimeMilliseconds(_stopWatch.elapsedMilliseconds);
499 }
500 state = newState;
501 this.exception = exception;
502 this.stackTrace = stackTrace;
503 _stopWatch.reset();
504 return window.animationFrame;
505 }
506
507 _update(CpuProfile sampleBuffer) {
508 sampleCount = profile.sampleCount.toString();
509 refreshTime = new DateTime.now().toString();
510 stackDepth = profile.stackDepth.toString();
511 sampleRate = profile.sampleRate.toStringAsFixed(0);
512 if (profile.sampleCount == 0) {
513 timeSpan = '0s';
514 } else {
515 timeSpan = formatTime(profile.timeSpan);
516 }
517 }
518
519 void tagSelectorChanged(oldValue) {
520 reload(profile.isolate);
521 }
522
523 Function onSampleBufferUpdate;
524 @observable bool showTagSelector = true;
525 @observable bool profileVM = false;
526 @observable String sampleCount = '';
527 @observable String refreshTime = '';
528 @observable String sampleRate = '';
529 @observable String stackDepth = '';
530 @observable String timeSpan = '';
531 @observable String fetchTime = '';
532 @observable String loadTime = '';
533 @observable String tagSelector;
534 @observable String state = kFetchingState;
535 @observable var exception;
536 @observable var stackTrace;
537
538 static const kDisabledState = 'kDisabled';
539 static const kExceptionState = 'Exception';
540 static const kFetchingState = 'kFetching';
541 static const kLoadedState = 'kLoaded';
542 static const kLoadingState = 'kLoading';
543 static const kNotLoadedState = 'kNotLoaded';
544
545 Isolate isolate;
546 Class allocationProfileClass;
547
548 final CpuProfile profile = new CpuProfile();
549 final Stopwatch _stopWatch = new Stopwatch();
550 }
551
552 @CustomTag('stack-trace-tree-config')
553 class StackTraceTreeConfigElement extends ObservatoryElement {
554 StackTraceTreeConfigElement.created() : super.created();
555
556 attached() {
557 super.attached();
558 var filterElement = shadowRoot.querySelector('#filterInput');
559 keyDownSubscription = filterElement.onKeyDown.listen(_onKeyDown);
560 blurSubscription = filterElement.onBlur.listen(_onBlur);
561 }
562
563 detached() { 79 detached() {
564 super.detached(); 80 super.detached(); _r.disable(notify: true);
565 keyDownSubscription?.cancel(); 81 children = [];
566 blurSubscription?.cancel();
567 }
568
569 void _onKeyDown(KeyboardEvent keyEvent) {
570 if (keyEvent.keyCode == 13) {
571 // On enter, update the filter string.
572 filterString =
573 (shadowRoot.querySelector('#filterInput') as InputElement).value;
574 if (onTreeConfigChange == null) {
575 return;
576 }
577 onTreeConfigChange(modeSelector, directionSelector, filterString);
578 }
579 }
580
581 void _onBlur(Event event) {
582 // Input box has lost focus, update the display to match the active
583 // filter string.
584 (shadowRoot.querySelector('#filterInput') as InputElement).value =
585 filterString;
586 }
587
588 void modeSelectorChanged(oldValue) {
589 if (onTreeConfigChange == null) {
590 return;
591 }
592 onTreeConfigChange(modeSelector, directionSelector, filterString);
593 }
594
595 void directionSelectorChanged(oldValue) {
596 if (onTreeConfigChange == null) {
597 return;
598 }
599 onTreeConfigChange(modeSelector, directionSelector, filterString);
600 }
601
602 Function onTreeConfigChange;
603 StreamSubscription keyDownSubscription;
604 StreamSubscription blurSubscription;
605 @observable bool show = true;
606 @observable bool showModeSelector = true;
607 @observable bool showDirectionSelector = true;
608 @observable bool showFilter = true;
609 @observable String modeSelector = 'Function';
610 @observable String directionSelector = 'Up';
611 @observable String filterString;
612 }
613
614 class FunctionCallTreeNodeRow extends VirtualTreeRow {
615 final CpuProfile profile;
616 final FunctionCallTreeNode node;
617 String selfPercent;
618 String totalPercent;
619 String percent;
620
621 static const kHotThreshold = 0.05; // 5%.
622 static const kMediumThreshold = 0.02; // 2%.
623
624 double _percent(bool self) {
625 if (self) {
626 return node.profileFunction.normalizedExclusiveTicks;
627 } else {
628 return node.profileFunction.normalizedInclusiveTicks;
629 }
630 }
631
632 bool isHot(bool self) => _percent(self) > kHotThreshold;
633 bool isMedium(bool self) => _percent(self) > kMediumThreshold;
634
635 String rowClass(bool self) {
636 if (isHot(self)) {
637 return 'hotProfile';
638 } else if (isMedium(self)) {
639 return 'mediumProfile';
640 } else {
641 return 'coldProfile';
642 }
643 }
644
645 FunctionCallTreeNodeRow(VirtualTree tree,
646 int depth,
647 this.profile,
648 FunctionCallTreeNode node)
649 : node = node,
650 super(tree, depth) {
651 if ((node.profileFunction.function.kind == M.FunctionKind.tag) &&
652 (node.profileFunction.normalizedExclusiveTicks == 0) &&
653 (node.profileFunction.normalizedInclusiveTicks == 0)) {
654 selfPercent = '';
655 totalPercent = '';
656 } else {
657 selfPercent = Utils.formatPercentNormalized(
658 node.profileFunction.normalizedExclusiveTicks);
659 totalPercent = Utils.formatPercentNormalized(
660 node.profileFunction.normalizedInclusiveTicks);
661 }
662 percent = Utils.formatPercentNormalized(node.percentage);
663 }
664
665 void onRender(DivElement rowDiv) {
666 rowDiv.children.add(makeGap(ems:0.1));
667 rowDiv.children.add(
668 makeText(totalPercent,
669 toolTip: 'global % on stack'));
670 rowDiv.children.add(makeGap());
671 rowDiv.children.add(
672 makeText(selfPercent,
673 toolTip: 'global % executing'));
674 rowDiv.children.add(makeGap());
675 rowDiv.children.add(makeIndenter(depth, colored: false));
676 rowDiv.children.add(makeColorBar(depth));
677 rowDiv.children.add(makeGap(ems: 1.0));
678 rowDiv.children.add(makeExpander());
679 rowDiv.children.add(
680 makeText(percent, toolTip: 'tree node %', flexBasis: null));
681 rowDiv.children.add(makeGap(ems: 0.5));
682 functionRef.ref = node.profileFunction.function;
683 rowDiv.children.add(functionRef);
684 }
685
686 var functionRef = new Element.tag('function-ref');
687
688 int get childCount => node.children.length;
689
690 void onShow() {
691 if (children.length > 0) {
692 return;
693 }
694 for (var childNode in node.children) {
695 var row = new FunctionCallTreeNodeRow(tree, depth + 1, profile, childNode) ;
696 children.add(row);
697 }
698 }
699 }
700
701
702 class CodeCallTreeNodeRow extends VirtualTreeRow {
703 final CpuProfile profile;
704 final CodeCallTreeNode node;
705 String selfPercent;
706 String totalPercent;
707 String percent;
708
709 static const kHotThreshold = 0.05; // 5%.
710 static const kMediumThreshold = 0.02; // 2%.
711
712 double _percent(bool self) {
713 if (self) {
714 return node.profileCode.normalizedExclusiveTicks;
715 } else {
716 return node.profileCode.normalizedInclusiveTicks;
717 }
718 }
719
720 bool isHot(bool self) => _percent(self) > kHotThreshold;
721 bool isMedium(bool self) => _percent(self) > kMediumThreshold;
722
723 String rowClass(bool self) {
724 if (isHot(self)) {
725 return 'hotProfile';
726 } else if (isMedium(self)) {
727 return 'mediumProfile';
728 } else {
729 return 'coldProfile';
730 }
731 }
732
733 CodeCallTreeNodeRow(VirtualTree tree,
734 int depth,
735 this.profile,
736 CodeCallTreeNode node)
737 : node = node,
738 super(tree, depth) {
739 if ((node.profileCode.code.kind == M.CodeKind.tag) &&
740 (node.profileCode.normalizedExclusiveTicks == 0) &&
741 (node.profileCode.normalizedInclusiveTicks == 0)) {
742 selfPercent = '';
743 totalPercent = '';
744 } else {
745 selfPercent = Utils.formatPercentNormalized(
746 node.profileCode.normalizedExclusiveTicks);
747 totalPercent = Utils.formatPercentNormalized(
748 node.profileCode.normalizedInclusiveTicks);
749 }
750 percent = Utils.formatPercentNormalized(node.percentage);
751 }
752
753 void onRender(DivElement rowDiv) {
754 rowDiv.children.add(makeGap(ems:0.1));
755 rowDiv.children.add(
756 makeText(totalPercent,
757 toolTip: 'global % on stack'));
758 rowDiv.children.add(makeGap());
759 rowDiv.children.add(
760 makeText(selfPercent,
761 toolTip: 'global % executing'));
762 rowDiv.children.add(makeGap());
763 rowDiv.children.add(makeIndenter(depth, colored: false));
764 rowDiv.children.add(makeColorBar(depth));
765 rowDiv.children.add(makeGap(ems: 1.0));
766 rowDiv.children.add(makeExpander());
767 rowDiv.children.add(
768 makeText(percent, toolTip: 'tree node %', flexBasis: null));
769 rowDiv.children.add(makeGap(ems: 0.5));
770 codeRef.ref = node.profileCode.code;
771 rowDiv.children.add(codeRef);
772 }
773
774 var codeRef = new Element.tag('code-ref');
775
776 int get childCount => node.children.length;
777
778 void onShow() {
779 if (children.length > 0) {
780 return;
781 }
782 for (var childNode in node.children) {
783 var row = new CodeCallTreeNodeRow(tree, depth + 1, profile, childNode);
784 children.add(row);
785 }
786 }
787 }
788
789 /// Displays a CpuProfile
790 @CustomTag('cpu-profile')
791 class CpuProfileElement extends ObservatoryElement {
792 CpuProfileElement.created() : super.created() {
793 _updateTask = new Task(update);
794 _renderTask = new Task(render);
795 }
796
797 attached() {
798 super.attached();
799 sampleBufferControlElement =
800 shadowRoot.querySelector('#sampleBufferControl');
801 assert(sampleBufferControlElement != null);
802 sampleBufferControlElement.onSampleBufferUpdate = onSampleBufferChange;
803 stackTraceTreeConfigElement =
804 shadowRoot.querySelector('#stackTraceTreeConfig');
805 assert(stackTraceTreeConfigElement != null);
806 stackTraceTreeConfigElement.onTreeConfigChange = onTreeConfigChange;
807 cpuProfileVirtualTreeElement = shadowRoot.querySelector('#cpuProfileVirtualT ree');
808 assert(cpuProfileVirtualTreeElement != null);
809 cpuProfileVirtualTreeElement.profile = sampleBufferControlElement.profile;
810 _resizeSubscription = window.onResize.listen((_) => _updateSize());
811 _updateTask.queue();
812 _updateSize();
813 }
814
815 detached() {
816 super.detached();
817 if (_resizeSubscription != null) {
818 _resizeSubscription.cancel();
819 }
820 }
821
822 _updateSize() {
823 var applySize = (e) {
824 Rectangle rect = e.getBoundingClientRect();
825 final totalHeight = window.innerHeight;
826 final bottomMargin = 200;
827 final mainHeight = totalHeight - bottomMargin;
828 e.style.setProperty('height', '${mainHeight}px');
829 };
830 HtmlElement e2 = $['cpuProfileVirtualTree'];
831 applySize(e2);
832 }
833
834 isolateChanged(oldValue) {
835 _updateTask.queue();
836 }
837
838 update() {
839 if (sampleBufferControlElement != null) {
840 sampleBufferControlElement.reload(isolate);
841 }
842 }
843
844 onSampleBufferChange(CpuProfile sampleBuffer) {
845 _renderTask.queue();
846 }
847
848 onTreeConfigChange(String modeSelector,
849 String directionSelector,
850 String filterString) {
851 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
852 if (directionSelector != 'Up') {
853 direction = ProfileTreeDirection.Inclusive;
854 }
855 ProfileTreeMode mode = ProfileTreeMode.Function;
856 if (modeSelector == 'Code') {
857 mode = ProfileTreeMode.Code;
858 }
859 // Clear the filter.
860 cpuProfileVirtualTreeElement.filter = null;
861 if (filterString != null) {
862 filterString = filterString.trim();
863 if (filterString.isNotEmpty) {
864 cpuProfileVirtualTreeElement.filter = (CallTreeNode node) {
865 return node.name.contains(filterString);
866 };
867 }
868 }
869 cpuProfileVirtualTreeElement.direction = direction;
870 cpuProfileVirtualTreeElement.mode = mode;
871 _renderTask.queue();
872 }
873
874 Future clearCpuProfile() async {
875 await isolate.invokeRpc('_clearCpuProfile', { });
876 _updateTask.queue();
877 return new Future.value(null);
878 }
879
880 Future refresh() {
881 _updateTask.queue();
882 return new Future.value(null);
883 }
884
885 render() {
886 cpuProfileVirtualTreeElement.render();
887 }
888
889 @published Isolate isolate;
890
891 StreamSubscription _resizeSubscription;
892 Task _updateTask;
893 Task _renderTask;
894 SampleBufferControlElement sampleBufferControlElement;
895 StackTraceTreeConfigElement stackTraceTreeConfigElement;
896 CpuProfileVirtualTreeElement cpuProfileVirtualTreeElement;
897 }
898
899 class NameSortedTable extends SortedTable {
900 NameSortedTable(columns) : super(columns);
901 @override
902 dynamic getSortKeyFor(int row, int col) {
903 if (col == FUNCTION_COLUMN) {
904 // Use name as sort key.
905 return rows[row].values[col].name;
906 }
907 return super.getSortKeyFor(row, col);
908 }
909
910 SortedTableRow rowFromIndex(int tableIndex) {
911 final modelIndex = sortedRows[tableIndex];
912 return rows[modelIndex];
913 }
914
915 static const FUNCTION_SPACER_COLUMNS = const [];
916 static const FUNCTION_COLUMN = 2;
917 TableRowElement _makeFunctionRow() {
918 var tr = new TableRowElement();
919 var cell;
920
921 // Add percentage.
922 cell = tr.insertCell(-1);
923 cell = tr.insertCell(-1);
924
925 // Add function ref.
926 cell = tr.insertCell(-1);
927 var functionRef = new Element.tag('function-ref');
928 cell.children.add(functionRef);
929
930 return tr;
931 }
932
933 static const CALL_SPACER_COLUMNS = const [];
934 static const CALL_FUNCTION_COLUMN = 1;
935 TableRowElement _makeCallRow() {
936 var tr = new TableRowElement();
937 var cell;
938
939 // Add percentage.
940 cell = tr.insertCell(-1);
941 // Add function ref.
942 cell = tr.insertCell(-1);
943 var functionRef = new Element.tag('function-ref');
944 cell.children.add(functionRef);
945 return tr;
946 }
947
948 _updateRow(TableRowElement tr,
949 int rowIndex,
950 List spacerColumns,
951 int refColumn) {
952 var row = rows[rowIndex];
953 // Set reference
954 var ref = tr.children[refColumn].children[0];
955 ref.ref = row.values[refColumn];
956
957 for (var i = 0; i < row.values.length; i++) {
958 if (spacerColumns.contains(i) || (i == refColumn)) {
959 // Skip spacer columns.
960 continue;
961 }
962 var cell = tr.children[i];
963 cell.title = row.values[i].toString();
964 cell.text = getFormattedValue(rowIndex, i);
965 }
966 }
967
968 _updateTableView(HtmlElement table,
969 HtmlElement makeEmptyRow(),
970 void onRowClick(TableRowElement tr),
971 List spacerColumns,
972 int refColumn) {
973 assert(table != null);
974
975 // Resize DOM table.
976 if (table.children.length > sortedRows.length) {
977 // Shrink the table.
978 var deadRows = table.children.length - sortedRows.length;
979 for (var i = 0; i < deadRows; i++) {
980 table.children.removeLast();
981 }
982 } else if (table.children.length < sortedRows.length) {
983 // Grow table.
984 var newRows = sortedRows.length - table.children.length;
985 for (var i = 0; i < newRows; i++) {
986 var row = makeEmptyRow();
987 row.onClick.listen((e) {
988 e.stopPropagation();
989 e.preventDefault();
990 onRowClick(row);
991 });
992 table.children.add(row);
993 }
994 }
995
996 assert(table.children.length == sortedRows.length);
997
998 // Fill table.
999 for (var i = 0; i < sortedRows.length; i++) {
1000 var rowIndex = sortedRows[i];
1001 var tr = table.children[i];
1002 _updateRow(tr, rowIndex, spacerColumns, refColumn);
1003 }
1004 }
1005 }
1006
1007 @CustomTag('cpu-profile-table')
1008 class CpuProfileTableElement extends ObservatoryElement {
1009 CpuProfileTableElement.created() : super.created() {
1010 _updateTask = new Task(update);
1011 _renderTask = new Task(render);
1012 var columns = [
1013 new SortedTableColumn.withFormatter('Executing (%)',
1014 Utils.formatPercentNormalized),
1015 new SortedTableColumn.withFormatter('In stack (%)',
1016 Utils.formatPercentNormalized),
1017 new SortedTableColumn('Method'),
1018 ];
1019 profileTable = new NameSortedTable(columns);
1020 profileTable.sortColumnIndex = 0;
1021
1022 columns = [
1023 new SortedTableColumn.withFormatter('Callees (%)',
1024 Utils.formatPercentNormalized),
1025 new SortedTableColumn('Method')
1026 ];
1027 profileCalleesTable = new NameSortedTable(columns);
1028 profileCalleesTable.sortColumnIndex = 0;
1029
1030 columns = [
1031 new SortedTableColumn.withFormatter('Callers (%)',
1032 Utils.formatPercentNormalized),
1033 new SortedTableColumn('Method')
1034 ];
1035 profileCallersTable = new NameSortedTable(columns);
1036 profileCallersTable.sortColumnIndex = 0;
1037 }
1038
1039 attached() {
1040 super.attached();
1041 sampleBufferControlElement =
1042 shadowRoot.querySelector('#sampleBufferControl');
1043 assert(sampleBufferControlElement != null);
1044 sampleBufferControlElement.onSampleBufferUpdate = onSampleBufferChange;
1045 // Disable the tag selector- we always want no tags.
1046 sampleBufferControlElement.tagSelector = 'None';
1047 sampleBufferControlElement.showTagSelector = false;
1048 stackTraceTreeConfigElement =
1049 shadowRoot.querySelector('#stackTraceTreeConfig');
1050 assert(stackTraceTreeConfigElement != null);
1051 stackTraceTreeConfigElement.onTreeConfigChange = onTreeConfigChange;
1052 stackTraceTreeConfigElement.modeSelector = 'Function';
1053 stackTraceTreeConfigElement.showModeSelector = false;
1054 stackTraceTreeConfigElement.directionSelector = 'Down';
1055 stackTraceTreeConfigElement.showDirectionSelector = false;
1056 cpuProfileTreeElement = shadowRoot.querySelector('#cpuProfileTree');
1057 assert(cpuProfileTreeElement != null);
1058 cpuProfileTreeElement.profile = sampleBufferControlElement.profile;
1059 _updateTask.queue();
1060 _resizeSubscription = window.onResize.listen((_) => _updateSize());
1061 _updateSize();
1062 }
1063
1064 detached() {
1065 super.detached();
1066 if (_resizeSubscription != null) {
1067 _resizeSubscription.cancel();
1068 }
1069 }
1070
1071 _updateSize() {
1072 HtmlElement e = $['main'];
1073 final totalHeight = window.innerHeight;
1074 final top = e.offset.top;
1075 final bottomMargin = 32;
1076 final mainHeight = totalHeight - top - bottomMargin;
1077 e.style.setProperty('height', '${mainHeight}px');
1078 }
1079
1080 isolateChanged(oldValue) {
1081 _updateTask.queue();
1082 }
1083
1084 update() {
1085 _clearView();
1086 if (sampleBufferControlElement != null) {
1087 sampleBufferControlElement.reload(isolate).whenComplete(checkParameters);
1088 }
1089 }
1090
1091 onSampleBufferChange(CpuProfile sampleBuffer) {
1092 _renderTask.queue();
1093 }
1094
1095 onTreeConfigChange(String modeSelector,
1096 String directionSelector,
1097 String filterString) {
1098 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
1099 if (directionSelector != 'Up') {
1100 direction = ProfileTreeDirection.Inclusive;
1101 }
1102 ProfileTreeMode mode = ProfileTreeMode.Function;
1103 if (modeSelector == 'Code') {
1104 mode = ProfileTreeMode.Code;
1105 }
1106 cpuProfileTreeElement.direction = direction;
1107 cpuProfileTreeElement.mode = mode;
1108 _renderTask.queue();
1109 }
1110
1111 Future clearCpuProfile() async {
1112 await isolate.invokeRpc('_clearCpuProfile', { });
1113 _updateTask.queue();
1114 return new Future.value(null);
1115 }
1116
1117 Future refresh() {
1118 _updateTask.queue();
1119 return new Future.value(null);
1120 }
1121
1122 render() {
1123 _updateView();
1124 }
1125
1126 checkParameters() {
1127 if (isolate == null) {
1128 return;
1129 }
1130 var functionId = app.locationManager.uri.queryParameters['functionId'];
1131 var functionName =
1132 app.locationManager.uri.queryParameters['functionName'];
1133 if (functionId == '') {
1134 // Fallback to searching by name.
1135 _focusOnFunction(_findFunction(functionName));
1136 } else {
1137 if (functionId == null) {
1138 _focusOnFunction(null);
1139 return;
1140 }
1141 isolate.getObject(functionId).then((func) => _focusOnFunction(func));
1142 }
1143 }
1144
1145 _clearView() {
1146 profileTable.clearRows();
1147 _renderTable();
1148 }
1149
1150 _updateView() {
1151 _buildFunctionTable();
1152 _renderTable();
1153 _updateFunctionTreeView();
1154 }
1155
1156 int _findFunctionRow(ServiceFunction function) {
1157 for (var i = 0; i < profileTable.sortedRows.length; i++) {
1158 var rowIndex = profileTable.sortedRows[i];
1159 var row = profileTable.rows[rowIndex];
1160 if (row.values[NameSortedTable.FUNCTION_COLUMN] == function) {
1161 return i;
1162 }
1163 }
1164 return -1;
1165 }
1166
1167 _scrollToFunction(ServiceFunction function) {
1168 TableSectionElement tableBody = $['profile-table'];
1169 var row = _findFunctionRow(function);
1170 if (row == -1) {
1171 return;
1172 }
1173 tableBody.children[row].classes.remove('shake');
1174 // trigger reflow.
1175 tableBody.children[row].offsetHeight;
1176 tableBody.children[row].scrollIntoView(ScrollAlignment.CENTER);
1177 tableBody.children[row].classes.add('shake');
1178 // Focus on clicked function.
1179 _focusOnFunction(function);
1180 }
1181
1182 _clearFocusedFunction() {
1183 TableSectionElement tableBody = $['profile-table'];
1184 // Clear current focus.
1185 if (focusedRow != null) {
1186 tableBody.children[focusedRow].classes.remove('focused');
1187 }
1188 focusedRow = null;
1189 focusedFunction = null;
1190 }
1191
1192 ServiceFunction _findFunction(String functionName) {
1193 for (var func in profile.functions) {
1194 if (func.function.name == functionName) {
1195 return func.function;
1196 }
1197 }
1198 return null;
1199 }
1200
1201 _focusOnFunction(ServiceFunction function) {
1202 if (focusedFunction == function) {
1203 // Do nothing.
1204 return;
1205 }
1206
1207 _clearFocusedFunction();
1208
1209 if (function == null) {
1210 _updateFunctionTreeView();
1211 _clearCallTables();
1212 return;
1213 }
1214
1215 var row = _findFunctionRow(function);
1216 if (row == -1) {
1217 _updateFunctionTreeView();
1218 _clearCallTables();
1219 return;
1220 }
1221
1222 focusedRow = row;
1223 focusedFunction = function;
1224
1225 TableSectionElement tableBody = $['profile-table'];
1226 tableBody.children[focusedRow].classes.add('focused');
1227 _updateFunctionTreeView();
1228 _buildCallersTable(focusedFunction);
1229 _buildCalleesTable(focusedFunction);
1230 }
1231
1232 _onRowClick(TableRowElement tr) {
1233 var tableBody = $['profile-table'];
1234 var row = profileTable.rowFromIndex(tableBody.children.indexOf(tr));
1235 var function = row.values[NameSortedTable.FUNCTION_COLUMN];
1236 app.locationManager.goReplacingParameters(
1237 {
1238 'functionId': function.id,
1239 'functionName': function.vmName
1240 }
1241 );
1242 }
1243
1244 _renderTable() {
1245 profileTable._updateTableView($['profile-table'],
1246 profileTable._makeFunctionRow,
1247 _onRowClick,
1248 NameSortedTable.FUNCTION_SPACER_COLUMNS,
1249 NameSortedTable.FUNCTION_COLUMN);
1250 }
1251
1252 _buildFunctionTable() {
1253 for (var func in profile.functions) {
1254 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) {
1255 // Skip.
1256 continue;
1257 }
1258 var row = [
1259 func.normalizedExclusiveTicks,
1260 func.normalizedInclusiveTicks,
1261 func.function,
1262 ];
1263 profileTable.addRow(new SortedTableRow(row));
1264 }
1265 profileTable.sort();
1266 }
1267
1268 _renderCallTable(TableSectionElement view,
1269 NameSortedTable model,
1270 void onRowClick(TableRowElement tr)) {
1271 model._updateTableView(view,
1272 model._makeCallRow,
1273 onRowClick,
1274 NameSortedTable.CALL_SPACER_COLUMNS,
1275 NameSortedTable.CALL_FUNCTION_COLUMN);
1276 }
1277
1278 _buildCallTable(Map<ProfileFunction, int> calls,
1279 NameSortedTable model) {
1280 model.clearRows();
1281 if (calls == null) {
1282 return;
1283 }
1284 var sum = 0;
1285 calls.values.forEach((i) => sum += i);
1286 calls.forEach((func, count) {
1287 var row = [
1288 count / sum,
1289 func.function,
1290 ];
1291 model.addRow(new SortedTableRow(row));
1292 });
1293 model.sort();
1294 }
1295
1296 _clearCallTables() {
1297 _buildCallersTable(null);
1298 _buildCalleesTable(null);
1299 }
1300
1301 _onCallersClick(TableRowElement tr) {
1302 var table = $['callers-table'];
1303 final row = profileCallersTable.rowFromIndex(table.children.indexOf(tr));
1304 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN];
1305 _scrollToFunction(function);
1306 }
1307
1308 _buildCallersTable(ServiceFunction function) {
1309 var calls = (function != null) ? function.profile.callers : null;
1310 var table = $['callers-table'];
1311 _buildCallTable(calls, profileCallersTable);
1312 _renderCallTable(table, profileCallersTable, _onCallersClick);
1313 }
1314
1315 _onCalleesClick(TableRowElement tr) {
1316 var table = $['callees-table'];
1317 final row = profileCalleesTable.rowFromIndex(table.children.indexOf(tr));
1318 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN];
1319 _scrollToFunction(function);
1320 }
1321
1322 _buildCalleesTable(ServiceFunction function) {
1323 var calls = (function != null) ? function.profile.callees : null;
1324 var table = $['callees-table'];
1325 _buildCallTable(calls, profileCalleesTable);
1326 _renderCallTable(table, profileCalleesTable, _onCalleesClick);
1327 }
1328
1329 _changeSort(Element target, NameSortedTable table) {
1330 if (target is TableCellElement) {
1331 if (table.sortColumnIndex != target.cellIndex) {
1332 table.sortColumnIndex = target.cellIndex;
1333 table.sortDescending = true;
1334 } else {
1335 table.sortDescending = !profileTable.sortDescending;
1336 }
1337 table.sort();
1338 }
1339 }
1340
1341 changeSortProfile(Event e, var detail, Element target) {
1342 _changeSort(target, profileTable);
1343 _renderTable();
1344 }
1345
1346 changeSortCallers(Event e, var detail, Element target) {
1347 _changeSort(target, profileCallersTable);
1348 _renderCallTable($['callers-table'], profileCallersTable, _onCallersClick);
1349 }
1350
1351 changeSortCallees(Event e, var detail, Element target) {
1352 _changeSort(target, profileCalleesTable);
1353 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick);
1354 }
1355
1356 //////
1357 ///
1358 /// Function tree.
1359 ///
1360 TableTree functionTree;
1361 _updateFunctionTreeView() {
1362 cpuProfileTreeElement.filter = (FunctionCallTreeNode node) {
1363 return node.profileFunction.function == focusedFunction;
1364 };
1365 cpuProfileTreeElement.render();
1366 }
1367
1368 @published Isolate isolate;
1369 @observable NameSortedTable profileTable;
1370 @observable NameSortedTable profileCallersTable;
1371 @observable NameSortedTable profileCalleesTable;
1372 @observable ServiceFunction focusedFunction;
1373 @observable int focusedRow;
1374
1375
1376 StreamSubscription _resizeSubscription;
1377 Task _updateTask;
1378 Task _renderTask;
1379
1380 CpuProfile get profile => sampleBufferControlElement.profile;
1381 SampleBufferControlElement sampleBufferControlElement;
1382 StackTraceTreeConfigElement stackTraceTreeConfigElement;
1383 CpuProfileVirtualTreeElement cpuProfileTreeElement;
1384 }
1385
1386 enum ProfileTreeDirection {
1387 Exclusive,
1388 Inclusive
1389 }
1390
1391 enum ProfileTreeMode {
1392 Code,
1393 Function,
1394 }
1395
1396 @CustomTag('cpu-profile-virtual-tree')
1397 class CpuProfileVirtualTreeElement extends ObservatoryElement {
1398 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
1399 ProfileTreeMode mode = ProfileTreeMode.Function;
1400 CpuProfile profile;
1401 VirtualTree virtualTree;
1402 CallTreeNodeFilter filter;
1403 StreamSubscription _resizeSubscription;
1404 @observable bool show = true;
1405
1406 CpuProfileVirtualTreeElement.created() : super.created();
1407
1408 attached() {
1409 super.attached();
1410 _resizeSubscription = window.onResize.listen((_) => _updateSize());
1411 }
1412
1413 detached() {
1414 super.detached();
1415 _resizeSubscription?.cancel();
1416 } 82 }
1417 83
1418 void render() { 84 void render() {
1419 _updateView(); 85 var content = [
86 new NavBarElement(queue: _r.queue)
87 ..children = [
88 new NavTopMenuElement(queue: _r.queue),
89 new NavVMMenuElement(_vm, _vmUpdates, queue: _r.queue),
90 new NavIsolateMenuElement(_isolate, _isolateUpdates, queue: _r.queue),
91 new NavMenuElement('cpu profile', link: Uris.profiler(_isolate),
92 last: true, queue: _r.queue),
93 new NavRefreshElement(queue: _r.queue)
94 ..onRefresh.listen(_refresh),
95 new NavRefreshElement(label: 'Clear', queue: _r.queue)
96 ..onRefresh.listen(_clearCpuProfile),
97 new NavNotifyElement(_notifications, queue: _r.queue)
98 ],
99 new SampleBufferControlElement(_progress, selectedTag: _tag,
100 queue: _r.queue)
101 ..onTagChange.listen((e) {
102 _tag = e.element.selectedTag;
103 _request();
104 }),
105 ];
106 if (_progress.status == M.SampleProfileLoadingStatus.Loaded) {
107 CpuProfileVirtualTreeElement tree;
108 content.addAll([
109 new BRElement(),
110 new StackTraceTreeConfigElement(queue: _r.queue)
111 ..onModeChange.listen((e) {
112 tree.mode = e.element.mode;
113 })
114 ..onFilterChange.listen((e) {
115 var filterString = e.element.filter.trim();
116 tree.filter = filterString.isNotEmpty
117 ? (node) { return node.name.contains(filterString); }
118 : null;
119 })
120 ..onDirectionChange.listen((e) {
121 tree.direction = e.element.direction;
122 }),
123 new BRElement(),
124 tree = new CpuProfileVirtualTreeElement(_isolate, _progress.profile,
125 queue: _r.queue)
126 ]);
127 }
128 children = content;
1420 } 129 }
1421 130
1422 showChanged(oldValue) { 131 Future _request({bool clear: false, bool forceFetch: false}) async {
1423 if (show) { 132 _r.dirty();
1424 virtualTree?.root?.style?.display = 'block'; 133 _progress = _profiles.get(isolate, _tag, clear: clear,
1425 } else { 134 forceFetch: forceFetch);
1426 virtualTree?.root?.style?.display = 'none'; 135 await _progress.onProgress.last;
1427 } 136 _r.dirty();
1428 } 137 }
1429 138
1430 void _updateView() { 139 Future _clearCpuProfile(RefreshEvent e) async {
1431 _updateSize(); 140 e.element.disabled = true;
1432 virtualTree?.clear(); 141 await _request(clear: true);
1433 virtualTree?.uninstall(); 142 e.element.disabled = false;
1434 virtualTree = null;
1435 bool exclusive = direction == ProfileTreeDirection.Exclusive;
1436 if (mode == ProfileTreeMode.Code) {
1437 _buildCodeTree(exclusive);
1438 } else {
1439 assert(mode == ProfileTreeMode.Function);
1440 _buildFunctionTree(exclusive);
1441 }
1442 virtualTree?.refresh();
1443 } 143 }
1444 144
1445 void _updateSize() { 145 Future _refresh(e) async {
1446 var treeBody = shadowRoot.querySelector('#tree'); 146 e.element.disabled = true;
1447 assert(treeBody != null); 147 await _request(forceFetch: true);
1448 int windowHeight = window.innerHeight - 32; 148 e.element.disabled = false;
1449 treeBody.style.height = '${windowHeight}px';
1450 }
1451
1452 void _buildFunctionTree(bool exclusive) {
1453 var treeBody = shadowRoot.querySelector('#tree');
1454 assert(treeBody != null);
1455 virtualTree = new VirtualTree(32, treeBody);
1456 if (profile == null) {
1457 return;
1458 }
1459 var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive');
1460 if (tree == null) {
1461 return;
1462 }
1463 if (filter != null) {
1464 tree = tree.filtered(filter);
1465 }
1466 for (var child in tree.root.children) {
1467 virtualTree.rows.add(
1468 new FunctionCallTreeNodeRow(virtualTree, 0, profile, child));
1469 }
1470 if (virtualTree.rows.length == 1) {
1471 virtualTree.rows[0].expanded = true;
1472 }
1473 }
1474
1475 void _buildCodeTree(bool exclusive) {
1476 var treeBody = shadowRoot.querySelector('#tree');
1477 assert(treeBody != null);
1478 virtualTree = new VirtualTree(32, treeBody);
1479 if (profile == null) {
1480 return;
1481 }
1482 var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive');
1483 if (tree == null) {
1484 return;
1485 }
1486 if (filter != null) {
1487 tree = tree.filtered(filter);
1488 }
1489 for (var child in tree.root.children) {
1490 virtualTree.rows.add(
1491 new CodeCallTreeNodeRow(virtualTree, 0, profile, child));
1492 }
1493 if (virtualTree.rows.length == 1) {
1494 virtualTree.rows[0].expanded = true;
1495 }
1496 } 149 }
1497 } 150 }
1498
1499 @CustomTag('cpu-profile-tree')
1500 class CpuProfileTreeElement extends ObservatoryElement {
1501 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive;
1502 ProfileTreeMode mode = ProfileTreeMode.Function;
1503 CpuProfile profile;
1504 TableTree codeTree;
1505 TableTree functionTree;
1506 CallTreeNodeFilter functionFilter;
1507 @observable bool show = true;
1508
1509 CpuProfileTreeElement.created() : super.created();
1510
1511 void render() {
1512 _updateView();
1513 }
1514
1515 showChanged(oldValue) {
1516 var treeTable = shadowRoot.querySelector('#treeTable');
1517 assert(treeTable != null);
1518 treeTable.style.display = show ? 'table' : 'none';
1519 }
1520
1521 void _updateView() {
1522 if (functionTree != null) {
1523 functionTree.clear();
1524 functionTree = null;
1525 }
1526 if (codeTree != null) {
1527 codeTree.clear();
1528 codeTree = null;
1529 }
1530 bool exclusive = direction == ProfileTreeDirection.Exclusive;
1531 if (mode == ProfileTreeMode.Code) {
1532 _buildCodeTree(exclusive);
1533 } else {
1534 assert(mode == ProfileTreeMode.Function);
1535 _buildFunctionTree(exclusive);
1536 }
1537 }
1538
1539 void _buildFunctionTree(bool exclusive) {
1540 if (functionTree == null) {
1541 var tableBody = shadowRoot.querySelector('#treeBody');
1542 assert(tableBody != null);
1543 functionTree = new TableTree(tableBody, 2);
1544 }
1545 if (profile == null) {
1546 return;
1547 }
1548 var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive');
1549 if (tree == null) {
1550 return;
1551 }
1552 if (functionFilter != null) {
1553 tree = tree.filtered(functionFilter);
1554 }
1555 var rootRow =
1556 new FunctionProfileTreeRow(functionTree, null, profile, tree.root);
1557 functionTree.initialize(rootRow);
1558 }
1559
1560 void _buildCodeTree(bool exclusive) {
1561 if (codeTree == null) {
1562 var tableBody = shadowRoot.querySelector('#treeBody');
1563 assert(tableBody != null);
1564 codeTree = new TableTree(tableBody, 2);
1565 }
1566 if (profile == null) {
1567 return;
1568 }
1569 var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive');
1570 if (tree == null) {
1571 return;
1572 }
1573 var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root);
1574 codeTree.initialize(rootRow);
1575 }
1576 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698