network_bench.js (8956B)
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 /* eslint-env node */ 6 7 const { logTest, logTask } = require("./utils/profiling"); 8 9 const fs = require("fs"); 10 const path = require("path"); 11 12 async function waitForH3(maxRetries, browser, url, commands, context) { 13 let attempt = 0; 14 while (attempt < maxRetries) { 15 await commands.navigate(url); 16 17 // Get performance entries to check the protocol 18 let protocolInfo = await commands.js.run( 19 ` 20 // Get all performance entries 21 const entries = performance.getEntries(); 22 23 // Create an array to store the results 24 const protocolInfo = entries.map(entry => ({ 25 name: entry.name, 26 protocol: entry.nextHopProtocol, 27 })); 28 29 return protocolInfo; 30 ` 31 ); 32 33 // Log the protocol information 34 context.log.info("protocolInfo: " + JSON.stringify(protocolInfo)); 35 36 // Check if the main document is using h3 protocol 37 const normalizeUrl = url => url.replace(/\/+$/, ""); 38 const isH3 = protocolInfo.some( 39 entry => 40 normalizeUrl(entry.name) === normalizeUrl(url) && 41 entry.protocol === "h3" 42 ); 43 44 if (isH3) { 45 context.log.info("Protocol h3 detected!"); 46 return; // Exit the function if h3 is found 47 } 48 49 // Increment the attempt counter and retry 50 attempt++; 51 context.log.info( 52 `Retry attempt ${attempt} - Protocol is not h3. Retrying...` 53 ); 54 if (browser === "firefox") { 55 const script = ` 56 Services.obs.notifyObservers(null, "net:cancel-all-connections"); 57 Services.obs.notifyObservers(null, "network:reset-http3-excluded-list"); 58 `; 59 commands.js.runPrivileged(script); 60 } 61 context.log.info("Waiting 3s to close connection..."); 62 await commands.wait.byTime(3000); 63 } 64 } 65 66 async function waitForComplete(timeout, commands, context, id) { 67 let starttime = performance.now(); 68 let status = ""; 69 let goodput = 0; 70 71 while (performance.now() - starttime < timeout) { 72 status = await commands.js.run( 73 `return document.getElementById('${id}').innerHTML;` 74 ); 75 76 if (status.startsWith("success")) { 77 goodput = parseFloat(status.split(":")[1]); 78 status = "success"; 79 break; 80 } else if (status.startsWith("error")) { 81 break; 82 } 83 84 await commands.wait.byTime(1000); 85 } 86 87 let endtime = performance.now(); 88 89 return { 90 start: starttime, 91 end: endtime, 92 status, 93 goodput, 94 }; 95 } 96 97 module.exports = logTest( 98 "download/upload test", 99 async function (context, commands) { 100 let serverUrl = `${context.options.browsertime.server_url}`; 101 let iterations = `${context.options.browsertime.iterations}`; 102 const [protocol, testType] = 103 `${context.options.browsertime.test_type}`.split("_"); 104 105 await commands.measure.start(serverUrl); 106 let accumulatedResults = []; 107 let browserName = ""; 108 let file_size = parseInt(context.options.browsertime.test_file_size, 10); 109 if (Number.isNaN(file_size)) { 110 // default is 32MB 111 file_size = 32000000; 112 } 113 for (let iteration = 0; iteration < iterations; iteration++) { 114 await logTask(context, "cycle " + iteration, async function () { 115 const driver = context.selenium.driver; 116 const webdriver = context.selenium.webdriver; 117 let capabilities = await driver.getCapabilities(); 118 browserName = capabilities.get("browserName"); 119 120 if (protocol === "h3") { 121 await waitForH3(10, browserName, serverUrl, commands, context); 122 } else { 123 await commands.navigate(serverUrl); 124 } 125 126 if (testType === "download") { 127 const downloadItem = await driver.findElement( 128 webdriver.By.id("downloadBtn") 129 ); 130 131 const actions = driver.actions({ async: true }); 132 await actions.move({ origin: downloadItem }).click().perform(); 133 134 // Start the test and wait for the upload to complete 135 let results = await waitForComplete( 136 1200000, 137 commands, 138 context, 139 "download_status" 140 ); 141 let downloadTime = results.end - results.start; 142 // Store result in megabit/seconds 143 let downloadGoodput = (file_size * 8) / ((downloadTime / 1000) * 1e6); 144 context.log.info( 145 "download results: " + 146 results.status + 147 " duration: " + 148 downloadTime + 149 "ms, downloadGoodput: " + 150 downloadGoodput + 151 "Mbit/s" 152 ); 153 accumulatedResults.push(downloadGoodput); 154 } else if (testType === "upload") { 155 const uploadItem = await driver.findElement( 156 webdriver.By.id("fileUpload") 157 ); 158 159 let tagName = await uploadItem.getTagName(); 160 if (tagName === "input") { 161 if (context.options.browsertime.moz_fetch_dir == "None") { 162 context.log.error( 163 "This test depends on the fetch task. Download the file, 'https://github.com/mozilla/perf-automation/raw/master/test_files/upload-test-32MB.dat' and set the os environment variable MOZ_FETCHES_DIR to that directory." 164 ); 165 } 166 167 let localFilePath = path.join( 168 `${context.options.browsertime.moz_fetch_dir}`, 169 `${context.options.browsertime.test_file_name}` 170 ); 171 if (!fs.existsSync(localFilePath)) { 172 localFilePath = path.join( 173 `${context.options.browsertime.moz_fetch_dir}`, 174 "upload-test-32MB.dat" 175 ); 176 } 177 178 context.log.info("Sending file path: " + localFilePath); 179 await uploadItem.sendKeys(localFilePath); 180 } else { 181 const actions = driver.actions({ async: true }); 182 await actions.move({ origin: uploadItem }).click().perform(); 183 } 184 185 // Start the test and wait for the upload to complete 186 let results = await waitForComplete( 187 1200000, 188 commands, 189 context, 190 "upload_status" 191 ); 192 let uploadTime = results.end - results.start; 193 194 // Store result in megabit/seconds 195 let uploadGoodput = Number.isNaN(results.goodput) 196 ? (file_size * 8) / ((uploadTime / 1000) * 1e6) 197 : results.goodput; 198 context.log.info( 199 "upload results: " + 200 results.status + 201 " duration: " + 202 uploadTime + 203 "ms, uploadGoodput: " + 204 uploadGoodput + 205 "Mbit/s" 206 ); 207 if (results.status === "success") { 208 accumulatedResults.push(uploadGoodput); 209 } 210 } else { 211 context.log.error("Unsupported test type:" + testType); 212 } 213 }); 214 215 if (browserName === "firefox" && protocol === "h3") { 216 const statsScript = ` 217 const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService( 218 Ci.nsIDashboard 219 ); 220 let promise = new Promise((resolve, reject) => { 221 gDashboard.requestHttp3ConnectionStats((data) => { 222 resolve(data); 223 }); 224 }); 225 let done = false; 226 let result = null; 227 let error = null; 228 promise 229 .catch(e => { 230 error = e; 231 }) 232 .then(r => { 233 result = r; 234 done = true; 235 }); 236 // Spin the event loop until done becomes true. 237 Services.tm.spinEventLoopUntil( 238 "requestHttpConnections", 239 () => done 240 ); 241 return result; 242 `; 243 let res = await commands.js.runPrivileged(statsScript); 244 context.log.info("HTTP/3 Connection Stats" + JSON.stringify(res)); 245 } 246 247 // No need to close the connection at the last run. 248 if (iteration != iterations - 1) { 249 await commands.navigate("about:blank"); 250 if (browserName === "firefox") { 251 const script = ` 252 Services.obs.notifyObservers(null, "net:cancel-all-connections"); 253 `; 254 commands.js.runPrivileged(script); 255 context.log.info("Waiting 3s to close connection..."); 256 await commands.wait.byTime(3000); 257 } else { 258 // TODO: configiure a shorter idle timeout at server side, so we 259 // don't have to wait that long. 260 context.log.info("Waiting 35s to close connection..."); 261 await commands.wait.byTime(35000); 262 } 263 } 264 } 265 266 if (testType === "download") { 267 commands.measure.addObject({ 268 custom_data: { "download-goodput": accumulatedResults }, 269 }); 270 } else if (testType === "upload") { 271 commands.measure.addObject({ 272 custom_data: { "upload-goodput": accumulatedResults }, 273 }); 274 } 275 } 276 );