From 911534d3521191847ba581a9b339fe9c147e1dc7 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 1 Sep 2025 18:57:42 +0200 Subject: [PATCH 1/7] params --- params.js | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 params.js diff --git a/params.js b/params.js new file mode 100644 index 00000000..f8048ce0 --- /dev/null +++ b/params.js @@ -0,0 +1,167 @@ +"use strict"; + +/* + * Copyright (C) 2025 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +export class Params { + // Enable a detailed developer menu to change the current Params. + developerMode = false; + startAutomatically = false; + + developerMode + constructor(searchParams = undefined) { + if (searchParams) + this._copyFromSearchParams(searchParams); + if (!this.developerMode) { + Object.freeze(this.viewport); + Object.freeze(this); + } + } + + _parseInt(value, errorMessage) { + const number = Number(value); + if (!Number.isInteger(number) && errorMessage) + throw new Error(`Invalid ${errorMessage} param: '${value}', expected int.`); + return parseInt(number); + } + + _copyFromSearchParams(searchParams) { + this.viewport = this._parseViewport(searchParams); + + const unused = Array.from(searchParams.keys()); + if (unused.length > 0) + console.error("Got unused search params", unused); + } + + _parseBooleanParam(searchParams, paramKey) { + if (!searchParams.has(paramKey)) + return false; + searchParams.delete(paramKey); + return true; + } + + _parseIntParam(searchParams, paramKey, minValue) { + if (!searchParams.has(paramKey)) + return defaultParams[paramKey]; + + const parsedValue = this._parseInt(searchParams.get(paramKey), "waitBeforeSync"); + if (parsedValue < minValue) + throw new Error(`Invalid ${paramKey} param: '${parsedValue}', value must be >= ${minValue}.`); + searchParams.delete(paramKey); + return parsedValue; + } + + _parseSuites(searchParams) { + if (searchParams.has("suite") || searchParams.has("suites")) { + if (searchParams.has("suite") && searchParams.has("suites")) + throw new Error("Params 'suite' and 'suites' can not be used together."); + const value = searchParams.get("suite") || searchParams.get("suites"); + const suites = value.split(","); + if (suites.length === 0) + throw new Error("No suites selected"); + searchParams.delete("suite"); + searchParams.delete("suites"); + return suites; + } + return defaultParams.suites; + } + + _parseTags(searchParams) { + if (!searchParams.has("tags")) + return defaultParams.tags; + if (this.suites.length) + throw new Error("'suites' and 'tags' cannot be used together."); + const tags = searchParams.get("tags").split(","); + searchParams.delete("tags"); + return tags; + } + + _parseEnumParam(searchParams, paramKey, enumArray) { + if (!searchParams.has(paramKey)) + return defaultParams[paramKey]; + const value = searchParams.get(paramKey); + if (!enumArray.includes(value)) + throw new Error(`Got invalid ${paramKey}: '${value}', choices are ${enumArray}`); + searchParams.delete(paramKey); + return value; + } + + _parseShuffleSeed(searchParams) { + if (!searchParams.has("shuffleSeed")) + return defaultParams.shuffleSeed; + let shuffleSeed = searchParams.get("shuffleSeed"); + if (shuffleSeed !== "off") { + if (shuffleSeed === "generate") { + shuffleSeed = Math.floor((Math.random() * 1) << 16); + console.log(`Generated a random suite order seed: ${shuffleSeed}`); + } else { + shuffleSeed = parseInt(shuffleSeed); + } + if (!Number.isInteger(shuffleSeed)) + throw new Error(`Invalid shuffle seed: '${shuffleSeed}', must be either 'off', 'generate' or an integer.`); + } + searchParams.delete("shuffleSeed"); + return shuffleSeed; + } + + + toCompleteSearchParamsObject() { + return this.toSearchParamsObject(false); + } + + toSearchParamsObject(filter = true) { + const rawUrlParams = { __proto__: null }; + for (const [key, value] of Object.entries(this)) { + // Skip over default values. + if (filter && value === defaultParams[key]) + continue; + rawUrlParams[key] = value; + } + + if (this.viewport.width !== defaultParams.viewport.width || this.viewport.height !== defaultParams.viewport.height) + rawUrlParams.viewport = `${this.viewport.width}x${this.viewport.height}`; + + if (this.suites.length) { + rawUrlParams.suites = this.suites.join(","); + } else if (this.tags.length) { + if (!(this.tags.length === 1 && this.tags[0] === "default")) + rawUrlParams.tags = this.tags.join(","); + } else { + rawUrlParams.suites = ""; + } + + return new URLSearchParams(rawUrlParams); + } + + toSearchParams() { + return this.toSearchParamsObject().toString(); + } +} + +export const defaultParams = new Params(); + +let maybeCustomParams = defaultParams; +export const params = maybeCustomParams; \ No newline at end of file From 214d6f25ac93523559fedd1e2a1383289f7a9c56 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 1 Sep 2025 18:58:04 +0200 Subject: [PATCH 2/7] adding params --- params.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/params.js b/params.js index f8048ce0..c6418141 100644 --- a/params.js +++ b/params.js @@ -26,7 +26,7 @@ */ -export class Params { +class JetStreamParams { // Enable a detailed developer menu to change the current Params. developerMode = false; startAutomatically = false; @@ -161,7 +161,7 @@ export class Params { } } -export const defaultParams = new Params(); +export const defaultParams = new JetStreamParams(); let maybeCustomParams = defaultParams; export const params = maybeCustomParams; \ No newline at end of file From db9bf75476a8c17eabd35fbf86855c1c84c37f59 Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 1 Sep 2025 19:14:18 +0200 Subject: [PATCH 3/7] fixed html --- JetStreamDriver.js | 41 +--------------------- index.html | 2 ++ params.js | 87 ++++++++++++++++++++++++++++------------------ 3 files changed, 56 insertions(+), 74 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 6192fc62..71b86fab 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -42,46 +42,7 @@ globalThis.startDelay ??= undefined; globalThis.shouldReport ??= false; globalThis.prefetchResources ??= true; -function getIntParam(urlParams, key) { - const rawValue = urlParams.get(key); - const value = parseInt(rawValue); - if (value <= 0) - throw new Error(`Expected positive value for ${key}, but got ${rawValue}`); - return value; -} - -function getBoolParam(urlParams, key) { - const rawValue = urlParams.get(key).toLowerCase() - return !(rawValue === "false" || rawValue === "0") - } - -function getTestListParam(urlParams, key) { - if (globalThis.testList?.length) - throw new Error(`Overriding previous testList=${globalThis.testList.join()} with ${key} url-parameter.`); - return urlParams.getAll(key); -} - -if (typeof(URLSearchParams) !== "undefined") { - const urlParameters = new URLSearchParams(window.location.search); - if (urlParameters.has("report")) - globalThis.shouldReport = urlParameters.get("report").toLowerCase() == "true"; - if (urlParameters.has("startDelay")) - globalThis.startDelay = getIntParam(urlParameters, "startDelay"); - if (globalThis.shouldReport && !globalThis.startDelay) - globalThis.startDelay = 4000; - if (urlParameters.has("tag")) - globalThis.testList = getTestListParam(urlParameters, "tag"); - if (urlParameters.has("test")) - globalThis.testList = getTestListParam(urlParameters, "test"); - if (urlParameters.has("iterationCount")) - globalThis.testIterationCount = getIntParam(urlParameters, "iterationCount"); - if (urlParameters.has("worstCaseCount")) - globalThis.testWorstCaseCount = getIntParam(urlParameters, "worstCaseCount"); - if (urlParameters.has("prefetchResources")) - globalThis.prefetchResources = getBoolParam(urlParameters, "prefetchResources"); -} - -if (!globalThis.prefetchResources) +if (!JetStreamParams.prefetchResources) console.warn("Disabling resource prefetching!"); // Used for the promise representing the current benchmark run. diff --git a/index.html b/index.html index 98992b58..09bd92b7 100644 --- a/index.html +++ b/index.html @@ -36,6 +36,7 @@ const isInBrowser = true; const isD8 = false; const isSpiderMonkey = false; + globalThis.JetStreamParamsSource = new URLSearchParams(globalThis?.location?.search); globalThis.allIsGood = true; window.onerror = function(e) { if (e == "Script error.") { @@ -65,6 +66,7 @@ } + diff --git a/params.js b/params.js index c6418141..10c8e34a 100644 --- a/params.js +++ b/params.js @@ -26,12 +26,18 @@ */ -class JetStreamParams { - // Enable a detailed developer menu to change the current Params. - developerMode = false; - startAutomatically = false; +class Params { + // Enable a detailed developer menu to change the current Params. + developerMode = false; + startAutomatically = false; + shouldReport = false; + startDelay = undefined; + + testList = []; + testIterationCount = undefined; + testWorstCaseCount = undefined; + prefetchResources = true; - developerMode constructor(searchParams = undefined) { if (searchParams) this._copyFromSearchParams(searchParams); @@ -49,49 +55,56 @@ class JetStreamParams { } _copyFromSearchParams(searchParams) { - this.viewport = this._parseViewport(searchParams); + this.startAutomatically = this._parseBooleanParam(searchParams, "startAutomatically"); + this.developerMode = this._parseBooleanParam(searchParams, "developerMode"); + this.shouldReport = this._parseBooleanParam(searchParams, "report"); + this.prefetchResources = this._parseBooleanParam(searchParams, "prefetchResources"); + + this.startDelay = this._parseIntParam(searchParams, "startDelay", 0); + if (this.shouldReport && !this.startDelay) + this.startDelay = 4000; + + for (const paramKey of ["tag", "tags", "test", "tests"]) + this.testList = this._parseTestListParam(searchParams, paramKey); + + this.testIterationCount = this._parseIntParam(searchParams, "iterationCount", 1); + this.testWorstCaseCount = this._parseIntParam(searchParams, "worstCaseCount", 1); const unused = Array.from(searchParams.keys()); if (unused.length > 0) console.error("Got unused search params", unused); } + _parseTestListParam(searchParams, key) { + if (this.testList?.length) + throw new Error(`Overriding previous testList=${this.testList.join()} with ${key} url-parameter.`); + const value = searchParams.getAll(key); + searchParams.delete(key); + return value; + } + _parseBooleanParam(searchParams, paramKey) { if (!searchParams.has(paramKey)) - return false; + return DefaultJetStreamParams[paramKey]; + const value = searchParams.get(paramKey).toLowerCase(); searchParams.delete(paramKey); - return true; + return !(value === "false" || value === "0"); } _parseIntParam(searchParams, paramKey, minValue) { if (!searchParams.has(paramKey)) - return defaultParams[paramKey]; + return DefaultJetStreamParams[paramKey]; - const parsedValue = this._parseInt(searchParams.get(paramKey), "waitBeforeSync"); + const parsedValue = this._parseInt(searchParams.get(paramKey), paramKey); if (parsedValue < minValue) throw new Error(`Invalid ${paramKey} param: '${parsedValue}', value must be >= ${minValue}.`); searchParams.delete(paramKey); return parsedValue; } - _parseSuites(searchParams) { - if (searchParams.has("suite") || searchParams.has("suites")) { - if (searchParams.has("suite") && searchParams.has("suites")) - throw new Error("Params 'suite' and 'suites' can not be used together."); - const value = searchParams.get("suite") || searchParams.get("suites"); - const suites = value.split(","); - if (suites.length === 0) - throw new Error("No suites selected"); - searchParams.delete("suite"); - searchParams.delete("suites"); - return suites; - } - return defaultParams.suites; - } - _parseTags(searchParams) { if (!searchParams.has("tags")) - return defaultParams.tags; + return DefaultJetStreamParams.tags; if (this.suites.length) throw new Error("'suites' and 'tags' cannot be used together."); const tags = searchParams.get("tags").split(","); @@ -101,7 +114,7 @@ class JetStreamParams { _parseEnumParam(searchParams, paramKey, enumArray) { if (!searchParams.has(paramKey)) - return defaultParams[paramKey]; + return DefaultJetStreamParams[paramKey]; const value = searchParams.get(paramKey); if (!enumArray.includes(value)) throw new Error(`Got invalid ${paramKey}: '${value}', choices are ${enumArray}`); @@ -111,7 +124,7 @@ class JetStreamParams { _parseShuffleSeed(searchParams) { if (!searchParams.has("shuffleSeed")) - return defaultParams.shuffleSeed; + return DefaultJetStreamParams.shuffleSeed; let shuffleSeed = searchParams.get("shuffleSeed"); if (shuffleSeed !== "off") { if (shuffleSeed === "generate") { @@ -136,12 +149,12 @@ class JetStreamParams { const rawUrlParams = { __proto__: null }; for (const [key, value] of Object.entries(this)) { // Skip over default values. - if (filter && value === defaultParams[key]) + if (filter && value === DefaultJetStreamParams[key]) continue; rawUrlParams[key] = value; } - if (this.viewport.width !== defaultParams.viewport.width || this.viewport.height !== defaultParams.viewport.height) + if (this.viewport.width !== DefaultJetStreamParams.viewport.width || this.viewport.height !== DefaultJetStreamParams.viewport.height) rawUrlParams.viewport = `${this.viewport.width}x${this.viewport.height}`; if (this.suites.length) { @@ -161,7 +174,13 @@ class JetStreamParams { } } -export const defaultParams = new JetStreamParams(); - -let maybeCustomParams = defaultParams; -export const params = maybeCustomParams; \ No newline at end of file +const DefaultJetStreamParams = new Params(); +let maybeCustomParams = DefaultJetStreamParams; +if (globalThis?.JetStreamParamsSource) { + try { + maybeCustomParams = new Params(globalThis?.JetStreamParamsSource); + } catch (e) { + console.error("Invalid Params", e, "\nUsing defaults as fallback:", maybeCustomParams); + } +} +const JetStreamParams = maybeCustomParams; \ No newline at end of file From 88411ba3bfc85f41d420cc786e9d240217c279cc Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 1 Sep 2025 19:55:15 +0200 Subject: [PATCH 4/7] update params parsing --- JetStreamDriver.js | 72 ++++++++----------- cli.js | 169 +++++++++++++++++++++++++++++++-------------- params.js | 151 +++++++++++++++------------------------- 3 files changed, 203 insertions(+), 189 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 71b86fab..017ea2d6 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -30,18 +30,6 @@ const measureTotalTimeAsSubtest = false; // Once we move to preloading all resou const defaultIterationCount = 120; const defaultWorstCaseCount = 4; -globalThis.performance ??= Date; -globalThis.RAMification ??= false; -globalThis.testIterationCount ??= undefined; -globalThis.testIterationCountMap ??= new Map(); -globalThis.testWorstCaseCount ??= undefined; -globalThis.testWorstCaseCountMap ??= new Map(); -globalThis.dumpJSONResults ??= false; -globalThis.testList ??= undefined; -globalThis.startDelay ??= undefined; -globalThis.shouldReport ??= false; -globalThis.prefetchResources ??= true; - if (!JetStreamParams.prefetchResources) console.warn("Disabling resource prefetching!"); @@ -71,20 +59,20 @@ function displayCategoryScores() { } function getIterationCount(plan) { - if (testIterationCountMap.has(plan.name)) - return testIterationCountMap.get(plan.name); - if (globalThis.testIterationCount) - return globalThis.testIterationCount; + if (JetStreamParams.testIterationCountMap.has(plan.name)) + return JetStreamParams.testIterationCountMap.get(plan.name); + if (JetStreamParams.testIterationCount) + return JetStreamParams.testIterationCount; if (plan.iterations) return plan.iterations; return defaultIterationCount; } function getWorstCaseCount(plan) { - if (testWorstCaseCountMap.has(plan.name)) - return testWorstCaseCountMap.get(plan.name); - if (globalThis.testWorstCaseCount) - return globalThis.testWorstCaseCount; + if (JetStreamParams.testWorstCaseCountMap.has(plan.name)) + return JetStreamParams.testWorstCaseCountMap.get(plan.name); + if (JetStreamParams.testWorstCaseCount) + return JetStreamParams.testWorstCaseCount; if (plan.worstCaseCount) return plan.worstCaseCount; return defaultWorstCaseCount; @@ -162,7 +150,7 @@ class ShellFileLoader { // share common code. load(url) { console.assert(!isInBrowser); - if (!globalThis.prefetchResources) + if (!JetStreamParams.prefetchResources) return `load("${url}");` if (this.requests.has(url)) { @@ -201,7 +189,7 @@ class Driver { if (isInBrowser) { statusElement = document.getElementById("status"); statusElement.innerHTML = ``; - } else if (!dumpJSONResults) + } else if (!JetStreamParams.dumpJSONResults) console.log("Starting JetStream3"); performance.mark("update-ui-start"); @@ -221,7 +209,7 @@ class Driver { performance.mark("update-ui"); benchmark.updateUIAfterRun(); - if (isInBrowser && globalThis.prefetchResources) { + if (isInBrowser && JetStreamParams.prefetchResources) { const cache = JetStream.blobDataCache; for (const file of benchmark.files) { const blobData = cache[file]; @@ -237,7 +225,7 @@ class Driver { if (measureTotalTimeAsSubtest) { if (isInBrowser) document.getElementById("benchmark-total-time-score").innerHTML = uiFriendlyNumber(totalTime); - else if (!dumpJSONResults) + else if (!JetStreamParams.dumpJSONResults) console.log("Total time:", uiFriendlyNumber(totalTime)); allScores.push(totalTime); } @@ -275,7 +263,7 @@ class Driver { if (showScoreDetails) displayCategoryScores(); statusElement.innerHTML = ""; - } else if (!dumpJSONResults) { + } else if (!JetStreamParams.dumpJSONResults) { console.log("\n"); for (let [category, scores] of categoryScores) console.log(`${category}: ${uiFriendlyScore(geomeanScore(scores))}`); @@ -365,8 +353,8 @@ class Driver { this.isReady = true; if (isInBrowser) { globalThis.dispatchEvent(new Event("JetStreamReady")); - if (typeof(globalThis.startDelay) !== "undefined") { - setTimeout(() => this.start(), globalThis.startDelay); + if (typeof(JetStreamParams.startDelay) !== "undefined") { + setTimeout(() => this.start(), JetStreamParams.startDelay); } } } @@ -471,7 +459,7 @@ class Driver { dumpJSONResultsIfNeeded() { - if (dumpJSONResults) { + if (JetStreamParams.dumpJSONResults) { console.log("\n"); console.log(this.resultsJSON()); console.log("\n"); @@ -490,7 +478,7 @@ class Driver { if (!isInBrowser) return; - if (!globalThis.shouldReport) + if (!JetStreamParams.shouldReport) return; const content = this.resultsJSON(); @@ -752,8 +740,8 @@ class Benchmark { if (this.plan.deterministicRandom) code += `Math.random.__resetSeed();`; - if (globalThis.customPreIterationCode) - code += customPreIterationCode; + if (JetStreamParams.customPreIterationCode) + code += JetStreamParams.customPreIterationCode; return code; } @@ -761,8 +749,8 @@ class Benchmark { get postIterationCode() { let code = ""; - if (globalThis.customPostIterationCode) - code += customPostIterationCode; + if (JetStreamParams.customPostIterationCode) + code += JetStreamParams.customPostIterationCode; return code; } @@ -796,7 +784,7 @@ class Benchmark { } else { const cache = JetStream.blobDataCache; for (const file of this.plan.files) { - scripts.addWithURL(globalThis.prefetchResources ? cache[file].blobURL : file); + scripts.addWithURL(JetStreamParams.prefetchResources ? cache[file].blobURL : file); } } @@ -810,7 +798,7 @@ class Benchmark { performance.mark(this.name); this.startTime = performance.now(); - if (RAMification) + if (JetStreamParams.RAMification) resetMemoryPeak(); let magicFrame; @@ -830,7 +818,7 @@ class Benchmark { this.endTime = performance.now(); performance.measure(this.name, this.name); - if (RAMification) { + if (JetStreamParams.RAMification) { const memoryFootprint = MemoryFootprint(); this.currentFootprint = memoryFootprint.current; this.peakFootprint = memoryFootprint.peak; @@ -847,7 +835,7 @@ class Benchmark { async doLoadBlob(resource) { const blobData = JetStream.blobDataCache[resource]; - if (!globalThis.prefetchResources) { + if (!JetStreamParams.prefetchResources) { blobData.blobURL = resource; return blobData; } @@ -1015,7 +1003,7 @@ class Benchmark { } updateUIBeforeRun() { - if (!dumpJSONResults) + if (!JetStreamParams.dumpJSONResults) console.log(`Running ${this.name}:`); if (isInBrowser) this.updateUIBeforeRunInBrowser(); @@ -1034,7 +1022,7 @@ class Benchmark { const scoreEntries = Object.entries(this.allScores()); if (isInBrowser) this.updateUIAfterRunInBrowser(scoreEntries); - if (dumpJSONResults) + if (JetStreamParams.dumpJSONResults) return; this.updateConsoleAfterRun(scoreEntries); } @@ -1093,7 +1081,7 @@ class Benchmark { name = legacyScoreNameMap[name]; console.log(` ${name}:`, uiFriendlyScore(value)); } - if (RAMification) { + if (JetStreamParams.RAMification) { console.log(" Current Footprint:", uiFriendlyNumber(this.currentFootprint)); console.log(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint)); } @@ -2616,8 +2604,8 @@ const defaultDisabledTags = []; if (!isInBrowser) defaultDisabledTags.push("WorkerTests"); -if (globalThis.testList?.length) { - benchmarks = processTestList(globalThis.testList); +if (JetStreamParams.testList.length) { + benchmarks = processTestList(JetStreamParams.testList); } else { benchmarks = findBenchmarksByTag("Default", defaultDisabledTags) } diff --git a/cli.js b/cli.js index 5429ccd8..c9bf96b9 100644 --- a/cli.js +++ b/cli.js @@ -21,48 +21,109 @@ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. -*/ + */ -load("./shell-config.js") +load("./shell-config.js"); -const cliFlags = { __proto__: null }; +const CLI_PARAMS = { + __proto__: null, + help: { help: "Print this help message.", param: "help" }, + "iteration-count": { + help: "Set the default iteration count.", + param: "testIterationCount", + }, + "worst-case-count": { + help: "Set the default worst-case count.", + param: "testWorstCaseCount", + }, + "dump-json-results": { + help: "Print summary json to the console.", + param: "dumpJSONResults", + }, + "dump-test-list": { + help: "Print test list instead of running.", + param: "dumpTestList", + }, + ramification: { + help: "Enable ramification support. See RAMification.py for more details.", + param: "RAMification", + }, + "no-prefetch": { + help: "Do not prefetch resources. Will add network overhead to measurements!", + param: "prefetchResources", + }, + test: { + help: "Run a specific test or comma-separated list of tests.", + param: "test", + }, + tag: { + help: "Run tests with a specific tag or comma-separated list of tags.", + param: "tag", + }, + "start-automatically": { + help: "Start the benchmark automatically.", + param: "startAutomatically", + }, + "developer-mode": { help: "Enable developer mode.", param: "developerMode" }, + report: { help: "Report results to a server.", param: "shouldReport" }, + "start-delay": { + help: "Delay before starting the benchmark.", + param: "startDelay", + }, + "custom-pre-iteration-code": { + help: "Custom code to run before each iteration.", + param: "customPreIterationCode", + }, + "custom-post-iteration-code": { + help: "Custom code to run after each iteration.", + param: "customPostIterationCode", + }, +}; + +const cliParams = new Map(); const cliArgs = []; + if (globalThis.arguments?.length) { - for (const argument of globalThis.arguments) - if (argument.startsWith("--")) { - const parts = argument.split("="); - cliFlags[parts[0].toLowerCase()] = parts.slice(1).join("="); - } else - cliArgs.push(argument); + for (const argument of globalThis.arguments) { + if (argument.startsWith("--")) { + parseCliFlag(argument); + } else { + cliArgs.push(argument); + } + } } -function getIntFlag(flags, flag) { - if (!(flag in flags)) - return undefined; - const rawValue = flags[flag]; - const value = parseInt(rawValue); - if (value <= 0) - throw new Error(`Expected positive value for ${flag}, but got ${rawValue}`); - return value; +function parseCliFlag(argument) { + const parts = argument.slice(2).split("="); + const flagName = parts[0]; + if (!(flagName in CLI_PARAMS)) { + const message =`Unknown flag: '--${flagName}'`; + help(message); + throw Error(message); + } + let value = parts.slice(1).join("="); + if (flagName === "no-prefetch") value = "false"; + else if (value === "") value = "true"; + cliParams.set(CLI_PARAMS[flagName].param, value); +} + + +if (cliArgs.length) { + let tests = cliParams.has("test") ? cliParams.get("tests").split(",") : [] + tests = tests.concat(cliArgs); + cliParams.set("test", tests.join(",")); } -if ("--iteration-count" in cliFlags) - globalThis.testIterationCount = getIntFlag(cliFlags, "--iteration-count"); -if ("--worst-case-count" in cliFlags) - globalThis.testWorstCaseCount = getIntFlag(cliFlags, "--worst-case-count"); -if ("--dump-json-results" in cliFlags) - globalThis.dumpJSONResults = true; -if (typeof runMode !== "undefined" && runMode == "RAMification") - globalThis.RAMification = true; -if ("--ramification" in cliFlags) - globalThis.RAMification = true; -if ("--no-prefetch" in cliFlags) - globalThis.prefetchResources = false; -if (cliArgs.length) - globalThis.testList = cliArgs; +if (cliParams.size) + globalThis.JetStreamParamsSource = cliParams; + +load("./params.js"); async function runJetStream() { + if (JetStreamParams !== DefaultJetStreamParams) { + console.warn(`Using non standard params: ${JSON.stringify(JetStreamParams, null, 2)}`) + } try { await JetStream.initialize(); await JetStream.start(); @@ -75,31 +136,37 @@ async function runJetStream() { load("./JetStreamDriver.js"); -if ("--help" in cliFlags) { - console.log("JetStream Driver Help"); +function help(message=undefined) { + if (message) + console.log(message); + else + console.log("JetStream Driver Help"); console.log(""); console.log("Options:"); - console.log(" --iteration-count: Set the default iteration count."); - console.log(" --worst-case-count: Set the default worst-case count"); - console.log(" --dump-json-results: Print summary json to the console."); - console.log(" --dump-test-list: Print test list instead of running."); - console.log(" --ramification: Enable ramification support. See RAMification.py for more details."); - console.log(" --no-prefetch: Do not prefetch resources. Will add network overhead to measurements!"); + for (const [flag, { help }] of Object.entries(CLI_PARAMS)) + console.log(` --${flag.padEnd(20)} ${help}`); console.log(""); - console.log("Available tags:"); - const tagNames = Array.from(benchmarksByTag.keys()).sort(); - for (const tagName of tagNames) - console.log(" ", tagName); - console.log(""); + + if (typeof benchmarksByTag !== "undefined") { + console.log("Available tags:"); + const tagNames = Array.from(benchmarksByTag.keys()).sort(); + for (const tagName of tagNames) console.log(" ", tagName); + console.log(""); + } + + if (typeof BENCHMARKS !== "undefined") { + console.log("Available tests:"); + const benchmarkNames = BENCHMARKS.map((b) => b.name).sort(); + for (const benchmark of benchmarkNames) console.log(" ", benchmark); + } +} - console.log("Available tests:"); - const benchmarkNames = BENCHMARKS.map(b => b.name).sort(); - for (const benchmark of benchmarkNames) - console.log(" ", benchmark); -} else if ("--dump-test-list" in cliFlags) { - JetStream.dumpTestList(); +if (cliParams.has("help")) { + help(); +} else if (cliParams.has("dumpTestList")) { + JetStream.dumpTestList(); } else { - runJetStream(); + runJetStream(); } diff --git a/params.js b/params.js index 10c8e34a..33e39b31 100644 --- a/params.js +++ b/params.js @@ -38,9 +38,17 @@ class Params { testWorstCaseCount = undefined; prefetchResources = true; - constructor(searchParams = undefined) { - if (searchParams) - this._copyFromSearchParams(searchParams); + RAMification = false; + dumpJSONResults = false; + testIterationCountMap = new Map(); + testWorstCaseCountMap = new Map(); + + customPreIterationCode = undefined; + customPostIterationCode = undefined; + + constructor(sourceParams = undefined) { + if (sourceParams) + this._copyFromSearchParams(sourceParams); if (!this.developerMode) { Object.freeze(this.viewport); Object.freeze(this); @@ -54,124 +62,75 @@ class Params { return parseInt(number); } - _copyFromSearchParams(searchParams) { - this.startAutomatically = this._parseBooleanParam(searchParams, "startAutomatically"); - this.developerMode = this._parseBooleanParam(searchParams, "developerMode"); - this.shouldReport = this._parseBooleanParam(searchParams, "report"); - this.prefetchResources = this._parseBooleanParam(searchParams, "prefetchResources"); + _copyFromSearchParams(sourceParams) { + this.startAutomatically = this._parseBooleanParam(sourceParams, "startAutomatically"); + this.developerMode = this._parseBooleanParam(sourceParams, "developerMode"); + this.shouldReport = this._parseBooleanParam(sourceParams, "report"); + this.prefetchResources = this._parseBooleanParam(sourceParams, "prefetchResources"); + this.RAMification = this._parseBooleanParam(sourceParams, "RAMification"); + this.dumpJSONResults = this._parseBooleanParam(sourceParams, "dumpJSONResults"); - this.startDelay = this._parseIntParam(searchParams, "startDelay", 0); + this.customPreIterationCode = this._parseStringParam(sourceParams, "customPreIterationCode"); + this.customPostIterationCode = this._parseStringParam(sourceParams, "customPostIterationCode"); + + this.startDelay = this._parseIntParam(sourceParams, "startDelay", 0); if (this.shouldReport && !this.startDelay) this.startDelay = 4000; for (const paramKey of ["tag", "tags", "test", "tests"]) - this.testList = this._parseTestListParam(searchParams, paramKey); + this.testList = this._parseTestListParam(sourceParams, paramKey); - this.testIterationCount = this._parseIntParam(searchParams, "iterationCount", 1); - this.testWorstCaseCount = this._parseIntParam(searchParams, "worstCaseCount", 1); + this.testIterationCount = this._parseIntParam(sourceParams, "iterationCount", 1); + this.testWorstCaseCount = this._parseIntParam(sourceParams, "worstCaseCount", 1); - const unused = Array.from(searchParams.keys()); + const unused = Array.from(sourceParams.keys()); if (unused.length > 0) console.error("Got unused search params", unused); } - _parseTestListParam(searchParams, key) { - if (this.testList?.length) - throw new Error(`Overriding previous testList=${this.testList.join()} with ${key} url-parameter.`); - const value = searchParams.getAll(key); - searchParams.delete(key); + _parseTestListParam(sourceParams, key) { + if (!sourceParams.has(key)) + return this.testList; + let testList = []; + if (sourceParams?.getAll) + testList = sourceParams?.getAll(key); + else { + // fallback for cli sourceParams which is just a Map; + testList = sourceParams.get(key).split(","); + } + sourceParams.delete(key); + if (this.testList.length > 0 && testList.length > 0) + throw new Error(`Overriding previous testList='${this.testList.join()}' with ${key} url-parameter.`); + return testList; + } + + _parseStringParam(sourceParams, paramKey) { + if (!sourceParams.has(paramKey)) + return DefaultJetStreamParams[paramKey]; + const value = sourceParams.get(paramKey); + sourceParams.delete(paramKey); return value; } - _parseBooleanParam(searchParams, paramKey) { - if (!searchParams.has(paramKey)) + _parseBooleanParam(sourceParams, paramKey) { + if (!sourceParams.has(paramKey)) return DefaultJetStreamParams[paramKey]; - const value = searchParams.get(paramKey).toLowerCase(); - searchParams.delete(paramKey); + const value = sourceParams.get(paramKey).toLowerCase(); + sourceParams.delete(paramKey); return !(value === "false" || value === "0"); } - _parseIntParam(searchParams, paramKey, minValue) { - if (!searchParams.has(paramKey)) + _parseIntParam(sourceParams, paramKey, minValue) { + if (!sourceParams.has(paramKey)) return DefaultJetStreamParams[paramKey]; - const parsedValue = this._parseInt(searchParams.get(paramKey), paramKey); + const parsedValue = this._parseInt(sourceParams.get(paramKey), paramKey); if (parsedValue < minValue) throw new Error(`Invalid ${paramKey} param: '${parsedValue}', value must be >= ${minValue}.`); - searchParams.delete(paramKey); + sourceParams.delete(paramKey); return parsedValue; } - _parseTags(searchParams) { - if (!searchParams.has("tags")) - return DefaultJetStreamParams.tags; - if (this.suites.length) - throw new Error("'suites' and 'tags' cannot be used together."); - const tags = searchParams.get("tags").split(","); - searchParams.delete("tags"); - return tags; - } - - _parseEnumParam(searchParams, paramKey, enumArray) { - if (!searchParams.has(paramKey)) - return DefaultJetStreamParams[paramKey]; - const value = searchParams.get(paramKey); - if (!enumArray.includes(value)) - throw new Error(`Got invalid ${paramKey}: '${value}', choices are ${enumArray}`); - searchParams.delete(paramKey); - return value; - } - - _parseShuffleSeed(searchParams) { - if (!searchParams.has("shuffleSeed")) - return DefaultJetStreamParams.shuffleSeed; - let shuffleSeed = searchParams.get("shuffleSeed"); - if (shuffleSeed !== "off") { - if (shuffleSeed === "generate") { - shuffleSeed = Math.floor((Math.random() * 1) << 16); - console.log(`Generated a random suite order seed: ${shuffleSeed}`); - } else { - shuffleSeed = parseInt(shuffleSeed); - } - if (!Number.isInteger(shuffleSeed)) - throw new Error(`Invalid shuffle seed: '${shuffleSeed}', must be either 'off', 'generate' or an integer.`); - } - searchParams.delete("shuffleSeed"); - return shuffleSeed; - } - - - toCompleteSearchParamsObject() { - return this.toSearchParamsObject(false); - } - - toSearchParamsObject(filter = true) { - const rawUrlParams = { __proto__: null }; - for (const [key, value] of Object.entries(this)) { - // Skip over default values. - if (filter && value === DefaultJetStreamParams[key]) - continue; - rawUrlParams[key] = value; - } - - if (this.viewport.width !== DefaultJetStreamParams.viewport.width || this.viewport.height !== DefaultJetStreamParams.viewport.height) - rawUrlParams.viewport = `${this.viewport.width}x${this.viewport.height}`; - - if (this.suites.length) { - rawUrlParams.suites = this.suites.join(","); - } else if (this.tags.length) { - if (!(this.tags.length === 1 && this.tags[0] === "default")) - rawUrlParams.tags = this.tags.join(","); - } else { - rawUrlParams.suites = ""; - } - - return new URLSearchParams(rawUrlParams); - } - - toSearchParams() { - return this.toSearchParamsObject().toString(); - } } const DefaultJetStreamParams = new Params(); From 41d06c9c847d59d759fc708ed19f37b19d72d09e Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 1 Sep 2025 19:56:34 +0200 Subject: [PATCH 5/7] better default check --- cli.js | 2 +- params.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cli.js b/cli.js index c9bf96b9..e49a0e4a 100644 --- a/cli.js +++ b/cli.js @@ -121,7 +121,7 @@ load("./params.js"); async function runJetStream() { - if (JetStreamParams !== DefaultJetStreamParams) { + if (!JetStreamParams.isDefault) { console.warn(`Using non standard params: ${JSON.stringify(JetStreamParams, null, 2)}`) } try { diff --git a/params.js b/params.js index 33e39b31..a22fc710 100644 --- a/params.js +++ b/params.js @@ -131,6 +131,9 @@ class Params { return parsedValue; } + get isDefault() { + return this === DefaultJetStreamParams; + } } const DefaultJetStreamParams = new Params(); From dbe5511ecc5d0c28261a7c89c244f87ef2cce51f Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Mon, 1 Sep 2025 22:15:03 +0200 Subject: [PATCH 6/7] fix unit-tests --- tests/unit-tests.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit-tests.js b/tests/unit-tests.js index 63a1efe2..507be43e 100644 --- a/tests/unit-tests.js +++ b/tests/unit-tests.js @@ -1,4 +1,5 @@ load("shell-config.js"); +load("params.js"); load("startup-helper/StartupBenchmark.js"); load("JetStreamDriver.js"); From 51234dc6fed688dc1d290848e368b25c79f88bbd Mon Sep 17 00:00:00 2001 From: Camillo Bruni Date: Tue, 2 Sep 2025 17:09:16 +0200 Subject: [PATCH 7/7] fixes --- cli.js | 5 +++-- params.js | 26 ++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cli.js b/cli.js index e49a0e4a..6c0ccb63 100644 --- a/cli.js +++ b/cli.js @@ -64,7 +64,6 @@ const CLI_PARAMS = { help: "Start the benchmark automatically.", param: "startAutomatically", }, - "developer-mode": { help: "Enable developer mode.", param: "developerMode" }, report: { help: "Report results to a server.", param: "shouldReport" }, "start-delay": { help: "Delay before starting the benchmark.", @@ -148,7 +147,9 @@ function help(message=undefined) { console.log(` --${flag.padEnd(20)} ${help}`); console.log(""); - + // If we had an early bailout during flag parsing we won't have + // JetStreamDriver.js loaded yet and thus none of the helper globals here + // have been defined yet. if (typeof benchmarksByTag !== "undefined") { console.log("Available tags:"); const tagNames = Array.from(benchmarksByTag.keys()).sort(); diff --git a/params.js b/params.js index a22fc710..37c22a66 100644 --- a/params.js +++ b/params.js @@ -49,17 +49,8 @@ class Params { constructor(sourceParams = undefined) { if (sourceParams) this._copyFromSearchParams(sourceParams); - if (!this.developerMode) { - Object.freeze(this.viewport); + if (!this.developerMode) Object.freeze(this); - } - } - - _parseInt(value, errorMessage) { - const number = Number(value); - if (!Number.isInteger(number) && errorMessage) - throw new Error(`Invalid ${errorMessage} param: '${value}', expected int.`); - return parseInt(number); } _copyFromSearchParams(sourceParams) { @@ -85,16 +76,16 @@ class Params { const unused = Array.from(sourceParams.keys()); if (unused.length > 0) - console.error("Got unused search params", unused); + console.error("Got unused source params", unused); } _parseTestListParam(sourceParams, key) { if (!sourceParams.has(key)) return this.testList; let testList = []; - if (sourceParams?.getAll) + if (sourceParams?.getAll) { testList = sourceParams?.getAll(key); - else { + } else { // fallback for cli sourceParams which is just a Map; testList = sourceParams.get(key).split(","); } @@ -131,6 +122,13 @@ class Params { return parsedValue; } + _parseInt(value, errorMessage) { + const number = Number(value); + if (!Number.isInteger(number) && errorMessage) + throw new Error(`Invalid ${errorMessage} param: '${value}', expected int.`); + return parseInt(number); + } + get isDefault() { return this === DefaultJetStreamParams; } @@ -145,4 +143,4 @@ if (globalThis?.JetStreamParamsSource) { console.error("Invalid Params", e, "\nUsing defaults as fallback:", maybeCustomParams); } } -const JetStreamParams = maybeCustomParams; \ No newline at end of file +const JetStreamParams = maybeCustomParams;