summaryrefslogtreecommitdiffstats
path: root/netwerk/test/perf
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /netwerk/test/perf
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/test/perf/.eslintrc.js12
-rw-r--r--netwerk/test/perf/hooks_throttling.py203
-rw-r--r--netwerk/test/perf/perftest.ini8
-rw-r--r--netwerk/test/perf/perftest_http3_cloudflareblog.js56
-rw-r--r--netwerk/test/perf/perftest_http3_controlled.js32
-rw-r--r--netwerk/test/perf/perftest_http3_facebook_scroll.js166
-rw-r--r--netwerk/test/perf/perftest_http3_google_image.js191
-rw-r--r--netwerk/test/perf/perftest_http3_google_search.js73
-rw-r--r--netwerk/test/perf/perftest_http3_lucasquicfetch.js134
-rw-r--r--netwerk/test/perf/perftest_http3_youtube_watch.js74
-rw-r--r--netwerk/test/perf/perftest_http3_youtube_watch_scroll.js86
11 files changed, 1035 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..3662662b42
--- /dev/null
+++ b/netwerk/test/perf/hooks_throttling.py
@@ -0,0 +1,203 @@
+# 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 time
+import http.client
+import os
+import json
+from urllib.parse import urlparse
+import sys
+
+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.enabled: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..a67e412907
--- /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.enabled: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..b3ea4b881e
--- /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.enabled: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..ca62d8947f
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_facebook_scroll.js
@@ -0,0 +1,166 @@
+// 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.enabled: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 == 0) {
+ 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..5b8cb6842c
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_google_image.js
@@ -0,0 +1,191 @@
+// 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.enabled: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 == 0) {
+ 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..4831e1a037
--- /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.enabled: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..b20042bf45
--- /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.enabled: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..2063140fcd
--- /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.enabled: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..b3b2928108
--- /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.enabled: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.",
+};