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 |