diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /netwerk/test/perf | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/test/perf')
-rw-r--r-- | netwerk/test/perf/.eslintrc.js | 12 | ||||
-rw-r--r-- | netwerk/test/perf/hooks_throttling.py | 202 | ||||
-rw-r--r-- | netwerk/test/perf/perftest.ini | 8 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_cloudflareblog.js | 56 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_controlled.js | 32 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_facebook_scroll.js | 165 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_google_image.js | 190 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_google_search.js | 73 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_lucasquicfetch.js | 134 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_youtube_watch.js | 74 | ||||
-rw-r--r-- | netwerk/test/perf/perftest_http3_youtube_watch_scroll.js | 86 |
11 files changed, 1032 insertions, 0 deletions
diff --git a/netwerk/test/perf/.eslintrc.js b/netwerk/test/perf/.eslintrc.js new file mode 100644 index 0000000000..f040032509 --- /dev/null +++ b/netwerk/test/perf/.eslintrc.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + env: { + browser: true, + node: true, + }, +}; diff --git a/netwerk/test/perf/hooks_throttling.py b/netwerk/test/perf/hooks_throttling.py new file mode 100644 index 0000000000..5f46b3f0d3 --- /dev/null +++ b/netwerk/test/perf/hooks_throttling.py @@ -0,0 +1,202 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +""" +Drives the throttling feature when the test calls our +controlled server. +""" +import http.client +import json +import os +import sys +import time +from urllib.parse import urlparse + +from mozperftest.test.browsertime import add_option +from mozperftest.utils import get_tc_secret + +ENDPOINTS = { + "linux": "h3.dev.mozaws.net", + "darwin": "h3.mac.dev.mozaws.net", + "win32": "h3.win.dev.mozaws.net", +} +CTRL_SERVER = ENDPOINTS[sys.platform] +TASK_CLUSTER = "TASK_ID" in os.environ.keys() +_SECRET = { + "throttler_host": f"https://{CTRL_SERVER}/_throttler", + "throttler_key": os.environ.get("WEBNETEM_KEY", ""), +} +if TASK_CLUSTER: + _SECRET.update(get_tc_secret()) + +if _SECRET["throttler_key"] == "": + if TASK_CLUSTER: + raise Exception("throttler_key not found in secret") + raise Exception("WEBNETEM_KEY not set") + +_TIMEOUT = 30 +WAIT_TIME = 60 * 10 +IDLE_TIME = 10 +BREATHE_TIME = 20 + + +class Throttler: + def __init__(self, env, host, key): + self.env = env + self.host = host + self.key = key + self.verbose = env.get_arg("verbose", False) + self.logger = self.verbose and self.env.info or self.env.debug + + def log(self, msg): + self.logger("[throttler] " + msg) + + def _request(self, action, data=None): + kw = {} + headers = {b"X-WEBNETEM-KEY": self.key} + verb = data is None and "GET" or "POST" + if data is not None: + data = json.dumps(data) + headers[b"Content-type"] = b"application/json" + + parsed = urlparse(self.host) + server = parsed.netloc + path = parsed.path + if action != "status": + path += "/" + action + + self.log(f"Calling {verb} {path}") + conn = http.client.HTTPSConnection(server, timeout=_TIMEOUT) + conn.request(verb, path, body=data, headers=headers, **kw) + resp = conn.getresponse() + res = resp.read() + if resp.status >= 400: + raise Exception(res) + res = json.loads(res) + return res + + def start(self, data=None): + self.log("Starting") + now = time.time() + acquired = False + + while time.time() - now < WAIT_TIME: + status = self._request("status") + if status.get("test_running"): + # a test is running + self.log("A test is already controlling the server") + self.log(f"Waiting {IDLE_TIME} seconds") + else: + try: + self._request("start_test") + acquired = True + break + except Exception: + # we got beat in the race + self.log("Someone else beat us") + time.sleep(IDLE_TIME) + + if not acquired: + raise Exception("Could not acquire the test server") + + if data is not None: + self._request("shape", data) + + def stop(self): + self.log("Stopping") + try: + self._request("reset") + finally: + self._request("stop_test") + + +def get_throttler(env): + host = _SECRET["throttler_host"] + key = _SECRET["throttler_key"].encode() + return Throttler(env, host, key) + + +_PROTOCOL = "h2", "h3" +_PAGE = "gallery", "news", "shopping", "photoblog" + +# set the network condition here. +# each item has a name and some netem options: +# +# loss_ratio: specify percentage of packets that will be lost +# loss_corr: specify a correlation factor for the random packet loss +# dup_ratio: specify percentage of packets that will be duplicated +# delay: specify an overall delay for each packet +# jitter: specify amount of jitter in milliseconds +# delay_jitter_corr: specify a correlation factor for the random jitter +# reorder_ratio: specify percentage of packets that will be reordered +# reorder_corr: specify a correlation factor for the random reordering +# +_THROTTLING = ( + {"name": "full"}, # no throttling. + {"name": "one", "delay": "20"}, + {"name": "two", "delay": "50"}, + {"name": "three", "delay": "100"}, + {"name": "four", "delay": "200"}, + {"name": "five", "delay": "300"}, +) + + +def get_test(): + """Iterate on test conditions. + + For each cycle, we return a combination of: protocol, page, throttling + settings. Each combination has a name, and that name will be used along with + the protocol as a prefix for each metrics. + """ + for proto in _PROTOCOL: + for page in _PAGE: + url = f"https://{CTRL_SERVER}/{page}.html" + for throttler_settings in _THROTTLING: + yield proto, page, url, throttler_settings + + +combo = get_test() + + +def before_cycle(metadata, env, cycle, script): + global combo + if "throttlable" not in script["tags"]: + return + throttler = get_throttler(env) + try: + proto, page, url, throttler_settings = next(combo) + except StopIteration: + combo = get_test() + proto, page, url, throttler_settings = next(combo) + + # setting the url for the browsertime script + add_option(env, "browsertime.url", url, overwrite=True) + + # enabling http if needed + if proto == "h3": + add_option(env, "firefox.preference", "network.http.http3.enable:true") + + # prefix used to differenciate metrics + name = throttler_settings["name"] + script["name"] = f"{name}_{proto}_{page}" + + # throttling the controlled server if needed + if throttler_settings != {"name": "full"}: + env.info("Calling the controlled server") + throttler.start(throttler_settings) + else: + env.info("No throttling for this call") + throttler.start() + + +def after_cycle(metadata, env, cycle, script): + if "throttlable" not in script["tags"]: + return + throttler = get_throttler(env) + try: + throttler.stop() + except Exception: + pass + + # give a chance for a competitive job to take over + time.sleep(BREATHE_TIME) diff --git a/netwerk/test/perf/perftest.ini b/netwerk/test/perf/perftest.ini new file mode 100644 index 0000000000..aa96f97bf6 --- /dev/null +++ b/netwerk/test/perf/perftest.ini @@ -0,0 +1,8 @@ +[perftest_http3_cloudflareblog.js] +[perftest_http3_controlled.js] +[perftest_http3_facebook_scroll.js] +[perftest_http3_google_image.js] +[perftest_http3_google_search.js] +[perftest_http3_lucasquicfetch.js] +[perftest_http3_youtube_watch.js] +[perftest_http3_youtube_watch_scroll.js] diff --git a/netwerk/test/perf/perftest_http3_cloudflareblog.js b/netwerk/test/perf/perftest_http3_cloudflareblog.js new file mode 100644 index 0000000000..4bbea56bbd --- /dev/null +++ b/netwerk/test/perf/perftest_http3_cloudflareblog.js @@ -0,0 +1,56 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function test(context, commands) { + let rootUrl = "https://blog.cloudflare.com/"; + let waitTime = 1000; + + if ( + (typeof context.options.browsertime !== "undefined") & + (typeof context.options.browsertime.waitTime !== "undefined") + ) { + waitTime = context.options.browsertime.waitTime; + } + + // Make firefox learn of HTTP/3 server + // XXX: Need to build an HTTP/3-specific conditioned profile + // to handle these pre-navigations. + await commands.navigate(rootUrl); + + let cycles = 1; + for (let cycle = 0; cycle < cycles; cycle++) { + // Measure initial pageload + await commands.measure.start("pageload"); + await commands.navigate(rootUrl); + await commands.measure.stop(); + commands.measure.result[0].browserScripts.pageinfo.url = + "Cloudflare Blog - Main"; + + // Wait for X seconds + await commands.wait.byTime(waitTime); + + // Measure navigation pageload + await commands.measure.start("pageload"); + await commands.click.byJsAndWait(` + document.querySelectorAll("article")[0].querySelector("a") + `); + await commands.measure.stop(); + commands.measure.result[1].browserScripts.pageinfo.url = + "Cloudflare Blog - Article"; + } +} + +module.exports = { + test, + owner: "Network Team", + name: "cloudflare", + component: "netwerk", + description: "User-journey live site test for cloudflare blog.", +}; diff --git a/netwerk/test/perf/perftest_http3_controlled.js b/netwerk/test/perf/perftest_http3_controlled.js new file mode 100644 index 0000000000..243c314b5b --- /dev/null +++ b/netwerk/test/perf/perftest_http3_controlled.js @@ -0,0 +1,32 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function test(context, commands) { + let url = context.options.browsertime.url; + + // Make firefox learn of HTTP/3 server + // XXX: Need to build an HTTP/3-specific conditioned profile + // to handle these pre-navigations. + await commands.navigate(url); + + // Measure initial pageload + await commands.measure.start("pageload"); + await commands.navigate(url); + await commands.measure.stop(); + commands.measure.result[0].browserScripts.pageinfo.url = url; +} + +module.exports = { + test, + owner: "Network Team", + name: "controlled", + description: "User-journey live site test for controlled server", + tags: ["throttlable"], +}; diff --git a/netwerk/test/perf/perftest_http3_facebook_scroll.js b/netwerk/test/perf/perftest_http3_facebook_scroll.js new file mode 100644 index 0000000000..7e736a0f5f --- /dev/null +++ b/netwerk/test/perf/perftest_http3_facebook_scroll.js @@ -0,0 +1,165 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function captureNetworkRequest(commands) { + var capture_network_request = []; + var capture_resource = await commands.js.run(` + return performance.getEntriesByType("resource"); + `); + for (var i = 0; i < capture_resource.length; i++) { + capture_network_request.push(capture_resource[i].name); + } + return capture_network_request; +} + +async function waitForScrollRequestsEnd( + prevCount, + maxStableCount, + timeout, + commands, + context +) { + let starttime = await commands.js.run(`return performance.now();`); + let endtime = await commands.js.run(`return performance.now();`); + let changing = true; + let newCount = -1; + let stableCount = 0; + + while ( + ((await commands.js.run(`return performance.now();`)) - starttime < + timeout) & + changing + ) { + // Wait a bit before making another round + await commands.wait.byTime(100); + newCount = (await captureNetworkRequest(commands)).length; + context.log.debug(`${newCount}, ${prevCount}, ${stableCount}`); + + // Check if we are approaching stability + if (newCount == prevCount) { + // Gather the end time now + if (stableCount == 0) { + endtime = await commands.js.run(`return performance.now();`); + } + stableCount++; + } else { + prevCount = newCount; + stableCount = 0; + } + + if (stableCount >= maxStableCount) { + // Stability achieved + changing = false; + } + } + + return { + start: starttime, + end: endtime, + numResources: newCount, + }; +} + +async function test(context, commands) { + let rootUrl = "https://www.facebook.com/lambofgod/"; + let waitTime = 1000; + let numScrolls = 5; + + const average = arr => arr.reduce((p, c) => p + c, 0) / arr.length; + + if (typeof context.options.browsertime !== "undefined") { + if (typeof context.options.browsertime.waitTime !== "undefined") { + waitTime = context.options.browsertime.waitTime; + } + if (typeof context.options.browsertime.numScrolls !== "undefined") { + numScrolls = context.options.browsertime.numScrolls; + } + } + + // Make firefox learn of HTTP/3 server + await commands.navigate(rootUrl); + + let cycles = 1; + for (let cycle = 0; cycle < cycles; cycle++) { + // Measure the pageload + await commands.measure.start("pageload"); + await commands.navigate(rootUrl); + await commands.measure.stop(); + + // Initial scroll to make the new user popup show + await commands.js.runAndWait( + `window.scrollTo({ top: 1000, behavior: 'smooth' })` + ); + await commands.wait.byTime(1000); + await commands.click.byLinkTextAndWait("Not Now"); + + let vals = []; + let badIterations = 0; + for (let iteration = 0; iteration < numScrolls; iteration++) { + // Clear old resources + await commands.js.run(`performance.clearResourceTimings();`); + + // Get current resource count + let currCount = (await captureNetworkRequest(commands)).length; + + // Scroll to a ridiculously high value for "infinite" down-scrolling + commands.js.runAndWait(` + window.scrollTo({ top: 100000000 }) + `); + + /* + The maxStableCount of 22 was chosen as a trade-off between fast iterations + and minimizing the number of bad iterations. + */ + let newInfo = await waitForScrollRequestsEnd( + currCount, + 22, + 120000, + commands, + context + ); + + // Gather metrics + let ndiff = newInfo.numResources - currCount; + let tdiff = (newInfo.end - newInfo.start) / 1000; + + // Check if we had a bad iteration + if (ndiff == 0) { + context.log.info("Bad iteration, redoing..."); + iteration--; + badIterations++; + if (badIterations == 5) { + throw new Error("Too many bad scroll iterations occurred"); + } + continue; + } + + vals.push(ndiff / tdiff); + + // Wait X seconds before scrolling again + await commands.wait.byTime(waitTime); + } + + if (!vals.length) { + throw new Error("No requestsPerSecond values were obtained"); + } + + commands.measure.result[0].browserScripts.pageinfo.requestsPerSecond = + average(vals); + } +} + +module.exports = { + test, + owner: "Network Team", + component: "netwerk", + name: "facebook-scroll", + description: "Measures the number of requests per second after a scroll.", +}; diff --git a/netwerk/test/perf/perftest_http3_google_image.js b/netwerk/test/perf/perftest_http3_google_image.js new file mode 100644 index 0000000000..8d0f788245 --- /dev/null +++ b/netwerk/test/perf/perftest_http3_google_image.js @@ -0,0 +1,190 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function getNumImagesLoaded(elementSelector, commands) { + return commands.js.run(` + let sum = 0; + document.querySelectorAll(${elementSelector}).forEach(e => { + sum += e.complete & e.naturalHeight != 0; + }); + return sum; + `); +} + +async function waitForImgLoadEnd( + prevCount, + maxStableCount, + iterationDelay, + timeout, + commands, + context, + counter, + elementSelector +) { + let starttime = await commands.js.run(`return performance.now();`); + let endtime = await commands.js.run(`return performance.now();`); + let changing = true; + let newCount = -1; + let stableCount = 0; + + while ( + ((await commands.js.run(`return performance.now();`)) - starttime < + timeout) & + changing + ) { + // Wait a bit before making another round + await commands.wait.byTime(iterationDelay); + newCount = await counter(elementSelector, commands); + context.log.debug(`${newCount}, ${prevCount}, ${stableCount}`); + + // Check if we are approaching stability + if (newCount == prevCount) { + // Gather the end time now + if (stableCount == 0) { + endtime = await commands.js.run(`return performance.now();`); + } + stableCount++; + } else { + prevCount = newCount; + stableCount = 0; + } + + if (stableCount >= maxStableCount) { + // Stability achieved + changing = false; + } + } + + return { + start: starttime, + end: endtime, + numResources: newCount, + }; +} + +async function test(context, commands) { + let rootUrl = "https://www.google.com/search?q=kittens&tbm=isch"; + let waitTime = 1000; + let numScrolls = 10; + + const average = arr => arr.reduce((p, c) => p + c, 0) / arr.length; + + if (typeof context.options.browsertime !== "undefined") { + if (typeof context.options.browsertime.waitTime !== "undefined") { + waitTime = context.options.browsertime.waitTime; + } + if (typeof context.options.browsertime.numScrolls !== "undefined") { + numScrolls = context.options.browsertime.numScrolls; + } + } + + // Make firefox learn of HTTP/3 server + await commands.navigate(rootUrl); + + let cycles = 1; + for (let cycle = 0; cycle < cycles; cycle++) { + // Measure the pageload + await commands.measure.start("pageload"); + await commands.navigate(rootUrl); + await commands.measure.stop(); + + async function getHeight() { + return commands.js.run(`return document.body.scrollHeight;`); + } + + let vals = []; + let badIterations = 0; + let prevHeight = 0; + for (let iteration = 0; iteration < numScrolls; iteration++) { + // Get current image count + let currCount = await getNumImagesLoaded(`".isv-r img"`, commands); + prevHeight = await getHeight(); + + // Scroll to a ridiculously high value for "infinite" down-scrolling + commands.js.runAndWait(` + window.scrollTo({ top: 100000000 }) + `); + + /* + The maxStableCount of 22 was chosen as a trade-off between fast iterations + and minimizing the number of bad iterations. + */ + let results = await waitForImgLoadEnd( + currCount, + 22, + 100, + 120000, + commands, + context, + getNumImagesLoaded, + `".isv-r img"` + ); + + // Gather metrics + let ndiff = results.numResources - currCount; + let tdiff = (results.end - results.start) / 1000; + + // Check if we had a bad iteration + if (ndiff == 0) { + // Check if the end of the search results was reached + if (prevHeight == (await getHeight())) { + context.log.info("Reached end of page."); + break; + } + context.log.info("Bad iteration, redoing..."); + iteration--; + badIterations++; + if (badIterations == 5) { + throw new Error("Too many bad scroll iterations occurred"); + } + continue; + } + + context.log.info(`${ndiff}, ${tdiff}`); + vals.push(ndiff / tdiff); + + // Wait X seconds before scrolling again + await commands.wait.byTime(waitTime); + } + + if (!vals.length) { + throw new Error("No requestsPerSecond values were obtained"); + } + + commands.measure.result[0].browserScripts.pageinfo.imagesPerSecond = + average(vals); + + // Test clicking and and opening an image + await commands.wait.byTime(waitTime); + commands.click.byJs(` + const links = document.querySelectorAll(".islib"); links[links.length-1] + `); + let results = await waitForImgLoadEnd( + 0, + 22, + 50, + 120000, + commands, + context, + getNumImagesLoaded, + `"#islsp img"` + ); + commands.measure.result[0].browserScripts.pageinfo.imageLoadTime = + results.end - results.start; + } +} + +module.exports = { + test, + owner: "Network Team", + component: "netwerk", + name: "g-image", + description: "Measures the number of images per second after a scroll.", +}; diff --git a/netwerk/test/perf/perftest_http3_google_search.js b/netwerk/test/perf/perftest_http3_google_search.js new file mode 100644 index 0000000000..8183e3a152 --- /dev/null +++ b/netwerk/test/perf/perftest_http3_google_search.js @@ -0,0 +1,73 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function test(context, commands) { + let rootUrl = "https://www.google.com/"; + let waitTime = 1000; + const driver = context.selenium.driver; + const webdriver = context.selenium.webdriver; + + if ( + (typeof context.options.browsertime !== "undefined") & + (typeof context.options.browsertime.waitTime !== "undefined") + ) { + waitTime = context.options.browsertime.waitTime; + } + + // Make firefox learn of HTTP/3 server + await commands.navigate(rootUrl); + + let cycles = 1; + for (let cycle = 0; cycle < cycles; cycle++) { + await commands.navigate(rootUrl); + await commands.wait.byTime(1000); + + // Set up the search + context.log.info("Setting up search"); + const searchfield = driver.findElement(webdriver.By.name("q")); + searchfield.sendKeys("Python\n"); + await commands.wait.byTime(5000); + + // Measure the search time + context.log.info("Start search"); + await commands.measure.start("pageload"); + await commands.click.byJs(`document.querySelector("input[name='btnK']")`); + await commands.wait.byTime(5000); + await commands.measure.stop(); + context.log.info("Done"); + + commands.measure.result[0].browserScripts.pageinfo.url = + "Google Search (Python)"; + + // Wait for X seconds + context.log.info(`Waiting for ${waitTime} milliseconds`); + await commands.wait.byTime(waitTime); + + // Go to the next search page and measure + context.log.info("Going to second page of search results"); + await commands.measure.start("pageload"); + await commands.click.byIdAndWait("pnnext"); + + // XXX: Increase wait time when we add latencies + await commands.wait.byTime(3000); + await commands.measure.stop(); + + commands.measure.result[1].browserScripts.pageinfo.url = + "Google Search (Python) - Next Page"; + } +} + +module.exports = { + test, + owner: "Network Team", + component: "netwerk", + name: "g-search", + description: "User-journey live site test for google search", +}; diff --git a/netwerk/test/perf/perftest_http3_lucasquicfetch.js b/netwerk/test/perf/perftest_http3_lucasquicfetch.js new file mode 100644 index 0000000000..49a5b4c824 --- /dev/null +++ b/netwerk/test/perf/perftest_http3_lucasquicfetch.js @@ -0,0 +1,134 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function getNumLoaded(commands) { + return commands.js.run(` + let sum = 0; + document.querySelectorAll("#imgContainer img").forEach(e => { + sum += e.complete & e.naturalHeight != 0; + }); + return sum; + `); +} + +async function waitForImgLoadEnd( + prevCount, + maxStableCount, + timeout, + commands, + context +) { + let starttime = await commands.js.run(`return performance.now();`); + let endtime = await commands.js.run(`return performance.now();`); + let changing = true; + let newCount = -1; + let stableCount = 0; + + while ( + ((await commands.js.run(`return performance.now();`)) - starttime < + timeout) & + changing + ) { + // Wait a bit before making another round + await commands.wait.byTime(100); + newCount = await getNumLoaded(commands); + context.log.debug(`${newCount}, ${prevCount}, ${stableCount}`); + + // Check if we are approaching stability + if (newCount == prevCount) { + // Gather the end time now + if (stableCount == 0) { + endtime = await commands.js.run(`return performance.now();`); + } + stableCount++; + } else { + prevCount = newCount; + stableCount = 0; + } + + if (stableCount >= maxStableCount) { + // Stability achieved + changing = false; + } + } + + return { + start: starttime, + end: endtime, + numResources: newCount, + }; +} + +async function test(context, commands) { + let rootUrl = "https://lucaspardue.com/quictilesfetch.html"; + let cycles = 5; + + if ( + (typeof context.options.browsertime !== "undefined") & + (typeof context.options.browsertime.cycles !== "undefined") + ) { + cycles = context.options.browsertime.cycles; + } + + // Make firefox learn of HTTP/3 server + // XXX: Need to build an HTTP/3-specific conditioned profile + // to handle these pre-navigations. + await commands.navigate(rootUrl); + + let combos = [ + [100, 1], + [100, 100], + [300, 300], + ]; + for (let cycle = 0; cycle < cycles; cycle++) { + for (let combo = 0; combo < combos.length; combo++) { + await commands.measure.start("pageload"); + await commands.navigate(rootUrl); + await commands.measure.stop(); + let last = commands.measure.result.length - 1; + commands.measure.result[ + last + ].browserScripts.pageinfo.url = `LucasQUIC (r=${combos[combo][0]}, p=${combos[combo][1]})`; + + // Set the input fields + await commands.js.runAndWait(` + document.querySelector("#maxReq").setAttribute( + "value", + ${combos[combo][0]} + ) + `); + await commands.js.runAndWait(` + document.querySelector("#reqGroup").setAttribute( + "value", + ${combos[combo][1]} + ) + `); + + // Start the test and wait for the images to finish loading + commands.click.byJs(`document.querySelector("button")`); + let results = await waitForImgLoadEnd(0, 40, 120000, commands, context); + + commands.measure.result[last].browserScripts.pageinfo.resourceLoadTime = + results.end - results.start; + commands.measure.result[last].browserScripts.pageinfo.imagesLoaded = + results.numResources; + commands.measure.result[last].browserScripts.pageinfo.imagesMissed = + combos[combo][0] - results.numResources; + } + } +} + +module.exports = { + test, + owner: "Network Team", + name: "lq-fetch", + component: "netwerk", + description: "Measures the amount of time it takes to load a set of images.", +}; diff --git a/netwerk/test/perf/perftest_http3_youtube_watch.js b/netwerk/test/perf/perftest_http3_youtube_watch.js new file mode 100644 index 0000000000..1fd525941d --- /dev/null +++ b/netwerk/test/perf/perftest_http3_youtube_watch.js @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function test(context, commands) { + let rootUrl = "https://www.youtube.com/watch?v=COU5T-Wafa4"; + let waitTime = 20000; + + if ( + (typeof context.options.browsertime !== "undefined") & + (typeof context.options.browsertime.waitTime !== "undefined") + ) { + waitTime = context.options.browsertime.waitTime; + } + + // Make firefox learn of HTTP/3 server + await commands.navigate(rootUrl); + + let cycles = 1; + for (let cycle = 0; cycle < cycles; cycle++) { + await commands.measure.start("pageload"); + await commands.navigate(rootUrl); + + // Make sure the video is running + if ( + await commands.js.run(`return document.querySelector("video").paused;`) + ) { + throw new Error("Video should be running but it's paused"); + } + + // Disable youtube autoplay + await commands.click.byIdAndWait("toggleButton"); + + // Start playback quality measurements + const start = await commands.js.run(`return performance.now();`); + while ( + !(await commands.js.run(` + return document.querySelector("video").ended; + `)) & + !(await commands.js.run(` + return document.querySelector("video").paused; + `)) & + ((await commands.js.run(`return performance.now();`)) - start < waitTime) + ) { + await commands.wait.byTime(5000); + context.log.info("playing..."); + } + + // Video done, now gather metrics + const playbackQuality = await commands.js.run( + `return document.querySelector("video").getVideoPlaybackQuality();` + ); + await commands.measure.stop(); + + commands.measure.result[0].browserScripts.pageinfo.droppedFrames = + playbackQuality.droppedVideoFrames; + commands.measure.result[0].browserScripts.pageinfo.decodedFrames = + playbackQuality.totalVideoFrames; + } +} + +module.exports = { + test, + owner: "Network Team", + component: "netwerk", + name: "youtube-noscroll", + description: "Measures quality of the video being played.", +}; diff --git a/netwerk/test/perf/perftest_http3_youtube_watch_scroll.js b/netwerk/test/perf/perftest_http3_youtube_watch_scroll.js new file mode 100644 index 0000000000..8f30fcc5e6 --- /dev/null +++ b/netwerk/test/perf/perftest_http3_youtube_watch_scroll.js @@ -0,0 +1,86 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +/* eslint-env node */ + +/* +Ensure the `--firefox.preference=network.http.http3.enable:true` is +set for this test. +*/ + +async function test(context, commands) { + let rootUrl = "https://www.youtube.com/watch?v=COU5T-Wafa4"; + let waitTime = 20000; + + if ( + (typeof context.options.browsertime !== "undefined") & + (typeof context.options.browsertime.waitTime !== "undefined") + ) { + waitTime = context.options.browsertime.waitTime; + } + + // Make firefox learn of HTTP/3 server + await commands.navigate(rootUrl); + + let cycles = 1; + for (let cycle = 0; cycle < cycles; cycle++) { + await commands.measure.start("pageload"); + await commands.navigate(rootUrl); + + // Make sure the video is running + // XXX: Should we start the video ourself? + if ( + await commands.js.run(`return document.querySelector("video").paused;`) + ) { + throw new Error("Video should be running but it's paused"); + } + + // Disable youtube autoplay + await commands.click.byIdAndWait("toggleButton"); + + // Start playback quality measurements + const start = await commands.js.run(`return performance.now();`); + let counter = 1; + let direction = 0; + while ( + !(await commands.js.run(` + return document.querySelector("video").ended; + `)) & + !(await commands.js.run(` + return document.querySelector("video").paused; + `)) & + ((await commands.js.run(`return performance.now();`)) - start < waitTime) + ) { + // Reset the scroll after going down 10 times + direction = counter * 1000; + if (direction > 10000) { + counter = -1; + } + counter++; + + await commands.js.runAndWait( + `window.scrollTo({ top: ${direction}, behavior: 'smooth' })` + ); + context.log.info("playing while scrolling..."); + } + + // Video done, now gather metrics + const playbackQuality = await commands.js.run( + `return document.querySelector("video").getVideoPlaybackQuality();` + ); + await commands.measure.stop(); + + commands.measure.result[0].browserScripts.pageinfo.droppedFrames = + playbackQuality.droppedVideoFrames; + commands.measure.result[0].browserScripts.pageinfo.decodedFrames = + playbackQuality.totalVideoFrames; + } +} + +module.exports = { + test, + owner: "Network Team", + component: "netwerk", + name: "youtube-scroll", + description: "Measures quality of the video being played.", +}; |