video_playback_latency.py (5092B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import filters 6 from base_python_support import BasePythonSupport 7 8 9 class VideoPlaybackLatency(BasePythonSupport): 10 def __init__(self, **kwargs): 11 super().__init__(**kwargs) 12 self._is_android = False 13 self._is_chrome = False 14 15 def setup_test(self, test, args): 16 from cmdline import CHROME_ANDROID_APPS, CHROMIUM_DISTROS, DESKTOP_APPS 17 18 self._is_android = args.app not in DESKTOP_APPS 19 self._is_chrome = ( 20 args.app in CHROMIUM_DISTROS or args.app in CHROME_ANDROID_APPS 21 ) 22 23 def modify_command(self, cmd, test): 24 # Because of the aspect ratio of Android during recording, 25 # most pixels are white, so we need to use a lower fraction. 26 fraction = "0.25" if self._is_android else "0.7" 27 28 # Firefox/Safari configuration allows video playback but 29 # Chrome needs an explicit switch to enable it. 30 if self._is_chrome: 31 cmd += [ 32 "--chrome.enableVideoAutoplay", 33 "true", 34 ] 35 36 cmd += [ 37 "--visualMetricsKeyColor", 38 "poster", 39 "0", 40 "128", 41 "220", 42 "255", 43 "220", 44 "255", 45 fraction, 46 "--visualMetricsKeyColor", 47 "firstFrame", 48 "220", 49 "255", 50 "0", 51 "60", 52 "0", 53 "60", 54 fraction, 55 "--visualMetricsKeyColor", 56 "secondFrame", 57 "0", 58 "60", 59 "0", 60 "60", 61 "220", 62 "255", 63 fraction, 64 "--visualMetricsKeyColor", 65 "lastFrame", 66 "220", 67 "255", 68 "220", 69 "255", 70 "0", 71 "128", 72 fraction, 73 ] 74 75 def handle_result(self, bt_result, raw_result, last_result=False, **kwargs): 76 measurements = { 77 "poster": [], 78 "posterEnd": [], 79 "firstFrame": [], 80 "secondFrame": [], 81 "lastFrame": [], 82 "estimatedFirstFrameLatency": [], 83 "estimatedAnyFrameLatency": [], 84 } 85 86 fps = 30.0 87 total_duration_ms = 1000.0 88 frame_duration_ms = total_duration_ms / fps 89 90 offsets = { 91 "firstFrame": 0.0, 92 "posterEnd": 0.0, 93 "secondFrame": frame_duration_ms * 3.0, 94 "lastFrame": total_duration_ms - frame_duration_ms, 95 } 96 97 # Gather the key frame start times of each page/cycle 98 for cycle in raw_result["visualMetrics"]: 99 measurement = {} 100 for key, frames in cycle["KeyColorFrames"].items(): 101 if key not in measurements or not len(frames): 102 continue 103 measurement[key] = frames[0]["startTimestamp"] 104 if key == "poster": 105 measurement["posterEnd"] = frames[0]["endTimestamp"] 106 107 for key in ["firstFrame", "posterEnd", "secondFrame", "lastFrame"]: 108 if key not in measurement: 109 continue 110 normalized_value = measurement[key] - offsets[key] 111 if normalized_value <= 0: 112 continue 113 measurements["estimatedFirstFrameLatency"].append(normalized_value) 114 break 115 116 for key, value in measurement.items(): 117 measurements[key].append(value) 118 if key not in offsets: 119 continue 120 normalized_value = value - offsets[key] 121 if normalized_value <= 0: 122 continue 123 measurements["estimatedAnyFrameLatency"].append(normalized_value) 124 125 for measurement, values in measurements.items(): 126 bt_result["measurements"].setdefault(measurement, []).extend(values) 127 128 def _build_subtest(self, measurement_name, replicates, test): 129 unit = test.get("unit", "ms") 130 if test.get("subtest_unit"): 131 unit = test.get("subtest_unit") 132 133 return { 134 "name": measurement_name, 135 "lowerIsBetter": test.get("lower_is_better", True), 136 "alertThreshold": float(test.get("alert_threshold", 2.0)), 137 "unit": unit, 138 "replicates": replicates, 139 "value": round(filters.geometric_mean(replicates), 3), 140 } 141 142 def summarize_test(self, test, suite, **kwargs): 143 suite["type"] = "pageload" 144 if suite["subtests"] == {}: 145 suite["subtests"] = [] 146 for measurement_name, replicates in test["measurements"].items(): 147 if not replicates: 148 continue 149 suite["subtests"].append( 150 self._build_subtest(measurement_name, replicates, test) 151 ) 152 suite["subtests"].sort(key=lambda subtest: subtest["name"])