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

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

Issue 2273993002: Converted Observatory cpu-profile-table element (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Addressed comments 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_table_element; 5 library cpu_profile_table_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 'sample_buffer_control.dart';
11 import 'stack_trace_tree_config.dart';
12 import 'cpu_profile/virtual_tree.dart';
13 import 'package:observatory/service.dart';
14 import 'package:observatory/app.dart';
15 import 'package:observatory/cpu_profile.dart';
16 import 'package:observatory/elements.dart';
17 import 'package:observatory/models.dart' as M; 9 import 'package:observatory/models.dart' as M;
18 import 'package:observatory/repositories.dart'; 10 import 'package:observatory/src/elements/containers/virtual_collection.dart';
19 import 'package:polymer/polymer.dart'; 11 import 'package:observatory/src/elements/cpu_profile/virtual_tree.dart';
20 12 import 'package:observatory/src/elements/function_ref.dart';
21 List<String> sorted(Set<String> attributes) { 13 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart';
22 var list = attributes.toList(); 14 import 'package:observatory/src/elements/helpers/tag.dart';
23 list.sort(); 15 import 'package:observatory/src/elements/helpers/uris.dart';
24 return list; 16 import 'package:observatory/src/elements/nav/bar.dart';
17 import 'package:observatory/src/elements/nav/isolate_menu.dart';
18 import 'package:observatory/src/elements/nav/menu.dart';
19 import 'package:observatory/src/elements/nav/notify.dart';
20 import 'package:observatory/src/elements/nav/refresh.dart';
21 import 'package:observatory/src/elements/nav/top_menu.dart';
22 import 'package:observatory/src/elements/nav/vm_menu.dart';
23 import 'package:observatory/src/elements/sample_buffer_control.dart';
24 import 'package:observatory/src/elements/stack_trace_tree_config.dart';
25 import 'package:observatory/utils.dart';
26
27 enum _Table {
28 functions,
29 caller,
30 callee
25 } 31 }
26 32
27 abstract class ProfileTreeRow<T> extends TableTreeRow { 33 enum _SortingField {
28 final CpuProfile profile; 34 exclusive,
29 final T node; 35 inclusive,
30 final String selfPercent; 36 caller,
31 final String percent; 37 callee,
32 bool _infoBoxShown = false; 38 method
33 HtmlElement infoBox; 39 }
34 HtmlElement infoButton; 40
35 41 enum _SortingDirection {
36 ProfileTreeRow(TableTree tree, TableTreeRow parent, 42 ascending,
37 this.profile, this.node, double selfPercent, double percent) 43 descending
38 : super(tree, parent), 44 }
39 selfPercent = Utils.formatPercentNormalized(selfPercent), 45
40 percent = Utils.formatPercentNormalized(percent); 46 class CpuProfileTableElement extends HtmlElement implements Renderable {
41 47 static const tag = const Tag<CpuProfileTableElement>('cpu-profile-table',
42 static _addToMemberList(DivElement memberList, Map<String, String> items) { 48 dependencies: const [
43 items.forEach((k, v) { 49 FunctionRefElement.tag,
44 var item = new DivElement(); 50 NavBarElement.tag,
45 item.classes.add('memberItem'); 51 NavTopMenuElement.tag,
46 var name = new DivElement(); 52 NavVMMenuElement.tag,
47 name.classes.add('memberName'); 53 NavIsolateMenuElement.tag,
48 name.text = k; 54 NavMenuElement.tag,
49 var value = new DivElement(); 55 NavRefreshElement.tag,
50 value.classes.add('memberValue'); 56 NavNotifyElement.tag,
51 value.text = v; 57 SampleBufferControlElement.tag,
52 item.children.add(name); 58 StackTraceTreeConfigElement.tag,
53 item.children.add(value); 59 CpuProfileVirtualTreeElement.tag,
54 memberList.children.add(item); 60 VirtualCollectionElement.tag
55 }); 61 ]);
56 } 62
57 63 RenderingScheduler<CpuProfileTableElement> _r;
58 makeInfoBox() { 64
59 if (infoBox != null) { 65 Stream<RenderedEvent<CpuProfileTableElement>> get onRendered => _r.onRendered;
60 return; 66
61 } 67 M.VM _vm;
62 infoBox = new DivElement(); 68 M.IsolateRef _isolate;
63 infoBox.classes.add('infoBox'); 69 M.EventRepository _events;
64 infoBox.classes.add('shadow'); 70 M.NotificationRepository _notifications;
65 infoBox.style.display = 'none'; 71 M.IsolateSampleProfileRepository _profiles;
66 listeners.add(infoBox.onClick.listen((e) => e.stopPropagation())); 72 Stream<M.SampleProfileLoadingProgressEvent> _progressStream;
67 } 73 M.SampleProfileLoadingProgress _progress;
68 74 final _sortingField = <_Table, _SortingField>{
69 makeInfoButton() { 75 _Table.functions : _SortingField.exclusive,
70 infoButton = new SpanElement(); 76 _Table.caller : _SortingField.caller,
71 infoButton.style.marginLeft = 'auto'; 77 _Table.callee : _SortingField.callee,
72 infoButton.style.marginRight = '1em';
73 infoButton.children.add(new Element.tag('icon-info-outline'));
74 listeners.add(infoButton.onClick.listen((event) {
75 event.stopPropagation();
76 toggleInfoBox();
77 }));
78 }
79
80 static const attributes = const {
81 'optimized' : const ['O', null, 'Optimized'],
82 'unoptimized' : const ['U', null, 'Unoptimized'],
83 'inlined' : const ['I', null, 'Inlined'],
84 'intrinsic' : const ['It', null, 'Intrinsic'],
85 'ffi' : const ['F', null, 'FFI'],
86 'dart' : const ['D', null, 'Dart'],
87 'tag' : const ['T', null, 'Tag'],
88 'native' : const ['N', null, 'Native'],
89 'stub': const ['S', null, 'Stub'],
90 'synthetic' : const ['?', null, 'Synthetic'],
91 }; 78 };
92 79 final _sortingDirection = <_Table, _SortingDirection>{
93 HtmlElement newAttributeBox(String attribute) { 80 _Table.functions : _SortingDirection.descending,
94 List attributeDetails = attributes[attribute]; 81 _Table.caller : _SortingDirection.descending,
95 if (attributeDetails == null) { 82 _Table.callee : _SortingDirection.descending,
96 print('could not find attribute $attribute'); 83 };
97 return null; 84 String _filter = '';
98 } 85
99 var element = new SpanElement(); 86
100 element.style.border = 'solid 2px #ECECEC'; 87 M.IsolateRef get isolate => _isolate;
101 element.style.height = '100%'; 88 M.NotificationRepository get notifications => _notifications;
102 element.style.display = 'inline-block'; 89 M.IsolateSampleProfileRepository get profiles => _profiles;
103 element.style.textAlign = 'center'; 90 M.VMRef get vm => _vm;
104 element.style.minWidth = '1.5em'; 91
105 element.style.fontWeight = 'bold'; 92 factory CpuProfileTableElement(M.VM vm, M.IsolateRef isolate,
106 if (attributeDetails[1] != null) { 93 M.EventRepository events,
107 element.style.backgroundColor = attributeDetails[1]; 94 M.NotificationRepository notifications,
108 } 95 M.IsolateSampleProfileRepository profiles,
109 element.text = attributeDetails[0]; 96 {RenderingQueue queue}) {
110 element.title = attributeDetails[2]; 97 assert(vm != null);
111 return element; 98 assert(isolate != null);
112 } 99 assert(events != null);
113 100 assert(notifications != null);
114 onHide() { 101 assert(profiles != null);
115 super.onHide(); 102 CpuProfileTableElement e = document.createElement(tag.name);
116 infoBox = null; 103 e._r = new RenderingScheduler(e, queue: queue);
117 infoButton = null; 104 e._vm = vm;
118 } 105 e._isolate = isolate;
119 106 e._events = events;
120 showInfoBox() { 107 e._notifications = notifications;
121 if ((infoButton == null) || (infoBox == null)) { 108 e._profiles = profiles;
122 return; 109 return e;
123 } 110 }
124 _infoBoxShown = true; 111
125 infoBox.style.display = 'block'; 112 CpuProfileTableElement.created() : super.created();
126 infoButton.children.clear(); 113
127 infoButton.children.add(new Element.tag('icon-info'));
128 }
129
130 hideInfoBox() {
131 _infoBoxShown = false;
132 if ((infoButton == null) || (infoBox == null)) {
133 return;
134 }
135 infoBox.style.display = 'none';
136 infoButton.children.clear();
137 infoButton.children.add(new Element.tag('icon-info-outline'));
138 }
139
140 toggleInfoBox() {
141 if (_infoBoxShown) {
142 hideInfoBox();
143 } else {
144 showInfoBox();
145 }
146 }
147
148 hideAllInfoBoxes() {
149 final List<ProfileTreeRow> rows = tree.rows;
150 for (var row in rows) {
151 row.hideInfoBox();
152 }
153 }
154
155 onClick(MouseEvent e) {
156 e.stopPropagation();
157 if (e.altKey) {
158 bool show = !_infoBoxShown;
159 hideAllInfoBoxes();
160 if (show) {
161 showInfoBox();
162 }
163 return;
164 }
165 super.onClick(e);
166 }
167
168 HtmlElement newCodeRef(ProfileCode code) {
169 var codeRef = new Element.tag('code-ref');
170 codeRef.ref = code.code;
171 return codeRef;
172 }
173
174 HtmlElement newFunctionRef(ProfileFunction function) {
175 var ref = new Element.tag('function-ref');
176 ref.ref = function.function;
177 return ref;
178 }
179
180 HtmlElement hr() {
181 var element = new HRElement();
182 return element;
183 }
184
185 HtmlElement div(String text) {
186 var element = new DivElement();
187 element.text = text;
188 return element;
189 }
190
191 HtmlElement br() {
192 return new BRElement();
193 }
194
195 HtmlElement span(String text) {
196 var element = new SpanElement();
197 element.style.minWidth = '1em';
198 element.text = text;
199 return element;
200 }
201 }
202
203 class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> {
204 CodeProfileTreeRow(TableTree tree, CodeProfileTreeRow parent,
205 CpuProfile profile, CodeCallTreeNode node)
206 : super(tree, parent, profile, node,
207 node.profileCode.normalizedExclusiveTicks,
208 node.percentage) {
209 // fill out attributes.
210 }
211
212 bool hasChildren() => node.children.length > 0;
213
214 void onShow() {
215 super.onShow();
216
217 if (children.length == 0) {
218 for (var childNode in node.children) {
219 var row = new CodeProfileTreeRow(tree, this, profile, childNode);
220 children.add(row);
221 }
222 }
223
224 // Fill in method column.
225 var methodColumn = flexColumns[0];
226 methodColumn.style.justifyContent = 'flex-start';
227 methodColumn.style.position = 'relative';
228
229 // Percent.
230 var percentNode = new DivElement();
231 percentNode.text = percent;
232 percentNode.style.minWidth = '5em';
233 percentNode.style.textAlign = 'right';
234 percentNode.title = 'Executing: $selfPercent';
235 methodColumn.children.add(percentNode);
236
237 // Gap.
238 var gap = new SpanElement();
239 gap.style.minWidth = '1em';
240 methodColumn.children.add(gap);
241
242 // Code link.
243 var codeRef = newCodeRef(node.profileCode);
244 codeRef.style.alignSelf = 'center';
245 methodColumn.children.add(codeRef);
246
247 gap = new SpanElement();
248 gap.style.minWidth = '1em';
249 methodColumn.children.add(gap);
250
251 for (var attribute in sorted(node.attributes)) {
252 methodColumn.children.add(newAttributeBox(attribute));
253 }
254
255 makeInfoBox();
256 methodColumn.children.add(infoBox);
257
258 infoBox.children.add(span('Code '));
259 infoBox.children.add(newCodeRef(node.profileCode));
260 infoBox.children.add(span(' '));
261 for (var attribute in sorted(node.profileCode.attributes)) {
262 infoBox.children.add(newAttributeBox(attribute));
263 }
264 infoBox.children.add(br());
265 infoBox.children.add(br());
266 var memberList = new DivElement();
267 memberList.classes.add('memberList');
268 infoBox.children.add(br());
269 infoBox.children.add(memberList);
270 ProfileTreeRow._addToMemberList(memberList, {
271 'Exclusive ticks' : node.profileCode.formattedExclusiveTicks,
272 'Cpu time' : node.profileCode.formattedCpuTime,
273 'Inclusive ticks' : node.profileCode.formattedInclusiveTicks,
274 'Call stack time' : node.profileCode.formattedOnStackTime,
275 });
276
277 makeInfoButton();
278 methodColumn.children.add(infoButton);
279
280 // Fill in self column.
281 var selfColumn = flexColumns[1];
282 selfColumn.style.position = 'relative';
283 selfColumn.style.alignItems = 'center';
284 selfColumn.text = selfPercent;
285 }
286 }
287
288 class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> {
289 FunctionProfileTreeRow(TableTree tree, FunctionProfileTreeRow parent,
290 CpuProfile profile, FunctionCallTreeNode node)
291 : super(tree, parent, profile, node,
292 node.profileFunction.normalizedExclusiveTicks,
293 node.percentage) {
294 // fill out attributes.
295 }
296
297 bool hasChildren() => node.children.length > 0;
298
299 onShow() {
300 super.onShow();
301 if (children.length == 0) {
302 for (var childNode in node.children) {
303 var row = new FunctionProfileTreeRow(tree, this, profile, childNode);
304 children.add(row);
305 }
306 }
307
308 var methodColumn = flexColumns[0];
309 methodColumn.style.justifyContent = 'flex-start';
310
311 var codeAndFunctionColumn = new DivElement();
312 codeAndFunctionColumn.classes.add('flex-column');
313 codeAndFunctionColumn.style.justifyContent = 'center';
314 codeAndFunctionColumn.style.width = '100%';
315 methodColumn.children.add(codeAndFunctionColumn);
316
317 var functionRow = new DivElement();
318 functionRow.classes.add('flex-row');
319 functionRow.style.position = 'relative';
320 functionRow.style.justifyContent = 'flex-start';
321 codeAndFunctionColumn.children.add(functionRow);
322
323 // Insert the parent percentage
324 var parentPercent = new SpanElement();
325 parentPercent.text = percent;
326 parentPercent.style.minWidth = '4em';
327 parentPercent.style.alignSelf = 'center';
328 parentPercent.style.textAlign = 'right';
329 parentPercent.title = 'Executing: $selfPercent';
330 functionRow.children.add(parentPercent);
331
332 // Gap.
333 var gap = new SpanElement();
334 gap.style.minWidth = '1em';
335 gap.text = ' ';
336 functionRow.children.add(gap);
337
338 var functionRef = new Element.tag('function-ref');
339 functionRef.ref = node.profileFunction.function;
340 functionRef.style.alignSelf = 'center';
341 functionRow.children.add(functionRef);
342
343 gap = new SpanElement();
344 gap.style.minWidth = '1em';
345 gap.text = ' ';
346 functionRow.children.add(gap);
347
348 for (var attribute in sorted(node.attributes)) {
349 functionRow.children.add(newAttributeBox(attribute));
350 }
351
352 makeInfoBox();
353 functionRow.children.add(infoBox);
354
355 if (M.hasDartCode(node.profileFunction.function.kind)) {
356 infoBox.children.add(div('Code for current node'));
357 infoBox.children.add(br());
358 var totalTicks = node.totalCodesTicks;
359 var numCodes = node.codes.length;
360 for (var i = 0; i < numCodes; i++) {
361 var codeRowSpan = new DivElement();
362 codeRowSpan.style.paddingLeft = '1em';
363 infoBox.children.add(codeRowSpan);
364 var nodeCode = node.codes[i];
365 var ticks = nodeCode.ticks;
366 var percentage = Utils.formatPercent(ticks, totalTicks);
367 var percentageSpan = new SpanElement();
368 percentageSpan.style.display = 'inline-block';
369 percentageSpan.text = '$percentage';
370 percentageSpan.style.minWidth = '5em';
371 percentageSpan.style.textAlign = 'right';
372 codeRowSpan.children.add(percentageSpan);
373 var codeRef = new Element.tag('code-ref');
374 codeRef.ref = nodeCode.code.code;
375 codeRef.style.marginLeft = '1em';
376 codeRef.style.marginRight = 'auto';
377 codeRef.style.width = '100%';
378 codeRowSpan.children.add(codeRef);
379 }
380 infoBox.children.add(hr());
381 }
382 infoBox.children.add(span('Function '));
383 infoBox.children.add(newFunctionRef(node.profileFunction));
384 infoBox.children.add(span(' '));
385 for (var attribute in sorted(node.profileFunction.attributes)) {
386 infoBox.children.add(newAttributeBox(attribute));
387 }
388 var memberList = new DivElement();
389 memberList.classes.add('memberList');
390 infoBox.children.add(br());
391 infoBox.children.add(br());
392 infoBox.children.add(memberList);
393 infoBox.children.add(br());
394 ProfileTreeRow._addToMemberList(memberList, {
395 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks,
396 'Cpu time' : node.profileFunction.formattedCpuTime,
397 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks,
398 'Call stack time' : node.profileFunction.formattedOnStackTime,
399 });
400
401 if (M.hasDartCode(node.profileFunction.function.kind)) {
402 infoBox.children.add(div('Code containing function'));
403 infoBox.children.add(br());
404 var totalTicks = profile.sampleCount;
405 var codes = node.profileFunction.profileCodes;
406 var numCodes = codes.length;
407 for (var i = 0; i < numCodes; i++) {
408 var codeRowSpan = new DivElement();
409 codeRowSpan.style.paddingLeft = '1em';
410 infoBox.children.add(codeRowSpan);
411 var profileCode = codes[i];
412 var code = profileCode.code;
413 var ticks = profileCode.inclusiveTicks;
414 var percentage = Utils.formatPercent(ticks, totalTicks);
415 var percentageSpan = new SpanElement();
416 percentageSpan.style.display = 'inline-block';
417 percentageSpan.text = '$percentage';
418 percentageSpan.style.minWidth = '5em';
419 percentageSpan.style.textAlign = 'right';
420 percentageSpan.title = 'Inclusive ticks';
421 codeRowSpan.children.add(percentageSpan);
422 var codeRef = new Element.tag('code-ref');
423 codeRef.ref = code;
424 codeRef.style.marginLeft = '1em';
425 codeRef.style.marginRight = 'auto';
426 codeRef.style.width = '100%';
427 codeRowSpan.children.add(codeRef);
428 }
429 }
430
431 makeInfoButton();
432 methodColumn.children.add(infoButton);
433
434 // Fill in self column.
435 var selfColumn = flexColumns[1];
436 selfColumn.style.position = 'relative';
437 selfColumn.style.alignItems = 'center';
438 selfColumn.text = selfPercent;
439 }
440 }
441
442 class NameSortedTable extends SortedTable {
443 NameSortedTable(columns) : super(columns);
444 @override 114 @override
445 dynamic getSortKeyFor(int row, int col) {
446 if (col == FUNCTION_COLUMN) {
447 // Use name as sort key.
448 return rows[row].values[col].name;
449 }
450 return super.getSortKeyFor(row, col);
451 }
452
453 SortedTableRow rowFromIndex(int tableIndex) {
454 final modelIndex = sortedRows[tableIndex];
455 return rows[modelIndex];
456 }
457
458 static const FUNCTION_SPACER_COLUMNS = const [];
459 static const FUNCTION_COLUMN = 2;
460 TableRowElement _makeFunctionRow() {
461 var tr = new TableRowElement();
462 var cell;
463
464 // Add percentage.
465 cell = tr.insertCell(-1);
466 cell = tr.insertCell(-1);
467
468 // Add function ref.
469 cell = tr.insertCell(-1);
470 var functionRef = new Element.tag('function-ref');
471 cell.children.add(functionRef);
472
473 return tr;
474 }
475
476 static const CALL_SPACER_COLUMNS = const [];
477 static const CALL_FUNCTION_COLUMN = 1;
478 TableRowElement _makeCallRow() {
479 var tr = new TableRowElement();
480 var cell;
481
482 // Add percentage.
483 cell = tr.insertCell(-1);
484 // Add function ref.
485 cell = tr.insertCell(-1);
486 var functionRef = new Element.tag('function-ref');
487 cell.children.add(functionRef);
488 return tr;
489 }
490
491 _updateRow(TableRowElement tr,
492 int rowIndex,
493 List spacerColumns,
494 int refColumn) {
495 var row = rows[rowIndex];
496 // Set reference
497 var ref = tr.children[refColumn].children[0];
498 ref.ref = row.values[refColumn];
499
500 for (var i = 0; i < row.values.length; i++) {
501 if (spacerColumns.contains(i) || (i == refColumn)) {
502 // Skip spacer columns.
503 continue;
504 }
505 var cell = tr.children[i];
506 cell.title = row.values[i].toString();
507 cell.text = getFormattedValue(rowIndex, i);
508 }
509 }
510
511 _updateTableView(HtmlElement table,
512 HtmlElement makeEmptyRow(),
513 void onRowClick(TableRowElement tr),
514 List spacerColumns,
515 int refColumn) {
516 assert(table != null);
517
518 // Resize DOM table.
519 if (table.children.length > sortedRows.length) {
520 // Shrink the table.
521 var deadRows = table.children.length - sortedRows.length;
522 for (var i = 0; i < deadRows; i++) {
523 table.children.removeLast();
524 }
525 } else if (table.children.length < sortedRows.length) {
526 // Grow table.
527 var newRows = sortedRows.length - table.children.length;
528 for (var i = 0; i < newRows; i++) {
529 var row = makeEmptyRow();
530 row.onClick.listen((e) {
531 e.stopPropagation();
532 e.preventDefault();
533 onRowClick(row);
534 });
535 table.children.add(row);
536 }
537 }
538
539 assert(table.children.length == sortedRows.length);
540
541 // Fill table.
542 for (var i = 0; i < sortedRows.length; i++) {
543 var rowIndex = sortedRows[i];
544 var tr = table.children[i];
545 _updateRow(tr, rowIndex, spacerColumns, refColumn);
546 }
547 }
548 }
549
550 @CustomTag('cpu-profile-table')
551 class CpuProfileTableElement extends ObservatoryElement {
552
553
554 CpuProfileTableElement.created() : super.created() {
555 _updateTask = new Task(update);
556 _renderTask = new Task(render);
557 var columns = [
558 new SortedTableColumn.withFormatter('Executing (%)',
559 Utils.formatPercentNormalized),
560 new SortedTableColumn.withFormatter('In stack (%)',
561 Utils.formatPercentNormalized),
562 new SortedTableColumn('Method'),
563 ];
564 profileTable = new NameSortedTable(columns);
565 profileTable.sortColumnIndex = 0;
566
567 columns = [
568 new SortedTableColumn.withFormatter('Callees (%)',
569 Utils.formatPercentNormalized),
570 new SortedTableColumn('Method')
571 ];
572 profileCalleesTable = new NameSortedTable(columns);
573 profileCalleesTable.sortColumnIndex = 0;
574
575 columns = [
576 new SortedTableColumn.withFormatter('Callers (%)',
577 Utils.formatPercentNormalized),
578 new SortedTableColumn('Method')
579 ];
580 profileCallersTable = new NameSortedTable(columns);
581 profileCallersTable.sortColumnIndex = 0;
582 }
583
584 attached() { 115 attached() {
585 super.attached(); 116 super.attached();
586 _updateTask.queue(); 117 _r.enable();
587 _resizeSubscription = window.onResize.listen((_) => _updateSize()); 118 _request();
588 _updateSize(); 119 }
589 } 120
590 121 @override
591 detached() { 122 detached() {
592 super.detached(); 123 super.detached();
593 if (_resizeSubscription != null) { 124 _r.disable(notify: true);
594 _resizeSubscription.cancel(); 125 children = [];
595 } 126 }
596 } 127
597 128 void render() {
598 _updateSize() { 129 var content = [
599 HtmlElement e = $['main']; 130 new NavBarElement(queue: _r.queue)
600 final totalHeight = window.innerHeight; 131 ..children = [
601 final top = e.offset.top; 132 new NavTopMenuElement(queue: _r.queue),
602 final bottomMargin = 32; 133 new NavVMMenuElement(_vm, _events, queue: _r.queue),
603 final mainHeight = totalHeight - top - bottomMargin; 134 new NavIsolateMenuElement(_isolate, _events, queue: _r.queue),
604 e.style.setProperty('height', '${mainHeight}px'); 135 new NavMenuElement('cpu profile (table)',
605 } 136 link: Uris.profiler(_isolate), last: true, queue: _r.queue),
606 137 new NavRefreshElement(queue: _r.queue)
607 isolateChanged(oldValue) { 138 ..onRefresh.listen(_refresh),
608 _updateTask.queue(); 139 new NavRefreshElement(label: 'Clear', queue: _r.queue)
609 } 140 ..onRefresh.listen(_clearCpuProfile),
610 141 new NavNotifyElement(_notifications, queue: _r.queue)
611 update() async { 142 ],
612 _clearView(); 143 ];
613 if (isolate == null) { 144 if (_progress == null) {
145 children = content;
614 return; 146 return;
615 } 147 }
616 final stream = _repository.get(isolate, M.SampleProfileTag.none); 148 content.add(new SampleBufferControlElement(_progress, _progressStream,
617 var progress = (await stream.first).progress; 149 showTag: false, queue: _r.queue));
618 shadowRoot.querySelector('#sampleBufferControl').children = [ 150 if (_progress.status == M.SampleProfileLoadingStatus.loaded) {
619 sampleBufferControlElement = 151 content.add(new BRElement());
620 new SampleBufferControlElement(progress, stream, showTag: false, 152 content.addAll(_createTables());
621 selectedTag: M.SampleProfileTag.none, queue: app.queue) 153 content.add(new BRElement());
154 content.addAll(_createTree());
155 }
156 children = content;
157 }
158
159 M.ProfileFunction _selected;
160 VirtualCollectionElement _functions;
161 VirtualCollectionElement _callers;
162 VirtualCollectionElement _callees;
163
164 List<Element> _createTables() {
165 _functions = _functions ?? new VirtualCollectionElement(
166 _createFunction,
167 _updateFunction,
168 createHeader: _createFunctionHeader,
169 queue: _r.queue
170 );
171 _functions.items = _progress.profile.functions.toList()
172 ..sort(_createSorter(_Table.functions));
173 _functions.takeIntoView(_selected);
174 _callers = _callers ?? new VirtualCollectionElement(
175 _createCaller,
176 _updateCaller,
177 createHeader: _createCallerHeader,
178 queue: _r.queue
179 );
180 _callees = _callees ?? new VirtualCollectionElement(
181 _createCallee,
182 _updateCallee,
183 createHeader: _createCalleeHeader,
184 queue: _r.queue
185 );
186 if (_selected != null) {
187 _callers.items = _selected.callers.keys.toList()
188 ..sort(_createSorter(_Table.caller));
189 _callees.items = _selected.callees.keys.toList()
190 ..sort(_createSorter(_Table.callee));
191 } else {
192 _callers.items = const [];
193 _callees.items = const [];
194 }
195 return [
196 new DivElement()..classes = ['profile-trees']
197 ..children = [
198 new DivElement()..classes = ['profile-trees-all']
199 ..children = [_functions],
200 new DivElement()..classes = ['profile-trees-current']
201 ..children = [
202 new DivElement()..classes = ['profile-trees-caller']
203 ..children = [_callers],
204 new DivElement()..classes = ['profile-trees-selected']
205 ..children = _selected == null
206 ? [new SpanElement()..text = 'No element selected']
207 : [new FunctionRefElement(_isolate, _selected.function,
208 queue : _r.queue)],
209 new DivElement()..classes = ['profile-trees-callee']
210 ..children = [_callees]
211 ]
212 ]
622 ]; 213 ];
623 if (M.isSampleProcessRunning(progress.status)) { 214 }
624 progress = (await stream.last).progress; 215
625 } 216 Element _createFunction() {
626 if (progress.status == M.SampleProfileLoadingStatus.loaded) { 217 final element = new DivElement()
627 _profile = progress.profile; 218 ..classes = const ['function-item']
628 shadowRoot.querySelector('#stackTraceTreeConfig').children = [ 219 ..children = [
629 stackTraceTreeConfigElement = 220 new SpanElement()..classes = const ['exclusive']
630 new StackTraceTreeConfigElement(showMode: false, 221 ..text = '0%',
631 showDirection: false, mode: ProfileTreeMode.function, 222 new SpanElement()..classes = const ['inclusive']
632 direction: M.ProfileTreeDirection.exclusive, queue: app.queue) 223 ..text = '0%',
633 ..onModeChange.listen((e) { 224 new SpanElement()..classes = const ['name']
634 cpuProfileTreeElement.mode = e.element.mode; 225 ];
635 _renderTask.queue(); 226 element.onClick.listen((_) {
636 }) 227 _selected = _functions.getItemFromElement(element);
637 ..onDirectionChange.listen((e) { 228 _r.dirty();
638 cpuProfileTreeElement.direction = e.element.direction; 229 });
639 _renderTask.queue(); 230 return element;
640 }) 231 }
641 ..onFilterChange.listen((e) =>_renderTask.queue()) 232
642 ]; 233 void _updateFunction(Element e, M.ProfileFunction item, int index) {
643 shadowRoot.querySelector('#cpuProfileTree').children = [ 234 if (item == _selected) {
644 cpuProfileTreeElement = 235 e.classes = const ['function-item', 'selected'];
645 new CpuProfileVirtualTreeElement(isolate, _profile,
646 queue: app.queue)
647 ];
648 }
649 _renderTask.queue();
650 }
651
652 Future clearCpuProfile() async {
653 await isolate.invokeRpc('_clearCpuProfile', { });
654 _updateTask.queue();
655 return new Future.value(null);
656 }
657
658 Future refresh() {
659 _updateTask.queue();
660 return new Future.value(null);
661 }
662
663 render() {
664 _updateView();
665 }
666
667 checkParameters() {
668 if (isolate == null) {
669 return;
670 }
671 var functionId = app.locationManager.uri.queryParameters['functionId'];
672 var functionName =
673 app.locationManager.uri.queryParameters['functionName'];
674 if (functionId == '') {
675 // Fallback to searching by name.
676 _focusOnFunction(_findFunction(functionName));
677 } else { 236 } else {
678 if (functionId == null) { 237 e.classes = const ['function-item'];
679 _focusOnFunction(null); 238 }
680 return; 239 e.children[0].text = Utils.formatPercentNormalized(_getExclusiveT(item));
240 e.children[1].text = Utils.formatPercentNormalized(_getInclusiveT(item));
241 e.children[2] = new FunctionRefElement(_isolate, item.function,
242 queue: _r.queue)..classes = const ['name'];
243 }
244
245 Element _createFunctionHeader() =>
246 new DivElement()
247 ..classes = const ['function-item']
248 ..children = [
249 _createHeaderButton(const ['exclusive'], 'Execution(%)',
250 _Table.functions,
251 _SortingField.exclusive,
252 _SortingDirection.descending),
253 _createHeaderButton(const ['inclusive'], 'Stack(%)',
254 _Table.functions,
255 _SortingField.inclusive,
256 _SortingDirection.descending),
257 _createHeaderButton(const ['name'], 'Method',
258 _Table.functions,
259 _SortingField.method,
260 _SortingDirection.descending),
261 ];
262
263 void _setSorting(_Table table,
264 _SortingField field,
265 _SortingDirection defaultDirection) {
266 if (_sortingField[table] == field) {
267 switch (_sortingDirection[table]) {
268 case _SortingDirection.descending:
269 _sortingDirection[table] = _SortingDirection.ascending;
270 break;
271 case _SortingDirection.ascending:
272 _sortingDirection[table] = _SortingDirection.descending;
273 break;
274 }
275 } else {
276 _sortingDirection[table] = defaultDirection;
277 _sortingField[table] = field;
681 } 278 }
682 isolate.getObject(functionId).then((func) => _focusOnFunction(func)); 279 _r.dirty();
683 } 280 }
684 } 281
685 282 Element _createCallee() {
686 _clearView() { 283 final element = new DivElement()
687 profileTable.clearRows(); 284 ..classes = const ['function-item']
688 _renderTable(); 285 ..children = [
689 } 286 new SpanElement()..classes = const ['inclusive']
690 287 ..text = '0%',
691 _updateView() { 288 new SpanElement()..classes = const ['name']
692 _buildFunctionTable(); 289 ];
693 _renderTable(); 290 element.onClick.listen((_) {
694 _updateFunctionTreeView(); 291 _selected = _callees.getItemFromElement(element);
695 } 292 _r.dirty();
696
697 int _findFunctionRow(ServiceFunction function) {
698 for (var i = 0; i < profileTable.sortedRows.length; i++) {
699 var rowIndex = profileTable.sortedRows[i];
700 var row = profileTable.rows[rowIndex];
701 if (row.values[NameSortedTable.FUNCTION_COLUMN] == function) {
702 return i;
703 }
704 }
705 return -1;
706 }
707
708 _scrollToFunction(ServiceFunction function) {
709 TableSectionElement tableBody = $['profile-table'];
710 var row = _findFunctionRow(function);
711 if (row == -1) {
712 return;
713 }
714 tableBody.children[row].classes.remove('shake');
715 // trigger reflow.
716 tableBody.children[row].offsetHeight;
717 tableBody.children[row].scrollIntoView(ScrollAlignment.CENTER);
718 tableBody.children[row].classes.add('shake');
719 // Focus on clicked function.
720 _focusOnFunction(function);
721 }
722
723 _clearFocusedFunction() {
724 TableSectionElement tableBody = $['profile-table'];
725 // Clear current focus.
726 if (focusedRow != null) {
727 tableBody.children[focusedRow].classes.remove('focused');
728 }
729 focusedRow = null;
730 focusedFunction = null;
731 }
732
733 ServiceFunction _findFunction(String functionName) {
734 for (var func in _profile.functions) {
735 if (func.function.name == functionName) {
736 return func.function;
737 }
738 }
739 return null;
740 }
741
742 _focusOnFunction(ServiceFunction function) {
743 if (focusedFunction == function) {
744 // Do nothing.
745 return;
746 }
747
748 _clearFocusedFunction();
749
750 if (function == null) {
751 _updateFunctionTreeView();
752 _clearCallTables();
753 return;
754 }
755
756 var row = _findFunctionRow(function);
757 if (row == -1) {
758 _updateFunctionTreeView();
759 _clearCallTables();
760 return;
761 }
762
763 focusedRow = row;
764 focusedFunction = function;
765
766 TableSectionElement tableBody = $['profile-table'];
767 tableBody.children[focusedRow].classes.add('focused');
768 _updateFunctionTreeView();
769 _buildCallersTable(focusedFunction);
770 _buildCalleesTable(focusedFunction);
771 }
772
773 _onRowClick(TableRowElement tr) {
774 var tableBody = $['profile-table'];
775 var row = profileTable.rowFromIndex(tableBody.children.indexOf(tr));
776 var function = row.values[NameSortedTable.FUNCTION_COLUMN];
777 app.locationManager.goReplacingParameters(
778 {
779 'functionId': function.id,
780 'functionName': function.vmName
781 }
782 );
783 }
784
785 _renderTable() {
786 profileTable._updateTableView($['profile-table'],
787 profileTable._makeFunctionRow,
788 _onRowClick,
789 NameSortedTable.FUNCTION_SPACER_COLUMNS,
790 NameSortedTable.FUNCTION_COLUMN);
791 }
792
793 _buildFunctionTable() {
794 for (var func in _profile.functions) {
795 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) {
796 // Skip.
797 continue;
798 }
799 var row = [
800 func.normalizedExclusiveTicks,
801 func.normalizedInclusiveTicks,
802 func.function,
803 ];
804 profileTable.addRow(new SortedTableRow(row));
805 }
806 profileTable.sort();
807 }
808
809 _renderCallTable(TableSectionElement view,
810 NameSortedTable model,
811 void onRowClick(TableRowElement tr)) {
812 model._updateTableView(view,
813 model._makeCallRow,
814 onRowClick,
815 NameSortedTable.CALL_SPACER_COLUMNS,
816 NameSortedTable.CALL_FUNCTION_COLUMN);
817 }
818
819 _buildCallTable(Map<ProfileFunction, int> calls,
820 NameSortedTable model) {
821 model.clearRows();
822 if (calls == null) {
823 return;
824 }
825 var sum = 0;
826 calls.values.forEach((i) => sum += i);
827 calls.forEach((func, count) {
828 var row = [
829 count / sum,
830 func.function,
831 ];
832 model.addRow(new SortedTableRow(row));
833 }); 293 });
834 model.sort(); 294 return element;
835 } 295 }
836 296
837 _clearCallTables() { 297 void _updateCallee(Element e, item, int index) {
838 _buildCallersTable(null); 298 e.children[0].text = Utils.formatPercentNormalized(_getCalleeT(item));
839 _buildCalleesTable(null); 299 e.children[1] = new FunctionRefElement(_isolate, item.function,
840 } 300 queue: _r.queue)..classes = const ['name'];
841 301 }
842 _onCallersClick(TableRowElement tr) { 302
843 var table = $['callers-table']; 303 Element _createCalleeHeader() =>
844 final row = profileCallersTable.rowFromIndex(table.children.indexOf(tr)); 304 new DivElement()
845 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; 305 ..classes = const ['function-item']
846 _scrollToFunction(function); 306 ..children = [
847 } 307 _createHeaderButton(const ['inclusive'], 'Callees(%)',
848 308 _Table.callee,
849 _buildCallersTable(ServiceFunction function) { 309 _SortingField.callee,
850 var calls = (function != null) ? function.profile.callers : null; 310 _SortingDirection.descending),
851 var table = $['callers-table']; 311 _createHeaderButton(const ['name'], 'Method',
852 _buildCallTable(calls, profileCallersTable); 312 _Table.callee,
853 _renderCallTable(table, profileCallersTable, _onCallersClick); 313 _SortingField.method,
854 } 314 _SortingDirection.descending),
855 315 ];
856 _onCalleesClick(TableRowElement tr) { 316
857 var table = $['callees-table']; 317 Element _createCaller() {
858 final row = profileCalleesTable.rowFromIndex(table.children.indexOf(tr)); 318 final element = new DivElement()
859 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; 319 ..classes = const ['function-item']
860 _scrollToFunction(function); 320 ..children = [
861 } 321 new SpanElement()..classes = const ['inclusive']
862 322 ..text = '0%',
863 _buildCalleesTable(ServiceFunction function) { 323 new SpanElement()..classes = const ['name']
864 var calls = (function != null) ? function.profile.callees : null; 324 ];
865 var table = $['callees-table']; 325 element.onClick.listen((_) {
866 _buildCallTable(calls, profileCalleesTable); 326 _selected = _callers.getItemFromElement(element);
867 _renderCallTable(table, profileCalleesTable, _onCalleesClick); 327 _r.dirty();
868 } 328 });
869 329 return element;
870 _changeSort(Element target, NameSortedTable table) { 330 }
871 if (target is TableCellElement) { 331
872 if (table.sortColumnIndex != target.cellIndex) { 332 void _updateCaller(Element e, item, int index) {
873 table.sortColumnIndex = target.cellIndex; 333 e.children[0].text = Utils.formatPercentNormalized(_getCallerT(item));
874 table.sortDescending = true; 334 e.children[1] = new FunctionRefElement(_isolate, item.function,
875 } else { 335 queue: _r.queue)..classes = const ['name'];
876 table.sortDescending = !profileTable.sortDescending; 336 }
877 } 337
878 table.sort(); 338 Element _createCallerHeader() =>
879 } 339 new DivElement()
880 } 340 ..classes = const ['function-item']
881 341 ..children = [
882 changeSortProfile(Event e, var detail, Element target) { 342 _createHeaderButton(const ['inclusive'], 'Callers(%)',
883 _changeSort(target, profileTable); 343 _Table.caller,
884 _renderTable(); 344 _SortingField.caller,
885 } 345 _SortingDirection.descending),
886 346 _createHeaderButton(const ['name'], 'Method',
887 changeSortCallers(Event e, var detail, Element target) { 347 _Table.caller,
888 _changeSort(target, profileCallersTable); 348 _SortingField.method,
889 _renderCallTable($['callers-table'], profileCallersTable, _onCallersClick); 349 _SortingDirection.descending),
890 } 350 ];
891 351
892 changeSortCallees(Event e, var detail, Element target) { 352 ButtonElement _createHeaderButton(List<String> classes,
893 _changeSort(target, profileCalleesTable); 353 String text,
894 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); 354 _Table table,
895 } 355 _SortingField field,
896 356 _SortingDirection direction) =>
897 ////// 357 new ButtonElement()..classes = classes
898 /// 358 ..text = _sortingField[table] != field ? text :
899 /// Function tree. 359 _sortingDirection[table] == _SortingDirection.ascending
900 /// 360 ? '$textâ–¼' : '$textâ–²'
901 TableTree functionTree; 361 ..onClick.listen((_) => _setSorting(table, field, direction));
902 _updateFunctionTreeView() { 362
903 if (cpuProfileTreeElement == null) { 363 List<Element> _createTree() {
904 return; 364 CpuProfileVirtualTreeElement tree;
905 } 365 return [
906 cpuProfileTreeElement.filter = (FunctionCallTreeNode node) { 366 new StackTraceTreeConfigElement(showMode: false,
907 return node.profileFunction.function == focusedFunction; 367 showDirection: false, mode: ProfileTreeMode.function,
908 }; 368 direction: M.ProfileTreeDirection.exclusive, filter: _filter,
909 } 369 queue: _r.queue)
910 370 ..onFilterChange.listen((e) {
911 @published Isolate isolate; 371 _filter = e.element.filter.trim();
912 @observable NameSortedTable profileTable; 372 tree.filters = _filter.isNotEmpty
913 @observable NameSortedTable profileCallersTable; 373 ? [_filterTree, (node) { return node.name.contains(_filter); }]
914 @observable NameSortedTable profileCalleesTable; 374 : [_filterTree];
915 @observable ServiceFunction focusedFunction; 375 }),
916 @observable int focusedRow; 376 new BRElement(),
917 377 tree = new CpuProfileVirtualTreeElement(_isolate, _progress.profile,
918 378 mode: ProfileTreeMode.function,
919 StreamSubscription _resizeSubscription; 379 direction: M.ProfileTreeDirection.exclusive,
920 Task _updateTask; 380 queue: _r.queue)
921 Task _renderTask; 381 ..filters = _filter.isNotEmpty
922 382 ? [_filterTree, (node) { return node.name.contains(_filter); }]
923 IsolateSampleProfileRepository _repository = 383 : [_filterTree]
924 new IsolateSampleProfileRepository(); 384 ];
925 CpuProfile _profile; 385 }
926 SampleBufferControlElement sampleBufferControlElement; 386
927 StackTraceTreeConfigElement stackTraceTreeConfigElement; 387 bool _filterTree(M.FunctionCallTreeNode node) =>
928 CpuProfileVirtualTreeElement cpuProfileTreeElement; 388 node.profileFunction == _selected;
389
390 Future _request({bool clear: false, bool forceFetch: false}) async {
391 _progress = null;
392 _progressStream = _profiles.get(isolate, M.SampleProfileTag.none,
393 clear: clear, forceFetch: forceFetch);
394 _r.dirty();
395 _progress = (await _progressStream.first).progress;
396 _r.dirty();
397 if (M.isSampleProcessRunning(_progress.status)) {
398 _progress = (await _progressStream.last).progress;
399 _r.dirty();
400 }
401 }
402
403 Future _clearCpuProfile(RefreshEvent e) async {
404 e.element.disabled = true;
405 await _request(clear: true);
406 e.element.disabled = false;
407 }
408
409 Future _refresh(e) async {
410 e.element.disabled = true;
411 await _request(forceFetch: true);
412 e.element.disabled = false;
413 }
414
415 _createSorter(_Table table) {
416 var getter;
417 switch (_sortingField[table]) {
418 case _SortingField.exclusive:
419 getter = _getExclusiveT;
420 break;
421 case _SortingField.inclusive:
422 getter = _getInclusiveT;
423 break;
424 case _SortingField.callee:
425 getter = _getCalleeT;
426 break;
427 case _SortingField.caller:
428 getter = _getCallerT;
429 break;
430 case _SortingField.method:
431 getter = (M.ProfileFunction s) =>
432 M.getFunctionFullName(s.function);
433 break;
434 }
435 switch (_sortingDirection[table]) {
436 case _SortingDirection.ascending:
437 return (a, b) => getter(a).compareTo(getter(b));
438 case _SortingDirection.descending:
439 return (a, b) => getter(b).compareTo(getter(a));
440 }
441 }
442
443 static double _getExclusiveT(M.ProfileFunction f) =>
444 f.normalizedExclusiveTicks;
445 static double _getInclusiveT(M.ProfileFunction f) =>
446 f.normalizedExclusiveTicks;
447 double _getCalleeT(M.ProfileFunction f) =>
448 _selected.callees[f] / _selected.callees.values.reduce((a, b) => a + b);
449 double _getCallerT(M.ProfileFunction f) =>
450 _selected.callers[f] / _selected.callers.values.reduce((a, b) => a + b);
929 } 451 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698