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