| 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_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'; | 9 import 'observatory_element.dart'; |
| 10 import 'package:observatory/models.dart' as M; | 10 import 'sample_buffer_control.dart'; |
| 11 import 'stack_trace_tree_config.dart'; |
| 12 import 'cpu_profile/virtual_tree.dart'; |
| 11 import 'package:observatory/service.dart'; | 13 import 'package:observatory/service.dart'; |
| 12 import 'package:observatory/app.dart'; | 14 import 'package:observatory/app.dart'; |
| 13 import 'package:observatory/cpu_profile.dart'; | 15 import 'package:observatory/cpu_profile.dart'; |
| 14 import 'package:observatory/elements.dart'; | 16 import 'package:observatory/elements.dart'; |
| 17 import 'package:observatory/models.dart' as M; |
| 18 import 'package:observatory/repositories.dart'; |
| 15 import 'package:polymer/polymer.dart'; | 19 import 'package:polymer/polymer.dart'; |
| 16 | 20 |
| 17 List<String> sorted(Set<String> attributes) { | 21 List<String> sorted(Set<String> attributes) { |
| 18 var list = attributes.toList(); | 22 var list = attributes.toList(); |
| 19 list.sort(); | 23 list.sort(); |
| 20 return list; | 24 return list; |
| 21 } | 25 } |
| 22 | 26 |
| 23 abstract class ProfileTreeRow<T> extends TableTreeRow { | 27 abstract class ProfileTreeRow<T> extends TableTreeRow { |
| 24 final CpuProfile profile; | 28 final CpuProfile profile; |
| (...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 428 methodColumn.children.add(infoButton); | 432 methodColumn.children.add(infoButton); |
| 429 | 433 |
| 430 // Fill in self column. | 434 // Fill in self column. |
| 431 var selfColumn = flexColumns[1]; | 435 var selfColumn = flexColumns[1]; |
| 432 selfColumn.style.position = 'relative'; | 436 selfColumn.style.position = 'relative'; |
| 433 selfColumn.style.alignItems = 'center'; | 437 selfColumn.style.alignItems = 'center'; |
| 434 selfColumn.text = selfPercent; | 438 selfColumn.text = selfPercent; |
| 435 } | 439 } |
| 436 } | 440 } |
| 437 | 441 |
| 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() { | |
| 564 super.detached(); | |
| 565 keyDownSubscription?.cancel(); | |
| 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 { | 442 class NameSortedTable extends SortedTable { |
| 900 NameSortedTable(columns) : super(columns); | 443 NameSortedTable(columns) : super(columns); |
| 901 @override | 444 @override |
| 902 dynamic getSortKeyFor(int row, int col) { | 445 dynamic getSortKeyFor(int row, int col) { |
| 903 if (col == FUNCTION_COLUMN) { | 446 if (col == FUNCTION_COLUMN) { |
| 904 // Use name as sort key. | 447 // Use name as sort key. |
| 905 return rows[row].values[col].name; | 448 return rows[row].values[col].name; |
| 906 } | 449 } |
| 907 return super.getSortKeyFor(row, col); | 450 return super.getSortKeyFor(row, col); |
| 908 } | 451 } |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 999 for (var i = 0; i < sortedRows.length; i++) { | 542 for (var i = 0; i < sortedRows.length; i++) { |
| 1000 var rowIndex = sortedRows[i]; | 543 var rowIndex = sortedRows[i]; |
| 1001 var tr = table.children[i]; | 544 var tr = table.children[i]; |
| 1002 _updateRow(tr, rowIndex, spacerColumns, refColumn); | 545 _updateRow(tr, rowIndex, spacerColumns, refColumn); |
| 1003 } | 546 } |
| 1004 } | 547 } |
| 1005 } | 548 } |
| 1006 | 549 |
| 1007 @CustomTag('cpu-profile-table') | 550 @CustomTag('cpu-profile-table') |
| 1008 class CpuProfileTableElement extends ObservatoryElement { | 551 class CpuProfileTableElement extends ObservatoryElement { |
| 552 |
| 553 |
| 1009 CpuProfileTableElement.created() : super.created() { | 554 CpuProfileTableElement.created() : super.created() { |
| 1010 _updateTask = new Task(update); | 555 _updateTask = new Task(update); |
| 1011 _renderTask = new Task(render); | 556 _renderTask = new Task(render); |
| 1012 var columns = [ | 557 var columns = [ |
| 1013 new SortedTableColumn.withFormatter('Executing (%)', | 558 new SortedTableColumn.withFormatter('Executing (%)', |
| 1014 Utils.formatPercentNormalized), | 559 Utils.formatPercentNormalized), |
| 1015 new SortedTableColumn.withFormatter('In stack (%)', | 560 new SortedTableColumn.withFormatter('In stack (%)', |
| 1016 Utils.formatPercentNormalized), | 561 Utils.formatPercentNormalized), |
| 1017 new SortedTableColumn('Method'), | 562 new SortedTableColumn('Method'), |
| 1018 ]; | 563 ]; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1031 new SortedTableColumn.withFormatter('Callers (%)', | 576 new SortedTableColumn.withFormatter('Callers (%)', |
| 1032 Utils.formatPercentNormalized), | 577 Utils.formatPercentNormalized), |
| 1033 new SortedTableColumn('Method') | 578 new SortedTableColumn('Method') |
| 1034 ]; | 579 ]; |
| 1035 profileCallersTable = new NameSortedTable(columns); | 580 profileCallersTable = new NameSortedTable(columns); |
| 1036 profileCallersTable.sortColumnIndex = 0; | 581 profileCallersTable.sortColumnIndex = 0; |
| 1037 } | 582 } |
| 1038 | 583 |
| 1039 attached() { | 584 attached() { |
| 1040 super.attached(); | 585 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(); | 586 _updateTask.queue(); |
| 1060 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | 587 _resizeSubscription = window.onResize.listen((_) => _updateSize()); |
| 1061 _updateSize(); | 588 _updateSize(); |
| 1062 } | 589 } |
| 1063 | 590 |
| 1064 detached() { | 591 detached() { |
| 1065 super.detached(); | 592 super.detached(); |
| 1066 if (_resizeSubscription != null) { | 593 if (_resizeSubscription != null) { |
| 1067 _resizeSubscription.cancel(); | 594 _resizeSubscription.cancel(); |
| 1068 } | 595 } |
| 1069 } | 596 } |
| 1070 | 597 |
| 1071 _updateSize() { | 598 _updateSize() { |
| 1072 HtmlElement e = $['main']; | 599 HtmlElement e = $['main']; |
| 1073 final totalHeight = window.innerHeight; | 600 final totalHeight = window.innerHeight; |
| 1074 final top = e.offset.top; | 601 final top = e.offset.top; |
| 1075 final bottomMargin = 32; | 602 final bottomMargin = 32; |
| 1076 final mainHeight = totalHeight - top - bottomMargin; | 603 final mainHeight = totalHeight - top - bottomMargin; |
| 1077 e.style.setProperty('height', '${mainHeight}px'); | 604 e.style.setProperty('height', '${mainHeight}px'); |
| 1078 } | 605 } |
| 1079 | 606 |
| 1080 isolateChanged(oldValue) { | 607 isolateChanged(oldValue) { |
| 1081 _updateTask.queue(); | 608 _updateTask.queue(); |
| 1082 } | 609 } |
| 1083 | 610 |
| 1084 update() { | 611 update() async { |
| 1085 _clearView(); | 612 _clearView(); |
| 1086 if (sampleBufferControlElement != null) { | 613 if (isolate == null) { |
| 1087 sampleBufferControlElement.reload(isolate).whenComplete(checkParameters); | 614 return; |
| 1088 } | 615 } |
| 1089 } | 616 final stream = _repository.get(isolate, M.SampleProfileTag.none); |
| 1090 | 617 var progress = (await stream.first).progress; |
| 1091 onSampleBufferChange(CpuProfile sampleBuffer) { | 618 shadowRoot.querySelector('#sampleBufferControl').children = [ |
| 619 sampleBufferControlElement = |
| 620 new SampleBufferControlElement(progress, stream, showTag: false, |
| 621 selectedTag: M.SampleProfileTag.none, queue: app.queue) |
| 622 ]; |
| 623 if (M.isSampleProcessRunning(progress.status)) { |
| 624 progress = (await stream.last).progress; |
| 625 } |
| 626 if (progress.status == M.SampleProfileLoadingStatus.loaded) { |
| 627 _profile = progress.profile; |
| 628 shadowRoot.querySelector('#stackTraceTreeConfig').children = [ |
| 629 stackTraceTreeConfigElement = |
| 630 new StackTraceTreeConfigElement(showMode: false, |
| 631 showDirection: false, mode: ProfileTreeMode.function, |
| 632 direction: M.ProfileTreeDirection.exclusive, queue: app.queue) |
| 633 ..onModeChange.listen((e) { |
| 634 cpuProfileTreeElement.mode = e.element.mode; |
| 635 _renderTask.queue(); |
| 636 }) |
| 637 ..onDirectionChange.listen((e) { |
| 638 cpuProfileTreeElement.direction = e.element.direction; |
| 639 _renderTask.queue(); |
| 640 }) |
| 641 ..onFilterChange.listen((e) =>_renderTask.queue()) |
| 642 ]; |
| 643 shadowRoot.querySelector('#cpuProfileTree').children = [ |
| 644 cpuProfileTreeElement = |
| 645 new CpuProfileVirtualTreeElement(isolate, _profile, |
| 646 queue: app.queue) |
| 647 ]; |
| 648 } |
| 1092 _renderTask.queue(); | 649 _renderTask.queue(); |
| 1093 } | 650 } |
| 1094 | 651 |
| 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 { | 652 Future clearCpuProfile() async { |
| 1112 await isolate.invokeRpc('_clearCpuProfile', { }); | 653 await isolate.invokeRpc('_clearCpuProfile', { }); |
| 1113 _updateTask.queue(); | 654 _updateTask.queue(); |
| 1114 return new Future.value(null); | 655 return new Future.value(null); |
| 1115 } | 656 } |
| 1116 | 657 |
| 1117 Future refresh() { | 658 Future refresh() { |
| 1118 _updateTask.queue(); | 659 _updateTask.queue(); |
| 1119 return new Future.value(null); | 660 return new Future.value(null); |
| 1120 } | 661 } |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1183 TableSectionElement tableBody = $['profile-table']; | 724 TableSectionElement tableBody = $['profile-table']; |
| 1184 // Clear current focus. | 725 // Clear current focus. |
| 1185 if (focusedRow != null) { | 726 if (focusedRow != null) { |
| 1186 tableBody.children[focusedRow].classes.remove('focused'); | 727 tableBody.children[focusedRow].classes.remove('focused'); |
| 1187 } | 728 } |
| 1188 focusedRow = null; | 729 focusedRow = null; |
| 1189 focusedFunction = null; | 730 focusedFunction = null; |
| 1190 } | 731 } |
| 1191 | 732 |
| 1192 ServiceFunction _findFunction(String functionName) { | 733 ServiceFunction _findFunction(String functionName) { |
| 1193 for (var func in profile.functions) { | 734 for (var func in _profile.functions) { |
| 1194 if (func.function.name == functionName) { | 735 if (func.function.name == functionName) { |
| 1195 return func.function; | 736 return func.function; |
| 1196 } | 737 } |
| 1197 } | 738 } |
| 1198 return null; | 739 return null; |
| 1199 } | 740 } |
| 1200 | 741 |
| 1201 _focusOnFunction(ServiceFunction function) { | 742 _focusOnFunction(ServiceFunction function) { |
| 1202 if (focusedFunction == function) { | 743 if (focusedFunction == function) { |
| 1203 // Do nothing. | 744 // Do nothing. |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1243 | 784 |
| 1244 _renderTable() { | 785 _renderTable() { |
| 1245 profileTable._updateTableView($['profile-table'], | 786 profileTable._updateTableView($['profile-table'], |
| 1246 profileTable._makeFunctionRow, | 787 profileTable._makeFunctionRow, |
| 1247 _onRowClick, | 788 _onRowClick, |
| 1248 NameSortedTable.FUNCTION_SPACER_COLUMNS, | 789 NameSortedTable.FUNCTION_SPACER_COLUMNS, |
| 1249 NameSortedTable.FUNCTION_COLUMN); | 790 NameSortedTable.FUNCTION_COLUMN); |
| 1250 } | 791 } |
| 1251 | 792 |
| 1252 _buildFunctionTable() { | 793 _buildFunctionTable() { |
| 1253 for (var func in profile.functions) { | 794 for (var func in _profile.functions) { |
| 1254 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) { | 795 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) { |
| 1255 // Skip. | 796 // Skip. |
| 1256 continue; | 797 continue; |
| 1257 } | 798 } |
| 1258 var row = [ | 799 var row = [ |
| 1259 func.normalizedExclusiveTicks, | 800 func.normalizedExclusiveTicks, |
| 1260 func.normalizedInclusiveTicks, | 801 func.normalizedInclusiveTicks, |
| 1261 func.function, | 802 func.function, |
| 1262 ]; | 803 ]; |
| 1263 profileTable.addRow(new SortedTableRow(row)); | 804 profileTable.addRow(new SortedTableRow(row)); |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1352 _changeSort(target, profileCalleesTable); | 893 _changeSort(target, profileCalleesTable); |
| 1353 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); | 894 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); |
| 1354 } | 895 } |
| 1355 | 896 |
| 1356 ////// | 897 ////// |
| 1357 /// | 898 /// |
| 1358 /// Function tree. | 899 /// Function tree. |
| 1359 /// | 900 /// |
| 1360 TableTree functionTree; | 901 TableTree functionTree; |
| 1361 _updateFunctionTreeView() { | 902 _updateFunctionTreeView() { |
| 903 if (cpuProfileTreeElement == null) { |
| 904 return; |
| 905 } |
| 1362 cpuProfileTreeElement.filter = (FunctionCallTreeNode node) { | 906 cpuProfileTreeElement.filter = (FunctionCallTreeNode node) { |
| 1363 return node.profileFunction.function == focusedFunction; | 907 return node.profileFunction.function == focusedFunction; |
| 1364 }; | 908 }; |
| 1365 cpuProfileTreeElement.render(); | |
| 1366 } | 909 } |
| 1367 | 910 |
| 1368 @published Isolate isolate; | 911 @published Isolate isolate; |
| 1369 @observable NameSortedTable profileTable; | 912 @observable NameSortedTable profileTable; |
| 1370 @observable NameSortedTable profileCallersTable; | 913 @observable NameSortedTable profileCallersTable; |
| 1371 @observable NameSortedTable profileCalleesTable; | 914 @observable NameSortedTable profileCalleesTable; |
| 1372 @observable ServiceFunction focusedFunction; | 915 @observable ServiceFunction focusedFunction; |
| 1373 @observable int focusedRow; | 916 @observable int focusedRow; |
| 1374 | 917 |
| 1375 | 918 |
| 1376 StreamSubscription _resizeSubscription; | 919 StreamSubscription _resizeSubscription; |
| 1377 Task _updateTask; | 920 Task _updateTask; |
| 1378 Task _renderTask; | 921 Task _renderTask; |
| 1379 | 922 |
| 1380 CpuProfile get profile => sampleBufferControlElement.profile; | 923 IsolateSampleProfileRepository _repository = |
| 924 new IsolateSampleProfileRepository(); |
| 925 CpuProfile _profile; |
| 1381 SampleBufferControlElement sampleBufferControlElement; | 926 SampleBufferControlElement sampleBufferControlElement; |
| 1382 StackTraceTreeConfigElement stackTraceTreeConfigElement; | 927 StackTraceTreeConfigElement stackTraceTreeConfigElement; |
| 1383 CpuProfileVirtualTreeElement cpuProfileTreeElement; | 928 CpuProfileVirtualTreeElement cpuProfileTreeElement; |
| 1384 } | 929 } |
| 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 } | |
| 1417 | |
| 1418 void render() { | |
| 1419 _updateView(); | |
| 1420 } | |
| 1421 | |
| 1422 showChanged(oldValue) { | |
| 1423 if (show) { | |
| 1424 virtualTree?.root?.style?.display = 'block'; | |
| 1425 } else { | |
| 1426 virtualTree?.root?.style?.display = 'none'; | |
| 1427 } | |
| 1428 } | |
| 1429 | |
| 1430 void _updateView() { | |
| 1431 _updateSize(); | |
| 1432 virtualTree?.clear(); | |
| 1433 virtualTree?.uninstall(); | |
| 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 } | |
| 1444 | |
| 1445 void _updateSize() { | |
| 1446 var treeBody = shadowRoot.querySelector('#tree'); | |
| 1447 assert(treeBody != null); | |
| 1448 int windowHeight = window.innerHeight - 32; | |
| 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 } | |
| 1497 } | |
| 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 } | |
| OLD | NEW |