OLD | NEW |
---|---|
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 'dart:math' as Math; | |
9 import 'observatory_element.dart'; | 10 import 'observatory_element.dart'; |
10 import 'package:logging/logging.dart'; | 11 import 'package:logging/logging.dart'; |
11 import 'package:observatory/service.dart'; | 12 import 'package:observatory/service.dart'; |
12 import 'package:observatory/app.dart'; | 13 import 'package:observatory/app.dart'; |
13 import 'package:observatory/cpu_profile.dart'; | 14 import 'package:observatory/cpu_profile.dart'; |
14 import 'package:observatory/elements.dart'; | 15 import 'package:observatory/elements.dart'; |
15 import 'package:polymer/polymer.dart'; | 16 import 'package:polymer/polymer.dart'; |
16 | 17 |
17 List<String> sorted(Set<String> attributes) { | 18 List<String> sorted(Set<String> attributes) { |
18 var list = attributes.toList(); | 19 var list = attributes.toList(); |
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
218 // Fill in method column. | 219 // Fill in method column. |
219 var methodColumn = flexColumns[0]; | 220 var methodColumn = flexColumns[0]; |
220 methodColumn.style.justifyContent = 'flex-start'; | 221 methodColumn.style.justifyContent = 'flex-start'; |
221 methodColumn.style.position = 'relative'; | 222 methodColumn.style.position = 'relative'; |
222 | 223 |
223 // Percent. | 224 // Percent. |
224 var percentNode = new DivElement(); | 225 var percentNode = new DivElement(); |
225 percentNode.text = percent; | 226 percentNode.text = percent; |
226 percentNode.style.minWidth = '5em'; | 227 percentNode.style.minWidth = '5em'; |
227 percentNode.style.textAlign = 'right'; | 228 percentNode.style.textAlign = 'right'; |
228 percentNode.title = 'Self: $selfPercent'; | 229 percentNode.title = 'Executing: $selfPercent'; |
229 methodColumn.children.add(percentNode); | 230 methodColumn.children.add(percentNode); |
230 | 231 |
231 // Gap. | 232 // Gap. |
232 var gap = new SpanElement(); | 233 var gap = new SpanElement(); |
233 gap.style.minWidth = '1em'; | 234 gap.style.minWidth = '1em'; |
234 methodColumn.children.add(gap); | 235 methodColumn.children.add(gap); |
235 | 236 |
236 // Code link. | 237 // Code link. |
237 var codeRef = newCodeRef(node.profileCode); | 238 var codeRef = newCodeRef(node.profileCode); |
238 codeRef.style.alignSelf = 'center'; | 239 codeRef.style.alignSelf = 'center'; |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
313 functionRow.style.position = 'relative'; | 314 functionRow.style.position = 'relative'; |
314 functionRow.style.justifyContent = 'flex-start'; | 315 functionRow.style.justifyContent = 'flex-start'; |
315 codeAndFunctionColumn.children.add(functionRow); | 316 codeAndFunctionColumn.children.add(functionRow); |
316 | 317 |
317 // Insert the parent percentage | 318 // Insert the parent percentage |
318 var parentPercent = new SpanElement(); | 319 var parentPercent = new SpanElement(); |
319 parentPercent.text = percent; | 320 parentPercent.text = percent; |
320 parentPercent.style.minWidth = '4em'; | 321 parentPercent.style.minWidth = '4em'; |
321 parentPercent.style.alignSelf = 'center'; | 322 parentPercent.style.alignSelf = 'center'; |
322 parentPercent.style.textAlign = 'right'; | 323 parentPercent.style.textAlign = 'right'; |
323 parentPercent.title = 'Self: $selfPercent'; | 324 parentPercent.title = 'Executing: $selfPercent'; |
324 functionRow.children.add(parentPercent); | 325 functionRow.children.add(parentPercent); |
325 | 326 |
326 // Gap. | 327 // Gap. |
327 var gap = new SpanElement(); | 328 var gap = new SpanElement(); |
328 gap.style.minWidth = '1em'; | 329 gap.style.minWidth = '1em'; |
329 gap.text = ' '; | 330 gap.text = ' '; |
330 functionRow.children.add(gap); | 331 functionRow.children.add(gap); |
331 | 332 |
332 var functionRef = new Element.tag('function-ref'); | 333 var functionRef = new Element.tag('function-ref'); |
333 functionRef.ref = node.profileFunction.function; | 334 functionRef.ref = node.profileFunction.function; |
334 functionRef.style.alignSelf = 'center'; | 335 functionRef.style.alignSelf = 'center'; |
335 functionRow.children.add(functionRef); | 336 functionRow.children.add(functionRef); |
336 | 337 |
337 gap = new SpanElement(); | 338 gap = new SpanElement(); |
338 gap.style.minWidth = '1em'; | 339 gap.style.minWidth = '1em'; |
339 gap.text = ' '; | 340 gap.text = ' '; |
340 functionRow.children.add(gap); | 341 functionRow.children.add(gap); |
341 | 342 |
342 for (var attribute in sorted(node.attributes)) { | 343 for (var attribute in sorted(node.attributes)) { |
343 functionRow.children.add(newAttributeBox(attribute)); | 344 functionRow.children.add(newAttributeBox(attribute)); |
344 } | 345 } |
345 | 346 |
346 makeInfoBox(); | 347 makeInfoBox(); |
347 functionRow.children.add(infoBox); | 348 functionRow.children.add(infoBox); |
348 | 349 |
349 if (node.profileFunction.function.kind.hasDartCode()) { | 350 if (node.profileFunction.function.kind.hasDartCode()) { |
350 infoBox.children.add(div('Hot code for current node')); | 351 infoBox.children.add(div('Code for current node')); |
351 infoBox.children.add(br()); | 352 infoBox.children.add(br()); |
352 var totalTicks = node.totalCodesTicks; | 353 var totalTicks = node.totalCodesTicks; |
353 var numCodes = node.codes.length; | 354 var numCodes = node.codes.length; |
354 for (var i = 0; i < numCodes; i++) { | 355 for (var i = 0; i < numCodes; i++) { |
355 var codeRowSpan = new DivElement(); | 356 var codeRowSpan = new DivElement(); |
356 codeRowSpan.style.paddingLeft = '1em'; | 357 codeRowSpan.style.paddingLeft = '1em'; |
357 infoBox.children.add(codeRowSpan); | 358 infoBox.children.add(codeRowSpan); |
358 var nodeCode = node.codes[i]; | 359 var nodeCode = node.codes[i]; |
359 var ticks = nodeCode.ticks; | 360 var ticks = nodeCode.ticks; |
360 var percentage = Utils.formatPercent(ticks, totalTicks); | 361 var percentage = Utils.formatPercent(ticks, totalTicks); |
(...skipping 25 matching lines...) Expand all Loading... | |
386 infoBox.children.add(memberList); | 387 infoBox.children.add(memberList); |
387 infoBox.children.add(br()); | 388 infoBox.children.add(br()); |
388 ProfileTreeRow._addToMemberList(memberList, { | 389 ProfileTreeRow._addToMemberList(memberList, { |
389 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks, | 390 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks, |
390 'Cpu time' : node.profileFunction.formattedCpuTime, | 391 'Cpu time' : node.profileFunction.formattedCpuTime, |
391 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks, | 392 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks, |
392 'Call stack time' : node.profileFunction.formattedOnStackTime, | 393 'Call stack time' : node.profileFunction.formattedOnStackTime, |
393 }); | 394 }); |
394 | 395 |
395 if (node.profileFunction.function.kind.hasDartCode()) { | 396 if (node.profileFunction.function.kind.hasDartCode()) { |
396 infoBox.children.add(div('Hot code containing function')); | 397 infoBox.children.add(div('Code containing function')); |
397 infoBox.children.add(br()); | 398 infoBox.children.add(br()); |
398 var totalTicks = profile.sampleCount; | 399 var totalTicks = profile.sampleCount; |
399 var codes = node.profileFunction.profileCodes; | 400 var codes = node.profileFunction.profileCodes; |
400 var numCodes = codes.length; | 401 var numCodes = codes.length; |
401 for (var i = 0; i < numCodes; i++) { | 402 for (var i = 0; i < numCodes; i++) { |
402 var codeRowSpan = new DivElement(); | 403 var codeRowSpan = new DivElement(); |
403 codeRowSpan.style.paddingLeft = '1em'; | 404 codeRowSpan.style.paddingLeft = '1em'; |
404 infoBox.children.add(codeRowSpan); | 405 infoBox.children.add(codeRowSpan); |
405 var profileCode = codes[i]; | 406 var profileCode = codes[i]; |
406 var code = profileCode.code; | 407 var code = profileCode.code; |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
504 _sw.reset(); | 505 _sw.reset(); |
505 _sw.start(); | 506 _sw.start(); |
506 state = 'Requested'; | 507 state = 'Requested'; |
507 } | 508 } |
508 | 509 |
509 _onFetchFinished() { | 510 _onFetchFinished() { |
510 _sw.stop(); | 511 _sw.stop(); |
511 fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); | 512 fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); |
512 } | 513 } |
513 | 514 |
514 _onLoadStarted() { | 515 _onLoadStarted() async { |
515 _sw.reset(); | 516 _sw.reset(); |
516 _sw.start(); | 517 _sw.start(); |
517 state = 'Loading'; | 518 state = 'Loading'; |
519 var num = await window.animationFrame; | |
520 print('$num'); | |
turnidge
2015/04/07 21:24:31
Does this print belong here still?
Cutch
2015/04/14 21:45:15
Done.
| |
518 } | 521 } |
519 | 522 |
520 _onLoadFinished() { | 523 _onLoadFinished() { |
521 _sw.stop(); | 524 _sw.stop(); |
522 loadTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); | 525 loadTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); |
523 state = 'Loaded'; | 526 state = 'Loaded'; |
524 } | 527 } |
525 | 528 |
526 Future _getCpuProfile() { | 529 Future _getCpuProfile() { |
527 profile.clear(); | 530 profile.clear(); |
528 if (functionTree != null) { | 531 if (functionTree != null) { |
529 functionTree.clear(); | 532 functionTree.clear(); |
530 functionTree = null; | 533 functionTree = null; |
531 } | 534 } |
532 if (codeTree != null) { | 535 if (codeTree != null) { |
533 codeTree.clear(); | 536 codeTree.clear(); |
534 codeTree = null; | 537 codeTree = null; |
535 } | 538 } |
536 if (isolate == null) { | 539 if (isolate == null) { |
537 return new Future.value(null); | 540 return new Future.value(null); |
538 } | 541 } |
539 _onFetchStarted(); | 542 _onFetchStarted(); |
540 return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) | 543 return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) |
541 .then((response) { | 544 .then((response) async { |
542 _onFetchFinished(); | 545 _onFetchFinished(); |
543 _onLoadStarted(); | 546 await _onLoadStarted(); |
544 try { | 547 try { |
545 profile.load(isolate, response); | 548 profile.load(isolate, response); |
546 _onLoadFinished(); | 549 _onLoadFinished(); |
547 _updateView(); | 550 _updateView(); |
548 } catch (e, st) { | 551 } catch (e, st) { |
549 state = 'Exception'; | 552 state = 'Exception'; |
550 exception = e; | 553 exception = e; |
551 stackTrace = st; | 554 stackTrace = st; |
552 } | 555 } |
553 }).catchError((e, st) { | 556 }).catchError((e, st) { |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
604 codeTree = new TableTree(tableBody, 2); | 607 codeTree = new TableTree(tableBody, 2); |
605 } | 608 } |
606 var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive'); | 609 var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive'); |
607 if (tree == null) { | 610 if (tree == null) { |
608 return; | 611 return; |
609 } | 612 } |
610 var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root); | 613 var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root); |
611 codeTree.initialize(rootRow); | 614 codeTree.initialize(rootRow); |
612 } | 615 } |
613 } | 616 } |
617 | |
618 class NameSortedTable extends SortedTable { | |
619 NameSortedTable(columns) : super(columns); | |
620 @override | |
621 dynamic getSortKeyFor(int row, int col) { | |
622 if (col == FUNCTION_COLUMN) { | |
623 // Use name as sort key. | |
624 return rows[row].values[col].name; | |
625 } | |
626 return super.getSortKeyFor(row, col); | |
627 } | |
628 | |
629 SortedTableRow rowFromIndex(int tableIndex) { | |
630 final modelIndex = sortedRows[tableIndex]; | |
631 return rows[modelIndex]; | |
632 } | |
633 | |
634 static const FUNCTION_SPACER_COLUMNS = const []; | |
635 static const FUNCTION_COLUMN = 2; | |
636 TableRowElement _makeFunctionRow() { | |
637 var tr = new TableRowElement(); | |
638 var cell; | |
639 | |
640 // Add percentage. | |
641 cell = tr.insertCell(-1); | |
642 cell = tr.insertCell(-1); | |
643 | |
644 // Add function ref. | |
645 cell = tr.insertCell(-1); | |
646 var functionRef = new Element.tag('function-ref'); | |
647 cell.children.add(functionRef); | |
648 | |
649 return tr; | |
650 } | |
651 | |
652 static const CALL_SPACER_COLUMNS = const []; | |
653 static const CALL_FUNCTION_COLUMN = 1; | |
654 TableRowElement _makeCallRow() { | |
655 var tr = new TableRowElement(); | |
656 var cell; | |
657 | |
658 // Add percentage. | |
659 cell = tr.insertCell(-1); | |
660 // Add function ref. | |
661 cell = tr.insertCell(-1); | |
662 var functionRef = new Element.tag('function-ref'); | |
663 cell.children.add(functionRef); | |
664 return tr; | |
665 } | |
666 | |
667 _updateRow(TableRowElement tr, | |
668 int rowIndex, | |
669 List spacerColumns, | |
670 int refColumn) { | |
671 var row = rows[rowIndex]; | |
672 // Set reference | |
673 var ref = tr.children[refColumn].children[0]; | |
674 ref.ref = row.values[refColumn]; | |
675 | |
676 for (var i = 0; i < row.values.length; i++) { | |
677 if (spacerColumns.contains(i) || (i == refColumn)) { | |
678 // Skip spacer columns. | |
679 continue; | |
680 } | |
681 var cell = tr.children[i]; | |
682 cell.title = row.values[i].toString(); | |
683 cell.text = getFormattedValue(rowIndex, i); | |
684 } | |
685 } | |
686 | |
687 _updateTableView(HtmlElement table, | |
688 HtmlElement makeEmptyRow(), | |
689 void onRowClick(TableRowElement tr), | |
690 List spacerColumns, | |
691 int refColumn) { | |
692 assert(table != null); | |
693 | |
694 // Resize DOM table. | |
695 if (table.children.length > sortedRows.length) { | |
696 // Shrink the table. | |
697 var deadRows = table.children.length - sortedRows.length; | |
698 for (var i = 0; i < deadRows; i++) { | |
699 table.children.removeLast(); | |
700 } | |
701 } else if (table.children.length < sortedRows.length) { | |
702 // Grow table. | |
703 var newRows = sortedRows.length - table.children.length; | |
704 for (var i = 0; i < newRows; i++) { | |
705 var row = makeEmptyRow(); | |
706 row.onClick.listen((e) { | |
707 e.stopPropagation(); | |
708 e.preventDefault(); | |
709 onRowClick(row); | |
710 }); | |
711 table.children.add(row); | |
712 } | |
713 } | |
714 | |
715 assert(table.children.length == sortedRows.length); | |
716 | |
717 // Fill table. | |
718 for (var i = 0; i < sortedRows.length; i++) { | |
719 var rowIndex = sortedRows[i]; | |
720 var tr = table.children[i]; | |
721 _updateRow(tr, rowIndex, spacerColumns, refColumn); | |
722 } | |
723 } | |
724 } | |
725 | |
726 @CustomTag('cpu-profile-table') | |
727 class CpuProfileTableElement extends ObservatoryElement { | |
728 final Stopwatch _sw = new Stopwatch(); | |
729 final CpuProfile profile = new CpuProfile(); | |
730 StreamSubscription _resizeSubscription; | |
731 @observable NameSortedTable profileTable; | |
732 @observable NameSortedTable profileCallersTable; | |
733 @observable NameSortedTable profileCalleesTable; | |
734 @observable ServiceFunction focusedFunction; | |
735 @observable int focusedRow; | |
736 @observable String fetchTime = ''; | |
737 @observable String loadTime = ''; | |
738 @observable String state = 'Requested'; | |
739 @observable var exception; | |
740 @observable var stackTrace; | |
741 @observable Isolate isolate; | |
742 @observable String sampleCount = ''; | |
743 @observable String refreshTime = ''; | |
744 @observable String sampleRate = ''; | |
745 @observable String stackDepth = ''; | |
746 @observable String timeSpan = ''; | |
747 @observable String directionSelector = 'Up'; | |
748 | |
749 CpuProfileTableElement.created() : super.created() { | |
750 var columns = [ | |
751 new SortedTableColumn.withFormatter('Executing (%)', | |
752 Utils.formatPercentNormalized), | |
753 new SortedTableColumn.withFormatter('In stack (%)', | |
754 Utils.formatPercentNormalized), | |
755 new SortedTableColumn('Method'), | |
756 ]; | |
757 profileTable = new NameSortedTable(columns); | |
758 profileTable.sortColumnIndex = 0; | |
759 | |
760 columns = [ | |
761 new SortedTableColumn.withFormatter('Callees (%)', | |
762 Utils.formatPercentNormalized), | |
763 new SortedTableColumn('Method') | |
764 ]; | |
765 profileCalleesTable = new NameSortedTable(columns); | |
766 profileCalleesTable.sortColumnIndex = 0; | |
767 | |
768 columns = [ | |
769 new SortedTableColumn.withFormatter('Callers (%)', | |
770 Utils.formatPercentNormalized), | |
771 new SortedTableColumn('Method') | |
772 ]; | |
773 profileCallersTable = new NameSortedTable(columns); | |
774 profileCallersTable.sortColumnIndex = 0; | |
775 } | |
776 | |
777 attached() { | |
778 super.attached(); | |
779 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | |
780 _updateSize(); | |
781 } | |
782 | |
783 detached() { | |
784 super.detached(); | |
785 if (_resizeSubscription != null) { | |
786 _resizeSubscription.cancel(); | |
787 } | |
788 } | |
789 | |
790 _updateSize() { | |
791 HtmlElement e = $['main']; | |
792 final totalHeight = window.innerHeight; | |
793 final top = e.offset.top; | |
794 final bottomMargin = 32; | |
795 final mainHeight = totalHeight - top - bottomMargin; | |
796 e.style.setProperty('height', '${mainHeight}px'); | |
797 } | |
798 | |
799 isolateChanged() { | |
800 _getCpuProfile().whenComplete(checkParameters); | |
801 } | |
802 | |
803 checkParameters() { | |
804 var functionId = app.locationManager.fragment.parameters['functionId']; | |
805 if (functionId == null) { | |
806 _focusOnFunction(null); | |
807 return; | |
808 } | |
809 if (isolate == null) { | |
810 return; | |
811 } | |
812 isolate.getObject(functionId).then((func) => _focusOnFunction(func)); | |
813 } | |
814 | |
815 void directionSelectorChanged(oldValue) { | |
816 _updateFunctionTreeView(); | |
817 } | |
818 | |
819 void refresh(var done) { | |
820 _getCpuProfile().whenComplete(done); | |
821 } | |
822 | |
823 void clear(var done) { | |
824 _clearCpuProfile().whenComplete(done); | |
825 } | |
826 | |
827 _onFetchStarted() { | |
828 _sw.reset(); | |
829 _sw.start(); | |
830 state = 'Requested'; | |
831 } | |
832 | |
833 _onFetchFinished() { | |
834 _sw.stop(); | |
835 fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); | |
836 } | |
837 | |
838 _onLoadStarted() { | |
839 _sw.reset(); | |
840 _sw.start(); | |
841 state = 'Loading'; | |
842 } | |
843 | |
844 _onLoadFinished() { | |
845 _sw.stop(); | |
846 loadTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); | |
847 state = 'Loaded'; | |
848 } | |
849 | |
850 Future _clearCpuProfile() { | |
851 profile.clear(); | |
852 _clearView(); | |
853 if (isolate == null) { | |
854 return new Future.value(null); | |
855 } | |
856 return isolate.invokeRpc('clearCpuProfile', { }) | |
857 .then((ServiceMap response) { | |
858 _updateView(); | |
859 }); | |
860 } | |
861 | |
862 Future _getCpuProfile() { | |
863 profile.clear(); | |
864 _clearView(); | |
865 if (isolate == null) { | |
866 return new Future.value(null); | |
867 } | |
868 _onFetchStarted(); | |
869 return isolate.invokeRpc('getCpuProfile', { 'tags': 'None' }) | |
870 .then((response) { | |
871 _onFetchFinished(); | |
872 _onLoadStarted(); | |
873 try { | |
874 profile.load(isolate, response); | |
875 profile.markFunctionCalls(); | |
876 _onLoadFinished(); | |
877 _updateView(); | |
878 } catch (e, st) { | |
879 state = 'Exception'; | |
880 exception = e; | |
881 stackTrace = st; | |
882 } | |
883 }).catchError((e, st) { | |
884 state = 'Exception'; | |
885 exception = e; | |
886 stackTrace = st; | |
887 }); | |
888 } | |
889 | |
890 _clearView() { | |
891 profileTable.clearRows(); | |
892 _renderTable(); | |
893 } | |
894 | |
895 _updateView() { | |
896 sampleCount = profile.sampleCount.toString(); | |
897 refreshTime = new DateTime.now().toString(); | |
898 stackDepth = profile.stackDepth.toString(); | |
899 sampleRate = profile.sampleRate.toStringAsFixed(0); | |
900 timeSpan = formatTime(profile.timeSpan); | |
901 _buildFunctionTable(); | |
902 _renderTable(); | |
903 _updateFunctionTreeView(); | |
904 } | |
905 | |
906 int _findFunctionRow(ServiceFunction function) { | |
907 for (var i = 0; i < profileTable.sortedRows.length; i++) { | |
908 var rowIndex = profileTable.sortedRows[i]; | |
909 var row = profileTable.rows[rowIndex]; | |
910 if (row.values[NameSortedTable.FUNCTION_COLUMN] == function) { | |
911 return i; | |
912 } | |
913 } | |
914 return -1; | |
915 } | |
916 | |
917 _scrollToFunction(ServiceFunction function) { | |
918 TableSectionElement tableBody = $['profile-table']; | |
919 var row = _findFunctionRow(function); | |
920 if (row == -1) { | |
921 return; | |
922 } | |
923 tableBody.children[row].classes.remove('shake'); | |
924 // trigger reflow. | |
925 tableBody.children[row].offsetHeight; | |
926 tableBody.children[row].scrollIntoView(ScrollAlignment.CENTER); | |
927 tableBody.children[row].classes.add('shake'); | |
928 } | |
929 | |
930 _clearFocusedFunction() { | |
931 TableSectionElement tableBody = $['profile-table']; | |
932 // Clear current focus. | |
933 if (focusedRow != null) { | |
934 tableBody.children[focusedRow].classes.remove('focused'); | |
935 } | |
936 focusedRow = null; | |
937 focusedFunction = null; | |
938 } | |
939 | |
940 _focusOnFunction(ServiceFunction function) { | |
941 if (focusedFunction == function) { | |
942 return; | |
943 } | |
944 | |
945 _clearFocusedFunction(); | |
946 | |
947 if (function == null) { | |
948 _updateFunctionTreeView(); | |
949 return; | |
950 } | |
951 | |
952 var row = _findFunctionRow(function); | |
953 if (row == -1) { | |
954 _updateFunctionTreeView(); | |
955 return; | |
956 } | |
957 | |
958 focusedRow = row; | |
959 focusedFunction = function; | |
960 | |
961 app.locationManager.goParameter( | |
962 { | |
963 'functionId': focusedFunction.id | |
964 } | |
965 ); | |
966 | |
967 TableSectionElement tableBody = $['profile-table']; | |
968 tableBody.children[focusedRow].classes.add('focused'); | |
969 _updateFunctionTreeView(); | |
970 } | |
971 | |
972 _onRowClick(TableRowElement tr) { | |
973 var tableBody = $['profile-table']; | |
974 var row = profileTable.rowFromIndex(tableBody.children.indexOf(tr)); | |
975 var function = row.values[NameSortedTable.FUNCTION_COLUMN]; | |
976 _focusOnFunction(function); | |
977 _buildCallersTable(function); | |
978 _buildCalleesTable(function); | |
979 } | |
980 | |
981 _renderTable() { | |
982 profileTable._updateTableView($['profile-table'], | |
983 profileTable._makeFunctionRow, | |
984 _onRowClick, | |
985 NameSortedTable.FUNCTION_SPACER_COLUMNS, | |
986 NameSortedTable.FUNCTION_COLUMN); | |
987 } | |
988 | |
989 _buildFunctionTable() { | |
990 for (var func in profile.functions) { | |
991 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) { | |
992 // Skip. | |
993 continue; | |
994 } | |
995 var row = [ | |
996 func.normalizedExclusiveTicks, | |
997 func.normalizedInclusiveTicks, | |
998 func.function, | |
999 ]; | |
1000 profileTable.addRow(new SortedTableRow(row)); | |
1001 } | |
1002 profileTable.sort(); | |
1003 } | |
1004 | |
1005 _renderCallTable(TableSectionElement view, | |
1006 NameSortedTable model, | |
1007 void onRowClick(TableRowElement tr)) { | |
1008 model._updateTableView(view, | |
1009 model._makeCallRow, | |
1010 onRowClick, | |
1011 NameSortedTable.CALL_SPACER_COLUMNS, | |
1012 NameSortedTable.CALL_FUNCTION_COLUMN); | |
1013 } | |
1014 | |
1015 _buildCallTable(Map<ProfileFunction, int> calls, | |
1016 NameSortedTable model) { | |
1017 model.clearRows(); | |
1018 var sum = 0; | |
1019 calls.values.forEach((i) => sum += i); | |
1020 calls.forEach((func, count) { | |
1021 var row = [ | |
1022 count / sum, | |
1023 func.function, | |
1024 ]; | |
1025 model.addRow(new SortedTableRow(row)); | |
1026 }); | |
1027 model.sort(); | |
1028 } | |
1029 | |
1030 _onCallersClick(TableRowElement tr) { | |
1031 var table = $['callers-table']; | |
1032 final row = profileCallersTable.rowFromIndex(table.children.indexOf(tr)); | |
1033 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; | |
1034 _scrollToFunction(function); | |
1035 } | |
1036 | |
1037 _buildCallersTable(ServiceFunction function) { | |
1038 var profileFunction = function.profile; | |
1039 var calls = profileFunction.callers; | |
1040 var table = $['callers-table']; | |
1041 _buildCallTable(calls, profileCallersTable); | |
1042 _renderCallTable(table, profileCallersTable, _onCallersClick); | |
1043 } | |
1044 | |
1045 _onCalleesClick(TableRowElement tr) { | |
1046 var table = $['callees-table']; | |
1047 final row = profileCalleesTable.rowFromIndex(table.children.indexOf(tr)); | |
1048 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; | |
1049 _scrollToFunction(function); | |
1050 } | |
1051 | |
1052 _buildCalleesTable(ServiceFunction function) { | |
1053 var profileFunction = function.profile; | |
1054 var calls = profileFunction.callees; | |
1055 var table = $['callees-table']; | |
1056 _buildCallTable(calls, profileCalleesTable); | |
1057 _renderCallTable(table, profileCalleesTable, _onCalleesClick); | |
1058 } | |
1059 | |
1060 _changeSort(Element target, NameSortedTable table) { | |
1061 if (target is TableCellElement) { | |
1062 if (table.sortColumnIndex != target.cellIndex) { | |
1063 table.sortColumnIndex = target.cellIndex; | |
1064 table.sortDescending = true; | |
1065 } else { | |
1066 table.sortDescending = !profileTable.sortDescending; | |
1067 } | |
1068 table.sort(); | |
1069 } | |
1070 } | |
1071 | |
1072 changeSortProfile(Event e, var detail, Element target) { | |
1073 _changeSort(target, profileTable); | |
1074 _renderTable(); | |
1075 } | |
1076 | |
1077 changeSortCallers(Event e, var detail, Element target) { | |
1078 _changeSort(target, profileCallersTable); | |
1079 _renderCallTable($['callers-table'], profileCallersTable, _onCallersClick); | |
1080 } | |
1081 | |
1082 changeSortCallees(Event e, var detail, Element target) { | |
1083 _changeSort(target, profileCalleesTable); | |
1084 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); | |
1085 } | |
1086 | |
1087 ////// | |
1088 /// | |
1089 /// Function tree. | |
1090 /// | |
1091 TableTree functionTree; | |
1092 _updateFunctionTreeView() { | |
1093 if (functionTree != null) { | |
1094 functionTree.clear(); | |
1095 functionTree = null; | |
1096 } | |
1097 _buildFunctionTree(); | |
1098 } | |
1099 | |
1100 void _buildFunctionTree() { | |
1101 if (functionTree == null) { | |
1102 var tableBody = shadowRoot.querySelector('#treeBody'); | |
1103 assert(tableBody != null); | |
1104 functionTree = new TableTree(tableBody, 2); | |
1105 } | |
1106 if (focusedFunction == null) { | |
1107 return; | |
1108 } | |
1109 bool exclusive = directionSelector == 'Up'; | |
1110 var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive'); | |
1111 if (tree == null) { | |
1112 return; | |
1113 } | |
1114 var filter = (FunctionCallTreeNode node) { | |
1115 return node.profileFunction.function == focusedFunction; | |
1116 }; | |
1117 tree = tree.filtered(filter); | |
1118 var rootRow = | |
1119 new FunctionProfileTreeRow(functionTree, null, profile, tree.root); | |
1120 functionTree.initialize(rootRow); | |
1121 } | |
1122 } | |
OLD | NEW |