test_video_low_power_telemetry.html (7736B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=1737682 5 6 This is a test of the macOS video low power telemetry, defined as GFX_MACOS_VIDEO_LOW_POWER 7 enums in Histograms.json. The test will load some video media files, play them for some 8 number of frames, then check that appropriate telemetry has been recorded. Since the 9 telemetry fires based on the number of frames painted, the test is structured around 10 the `media.video_stats.enabled` pref, which allows us to monitor the number of frames 11 that have been shown. The telemetry fires every 600 frames. To make sure we have enough 12 painted frames to trigger telemetry, we run the media for many frames and then check 13 that the expected telemetry value has been recorded at least once. 14 15 The tests are run via the MediaTestManager, which loads and plays the video sequentially. 16 Since we want to test different telemetry outcomes, we have some special setup and teardown 17 steps we run before each video. We keep track of which test we are running with a simple 18 1-based index, `testIndex`. So our test structure is: 19 20 1) Set some initial preferences that need to be set for the whole test series. 21 2) Start MediaTestManager with a set of media to play. 22 3) When a video arrives, increment testIndex and call preTest. 23 4) Run the video for PAINTED_FRAMES frames, then call postTest, which checks telemetry 24 and cleans up. 25 5) Tell the MediaTestManager we've finished with that video, which triggers the next. 26 --> 27 28 <head> 29 <title>Test of macOS video low power telemetry</title> 30 <script src="/tests/SimpleTest/SimpleTest.js"></script> 31 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 32 <script type="text/javascript" src="manifest.js"></script> 33 </head> 34 <body> 35 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1737682">Mozilla Bug 1737682</a> 36 <pre id="test"> 37 <script class="testbody" type="text/javascript"> 38 // Parallel test must be disabled for media.video_stats.enabled is a global setting 39 // to prevent the setting from changing unexpectedly in the middle of the test. 40 PARALLEL_TESTS = 1; 41 SimpleTest.waitForExplicitFinish(); 42 43 // How many frames do we run? Must be more than the value set by the 44 // gfx.core-animation.low-power-telemetry-frames pref, because that's how 45 // many frames must be shown before telemetry is emitted. We present for 46 // longer than that to ensure that the telemetry is emitted at least once. 47 // This also has to be enough frames to overcome the animated "<video> is 48 // now fullscreen." popup that appears over the video. While that modal 49 // is visible and animating away, it will register as an overlay and 50 // prevent low power mode. As an estimate, we need at least 200 frames to 51 // ensure that animation has finished and there has been time to fire the 52 // telemetry at least once after it has finished. 53 const TELEMETRY_FRAMES = SpecialPowers.getIntPref("gfx.core-animation.low-power-telemetry-frames"); 54 const PAINTED_FRAMES = Math.max(Math.floor(TELEMETRY_FRAMES * 1.1), 200 + TELEMETRY_FRAMES); 55 info(`Running each video for ${PAINTED_FRAMES} frames.`); 56 57 // Taken from TelemetryHistogramEnums.h 58 const GFX_MACOS_VIDEO_LOW_POWER_LowPower = 1; 59 const GFX_MACOS_VIDEO_LOW_POWER_FailWindowed = 3; 60 61 var manager = new MediaTestManager; 62 63 // Define some state variables that we'll use to manage multiple setup, 64 // check, and teardown steps using the same preTest and postTest functions. 65 var testIndex = 0; 66 var testsComplete = false; 67 68 async function retrieveSnapshotAndClearTelemetry() { 69 return SpecialPowers.spawnChrome([], () => { 70 let hist = Services.telemetry.getHistogramById("GFX_MACOS_VIDEO_LOW_POWER"); 71 let snap = hist.snapshot(); 72 hist.clear(); 73 return snap; 74 }); 75 } 76 77 function checkSnapshot(snap, category) { 78 if (!snap) { 79 return; 80 } 81 82 // For debugging purposes, build a string of the snapshot values hashmap. 83 let valuesString = ''; 84 let keys = Object.keys(snap.values); 85 keys.forEach(k => { 86 valuesString += `[${k}] = ${snap.values[k]}, `; 87 }); 88 info(`Test ${testIndex} telemetry values are ${valuesString}`); 89 90 // Since we've run the media for more frames than the telemetry needs to 91 // fire, we should have at least 1 of the expected value. 92 let val = snap.values[category]; 93 if (!val) { 94 val = 0; 95 } 96 ok(val > 0, `Test ${testIndex} enum ${category} should have at least 1 occurrence; found ${val}.`); 97 } 98 99 async function doPreTest(v) { 100 if (testsComplete) { 101 manager.finished(v.token); 102 return; 103 } 104 105 switch (testIndex) { 106 case 1: { 107 // Test 1 (FailWindowed): No special setup. 108 break; 109 } 110 111 case 2: { 112 // Test 2 (LowPower): Enter fullscreen. 113 114 // Wait one frame to ensure the old video is no longer in the layer tree, which 115 // causes problems with the telemetry. 116 await new Promise(resolve => requestAnimationFrame(resolve)); 117 118 info("Attempting to enter fullscreen."); 119 ok(document.fullscreenEnabled, "Document should permit fullscreen-ing of elements."); 120 await SpecialPowers.wrap(v).requestFullscreen(); 121 ok(document.fullscreenElement, "Document should have one element in fullscreen."); 122 break; 123 } 124 } 125 } 126 127 async function doPostTest() { 128 info(`Test ${testIndex} attempting to retrieve telemetry.`); 129 let snap = await retrieveSnapshotAndClearTelemetry(); 130 ok(snap, `Test ${testIndex} should have telemetry.`); 131 132 switch (testIndex) { 133 case 1: { 134 // Test 1 (FailWindowed): Just check. 135 checkSnapshot(snap, GFX_MACOS_VIDEO_LOW_POWER_FailWindowed); 136 break; 137 } 138 139 case 2: { 140 // Test 2 (LowPower): Check, then exit fullscreen. 141 checkSnapshot(snap, GFX_MACOS_VIDEO_LOW_POWER_LowPower); 142 143 info("Attempting to exit fullscreen."); 144 await SpecialPowers.wrap(document).exitFullscreen(); 145 ok(!document.fullscreenElement, "Document should be out of fullscreen."); 146 147 // This is the last test. 148 testsComplete = true; 149 break; 150 } 151 } 152 } 153 154 function ontimeupdate(event) { 155 let v = event.target; 156 // Count painted frames to see when we should hit some telemetry threshholds. 157 if (v.mozPaintedFrames >= PAINTED_FRAMES) { 158 v.pause(); 159 v.removeEventListener("timeupdate", ontimeupdate); 160 161 doPostTest(v).then(() => { 162 let token = v.token; 163 removeNodeAndSource(v); 164 manager.finished(token); 165 }); 166 } 167 } 168 169 function startTest(test, token) { 170 manager.started(token); 171 172 testIndex++; 173 info(`Starting test ${testIndex} video ${test.name}.`); 174 175 let v = document.createElement('video'); 176 v.addEventListener("timeupdate", ontimeupdate); 177 v.token = token; 178 v.src = test.name; 179 v.loop = true; 180 document.body.appendChild(v); 181 182 doPreTest(v).then(() => { 183 info(`Playing test ${testIndex}.`); 184 v.play(); 185 }); 186 } 187 188 SpecialPowers.pushPrefEnv({"set": [ 189 ["media.video_stats.enabled", true], 190 ["gfx.core-animation.specialize-video", true], 191 ]}, 192 async function() { 193 // Clear out existing telemetry in case previous tests were displaying 194 // video. 195 info("Clearing initial telemetry."); 196 await retrieveSnapshotAndClearTelemetry(); 197 198 if (!gVideoLowPowerTests.length) { 199 ok(false, "Need at least one video in gVideoLowPowerTests."); 200 return; 201 } 202 203 let atLeast2Videos = gVideoLowPowerTests.slice(); 204 if (atLeast2Videos.length < 2) { 205 atLeast2Videos.splice(1, 0, atLeast2Videos[0]); 206 } 207 208 manager.runTests(atLeast2Videos, startTest); 209 }); 210 211 </script> 212 </pre> 213 </body> 214 </html>