| OLD | NEW |
| 1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
| 2 <!-- | 2 <!-- |
| 3 Copyright (c) 2015 The Chromium Authors. All rights reserved. | 3 Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
| 5 found in the LICENSE file. | 5 found in the LICENSE file. |
| 6 --> | 6 --> |
| 7 | 7 |
| 8 <link rel="import" href="/tracing/base/range_utils.html"> | 8 <link rel="import" href="/tracing/base/range_utils.html"> |
| 9 <link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.htm
l"> | 9 <link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.htm
l"> |
| 10 <link rel="import" href="/tracing/importer/proto_expectation.html"> | 10 <link rel="import" href="/tracing/importer/proto_expectation.html"> |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 66 MOUSE_DRAG_TYPE_NAMES, | 66 MOUSE_DRAG_TYPE_NAMES, |
| 67 PINCH_TYPE_NAMES, | 67 PINCH_TYPE_NAMES, |
| 68 TAP_TYPE_NAMES, | 68 TAP_TYPE_NAMES, |
| 69 FLING_TYPE_NAMES, | 69 FLING_TYPE_NAMES, |
| 70 TOUCH_TYPE_NAMES, | 70 TOUCH_TYPE_NAMES, |
| 71 SCROLL_TYPE_NAMES | 71 SCROLL_TYPE_NAMES |
| 72 ); | 72 ); |
| 73 | 73 |
| 74 var RENDERER_FLING_TITLE = 'InputHandlerProxy::HandleGestureFling::started'; | 74 var RENDERER_FLING_TITLE = 'InputHandlerProxy::HandleGestureFling::started'; |
| 75 | 75 |
| 76 // TODO(benjhayden) share with rail_ir_finder | |
| 77 var CSS_ANIMATION_TITLE = 'Animation'; | 76 var CSS_ANIMATION_TITLE = 'Animation'; |
| 78 | 77 |
| 79 // If there's less than this much time between the end of one event and the | 78 // If there's less than this much time between the end of one event and the |
| 80 // start of the next, then they might be merged. | 79 // start of the next, then they might be merged. |
| 81 // There was not enough thought given to this value, so if you have any slight | 80 // There was not enough thought given to this value, so if you have any slight |
| 82 // reason to change it, then please do so. It might also be good to split this | 81 // reason to change it, then please do so. It might also be good to split this |
| 83 // into multiple values. | 82 // into multiple values. |
| 84 var INPUT_MERGE_THRESHOLD_MS = 200; | 83 var INPUT_MERGE_THRESHOLD_MS = 200; |
| 85 var ANIMATION_MERGE_THRESHOLD_MS = 32; // 2x 60FPS frames | 84 var ANIMATION_MERGE_THRESHOLD_MS = 32; // 2x 60FPS frames |
| 86 | 85 |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 120 cb.call(opt_this, event); | 119 cb.call(opt_this, event); |
| 121 } | 120 } |
| 122 }); | 121 }); |
| 123 } | 122 } |
| 124 | 123 |
| 125 function causedFrame(event) { | 124 function causedFrame(event) { |
| 126 return event.associatedEvents.some( | 125 return event.associatedEvents.some( |
| 127 x => x.title === tr.model.helpers.IMPL_RENDERING_STATS); | 126 x => x.title === tr.model.helpers.IMPL_RENDERING_STATS); |
| 128 } | 127 } |
| 129 | 128 |
| 129 function getSortedFrameEventsByProcess(modelHelper) { |
| 130 var frameEventsByPid = {}; |
| 131 tr.b.iterItems(modelHelper.rendererHelpers, function(pid, rendererHelper) { |
| 132 frameEventsByPid[pid] = rendererHelper.getFrameEventsInRange( |
| 133 tr.model.helpers.IMPL_FRAMETIME_TYPE, modelHelper.model.bounds); |
| 134 }); |
| 135 return frameEventsByPid; |
| 136 } |
| 137 |
| 130 function getSortedInputEvents(modelHelper) { | 138 function getSortedInputEvents(modelHelper) { |
| 131 var inputEvents = []; | 139 var inputEvents = []; |
| 132 | 140 |
| 133 var browserProcess = modelHelper.browserHelper.process; | 141 var browserProcess = modelHelper.browserHelper.process; |
| 134 var mainThread = browserProcess.findAtMostOneThreadNamed( | 142 var mainThread = browserProcess.findAtMostOneThreadNamed( |
| 135 'CrBrowserMain'); | 143 'CrBrowserMain'); |
| 136 mainThread.asyncSliceGroup.iterateAllEvents(function(slice) { | 144 for (var slice of mainThread.asyncSliceGroup.getDescendantEvents()) { |
| 137 if (!slice.isTopLevel) | 145 if (!slice.isTopLevel) |
| 138 return; | 146 continue; |
| 139 | 147 |
| 140 if (!(slice instanceof tr.e.cc.InputLatencyAsyncSlice)) | 148 if (!(slice instanceof tr.e.cc.InputLatencyAsyncSlice)) |
| 141 return; | 149 continue; |
| 142 | 150 |
| 143 // TODO(beaudoin): This should never happen but it does. Investigate | 151 // TODO(beaudoin): This should never happen but it does. Investigate |
| 144 // the trace linked at in #1567 and remove that when it's fixed. | 152 // the trace linked at in #1567 and remove that when it's fixed. |
| 145 if (isNaN(slice.start) || | 153 if (isNaN(slice.start) || |
| 146 isNaN(slice.duration) || | 154 isNaN(slice.duration) || |
| 147 isNaN(slice.end)) | 155 isNaN(slice.end)) |
| 148 return; | 156 continue; |
| 149 | 157 |
| 150 inputEvents.push(slice); | 158 inputEvents.push(slice); |
| 151 }); | 159 } |
| 152 | 160 |
| 153 return inputEvents.sort(compareEvents); | 161 return inputEvents.sort(compareEvents); |
| 154 } | 162 } |
| 155 | 163 |
| 156 function findProtoExpectations(modelHelper, sortedInputEvents) { | 164 function findProtoExpectations(modelHelper, sortedInputEvents) { |
| 157 var protoExpectations = []; | 165 var protoExpectations = []; |
| 158 // This order is not important. Handlers are independent. | 166 // This order is not important. Handlers are independent. |
| 159 var handlers = [ | 167 var handlers = [ |
| 160 handleKeyboardEvents, | 168 handleKeyboardEvents, |
| 161 handleMouseResponseEvents, | 169 handleMouseResponseEvents, |
| (...skipping 555 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 717 // CSS Animations are merged into AnimationExpectations when they intersect. | 725 // CSS Animations are merged into AnimationExpectations when they intersect. |
| 718 function handleCSSAnimations(modelHelper, sortedInputEvents) { | 726 function handleCSSAnimations(modelHelper, sortedInputEvents) { |
| 719 // First find all the top-level CSS Animation async events. | 727 // First find all the top-level CSS Animation async events. |
| 720 var animationEvents = modelHelper.browserHelper. | 728 var animationEvents = modelHelper.browserHelper. |
| 721 getAllAsyncSlicesMatching(function(event) { | 729 getAllAsyncSlicesMatching(function(event) { |
| 722 return ((event.title === CSS_ANIMATION_TITLE) && | 730 return ((event.title === CSS_ANIMATION_TITLE) && |
| 723 event.isTopLevel && | 731 event.isTopLevel && |
| 724 (event.duration > 0)); | 732 (event.duration > 0)); |
| 725 }); | 733 }); |
| 726 | 734 |
| 727 // Memoize the frame events per process. | |
| 728 // There may be many Animation events for each process. We can save a | |
| 729 // significant amount of processing time by avoiding re-computing them for | |
| 730 // each animation. | |
| 731 var framesForProcess = {}; | |
| 732 | |
| 733 function getFramesForAnimationProcess(animation) { | |
| 734 var frames = framesForProcess[animation.parentContainer.parent.guid]; | |
| 735 if (frames === undefined) { | |
| 736 var rendererHelper = new tr.model.helpers.ChromeRendererHelper( | |
| 737 modelHelper, animation.parentContainer.parent); | |
| 738 // Collect all the frame events in the same renderer process as the css | |
| 739 // animation, and memoize them. | |
| 740 frames = rendererHelper.getFrameEventsInRange( | |
| 741 tr.model.helpers.IMPL_FRAMETIME_TYPE, modelHelper.model.bounds); | |
| 742 framesForProcess[animation.parentContainer.parent.guid] = frames; | |
| 743 } | |
| 744 return frames; | |
| 745 } | |
| 746 | 735 |
| 747 // Time ranges where animations are actually running will be collected here. | 736 // Time ranges where animations are actually running will be collected here. |
| 748 // Each element will contain {min, max, animation, frames}. | 737 // Each element will contain {min, max, animation}. |
| 749 var animationRanges = []; | 738 var animationRanges = []; |
| 750 | 739 |
| 751 // This helper function will be called when a time range is found | 740 // This helper function will be called when a time range is found |
| 752 // during which the animation is actually running. | 741 // during which the animation is actually running. |
| 753 // This helper function collects the frames that happened during the time | |
| 754 // range, and pushes it all to |animationRanges|. | |
| 755 function pushAnimationRange(start, end, animation) { | 742 function pushAnimationRange(start, end, animation) { |
| 756 var range = tr.b.Range.fromExplicitRange(start, end); | 743 var range = tr.b.Range.fromExplicitRange(start, end); |
| 757 range.animation = animation; | 744 range.animation = animation; |
| 758 | |
| 759 // Collect the frames that happened while the animation was running. | |
| 760 // A more general way to find these frames would be to collect all of | |
| 761 // the trace events caused by this animation, but that will require | |
| 762 // adding flow events to chrome: | |
| 763 // https://github.com/catapult-project/catapult/issues/1433 | |
| 764 range.frames = range.filterArray( | |
| 765 getFramesForAnimationProcess(animation), | |
| 766 function(frameEvent) { return frameEvent.start; }); | |
| 767 | |
| 768 // If a tree falls in a forest... | |
| 769 // If there were not actually any frames while the animation was | |
| 770 // running, then it wasn't really an animation, now, was it? | |
| 771 // Philosophy aside, the system_health Animation metrics fail hard if | |
| 772 // there are no frames in an AnimationExpectation. | |
| 773 if (range.frames.length === 0) | |
| 774 return; | |
| 775 | |
| 776 animationRanges.push(range); | 745 animationRanges.push(range); |
| 777 } | 746 } |
| 778 | 747 |
| 779 animationEvents.forEach(function(animation) { | 748 animationEvents.forEach(function(animation) { |
| 780 if (animation.subSlices.length === 0) { | 749 if (animation.subSlices.length === 0) { |
| 781 pushAnimationRange(animation.start, animation.end, animation); | 750 pushAnimationRange(animation.start, animation.end, animation); |
| 782 } else { | 751 } else { |
| 783 // Now run a state machine over the animation's subSlices, which | 752 // Now run a state machine over the animation's subSlices, which |
| 784 // indicate the animations running/paused/finished states, in order to | 753 // indicate the animations running/paused/finished states, in order to |
| 785 // find ranges where the animation was actually running. | 754 // find ranges where the animation was actually running. |
| (...skipping 23 matching lines...) Expand all Loading... |
| 809 } | 778 } |
| 810 }); | 779 }); |
| 811 | 780 |
| 812 // An animation was still running when the trace ended. | 781 // An animation was still running when the trace ended. |
| 813 if (start !== undefined) | 782 if (start !== undefined) |
| 814 pushAnimationRange(start, modelHelper.model.bounds.max, animation); | 783 pushAnimationRange(start, modelHelper.model.bounds.max, animation); |
| 815 } | 784 } |
| 816 }); | 785 }); |
| 817 | 786 |
| 818 // Now we have a set of time ranges when css animations were actually | 787 // Now we have a set of time ranges when css animations were actually |
| 819 // running, along with their frames. | 788 // running. |
| 820 // Now all that's left for this function is to merge over-lapping ranges | 789 // Leave merging intersecting animations to mergeIntersectingAnimations(), |
| 821 // into ProtoExpectations. | 790 // after findFrameEventsForAnimations removes frame-less animations. |
| 822 | 791 |
| 823 function merge(ranges) { | 792 return animationRanges.map(function(range) { |
| 824 var protoExpectation = new ProtoExpectation( | 793 var protoExpectation = new ProtoExpectation( |
| 825 ProtoExpectation.ANIMATION_TYPE, CSS_IR_NAME); | 794 ProtoExpectation.ANIMATION_TYPE, CSS_IR_NAME); |
| 826 ranges.forEach(function(range) { | 795 protoExpectation.start = range.min; |
| 827 protoExpectation.start = Math.min(protoExpectation.start, range.min); | 796 protoExpectation.end = range.max; |
| 828 protoExpectation.end = Math.max(protoExpectation.end, range.max); | 797 protoExpectation.associatedEvents.push(range.animation); |
| 829 protoExpectation.associatedEvents.push(range.animation); | |
| 830 protoExpectation.associatedEvents.addEventSet(range.frames); | |
| 831 }); | |
| 832 return protoExpectation; | 798 return protoExpectation; |
| 833 } | 799 }); |
| 834 | |
| 835 return tr.b.mergeRanges(animationRanges, | |
| 836 ANIMATION_MERGE_THRESHOLD_MS, | |
| 837 merge); | |
| 838 } | 800 } |
| 839 | 801 |
| 840 function postProcessProtoExpectations(protoExpectations) { | 802 function postProcessProtoExpectations(modelHelper, protoExpectations) { |
| 841 // protoExpectations is input only. Returns a modified set of | 803 // protoExpectations is input only. Returns a modified set of |
| 842 // ProtoExpectations. The order is important. | 804 // ProtoExpectations. The order is important. |
| 805 protoExpectations = findFrameEventsForAnimations( |
| 806 modelHelper, protoExpectations); |
| 843 protoExpectations = mergeIntersectingResponses(protoExpectations); | 807 protoExpectations = mergeIntersectingResponses(protoExpectations); |
| 844 protoExpectations = mergeIntersectingAnimations(protoExpectations); | 808 protoExpectations = mergeIntersectingAnimations(protoExpectations); |
| 845 protoExpectations = fixResponseAnimationStarts(protoExpectations); | 809 protoExpectations = fixResponseAnimationStarts(protoExpectations); |
| 846 protoExpectations = fixTapResponseTouchAnimations(protoExpectations); | 810 protoExpectations = fixTapResponseTouchAnimations(protoExpectations); |
| 847 return protoExpectations; | 811 return protoExpectations; |
| 848 } | 812 } |
| 849 | 813 |
| 850 // TouchStarts happen at the same time as ScrollBegins. | 814 // TouchStarts happen at the same time as ScrollBegins. |
| 851 // It's easier to let multiple handlers create multiple overlapping | 815 // It's easier to let multiple handlers create multiple overlapping |
| 852 // Responses and then merge them, rather than make the handlers aware of the | 816 // Responses and then merge them, rather than make the handlers aware of the |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 922 for (var i = 0; i < protoExpectations.length; ++i) { | 886 for (var i = 0; i < protoExpectations.length; ++i) { |
| 923 var otherPE = protoExpectations[i]; | 887 var otherPE = protoExpectations[i]; |
| 924 | 888 |
| 925 if (otherPE.irType !== pe.irType) | 889 if (otherPE.irType !== pe.irType) |
| 926 continue; | 890 continue; |
| 927 | 891 |
| 928 // Don't merge CSS Animations with any other types. | 892 // Don't merge CSS Animations with any other types. |
| 929 if (isCSS != otherPE.containsSliceTitle(CSS_ANIMATION_TITLE)) | 893 if (isCSS != otherPE.containsSliceTitle(CSS_ANIMATION_TITLE)) |
| 930 continue; | 894 continue; |
| 931 | 895 |
| 932 if (!otherPE.intersects(pe)) | 896 if (isCSS) { |
| 897 if (!pe.isNear(otherPE, ANIMATION_MERGE_THRESHOLD_MS)) |
| 898 continue; |
| 899 } else if (!otherPE.intersects(pe)) { |
| 933 continue; | 900 continue; |
| 901 } |
| 934 | 902 |
| 935 // Don't merge Fling Animations with any other types. | 903 // Don't merge Fling Animations with any other types. |
| 936 if (isFling != otherPE.containsTypeNames([INPUT_TYPE.FLING_START])) | 904 if (isFling != otherPE.containsTypeNames([INPUT_TYPE.FLING_START])) |
| 937 continue; | 905 continue; |
| 938 | 906 |
| 939 pe.merge(otherPE); | 907 pe.merge(otherPE); |
| 940 protoExpectations.splice(i, 1); | 908 protoExpectations.splice(i, 1); |
| 941 // Don't skip the next otherPE! | 909 // Don't skip the next otherPE! |
| 942 --i; | 910 --i; |
| 943 } | 911 } |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1024 | 992 |
| 1025 pe.merge(otherPE); | 993 pe.merge(otherPE); |
| 1026 protoExpectations.splice(i, 1); | 994 protoExpectations.splice(i, 1); |
| 1027 // Don't skip the next otherPE! | 995 // Don't skip the next otherPE! |
| 1028 --i; | 996 --i; |
| 1029 } | 997 } |
| 1030 } | 998 } |
| 1031 return newPEs; | 999 return newPEs; |
| 1032 } | 1000 } |
| 1033 | 1001 |
| 1002 function findFrameEventsForAnimations(modelHelper, protoExpectations) { |
| 1003 var newPEs = []; |
| 1004 var frameEventsByPid = getSortedFrameEventsByProcess(modelHelper); |
| 1005 |
| 1006 for (var pe of protoExpectations) { |
| 1007 if (pe.irType !== ProtoExpectation.ANIMATION_TYPE) { |
| 1008 newPEs.push(pe); |
| 1009 continue; |
| 1010 } |
| 1011 |
| 1012 var frameEvents = []; |
| 1013 // TODO(benjhayden): Use frame blame contexts here. |
| 1014 for (var pid of Object.keys(modelHelper.rendererHelpers)) { |
| 1015 var range = tr.b.Range.fromExplicitRange(pe.start, pe.end); |
| 1016 frameEvents.push.apply(frameEvents, |
| 1017 range.filterArray(frameEventsByPid[pid], e => e.start)); |
| 1018 } |
| 1019 |
| 1020 // If a tree falls in a forest... |
| 1021 // If there were not actually any frames while the animation was |
| 1022 // running, then it wasn't really an animation, now, was it? |
| 1023 // Philosophy aside, the system_health Animation metrics fail hard if |
| 1024 // there are no frames in an AnimationExpectation. |
| 1025 if (frameEvents.length === 0) { |
| 1026 pe.irType = ProtoExpectation.IGNORED_TYPE; |
| 1027 newPEs.push(pe); |
| 1028 continue; |
| 1029 } |
| 1030 |
| 1031 pe.associatedEvents.addEventSet(frameEvents); |
| 1032 newPEs.push(pe); |
| 1033 } |
| 1034 |
| 1035 return newPEs; |
| 1036 } |
| 1037 |
| 1034 // Check that none of the handlers accidentally ignored an input event. | 1038 // Check that none of the handlers accidentally ignored an input event. |
| 1035 function checkAllInputEventsHandled(sortedInputEvents, protoExpectations) { | 1039 function checkAllInputEventsHandled(sortedInputEvents, protoExpectations) { |
| 1036 var handledEvents = []; | 1040 var handledEvents = []; |
| 1037 protoExpectations.forEach(function(protoExpectation) { | 1041 protoExpectations.forEach(function(protoExpectation) { |
| 1038 protoExpectation.associatedEvents.forEach(function(event) { | 1042 protoExpectation.associatedEvents.forEach(function(event) { |
| 1039 // Ignore CSS Animations that might have multiple active ranges. | 1043 // Ignore CSS Animations that might have multiple active ranges. |
| 1040 if ((event.title === CSS_ANIMATION_TITLE) && | 1044 if ((event.title === CSS_ANIMATION_TITLE) && |
| 1041 (event.subSlices.length > 0)) | 1045 (event.subSlices.length > 0)) |
| 1042 return; | 1046 return; |
| 1043 | 1047 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1056 event.typeName, parseInt(event.start), parseInt(event.end)); | 1060 event.typeName, parseInt(event.start), parseInt(event.end)); |
| 1057 } | 1061 } |
| 1058 }); | 1062 }); |
| 1059 } | 1063 } |
| 1060 | 1064 |
| 1061 // Find ProtoExpectations, post-process them, convert them to real IRs. | 1065 // Find ProtoExpectations, post-process them, convert them to real IRs. |
| 1062 function findInputExpectations(modelHelper) { | 1066 function findInputExpectations(modelHelper) { |
| 1063 var sortedInputEvents = getSortedInputEvents(modelHelper); | 1067 var sortedInputEvents = getSortedInputEvents(modelHelper); |
| 1064 var protoExpectations = findProtoExpectations( | 1068 var protoExpectations = findProtoExpectations( |
| 1065 modelHelper, sortedInputEvents); | 1069 modelHelper, sortedInputEvents); |
| 1066 protoExpectations = postProcessProtoExpectations(protoExpectations); | 1070 protoExpectations = postProcessProtoExpectations( |
| 1071 modelHelper, protoExpectations); |
| 1067 checkAllInputEventsHandled(sortedInputEvents, protoExpectations); | 1072 checkAllInputEventsHandled(sortedInputEvents, protoExpectations); |
| 1068 | 1073 |
| 1069 var irs = []; | 1074 var irs = []; |
| 1070 protoExpectations.forEach(function(protoExpectation) { | 1075 protoExpectations.forEach(function(protoExpectation) { |
| 1071 var ir = protoExpectation.createInteractionRecord(modelHelper.model); | 1076 var ir = protoExpectation.createInteractionRecord(modelHelper.model); |
| 1072 if (ir) | 1077 if (ir) |
| 1073 irs.push(ir); | 1078 irs.push(ir); |
| 1074 }); | 1079 }); |
| 1075 return irs; | 1080 return irs; |
| 1076 } | 1081 } |
| 1077 | 1082 |
| 1078 return { | 1083 return { |
| 1079 findInputExpectations: findInputExpectations, | 1084 findInputExpectations: findInputExpectations, |
| 1080 compareEvents: compareEvents, | 1085 compareEvents: compareEvents, |
| 1081 CSS_ANIMATION_TITLE: CSS_ANIMATION_TITLE | 1086 CSS_ANIMATION_TITLE: CSS_ANIMATION_TITLE |
| 1082 }; | 1087 }; |
| 1083 }); | 1088 }); |
| 1084 </script> | 1089 </script> |
| OLD | NEW |