| "use strict"; |
| |
| /* |
| * Copyright (C) 2018-2024 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. |
| */ |
| |
| const measureTotalTimeAsSubtest = false; // Once we move to preloading all resources, it would be good to turn this on. |
| |
| const defaultIterationCount = 120; |
| const defaultWorstCaseCount = 4; |
| |
| if (!JetStreamParams.prefetchResources) |
| console.warn("Disabling resource prefetching!"); |
| |
| // Used for the promise representing the current benchmark run. |
| this.currentResolve = null; |
| this.currentReject = null; |
| |
| function displayCategoryScores() { |
| document.body.classList.add("details"); |
| } |
| |
| function getIterationCount(plan) { |
| 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 (JetStreamParams.testWorstCaseCountMap.has(plan.name)) |
| return JetStreamParams.testWorstCaseCountMap.get(plan.name); |
| if (JetStreamParams.testWorstCaseCount) |
| return JetStreamParams.testWorstCaseCount; |
| if (plan.worstCaseCount !== undefined) |
| return plan.worstCaseCount; |
| return defaultWorstCaseCount; |
| } |
| |
| if (isInBrowser) { |
| document.onkeydown = (keyboardEvent) => { |
| const key = keyboardEvent.key; |
| if (key === "d" || key === "D") |
| displayCategoryScores(); |
| }; |
| } |
| |
| function sum(values) { |
| console.assert(values instanceof Array); |
| let sum = 0; |
| for (let x of values) |
| sum += x; |
| return sum; |
| } |
| |
| function mean(values) { |
| const totalSum = sum(values) |
| return totalSum / values.length; |
| } |
| |
| function geomeanScore(values) { |
| console.assert(values instanceof Array); |
| let product = 1; |
| for (let x of values) |
| product *= x; |
| const score = product ** (1 / values.length); |
| // Allow 0 for uninitialized subScores(). |
| console.assert(score >= 0, `Got invalid score: ${score}`) |
| return score; |
| } |
| |
| function toScore(timeValue) { |
| return 5000 / Math.max(timeValue, 1); |
| } |
| |
| function toTimeValue(score) { |
| return 5000 / score; |
| } |
| |
| function updateUI() { |
| return new Promise((resolve) => { |
| if (isInBrowser) |
| requestAnimationFrame(() => setTimeout(resolve, 0)); |
| else |
| resolve(); |
| }); |
| } |
| |
| function uiFriendlyNumber(num) { |
| if (Number.isInteger(num)) |
| return num; |
| return num.toFixed(2); |
| } |
| |
| function uiFriendlyScore(num) { |
| return uiFriendlyNumber(num); |
| } |
| |
| function uiFriendlyDuration(time) { |
| return `${time.toFixed(2)} ms`; |
| } |
| |
| |
| function shellFriendlyLabel(label) { |
| const namePadding = 40; |
| return `${label}:`.padEnd(namePadding); |
| } |
| |
| const valuePadding = 10; |
| function shellFriendlyDuration(time) { |
| return `${uiFriendlyDuration(time)}`.padStart(valuePadding); |
| } |
| |
| function shellFriendlyScore(time) { |
| return `${uiFriendlyScore(time)} # `.padStart(valuePadding); |
| } |
| |
| |
| // TODO: Cleanup / remove / merge. This is only used for caching loads in the |
| // non-browser setting. In the browser we use exclusively `loadCache`, |
| // `loadBlob`, `doLoadBlob`, `prefetchResourcesForBrowser` etc., see below. |
| class ShellFileLoader { |
| constructor() { |
| this.requests = new Map; |
| } |
| |
| // Cache / memoize previously read files, because some workloads |
| // share common code. |
| load(url) { |
| console.assert(!isInBrowser); |
| if (!JetStreamParams.prefetchResources) |
| return `load("${url}");` |
| |
| if (this.requests.has(url)) { |
| return this.requests.get(url); |
| } |
| |
| const contents = readFile(url); |
| this.requests.set(url, contents); |
| return contents; |
| } |
| }; |
| |
| const shellFileLoader = new ShellFileLoader(); |
| |
| class Driver { |
| constructor(benchmarks) { |
| this.isReady = false; |
| this.isDone = false; |
| this.errors = []; |
| // Make benchmark list unique and sort it. |
| this.benchmarks = Array.from(new Set(benchmarks)); |
| this.benchmarks.sort((a, b) => a.plan.name.toLowerCase() < b.plan.name.toLowerCase() ? 1 : -1); |
| console.assert(this.benchmarks.length, "No benchmarks selected"); |
| // TODO: Cleanup / remove / merge `blobDataCache` and `loadCache` vs. |
| // the global `fileLoader` cache. |
| this.blobDataCache = { }; |
| this.loadCache = { }; |
| this.counter = { }; |
| this.counter.loadedResources = 0; |
| this.counter.totalResources = 0; |
| this.counter.failedPreloadResources = 0; |
| } |
| |
| async start() { |
| let statusElement = false; |
| if (isInBrowser) { |
| statusElement = document.getElementById("status"); |
| statusElement.innerHTML = `<label>Running...</label>`; |
| } else if (!JetStreamParams.dumpJSONResults) |
| console.log("Starting JetStream3"); |
| |
| performance.mark("update-ui-start"); |
| const start = performance.now(); |
| for (const benchmark of this.benchmarks) { |
| await benchmark.updateUIBeforeRun(); |
| await updateUI(); |
| performance.measure("runner update-ui", "update-ui-start"); |
| |
| try { |
| await benchmark.run(); |
| } catch(e) { |
| this.reportError(benchmark, e); |
| throw e; |
| } |
| |
| performance.mark("update-ui"); |
| benchmark.updateUIAfterRun(); |
| |
| if (isInBrowser && JetStreamParams.prefetchResources) { |
| const cache = JetStream.blobDataCache; |
| for (const file of benchmark.files) { |
| const blobData = cache[file]; |
| blobData.refCount--; |
| if (!blobData.refCount) |
| cache[file] = undefined; |
| } |
| } |
| } |
| performance.measure("runner update-ui", "update-ui-start"); |
| |
| const totalTime = performance.now() - start; |
| if (measureTotalTimeAsSubtest) { |
| if (isInBrowser) |
| document.getElementById("benchmark-total-time-score").innerHTML = uiFriendlyNumber(totalTime); |
| else if (!JetStreamParams.dumpJSONResults) |
| console.log("Total-Time:", uiFriendlyNumber(totalTime)); |
| allScores.push(totalTime); |
| } |
| |
| const allScores = []; |
| for (const benchmark of this.benchmarks) { |
| const score = benchmark.score; |
| console.assert(score > 0, `Invalid ${benchmark.name} score: ${score}`); |
| allScores.push(score); |
| } |
| |
| const categoryScores = new Map(); |
| const categoryTimes = new Map(); |
| for (const benchmark of this.benchmarks) { |
| for (let category of Object.keys(benchmark.subScores())) |
| categoryScores.set(category, []); |
| for (let category of Object.keys(benchmark.subTimes())) |
| categoryTimes.set(category, []); |
| } |
| |
| for (const benchmark of this.benchmarks) { |
| for (let [category, value] of Object.entries(benchmark.subScores())) { |
| const arr = categoryScores.get(category); |
| console.assert(value > 0, `Invalid ${benchmark.name} ${category} score: ${value}`); |
| arr.push(value); |
| } |
| for (let [category, value] of Object.entries(benchmark.subTimes())) { |
| const arr = categoryTimes.get(category); |
| console.assert(value > 0, `Invalid ${benchmark.name} ${category} time: ${value}`); |
| arr.push(value); |
| } |
| } |
| |
| const totalScore = geomeanScore(allScores); |
| console.assert(totalScore > 0, `Invalid total score: ${totalScore}`); |
| |
| if (isInBrowser) { |
| let summaryHtml = `<div class="score">${uiFriendlyScore(totalScore)}</div> |
| <label>Score</label>`; |
| summaryHtml += `<div class="benchmark benchmark-done">`; |
| for (let [category, scores] of categoryScores) { |
| summaryHtml += `<span class="result detail"> |
| <span>${uiFriendlyScore(geomeanScore(scores))}</span> |
| <label>${category}</label> |
| </span>`; |
| } |
| summaryHtml += "<br/>"; |
| for (let [category, times] of categoryTimes) { |
| summaryHtml += `<span class="result detail"> |
| <span>${uiFriendlyDuration(geomeanScore(times))}</span> |
| <label>${category}</label> |
| </span>`; |
| } |
| summaryHtml += "</div>"; |
| const summaryElement = document.getElementById("result-summary"); |
| summaryElement.classList.add("done"); |
| summaryElement.innerHTML = summaryHtml; |
| summaryElement.onclick = displayCategoryScores; |
| statusElement.innerHTML = ""; |
| } else if (!JetStreamParams.dumpJSONResults) { |
| console.log("Total:"); |
| for (let [category, scores] of categoryScores) { |
| console.log( |
| shellFriendlyLabel(`${category}-Score`), |
| shellFriendlyScore(geomeanScore(scores))); |
| } |
| for (let [category, times] of categoryTimes) { |
| console.log( |
| shellFriendlyLabel(`${category}-Time`), |
| shellFriendlyDuration(geomeanScore(times))); |
| } |
| console.log(""); |
| console.log(shellFriendlyLabel("Total-Score"), shellFriendlyScore(totalScore)); |
| console.log(shellFriendlyLabel("Total-Time"), shellFriendlyDuration(totalTime)); |
| console.log(""); |
| } |
| |
| this.reportScoreToRunBenchmarkRunner(); |
| this.dumpJSONResultsIfNeeded(); |
| this.isDone = true; |
| |
| if (isInBrowser) { |
| globalThis.dispatchEvent(new CustomEvent("JetStreamDone", { |
| detail: this.resultsObject() |
| })); |
| } |
| } |
| |
| prepareToRun() { |
| this.benchmarks.sort((a, b) => a.plan.name.toLowerCase() < b.plan.name.toLowerCase() ? 1 : -1); |
| |
| if (!isInBrowser) |
| return; |
| |
| let text = ""; |
| for (const benchmark of this.benchmarks) { |
| const scoreDescription = Object.keys(benchmark.allScores()); |
| const timeDescription = Object.keys(benchmark.allTimes()); |
| |
| const scoreIds = benchmark.allScoreIdentifiers(); |
| const overallScoreId = scoreIds.pop(); |
| const timeIds = benchmark.allTimeIdentifiers(); |
| |
| text += |
| `<div class="benchmark" id="benchmark-${benchmark.name}"> |
| <h3 class="benchmark-name">${benchmark.name} <a class="info" href="in-depth.html#${benchmark.name}">i</a></h3> |
| <h4 class="score" id="${overallScoreId}"> </h4> |
| <h4 class="plot" id="plot-${benchmark.name}"> </h4> |
| <p>`; |
| for (let i = 0; i < scoreIds.length; i++) { |
| const scoreId = scoreIds[i]; |
| const label = scoreDescription[i]; |
| text += `<span class="result score"><span id="${scoreId}"> </span><label>${label}</label></span>` |
| } |
| text += "<br/>"; |
| for (let i = 0; i < timeIds.length; i++) { |
| const timeId = timeIds[i]; |
| const label = timeDescription[i]; |
| text += `<span class="result detail"><span id="${timeId}"> </span><label>${label}</label></span>` |
| } |
| text += `</p></div>`; |
| } |
| |
| const timestamp = performance.now(); |
| document.getElementById('jetstreams').style.backgroundImage = `url('jetstreams.svg?${timestamp}')`; |
| const resultsTable = document.getElementById("results"); |
| resultsTable.innerHTML = text; |
| |
| document.getElementById("magic").textContent = ""; |
| document.addEventListener('keypress', (e) => { |
| if (e.key === "Enter") |
| JetStream.start(); |
| }); |
| } |
| |
| reportError(benchmark, error) { |
| this.pushError(benchmark.name, error); |
| |
| if (!isInBrowser) |
| return; |
| |
| for (const id of benchmark.allScoreIdentifiers()) |
| document.getElementById(id).innerHTML = "error"; |
| for (const id of benchmark.allTimeIdentifiers()) |
| document.getElementById(id).innerHTML = "error"; |
| const benchmarkResultsUI = document.getElementById(`benchmark-${benchmark.name}`); |
| benchmarkResultsUI.classList.remove("benchmark-running"); |
| benchmarkResultsUI.classList.add("benchmark-error"); |
| } |
| |
| pushError(name, error) { |
| this.errors.push({ |
| benchmark: name, |
| error: error.toString(), |
| stack: error.stack |
| }); |
| } |
| |
| async initialize() { |
| if (isInBrowser) |
| window.addEventListener("error", (e) => this.pushError("driver startup", e.error)); |
| await this.prefetchResources(); |
| this.prepareToRun(); |
| this.isReady = true; |
| if (isInBrowser) { |
| globalThis.dispatchEvent(new Event("JetStreamReady")); |
| if (typeof(JetStreamParams.startDelay) !== "undefined") { |
| setTimeout(() => this.start(), JetStreamParams.startDelay); |
| } |
| } |
| } |
| |
| async prefetchResources() { |
| if (!isInBrowser) { |
| for (const benchmark of this.benchmarks) |
| benchmark.prefetchResourcesForShell(); |
| return; |
| } |
| |
| // TODO: Cleanup the browser path of the preloading below and in |
| // `prefetchResourcesForBrowser` / `retryPrefetchResourcesForBrowser`. |
| const promises = []; |
| for (const benchmark of this.benchmarks) |
| promises.push(benchmark.prefetchResourcesForBrowser()); |
| await Promise.all(promises); |
| |
| const counter = JetStream.counter; |
| if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) { |
| for (const benchmark of this.benchmarks) { |
| const allFilesLoaded = await benchmark.retryPrefetchResourcesForBrowser(counter); |
| if (allFilesLoaded) |
| break; |
| } |
| |
| if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) { |
| // If we've failed to prefetch resources even after a sequential 1 by 1 retry, |
| // then fail out early rather than letting subtests fail with a hang. |
| globalThis.allIsGood = false; |
| throw new Error("Fetch failed"); |
| } |
| } |
| |
| JetStream.loadCache = { }; // Done preloading all the files. |
| |
| const statusElement = document.getElementById("status"); |
| statusElement.classList.remove('loading'); |
| statusElement.innerHTML = `<a href="javascript:JetStream.start()" class="button">Start Test</a>`; |
| statusElement.onclick = () => { |
| statusElement.onclick = null; |
| JetStream.start(); |
| return false; |
| } |
| } |
| |
| resultsObject(format = "run-benchmark") { |
| switch(format) { |
| case "run-benchmark": |
| return this.runBenchmarkResultsObject(); |
| case "simple": |
| return this.simpleResultsObject(); |
| default: |
| throw Error(`Unknown result format '${format}'`); |
| } |
| } |
| |
| runBenchmarkResultsObject() |
| { |
| let results = {}; |
| for (const benchmark of this.benchmarks) { |
| const subResults = {} |
| const subScores = benchmark.subScores(); |
| for (const name in subScores) { |
| subResults[name] = {"metrics": {"Time": {"current": [toTimeValue(subScores[name])]}}}; |
| } |
| results[benchmark.name] = { |
| "metrics" : { |
| "Score" : {"current" : [benchmark.score]}, |
| "Time": ["Geometric"], |
| }, |
| "tests": subResults, |
| }; |
| } |
| |
| results = {"JetStream3.0": {"metrics" : {"Score" : ["Geometric"]}, "tests" : results}}; |
| return results; |
| } |
| |
| simpleResultsObject() { |
| const results = {__proto__: null}; |
| for (const benchmark of this.benchmarks) { |
| if (!benchmark.isDone) |
| continue; |
| if (!benchmark.isSuccess) { |
| results[benchmark.name] = "FAILED"; |
| } else { |
| results[benchmark.name] = { |
| Score: benchmark.score, |
| ...benchmark.subScores(), |
| |
| }; |
| } |
| } |
| return results; |
| } |
| |
| resultsJSON(format = "run-benchmark") |
| { |
| return JSON.stringify(this.resultsObject(format)); |
| } |
| |
| dumpJSONResultsIfNeeded() |
| { |
| if (JetStreamParams.dumpJSONResults) { |
| console.log("\n"); |
| console.log(this.resultsJSON()); |
| console.log("\n"); |
| } |
| } |
| |
| dumpTestList() |
| { |
| for (const benchmark of this.benchmarks) { |
| console.log(benchmark.name); |
| } |
| } |
| |
| async reportScoreToRunBenchmarkRunner() |
| { |
| if (!isInBrowser) |
| return; |
| |
| if (!JetStreamParams.shouldReport) |
| return; |
| |
| const content = this.resultsJSON(); |
| await fetch("/report", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| "Content-Length": content.length, |
| "Connection": "close", |
| }, |
| body: content, |
| }); |
| } |
| }; |
| |
| const BenchmarkState = Object.freeze({ |
| READY: "READY", |
| SETUP: "SETUP", |
| RUNNING: "RUNNING", |
| FINALIZE: "FINALIZE", |
| ERROR: "ERROR", |
| DONE: "DONE" |
| }) |
| |
| |
| class Scripts { |
| constructor() { |
| this.scripts = []; |
| // Expose a globalThis.JetStream object to the workload. We use |
| // a proxy to prevent prototype access and throw on unknown properties. |
| this.add(` |
| const throwOnAccess = (name) => new Proxy({}, { |
| get(target, property, receiver) { |
| throw new Error(name + "." + property + " is not defined."); |
| } |
| }); |
| globalThis.JetStream = { |
| __proto__: throwOnAccess("JetStream"), |
| preload: { |
| __proto__: throwOnAccess("JetStream.preload"), |
| }, |
| }; |
| `); |
| this.add(` |
| performance.mark ??= function(name) { return { name }}; |
| performance.measure ??= function() {}; |
| performance.timeOrigin ??= performance.now(); |
| `); |
| } |
| |
| |
| run() { |
| throw new Error("Subclasses need to implement this"); |
| } |
| |
| add(text) { |
| throw new Error("Subclasses need to implement this"); |
| } |
| |
| addWithURL(url) { |
| throw new Error("addWithURL not supported"); |
| } |
| |
| addBrowserTest() { |
| this.add(` |
| globalThis.JetStream.isInBrowser = ${isInBrowser}; |
| globalThis.JetStream.isD8 = ${isD8}; |
| `); |
| } |
| |
| addDeterministicRandom() { |
| this.add(`(() => { |
| const initialSeed = 49734321; |
| let seed = initialSeed; |
| |
| Math.random = () => { |
| // Robert Jenkins' 32 bit integer hash function. |
| seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffff_ffff; |
| seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffff_ffff; |
| seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffff_ffff; |
| seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffff_ffff; |
| seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffff_ffff; |
| seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffff_ffff; |
| // Note that Math.random should return a value that is |
| // greater than or equal to 0 and less than 1. Here, we |
| // cast to uint32 first then divided by 2^32 for double. |
| return (seed >>> 0) / 0x1_0000_0000; |
| }; |
| |
| Math.random.__resetSeed = () => { |
| seed = initialSeed; |
| }; |
| })();`); |
| } |
| } |
| |
| class ShellScripts extends Scripts { |
| run() { |
| let globalObject; |
| let realm; |
| if (isD8) { |
| realm = Realm.createAllowCrossRealmAccess(); |
| globalObject = Realm.global(realm); |
| globalObject.loadString = function(s) { |
| return Realm.eval(realm, s); |
| }; |
| globalObject.readFile = read; |
| } else if (isSpiderMonkey) { |
| globalObject = newGlobal(); |
| globalObject.loadString = globalObject.evaluate; |
| globalObject.readFile = globalObject.readRelativeToScript; |
| } else |
| globalObject = runString(""); |
| |
| // Expose console copy in the realm so we don't accidentally modify |
| // the original object. |
| globalObject.console = Object.assign({}, console); |
| globalObject.self = globalObject; |
| globalObject.top = { |
| currentResolve, |
| currentReject |
| }; |
| |
| globalObject.performance ??= performance; |
| for (const script of this.scripts) |
| globalObject.loadString(script); |
| |
| return isD8 ? realm : globalObject; |
| } |
| |
| add(text) { |
| this.scripts.push(text); |
| } |
| |
| addWithURL(url) { |
| console.assert(false, "Should not reach here in CLI"); |
| } |
| } |
| |
| class BrowserScripts extends Scripts { |
| constructor() { |
| super(); |
| this.add("window.onerror = top.currentReject;"); |
| } |
| |
| run() { |
| const string = this.scripts.join("\n"); |
| const magic = document.getElementById("magic"); |
| magic.contentDocument.body.textContent = ""; |
| magic.contentDocument.body.innerHTML = `<iframe id="magicframe" frameborder="0">`; |
| |
| const magicFrame = magic.contentDocument.getElementById("magicframe"); |
| magicFrame.contentDocument.open(); |
| magicFrame.contentDocument.write(`<!DOCTYPE html> |
| <head> |
| <title>benchmark payload</title> |
| </head> |
| <body>${string}</body> |
| </html>`); |
| return magicFrame; |
| } |
| |
| |
| add(text) { |
| this.scripts.push(`<script>${text}</script>`); |
| } |
| |
| addWithURL(url) { |
| this.scripts.push(`<script src="${url}"></script>`); |
| } |
| } |
| |
| class Benchmark { |
| constructor(plan) |
| { |
| this.plan = plan; |
| this.tags = this.processTags(plan.tags) |
| this.iterations = getIterationCount(plan); |
| this.isAsync = !!plan.isAsync; |
| this.allowUtf16 = !!plan.allowUtf16; |
| this.scripts = null; |
| this.preloads = null; |
| this.results = []; |
| this._state = BenchmarkState.READY; |
| } |
| |
| processTags(rawTags) { |
| const tags = new Set(rawTags.map(each => each.toLowerCase())); |
| if (tags.size != rawTags.length) |
| throw new Error(`${this.name} got duplicate tags: ${rawTags.join()}`); |
| tags.add("all"); |
| if (!tags.has("default")) |
| tags.add("disabled"); |
| return tags; |
| } |
| |
| get name() { return this.plan.name; } |
| get files() { return this.plan.files; } |
| |
| get isDone() { |
| return this._state == BenchmarkState.DONE || this._state == BenchmarkState.ERROR; |
| } |
| get isSuccess() { return this._state = BenchmarkState.DONE; } |
| |
| hasAnyTag(...tags) { |
| return tags.some((tag) => this.tags.has(tag.toLowerCase())); |
| } |
| |
| get benchmarkArguments() { |
| return { |
| ...this.plan.arguments, |
| iterationCount: this.iterations, |
| }; |
| } |
| |
| get runnerCode() { |
| return `{ |
| const benchmark = new Benchmark(${JSON.stringify(this.benchmarkArguments)}); |
| const results = []; |
| const benchmarkName = "${this.name}"; |
| |
| for (let i = 0; i < ${this.iterations}; i++) { |
| ${this.preIterationCode} |
| |
| const iterationMarkLabel = benchmarkName + "-iteration-" + i; |
| const iterationStartMark = performance.mark(iterationMarkLabel); |
| |
| const start = performance.now(); |
| benchmark.runIteration(i); |
| const end = performance.now(); |
| |
| performance.measure(iterationMarkLabel, iterationMarkLabel); |
| |
| ${this.postIterationCode} |
| |
| results.push(Math.max(1, end - start)); |
| } |
| benchmark.validate?.(${this.iterations}); |
| top.currentResolve(results); |
| };`; |
| } |
| |
| processResults(results) { |
| this.results = Array.from(results); |
| return this.results; |
| } |
| |
| get score() { |
| const subScores = Object.values(this.subScores()); |
| return geomeanScore(subScores); |
| } |
| |
| get totalTime() { |
| const subTimes = Object.values(this.subTimes()); |
| return sum(subTimes); |
| } |
| |
| get wallTime() { |
| return this.endTime - this.startTime; |
| } |
| |
| subScores() { |
| throw new Error("Subclasses need to implement this"); |
| } |
| |
| subTimes() { |
| throw new Error("Subclasses need to implement this"); |
| } |
| |
| allScores() { |
| const allScores = this.subScores(); |
| allScores["Score"] = this.score; |
| return allScores; |
| } |
| |
| allTimes() { |
| const allTimes = this.subTimes(); |
| allTimes["Wall"] = this.wallTime; |
| allTimes["Total"] = this.totalTime; |
| return allTimes; |
| } |
| |
| get prerunCode() { return null; } |
| |
| get preIterationCode() { |
| let code = `benchmark.prepareForNextIteration?.();`; |
| if (this.plan.deterministicRandom) |
| code += `Math.random.__resetSeed();`; |
| |
| if (JetStreamParams.customPreIterationCode) |
| code += JetStreamParams.customPreIterationCode; |
| |
| return code; |
| } |
| |
| get postIterationCode() { |
| let code = ""; |
| |
| if (JetStreamParams.customPostIterationCode) |
| code += JetStreamParams.customPostIterationCode; |
| |
| return code; |
| } |
| |
| async run() { |
| if (this.isDone) |
| throw new Error(`Cannot run Benchmark ${this.name} twice`); |
| this._state = BenchmarkState.PREPARE; |
| const scripts = isInBrowser ? new BrowserScripts() : new ShellScripts(); |
| |
| if (!!this.plan.deterministicRandom) |
| scripts.addDeterministicRandom() |
| if (!!this.plan.exposeBrowserTest) |
| scripts.addBrowserTest(); |
| |
| if (this.plan.preload) { |
| let preloadCode = ""; |
| for (let [ variableName, blobURLOrPath ] of this.preloads) |
| preloadCode += `JetStream.preload[${JSON.stringify(variableName)}] = "${blobURLOrPath}";\n`; |
| scripts.add(preloadCode); |
| } |
| |
| const prerunCode = this.prerunCode; |
| if (prerunCode) |
| scripts.add(prerunCode); |
| |
| if (!isInBrowser) { |
| console.assert(this.scripts && this.scripts.length === this.plan.files.length); |
| for (const text of this.scripts) |
| scripts.add(text); |
| } else { |
| const cache = JetStream.blobDataCache; |
| for (const file of this.plan.files) { |
| scripts.addWithURL(JetStreamParams.prefetchResources ? cache[file].blobURL : file); |
| } |
| } |
| |
| const promise = new Promise((resolve, reject) => { |
| currentResolve = resolve; |
| currentReject = reject; |
| }); |
| |
| scripts.add(this.runnerCode); |
| |
| performance.mark(this.name); |
| this.startTime = performance.now(); |
| |
| if (JetStreamParams.RAMification) |
| resetMemoryPeak(); |
| |
| let magicFrame; |
| try { |
| this._state = BenchmarkState.RUNNING; |
| magicFrame = scripts.run(); |
| } catch(e) { |
| this._state = BenchmarkState.ERROR; |
| console.log("Error in runCode: ", e); |
| console.log(e.stack); |
| throw e; |
| } finally { |
| this._state = BenchmarkState.FINALIZE; |
| } |
| const results = await promise; |
| |
| this.endTime = performance.now(); |
| performance.measure(this.name, this.name); |
| |
| if (JetStreamParams.RAMification) { |
| const memoryFootprint = MemoryFootprint(); |
| this.currentFootprint = memoryFootprint.current; |
| this.peakFootprint = memoryFootprint.peak; |
| } |
| |
| this.processResults(results); |
| this._state = BenchmarkState.DONE; |
| |
| if (isInBrowser) |
| magicFrame.contentDocument.close(); |
| else if (isD8) |
| Realm.dispose(magicFrame); |
| } |
| |
| async doLoadBlob(resource) { |
| const blobData = JetStream.blobDataCache[resource]; |
| if (!JetStreamParams.prefetchResources) { |
| blobData.blobURL = resource; |
| return blobData; |
| } |
| let response; |
| let tries = 3; |
| while (tries--) { |
| let hasError = false; |
| try { |
| response = await fetch(resource, { cache: "no-store" }); |
| } catch (e) { |
| hasError = true; |
| } |
| if (!hasError && response.ok) |
| break; |
| if (tries) |
| continue; |
| throw new Error("Fetch failed"); |
| } |
| const blob = await response.blob(); |
| blobData.blob = blob; |
| blobData.blobURL = URL.createObjectURL(blob); |
| return blobData; |
| } |
| |
| async loadBlob(type, prop, resource, incrementRefCount = true) { |
| let blobData = JetStream.blobDataCache[resource]; |
| if (!blobData) { |
| blobData = { |
| type: type, |
| prop: prop, |
| resource: resource, |
| blob: null, |
| blobURL: null, |
| refCount: 0 |
| }; |
| JetStream.blobDataCache[resource] = blobData; |
| } |
| |
| if (incrementRefCount) |
| blobData.refCount++; |
| |
| let promise = JetStream.loadCache[resource]; |
| if (promise) |
| return promise; |
| |
| promise = this.doLoadBlob(resource); |
| JetStream.loadCache[resource] = promise; |
| return promise; |
| } |
| |
| updateCounter() { |
| const counter = JetStream.counter; |
| ++counter.loadedResources; |
| const statusElement = document.getElementById("status"); |
| statusElement.innerHTML = `Loading ${counter.loadedResources} of ${counter.totalResources} ...`; |
| } |
| |
| prefetchResourcesForBrowser() { |
| console.assert(isInBrowser); |
| |
| const promises = this.plan.files.map((file) => this.loadBlob("file", null, file).then((blobData) => { |
| if (!globalThis.allIsGood) |
| return; |
| this.updateCounter(); |
| }).catch((error) => { |
| // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error. |
| })); |
| |
| if (this.plan.preload) { |
| this.preloads = []; |
| for (let prop of Object.getOwnPropertyNames(this.plan.preload)) { |
| promises.push(this.loadBlob("preload", prop, this.plan.preload[prop]).then((blobData) => { |
| if (!globalThis.allIsGood) |
| return; |
| this.preloads.push([ blobData.prop, blobData.blobURL ]); |
| this.updateCounter(); |
| }).catch((error) => { |
| // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error. |
| if (!this.failedPreloads) |
| this.failedPreloads = { }; |
| this.failedPreloads[prop] = true; |
| JetStream.counter.failedPreloadResources++; |
| })); |
| } |
| } |
| |
| JetStream.counter.totalResources += promises.length; |
| return Promise.all(promises); |
| } |
| |
| async retryPrefetchResource(type, prop, file) { |
| console.assert(isInBrowser); |
| |
| const counter = JetStream.counter; |
| const blobData = JetStream.blobDataCache[file]; |
| if (blobData.blob) { |
| // The same preload blob may be used by multiple subtests. Though the blob is already loaded, |
| // we still need to check if this subtest failed to load it before. If so, handle accordingly. |
| if (type == "preload") { |
| if (this.failedPreloads && this.failedPreloads[blobData.prop]) { |
| this.failedPreloads[blobData.prop] = false; |
| this.preloads.push([ blobData.prop, blobData.blobURL ]); |
| counter.failedPreloadResources--; |
| } |
| } |
| return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources; |
| } |
| |
| // Retry fetching the resource. |
| JetStream.loadCache[file] = null; |
| await this.loadBlob(type, prop, file, false).then((blobData) => { |
| if (!globalThis.allIsGood) |
| return; |
| if (blobData.type == "preload") |
| this.preloads.push([ blobData.prop, blobData.blobURL ]); |
| this.updateCounter(); |
| }); |
| |
| if (!blobData.blob) { |
| globalThis.allIsGood = false; |
| throw new Error("Fetch failed"); |
| } |
| |
| return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources; |
| } |
| |
| async retryPrefetchResourcesForBrowser() { |
| console.assert(isInBrowser); |
| |
| const counter = JetStream.counter; |
| for (const resource of this.plan.files) { |
| const allDone = await this.retryPrefetchResource("file", null, resource); |
| if (allDone) |
| return true; // All resources loaded, nothing more to do. |
| } |
| |
| if (this.plan.preload) { |
| for (const prop of Object.getOwnPropertyNames(this.plan.preload)) { |
| const resource = this.plan.preload[prop]; |
| const allDone = await this.retryPrefetchResource("preload", prop, resource); |
| if (allDone) |
| return true; // All resources loaded, nothing more to do. |
| } |
| } |
| return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources; |
| } |
| |
| prefetchResourcesForShell() { |
| console.assert(!isInBrowser); |
| |
| console.assert(this.scripts === null, "This initialization should be called only once."); |
| this.scripts = this.plan.files.map(file => shellFileLoader.load(file)); |
| |
| console.assert(this.preloads === null, "This initialization should be called only once."); |
| this.preloads = Object.entries(this.plan.preload ?? {}); |
| } |
| |
| allScoreIdentifiers() { |
| const ids = Object.keys(this.allScores()).map(name => this.scoreIdentifier(name)); |
| return ids; |
| } |
| |
| scoreIdentifier(scoreName) { |
| return `results-cell-${this.name}-${scoreName}`; |
| } |
| |
| allTimeIdentifiers() { |
| const ids = Object.keys(this.allTimes()).map(name => this.timeIdentifier(name)); |
| return ids; |
| } |
| |
| timeIdentifier(scoreName) { |
| return `results-cell-${this.name}-${scoreName}-time`; |
| } |
| |
| updateUIBeforeRun() { |
| if (!JetStreamParams.dumpJSONResults) |
| console.log(`Running ${this.name}:`); |
| if (isInBrowser) |
| this.updateUIBeforeRunInBrowser(); |
| } |
| |
| updateUIBeforeRunInBrowser() { |
| const resultsBenchmarkUI = document.getElementById(`benchmark-${this.name}`); |
| resultsBenchmarkUI.classList.add("benchmark-running"); |
| resultsBenchmarkUI.scrollIntoView({ block: "nearest" }); |
| |
| for (const id of this.allScoreIdentifiers()) |
| document.getElementById(id).innerHTML = "..."; |
| for (const id of this.allTimeIdentifiers()) |
| document.getElementById(id).innerHTML = "..."; |
| } |
| |
| updateUIAfterRun() { |
| if (isInBrowser) |
| this.updateUIAfterRunInBrowser(); |
| if (JetStreamParams.dumpJSONResults) |
| return; |
| this.updateConsoleAfterRun(); |
| } |
| |
| updateUIAfterRunInBrowser() { |
| const benchmarkResultsUI = document.getElementById(`benchmark-${this.name}`); |
| benchmarkResultsUI.classList.remove("benchmark-running"); |
| benchmarkResultsUI.classList.add("benchmark-done"); |
| |
| for (const [name, value] of Object.entries(this.allScores())) |
| document.getElementById(this.scoreIdentifier(name)).innerHTML = uiFriendlyScore(value); |
| for (const [name, value] of Object.entries(this.allTimes())) |
| document.getElementById(this.timeIdentifier(name)).innerHTML = uiFriendlyDuration(value); |
| |
| this.renderScatterPlot(); |
| } |
| |
| updateConsoleAfterRun() { |
| for (let [name, value] of Object.entries(this.allScores())) { |
| if (!name.endsWith("Score")) |
| name = `${name}-Score`; |
| |
| this.logMetric(name, shellFriendlyScore(value)); |
| } |
| for (let [name, value] of Object.entries(this.allTimes())) { |
| this.logMetric(`${name}-Time`, shellFriendlyDuration(value)); |
| } |
| if (JetStreamParams.RAMification) { |
| this.logMetric("Current Footprint", uiFriendlyNumber(this.currentFootprint)); |
| this.logMetric("Peak Footprint", uiFriendlyNumber(this.peakFootprint)); |
| } |
| console.log(""); |
| } |
| |
| logMetric(name, value) { |
| console.log( |
| shellFriendlyLabel(`${this.name} ${name}`), |
| value); |
| } |
| |
| renderScatterPlot() { |
| const plotContainer = document.getElementById(`plot-${this.name}`); |
| if (!plotContainer || !this.results || this.results.length === 0) |
| return; |
| |
| const scoreElement = document.getElementById(this.scoreIdentifier("Score")); |
| const width = scoreElement.offsetWidth; |
| const height = scoreElement.offsetHeight; |
| |
| const padding = 5; |
| const maxResult = Math.max(...this.results); |
| const minResult = Math.min(...this.results); |
| |
| const xRatio = (width - 2 * padding) / (this.results.length - 1 || 1); |
| const yRatio = (height - 2 * padding) / (maxResult - minResult || 1); |
| const radius = Math.max(1.5, Math.min(2.5, 10 - (this.iterations / 10))); |
| |
| let circlesSVG = ""; |
| for (let i = 0; i < this.results.length; i++) { |
| const result = this.results[i]; |
| const cx = padding + i * xRatio; |
| const cy = height - padding - (result - minResult) * yRatio; |
| const title = `Iteration ${i + 1}: ${uiFriendlyDuration(result)}`; |
| circlesSVG += `<circle cx="${cx}" cy="${cy}" r="${radius}"><title>${title}</title></circle>`; |
| } |
| plotContainer.innerHTML = `<svg width="${width}px" height="${height}px">${circlesSVG}</svg>`; |
| } |
| }; |
| |
| class GroupedBenchmark extends Benchmark { |
| constructor(plan, benchmarks) { |
| super(plan); |
| console.assert(benchmarks.length); |
| for (const benchmark of benchmarks) { |
| // FIXME: Tags don't work for grouped tests anyway but if they did then this would be weird and probably wrong. |
| console.assert(!benchmark.hasAnyTag("Default"), `Grouped benchmark sub-benchmarks shouldn't have the "Default" tag`, benchmark.tags); |
| } |
| benchmarks.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? 1 : -1); |
| this.benchmarks = benchmarks; |
| } |
| |
| async prefetchResourcesForBrowser() { |
| for (const benchmark of this.benchmarks) |
| await benchmark.prefetchResourcesForBrowser(); |
| } |
| |
| async retryPrefetchResourcesForBrowser() { |
| for (const benchmark of this.benchmarks) |
| await benchmark.retryPrefetchResourcesForBrowser(); |
| } |
| |
| prefetchResourcesForShell() { |
| for (const benchmark of this.benchmarks) |
| benchmark.prefetchResourcesForShell(); |
| } |
| |
| get files() { |
| let files = []; |
| for (const benchmark of this.benchmarks) |
| files = files.concat(benchmark.files); |
| return files; |
| } |
| |
| async run() { |
| this._state = BenchmarkState.PREPARE; |
| performance.mark(this.name); |
| this.startTime = performance.now(); |
| |
| let benchmark; |
| try { |
| this._state = BenchmarkState.RUNNING; |
| for (benchmark of this.benchmarks) |
| await benchmark.run(); |
| } catch (e) { |
| this._state = BenchmarkState.ERROR; |
| console.log(`Error in runCode of grouped benchmark ${benchmark.name}: `, e); |
| console.log(e.stack); |
| throw e; |
| } finally { |
| this._state = BenchmarkState.FINALIZE; |
| } |
| |
| this.endTime = performance.now(); |
| performance.measure(this.name, this.name); |
| |
| this.processResults(); |
| this._state = BenchmarkState.DONE; |
| } |
| |
| processResults() { |
| this.results = []; |
| for (const benchmark of this.benchmarks) |
| this.results = this.results.concat(benchmark.results); |
| } |
| |
| subScores() { |
| const results = {}; |
| |
| for (const benchmark of this.benchmarks) { |
| let scores = benchmark.subScores(); |
| for (let subScore in scores) { |
| results[subScore] ??= []; |
| results[subScore].push(scores[subScore]); |
| } |
| } |
| |
| for (let subScore in results) |
| results[subScore] = geomeanScore(results[subScore]); |
| return results; |
| } |
| |
| subTimes() { |
| const results = {}; |
| |
| for (const benchmark of this.benchmarks) { |
| let times = benchmark.subTimes(); |
| for (let subTime in times) { |
| results[subTime] ??= []; |
| results[subTime].push(times[subTime]); |
| } |
| } |
| |
| for (let subTimes in results) |
| results[subTimes] = sum(results[subTimes]); |
| return results; |
| } |
| }; |
| |
| class DefaultBenchmark extends Benchmark { |
| constructor(...args) { |
| super(...args); |
| |
| this.worstCaseCount = getWorstCaseCount(this.plan); |
| this.firstIterationTime = null; |
| this.firstIterationScore = null; |
| this.worstTime = null; |
| this.worstScore = null; |
| this.averageTime = null; |
| this.averageScore = null; |
| if (this.worstCaseCount) |
| console.assert(this.iterations > this.worstCaseCount); |
| console.assert(this.worstCaseCount >= 0); |
| } |
| |
| processResults(results) { |
| results = super.processResults(results) |
| |
| this.firstIterationTime = results[0]; |
| this.firstIterationScore = toScore(results[0]); |
| |
| results = results.slice(1); |
| results.sort((a, b) => a < b ? 1 : -1); |
| for (let i = 0; i + 1 < results.length; ++i) |
| console.assert(results[i] >= results[i + 1]); |
| |
| if (this.worstCaseCount) { |
| const worstCase = []; |
| for (let i = 0; i < this.worstCaseCount; ++i) |
| worstCase.push(results[i]); |
| this.worstTime = mean(worstCase); |
| this.worstScore = toScore(this.worstTime); |
| } |
| this.averageTime = mean(results); |
| this.averageScore = toScore(this.averageTime); |
| } |
| |
| subScores() { |
| const scores = { "First": this.firstIterationScore } |
| if (this.worstCaseCount) |
| scores["Worst"] = this.worstScore; |
| if (this.iterations > 1) |
| scores["Average"] = this.averageScore; |
| return scores; |
| } |
| |
| subTimes() { |
| const times = { |
| "First": this.firstIterationTime, |
| }; |
| if (this.worstCaseCount) |
| times["Worst"] = this.worstTime; |
| if (this.iterations > 1) |
| times["Average"] = this.averageTime; |
| return times; |
| } |
| } |
| |
| class AsyncBenchmark extends DefaultBenchmark { |
| get prerunCode() { |
| let str = ""; |
| // FIXME: It would be nice if these were available to any benchmark not just async ones but since these functions |
| // are async they would only work in a context where the benchmark is async anyway. Long term, we should do away |
| // with this class and make all benchmarks async. |
| if (isInBrowser) { |
| str += ` |
| JetStream.getBinary = async function(blobURL) { |
| const response = await fetch(blobURL); |
| return new Int8Array(await response.arrayBuffer()); |
| }; |
| |
| JetStream.getString = async function(blobURL) { |
| const response = await fetch(blobURL); |
| return response.text(); |
| }; |
| |
| JetStream.dynamicImport = async function(blobURL) { |
| return await import(blobURL); |
| }; |
| `; |
| } else { |
| str += ` |
| JetStream.getBinary = async function(path) { |
| return new Int8Array(read(path, "binary")); |
| }; |
| |
| JetStream.getString = async function(path) { |
| return read(path); |
| }; |
| |
| JetStream.dynamicImport = async function(path) { |
| try { |
| return await import(path); |
| } catch (e) { |
| // In shells, relative imports require different paths, so try with and |
| // without the "./" prefix (e.g., JSC requires it). |
| return await import(path.slice("./".length)) |
| } |
| }; |
| `; |
| } |
| return str; |
| } |
| |
| get runnerCode() { |
| return ` |
| async function doRun() { |
| const benchmark = new Benchmark(${JSON.stringify(this.benchmarkArguments)}); |
| await benchmark.init?.(); |
| const results = []; |
| const benchmarkName = "${this.name}"; |
| |
| for (let i = 0; i < ${this.iterations}; i++) { |
| ${this.preIterationCode} |
| |
| const iterationMarkLabel = benchmarkName + "-iteration-" + i; |
| const iterationStartMark = performance.mark(iterationMarkLabel); |
| |
| const start = performance.now(); |
| await benchmark.runIteration(i); |
| const end = performance.now(); |
| |
| performance.measure(iterationMarkLabel, iterationMarkLabel); |
| |
| ${this.postIterationCode} |
| |
| results.push(Math.max(1, end - start)); |
| } |
| benchmark.validate?.(${this.iterations}); |
| top.currentResolve(results); |
| }; |
| doRun().catch((error) => { top.currentReject(error); });` |
| } |
| }; |
| |
| // Meant for wasm benchmarks that are directly compiled with an emcc build script. It might not work for benchmarks built as |
| // part of a larger project's build system or a wasm benchmark compiled from a language that doesn't compile with emcc. |
| class WasmEMCCBenchmark extends AsyncBenchmark { |
| get prerunCode() { |
| let str = ` |
| let verbose = false; |
| |
| let globalObject = this; |
| |
| abort = quit = function() { |
| if (verbose) |
| console.log('Intercepted quit/abort'); |
| }; |
| |
| const oldPrint = globalObject.print; |
| globalObject.print = globalObject.printErr = (...args) => { |
| if (verbose) |
| console.log('Intercepted print: ', ...args); |
| }; |
| |
| let Module = { |
| preRun: [], |
| postRun: [], |
| noInitialRun: true, |
| print: print, |
| printErr: printErr |
| }; |
| |
| globalObject.Module = Module; |
| ${super.prerunCode}; |
| `; |
| |
| return str; |
| } |
| }; |
| |
| class WSLBenchmark extends Benchmark { |
| constructor(...args) { |
| super(...args); |
| |
| this.stdlibTime = null; |
| this.stdlibScore = null; |
| this.mainRunTime = null; |
| this.mainRunScore = null; |
| } |
| |
| processResults(results) { |
| results = super.processResults(results); |
| this.stdlibTime = results[0]; |
| this.stdlibScore = toScore(results[0]); |
| this.mainRunTime = results[1]; |
| this.mainRunScore = toScore(results[1]); |
| } |
| |
| get runnerCode() { |
| return `{ |
| const benchmark = new Benchmark(${JSON.stringify(this.benchmarkArguments)}); |
| const benchmarkName = "${this.name}"; |
| |
| const results = []; |
| { |
| const markLabel = benchmarkName + "-stdlib"; |
| const startMark = performance.mark(markLabel); |
| |
| const start = performance.now(); |
| benchmark.buildStdlib(); |
| results.push(performance.now() - start); |
| |
| performance.measure(markLabel, markLabel); |
| } |
| |
| { |
| const markLabel = benchmarkName + "-mainRun"; |
| const startMark = performance.mark(markLabel); |
| |
| const start = performance.now(); |
| benchmark.run(); |
| results.push(performance.now() - start); |
| |
| performance.measure(markLabel, markLabel); |
| } |
| top.currentResolve(results); |
| }`; |
| } |
| |
| subTimes() { |
| return { |
| "Stdlib": this.stdlibTime, |
| "MainRun": this.mainRunTime, |
| }; |
| } |
| |
| subScores() { |
| return { |
| "Stdlib": this.stdlibScore, |
| "MainRun": this.mainRunScore, |
| }; |
| } |
| }; |
| |
| class WasmLegacyBenchmark extends Benchmark { |
| constructor(...args) { |
| super(...args); |
| |
| this.startupTime = null; |
| this.startupScore = null; |
| this.runTime = null; |
| this.runScore = null; |
| } |
| |
| processResults(results) { |
| results = super.processResults(results); |
| this.startupTime = results[0]; |
| this.startupScore= toScore(results[0]); |
| this.runTime = results[1]; |
| this.runScore = toScore(results[1]); |
| } |
| |
| get prerunCode() { |
| const str = ` |
| let verbose = false; |
| |
| let compileTime = null; |
| let runTime = null; |
| |
| let globalObject = this; |
| |
| globalObject.benchmarkTime = performance.now.bind(performance); |
| |
| globalObject.reportCompileTime = (t) => { |
| if (compileTime !== null) |
| throw new Error("called report compile time twice"); |
| compileTime = t; |
| }; |
| |
| globalObject.reportRunTime = (t) => { |
| if (runTime !== null) |
| throw new Error("called report run time twice") |
| runTime = t; |
| top.currentResolve([compileTime, runTime]); |
| }; |
| |
| abort = quit = function() { |
| if (verbose) |
| console.log('Intercepted quit/abort'); |
| }; |
| |
| const oldConsoleLog = globalObject.console.log; |
| globalObject.print = globalObject.printErr = (...args) => { |
| if (verbose) |
| oldConsoleLog('Intercepted print: ', ...args); |
| }; |
| |
| let Module = { |
| preRun: [], |
| postRun: [], |
| print: globalObject.print, |
| printErr: globalObject.print |
| }; |
| globalObject.Module = Module; |
| `; |
| return str; |
| } |
| |
| get runnerCode() { |
| let str = `JetStream.loadBlob = function(key, path, andThen) {`; |
| |
| if (isInBrowser) { |
| str += ` |
| const xhr = new XMLHttpRequest(); |
| xhr.open('GET', path, true); |
| xhr.responseType = 'arraybuffer'; |
| xhr.onload = function() { |
| Module[key] = new Int8Array(xhr.response); |
| andThen(); |
| }; |
| xhr.send(null); |
| `; |
| } else { |
| str += ` |
| Module[key] = new Int8Array(read(path, "binary")); |
| if (andThen == doRun) { |
| globalObject.read = (...args) => { |
| console.log("should not be inside read: ", ...args); |
| throw new Error; |
| }; |
| }; |
| |
| Promise.resolve(42).then(() => { |
| try { |
| andThen(); |
| } catch(e) { |
| console.log("error running wasm:", e); |
| console.log(e.stack); |
| throw e; |
| } |
| }); |
| `; |
| } |
| |
| str += "};\n"; |
| |
| const keys = Object.keys(this.plan.preload); |
| for (let i = 0; i < keys.length; ++i) { |
| str += `JetStream.loadBlob("${keys[i]}", "${this.plan.preload[keys[i]]}", () => {\n`; |
| } |
| if (this.plan.async) { |
| str += `doRun().catch((e) => { |
| console.log("error running wasm:", e); |
| console.log(e.stack) |
| throw e; |
| });`; |
| } else { |
| str += `doRun();` |
| } |
| for (let i = 0; i < keys.length; ++i) { |
| str += `})`; |
| } |
| str += `;`; |
| |
| return str; |
| } |
| |
| subScores() { |
| return { |
| "Startup": this.startupScore, |
| "Runtime": this.runScore, |
| }; |
| } |
| |
| subTimes() { |
| return { |
| "Startup": this.startupTime, |
| "Runtime": this.runTime, |
| }; |
| } |
| }; |
| |
| function dotnetPreloads(type) |
| { |
| return { |
| dotnetUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/dotnet.js`, |
| dotnetNativeUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/dotnet.native.js`, |
| dotnetRuntimeUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/dotnet.runtime.js`, |
| wasmBinaryUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/dotnet.native.wasm`, |
| icuCustomUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/icudt_CJK.dat`, |
| dllCollectionsConcurrentUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Collections.Concurrent.wasm`, |
| dllCollectionsUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Collections.wasm`, |
| dllComponentModelPrimitivesUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.ComponentModel.Primitives.wasm`, |
| dllComponentModelTypeConverterUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.ComponentModel.TypeConverter.wasm`, |
| dllDrawingPrimitivesUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Drawing.Primitives.wasm`, |
| dllDrawingUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Drawing.wasm`, |
| dllIOPipelinesUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.IO.Pipelines.wasm`, |
| dllLinqUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Linq.wasm`, |
| dllMemoryUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Memory.wasm`, |
| dllObjectModelUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.ObjectModel.wasm`, |
| dllPrivateCorelibUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Private.CoreLib.wasm`, |
| dllRuntimeInteropServicesJavaScriptUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Runtime.InteropServices.JavaScript.wasm`, |
| dllTextEncodingsWebUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Text.Encodings.Web.wasm`, |
| dllTextJsonUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/System.Text.Json.wasm`, |
| dllAppUrl: `./wasm/dotnet/build-${type}/wwwroot/_framework/dotnet.wasm`, |
| } |
| } |
| |
| let BENCHMARKS = [ |
| // ARES |
| new DefaultBenchmark({ |
| name: "Air", |
| files: [ |
| "./ARES-6/Air/symbols.js", |
| "./ARES-6/Air/tmp_base.js", |
| "./ARES-6/Air/arg.js", |
| "./ARES-6/Air/basic_block.js", |
| "./ARES-6/Air/code.js", |
| "./ARES-6/Air/frequented_block.js", |
| "./ARES-6/Air/inst.js", |
| "./ARES-6/Air/opcode.js", |
| "./ARES-6/Air/reg.js", |
| "./ARES-6/Air/stack_slot.js", |
| "./ARES-6/Air/tmp.js", |
| "./ARES-6/Air/util.js", |
| "./ARES-6/Air/custom.js", |
| "./ARES-6/Air/liveness.js", |
| "./ARES-6/Air/insertion_set.js", |
| "./ARES-6/Air/allocate_stack.js", |
| "./ARES-6/Air/payload-gbemu-executeIteration.js", |
| "./ARES-6/Air/payload-imaging-gaussian-blur-gaussianBlur.js", |
| "./ARES-6/Air/payload-airjs-ACLj8C.js", |
| "./ARES-6/Air/payload-typescript-scanIdentifier.js", |
| "./ARES-6/Air/benchmark.js", |
| ], |
| tags: ["Default", "ARES"], |
| }), |
| new DefaultBenchmark({ |
| name: "Basic", |
| files: [ |
| "./ARES-6/Basic/ast.js", |
| "./ARES-6/Basic/basic.js", |
| "./ARES-6/Basic/caseless_map.js", |
| "./ARES-6/Basic/lexer.js", |
| "./ARES-6/Basic/number.js", |
| "./ARES-6/Basic/parser.js", |
| "./ARES-6/Basic/random.js", |
| "./ARES-6/Basic/state.js", |
| "./ARES-6/Basic/benchmark.js", |
| ], |
| tags: ["Default", "ARES"], |
| }), |
| new DefaultBenchmark({ |
| name: "ML", |
| files: [ |
| "./ARES-6/ml/index.js", |
| "./ARES-6/ml/benchmark.js", |
| ], |
| iterations: 60, |
| tags: ["Default", "ARES"], |
| }), |
| new AsyncBenchmark({ |
| name: "Babylon", |
| files: [ |
| "./ARES-6/Babylon/index.js", |
| "./ARES-6/Babylon/benchmark.js", |
| ], |
| preload: { |
| airBlob: "./ARES-6/Babylon/air-blob.js", |
| basicBlob: "./ARES-6/Babylon/basic-blob.js", |
| inspectorBlob: "./ARES-6/Babylon/inspector-blob.js", |
| babylonBlob: "./ARES-6/Babylon/babylon-blob.js", |
| }, |
| tags: ["Default", "ARES"], |
| allowUtf16: true, |
| }), |
| // CDJS |
| new DefaultBenchmark({ |
| name: "cdjs", |
| files: [ |
| "./cdjs/constants.js", |
| "./cdjs/util.js", |
| "./cdjs/red_black_tree.js", |
| "./cdjs/call_sign.js", |
| "./cdjs/vector_2d.js", |
| "./cdjs/vector_3d.js", |
| "./cdjs/motion.js", |
| "./cdjs/reduce_collision_set.js", |
| "./cdjs/simulator.js", |
| "./cdjs/collision.js", |
| "./cdjs/collision_detector.js", |
| "./cdjs/benchmark.js", |
| ], |
| iterations: 60, |
| worstCaseCount: 3, |
| tags: ["Default", "CDJS"], |
| }), |
| // CodeLoad |
| new AsyncBenchmark({ |
| name: "first-inspector-code-load", |
| files: [ |
| "./code-load/code-first-load.js", |
| ], |
| preload: { |
| inspectorPayloadBlob: "./code-load/inspector-payload-minified.js", |
| }, |
| tags: ["Default", "CodeLoad"], |
| }), |
| new AsyncBenchmark({ |
| name: "multi-inspector-code-load", |
| files: [ |
| "./code-load/code-multi-load.js", |
| ], |
| preload: { |
| inspectorPayloadBlob: "./code-load/inspector-payload-minified.js", |
| }, |
| tags: ["Default", "CodeLoad"], |
| }), |
| // Octane |
| new DefaultBenchmark({ |
| name: "Box2D", |
| files: [ |
| "./Octane/box2d.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "octane-code-load", |
| files: [ |
| "./Octane/code-first-load.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "crypto", |
| files: [ |
| "./Octane/crypto.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "delta-blue", |
| files: [ |
| "./Octane/deltablue.js" |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "earley-boyer", |
| files: [ |
| "./Octane/earley-boyer.js" |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "gbemu", |
| files: [ |
| "./Octane/gbemu-part1.js", |
| "./Octane/gbemu-part2.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "mandreel", |
| files: [ |
| "./Octane/mandreel.js" |
| ], |
| iterations: 80, |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "navier-stokes", |
| files: [ |
| "./Octane/navier-stokes.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "pdfjs", |
| files: [ |
| "./Octane/pdfjs.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "raytrace", |
| files: [ |
| "./Octane/raytrace.js", |
| ], |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "regexp", |
| files: [ |
| "./Octane/regexp.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "richards", |
| files: [ |
| "./Octane/richards.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "splay", |
| files: [ |
| "./Octane/splay.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "Octane"], |
| }), |
| new DefaultBenchmark({ |
| name: "typescript-octane", |
| files: [ |
| "./Octane/typescript-compiler.js", |
| "./Octane/typescript-input.js", |
| "./Octane/typescript.js", |
| ], |
| iterations: 15, |
| worstCaseCount: 2, |
| deterministicRandom: true, |
| tags: ["Octane", "typescript"], |
| }), |
| // RexBench |
| new DefaultBenchmark({ |
| name: "FlightPlanner", |
| files: [ |
| "./RexBench/FlightPlanner/airways.js", |
| "./RexBench/FlightPlanner/waypoints.js", |
| "./RexBench/FlightPlanner/flight_planner.js", |
| "./RexBench/FlightPlanner/expectations.js", |
| "./RexBench/FlightPlanner/benchmark.js", |
| ], |
| tags: ["Default", "RexBench"], |
| }), |
| new DefaultBenchmark({ |
| name: "OfflineAssembler", |
| files: [ |
| "./RexBench/OfflineAssembler/registers.js", |
| "./RexBench/OfflineAssembler/instructions.js", |
| "./RexBench/OfflineAssembler/ast.js", |
| "./RexBench/OfflineAssembler/parser.js", |
| "./RexBench/OfflineAssembler/file.js", |
| "./RexBench/OfflineAssembler/LowLevelInterpreter.js", |
| "./RexBench/OfflineAssembler/LowLevelInterpreter32_64.js", |
| "./RexBench/OfflineAssembler/LowLevelInterpreter64.js", |
| "./RexBench/OfflineAssembler/InitBytecodes.js", |
| "./RexBench/OfflineAssembler/expected.js", |
| "./RexBench/OfflineAssembler/benchmark.js", |
| ], |
| iterations: 80, |
| tags: ["Default", "RexBench"], |
| }), |
| new DefaultBenchmark({ |
| name: "UniPoker", |
| files: [ |
| "./RexBench/UniPoker/poker.js", |
| "./RexBench/UniPoker/expected.js", |
| "./RexBench/UniPoker/benchmark.js", |
| ], |
| deterministicRandom: true, |
| // FIXME: UniPoker should not access isInBrowser. |
| exposeBrowserTest: true, |
| tags: ["Default", "RexBench"], |
| }), |
| // Simple |
| new DefaultBenchmark({ |
| name: "hash-map", |
| files: [ |
| "./simple/hash-map.js", |
| ], |
| tags: ["Default", "Simple"], |
| }), |
| new AsyncBenchmark({ |
| name: "doxbee-promise", |
| files: [ |
| "./simple/doxbee-promise.js", |
| ], |
| tags: ["Default", "Simple"], |
| }), |
| new AsyncBenchmark({ |
| name: "doxbee-async", |
| files: [ |
| "./simple/doxbee-async.js", |
| ], |
| tags: ["Default", "Simple"], |
| }), |
| // SeaMonster |
| new DefaultBenchmark({ |
| name: "ai-astar", |
| files: [ |
| "./SeaMonster/ai-astar.js" |
| ], |
| tags: ["Default", "SeaMonster"], |
| }), |
| new DefaultBenchmark({ |
| name: "gaussian-blur", |
| files: [ |
| "./SeaMonster/gaussian-blur.js", |
| ], |
| tags: ["Default", "SeaMonster"], |
| }), |
| new DefaultBenchmark({ |
| name: "stanford-crypto-aes", |
| files: [ |
| "./SeaMonster/sjlc.js", |
| "./SeaMonster/stanford-crypto-aes.js", |
| ], |
| tags: ["Default", "SeaMonster"], |
| }), |
| new DefaultBenchmark({ |
| name: "stanford-crypto-pbkdf2", |
| files: [ |
| "./SeaMonster/sjlc.js", |
| "./SeaMonster/stanford-crypto-pbkdf2.js" |
| ], |
| tags: ["Default", "SeaMonster"], |
| }), |
| new DefaultBenchmark({ |
| name: "stanford-crypto-sha256", |
| files: [ |
| "./SeaMonster/sjlc.js", |
| "./SeaMonster/stanford-crypto-sha256.js", |
| ], |
| tags: ["Default", "SeaMonster"], |
| }), |
| new DefaultBenchmark({ |
| name: "json-stringify-inspector", |
| files: [ |
| "./SeaMonster/inspector-json-payload.js", |
| "./SeaMonster/json-stringify-inspector.js", |
| ], |
| iterations: 20, |
| worstCaseCount: 2, |
| tags: ["Default", "SeaMonster"], |
| }), |
| new DefaultBenchmark({ |
| name: "json-parse-inspector", |
| files: [ |
| "./SeaMonster/inspector-json-payload.js", |
| "./SeaMonster/json-parse-inspector.js", |
| ], |
| iterations: 20, |
| worstCaseCount: 2, |
| tags: ["Default", "SeaMonster"], |
| }), |
| // BigInt |
| new AsyncBenchmark({ |
| name: "bigint-noble-bls12-381", |
| files: [ |
| "./bigint/web-crypto-sham.js", |
| "./bigint/noble-bls12-381-bundle.js", |
| "./bigint/noble-benchmark.js", |
| ], |
| iterations: 4, |
| worstCaseCount: 1, |
| deterministicRandom: true, |
| tags: ["BigIntNoble"], |
| }), |
| new AsyncBenchmark({ |
| name: "bigint-noble-secp256k1", |
| files: [ |
| "./bigint/web-crypto-sham.js", |
| "./bigint/noble-secp256k1-bundle.js", |
| "./bigint/noble-benchmark.js", |
| ], |
| deterministicRandom: true, |
| tags: ["BigIntNoble"], |
| }), |
| new AsyncBenchmark({ |
| name: "bigint-noble-ed25519", |
| files: [ |
| "./bigint/web-crypto-sham.js", |
| "./bigint/noble-ed25519-bundle.js", |
| "./bigint/noble-benchmark.js", |
| ], |
| iterations: 30, |
| deterministicRandom: true, |
| tags: ["Default", "BigIntNoble"], |
| }), |
| new DefaultBenchmark({ |
| name: "bigint-paillier", |
| files: [ |
| "./bigint/web-crypto-sham.js", |
| "./bigint/paillier-bundle.js", |
| "./bigint/paillier-benchmark.js", |
| ], |
| iterations: 10, |
| worstCaseCount: 2, |
| deterministicRandom: true, |
| tags: ["BigIntMisc"], |
| }), |
| new DefaultBenchmark({ |
| name: "bigint-bigdenary", |
| files: [ |
| "./bigint/bigdenary-bundle.js", |
| "./bigint/bigdenary-benchmark.js", |
| ], |
| iterations: 160, |
| worstCaseCount: 16, |
| tags: ["BigIntMisc"], |
| }), |
| // Proxy |
| new AsyncBenchmark({ |
| name: "proxy-mobx", |
| files: [ |
| "./proxy/common.js", |
| "./proxy/mobx-bundle.js", |
| "./proxy/mobx-benchmark.js", |
| ], |
| iterations: defaultIterationCount * 3, |
| worstCaseCount: defaultWorstCaseCount * 3, |
| tags: ["Default", "Proxy"], |
| }), |
| new AsyncBenchmark({ |
| name: "proxy-vue", |
| files: [ |
| "./proxy/common.js", |
| "./proxy/vue-bundle.js", |
| "./proxy/vue-benchmark.js", |
| ], |
| tags: ["Default", "Proxy"], |
| }), |
| new AsyncBenchmark({ |
| name: "web-ssr", |
| files: [ |
| "./web-ssr/benchmark.js", |
| ], |
| preload: { |
| // Debug Sources for nicer profiling. |
| // BUNDLE_BLOB: "./web-ssr/dist/bundle.js", |
| BUNDLE_BLOB: "./web-ssr/dist/bundle.min.js", |
| }, |
| tags: ["Default", "web", "ssr"], |
| iterations: 30, |
| }), |
| // Class fields |
| new DefaultBenchmark({ |
| name: "raytrace-public-class-fields", |
| files: [ |
| "./class-fields/raytrace-public-class-fields.js", |
| ], |
| tags: ["Default", "ClassFields"], |
| }), |
| new DefaultBenchmark({ |
| name: "raytrace-private-class-fields", |
| files: [ |
| "./class-fields/raytrace-private-class-fields.js", |
| ], |
| tags: ["Default", "ClassFields"], |
| }), |
| new AsyncBenchmark({ |
| name: "typescript-lib", |
| files: [ |
| "./TypeScript/src/mock/sys.js", |
| "./TypeScript/dist/bundle.js", |
| "./TypeScript/benchmark.js", |
| ], |
| preload: { |
| // Large test project: |
| // "tsconfig": "./TypeScript/src/gen/zod-medium/tsconfig.json", |
| // "files": "./TypeScript/src/gen/zod-medium/files.json", |
| "tsconfig": "./TypeScript/src/gen/immer-tiny/tsconfig.json", |
| "files": "./TypeScript/src/gen/immer-tiny/files.json", |
| }, |
| iterations: 1, |
| worstCaseCount: 0, |
| tags: ["Default", "typescript"], |
| }), |
| // Generators |
| new AsyncBenchmark({ |
| name: "async-fs", |
| files: [ |
| "./generators/async-file-system.js", |
| ], |
| iterations: 80, |
| worstCaseCount: 6, |
| deterministicRandom: true, |
| tags: ["Default", "Generators"], |
| }), |
| new DefaultBenchmark({ |
| name: "sync-fs", |
| files: [ |
| "./generators/sync-file-system.js", |
| ], |
| iterations: 80, |
| worstCaseCount: 6, |
| deterministicRandom: true, |
| tags: ["Default", "Generators"], |
| }), |
| new DefaultBenchmark({ |
| name: "lazy-collections", |
| files: [ |
| "./generators/lazy-collections.js", |
| ], |
| tags: ["Default", "Generators"], |
| }), |
| new DefaultBenchmark({ |
| name: "js-tokens", |
| files: [ |
| "./generators/js-tokens.js", |
| ], |
| tags: ["Default", "Generators"], |
| }), |
| new DefaultBenchmark({ |
| name: "threejs", |
| files: [ |
| "./threejs/three.js", |
| "./threejs/benchmark.js", |
| ], |
| deterministicRandom: true, |
| tags: ["Default", "ThreeJs"], |
| }), |
| // Wasm |
| new WasmEMCCBenchmark({ |
| name: "HashSet-wasm", |
| files: [ |
| "./wasm/HashSet/build/HashSet.js", |
| "./wasm/HashSet/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/HashSet/build/HashSet.wasm", |
| }, |
| iterations: 50, |
| // No longer run by-default: We have more realistic Wasm workloads by |
| // now, and it was over-incentivizing inlining. |
| tags: ["Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "quicksort-wasm", |
| files: [ |
| "./wasm/quicksort/build/quicksort.js", |
| "./wasm/quicksort/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/quicksort/build/quicksort.wasm", |
| }, |
| iterations: 50, |
| // No longer run by-default: We have more realistic Wasm workloads by |
| // now, and it was a small microbenchmark. |
| tags: ["Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "gcc-loops-wasm", |
| files: [ |
| "./wasm/gcc-loops/build/gcc-loops.js", |
| "./wasm/gcc-loops/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/gcc-loops/build/gcc-loops.wasm", |
| }, |
| iterations: 50, |
| // No longer run by-default: We have more realistic Wasm workloads by |
| // now, and it was a small microbenchmark. |
| tags: ["Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "tsf-wasm", |
| files: [ |
| "./wasm/TSF/build/tsf.js", |
| "./wasm/TSF/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/TSF/build/tsf.wasm", |
| }, |
| iterations: 50, |
| tags: ["Default", "Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "richards-wasm", |
| files: [ |
| "./wasm/richards/build/richards.js", |
| "./wasm/richards/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/richards/build/richards.wasm", |
| }, |
| iterations: 50, |
| tags: ["Default", "Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "sqlite3-wasm", |
| files: [ |
| "./polyfills/fast-text-encoding/1.0.3/text.js", |
| "./sqlite3/benchmark.js", |
| "./sqlite3/build/jswasm/speedtest1.js", |
| ], |
| preload: { |
| wasmBinary: "./sqlite3/build/jswasm/speedtest1.wasm", |
| }, |
| iterations: 30, |
| worstCaseCount: 2, |
| tags: ["Default", "Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "Dart-flute-complex-wasm", |
| files: [ |
| "./Dart/benchmark.js", |
| ], |
| preload: { |
| jsModule: "./Dart/build/flute.complex.dart2wasm.mjs", |
| wasmBinary: "./Dart/build/flute.complex.dart2wasm.wasm", |
| }, |
| iterations: 15, |
| worstCaseCount: 2, |
| // Not run by default because the `CupertinoTimePicker` widget is very allocation-heavy, |
| // leading to an unrealistic GC-dominated workload. See |
| // https://github.com/WebKit/JetStream/pull/97#issuecomment-3139924169 |
| // The todomvc workload below is less allocation heavy and a replacement for now. |
| // TODO: Revisit, once Dart/Flutter worked on this widget or workload. |
| tags: ["Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "Dart-flute-todomvc-wasm", |
| files: [ |
| "./Dart/benchmark.js", |
| ], |
| preload: { |
| jsModule: "./Dart/build/flute.todomvc.dart2wasm.mjs", |
| wasmBinary: "./Dart/build/flute.todomvc.dart2wasm.wasm", |
| }, |
| iterations: 30, |
| worstCaseCount: 2, |
| tags: ["Default", "Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "Kotlin-compose-wasm", |
| files: [ |
| "./Kotlin-compose/benchmark.js", |
| ], |
| preload: { |
| skikoJsModule: "./Kotlin-compose/build/skiko.mjs", |
| skikoWasmBinary: "./Kotlin-compose/build/skiko.wasm", |
| composeJsModule: "./Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs", |
| composeWasmBinary: "./Kotlin-compose/build/compose-benchmarks-benchmarks.wasm", |
| inputImageCompose: "./Kotlin-compose/build/compose-multiplatform.png", |
| inputImageCat: "./Kotlin-compose/build/example1_cat.jpg", |
| inputImageComposeCommunity: "./Kotlin-compose/build/example1_compose-community-primary.png", |
| inputFontItalic: "./Kotlin-compose/build/jetbrainsmono_italic.ttf", |
| inputFontRegular: "./Kotlin-compose/build/jetbrainsmono_regular.ttf" |
| }, |
| iterations: 15, |
| worstCaseCount: 2, |
| tags: ["Default", "Wasm"], |
| }), |
| new AsyncBenchmark({ |
| name: "transformersjs-bert-wasm", |
| files: [ |
| "./polyfills/fast-text-encoding/1.0.3/text.js", |
| "./transformersjs/benchmark.js", |
| "./transformersjs/task-bert.js", |
| ], |
| preload: { |
| transformersJsModule: "./transformersjs/build/transformers.js", |
| |
| onnxJsModule: "./transformersjs/build/onnxruntime-web/ort-wasm-simd-threaded.mjs", |
| onnxWasmBinary: "./transformersjs/build/onnxruntime-web/ort-wasm-simd-threaded.wasm", |
| |
| modelWeights: "./transformersjs/build/models/Xenova/distilbert-base-uncased-finetuned-sst-2-english/onnx/model_uint8.onnx", |
| modelConfig: "./transformersjs/build/models/Xenova/distilbert-base-uncased-finetuned-sst-2-english/config.json", |
| modelTokenizer: "./transformersjs/build/models/Xenova/distilbert-base-uncased-finetuned-sst-2-english/tokenizer.json", |
| modelTokenizerConfig: "./transformersjs/build/models/Xenova/distilbert-base-uncased-finetuned-sst-2-english/tokenizer_config.json", |
| }, |
| iterations: 30, |
| allowUtf16: true, |
| tags: ["Default", "Wasm", "transformersjs"], |
| }), |
| new AsyncBenchmark({ |
| name: "transformersjs-whisper-wasm", |
| files: [ |
| "./polyfills/fast-text-encoding/1.0.3/text.js", |
| "./transformersjs/benchmark.js", |
| "./transformersjs/task-whisper.js", |
| ], |
| preload: { |
| transformersJsModule: "./transformersjs/build/transformers.js", |
| |
| onnxJsModule: "./transformersjs/build/onnxruntime-web/ort-wasm-simd-threaded.mjs", |
| onnxWasmBinary: "./transformersjs/build/onnxruntime-web/ort-wasm-simd-threaded.wasm", |
| |
| modelEncoderWeights: "./transformersjs/build/models/Xenova/whisper-tiny.en/onnx/encoder_model_quantized.onnx", |
| modelDecoderWeights: "./transformersjs/build/models/Xenova/whisper-tiny.en/onnx/decoder_model_merged_quantized.onnx", |
| modelConfig: "./transformersjs/build/models/Xenova/whisper-tiny.en/config.json", |
| modelTokenizer: "./transformersjs/build/models/Xenova/whisper-tiny.en/tokenizer.json", |
| modelTokenizerConfig: "./transformersjs/build/models/Xenova/whisper-tiny.en/tokenizer_config.json", |
| modelPreprocessorConfig: "./transformersjs/build/models/Xenova/whisper-tiny.en/preprocessor_config.json", |
| modelGenerationConfig: "./transformersjs/build/models/Xenova/whisper-tiny.en/generation_config.json", |
| |
| inputFile: "./transformersjs/build/inputs/jfk.raw", |
| }, |
| iterations: 5, |
| worstCaseCount: 1, |
| allowUtf16: true, |
| tags: ["Default", "Wasm", "transformersjs"], |
| }), |
| new WasmLegacyBenchmark({ |
| name: "tfjs-wasm", |
| files: [ |
| "./wasm/tfjs-model-helpers.js", |
| "./wasm/tfjs-model-mobilenet-v3.js", |
| "./wasm/tfjs-model-mobilenet-v1.js", |
| "./wasm/tfjs-model-coco-ssd.js", |
| "./wasm/tfjs-model-use.js", |
| "./wasm/tfjs-model-use-vocab.js", |
| "./wasm/tfjs-bundle.js", |
| "./wasm/tfjs.js", |
| "./wasm/tfjs-benchmark.js", |
| ], |
| preload: { |
| tfjsBackendWasmBlob: "./wasm/tfjs-backend-wasm.wasm", |
| }, |
| async: true, |
| deterministicRandom: true, |
| exposeBrowserTest: true, |
| allowUtf16: true, |
| tags: ["Wasm"], |
| }), |
| new WasmLegacyBenchmark({ |
| name: "tfjs-wasm-simd", |
| files: [ |
| "./wasm/tfjs-model-helpers.js", |
| "./wasm/tfjs-model-mobilenet-v3.js", |
| "./wasm/tfjs-model-mobilenet-v1.js", |
| "./wasm/tfjs-model-coco-ssd.js", |
| "./wasm/tfjs-model-use.js", |
| "./wasm/tfjs-model-use-vocab.js", |
| "./wasm/tfjs-bundle.js", |
| "./wasm/tfjs.js", |
| "./wasm/tfjs-benchmark.js", |
| ], |
| preload: { |
| tfjsBackendWasmSimdBlob: "./wasm/tfjs-backend-wasm-simd.wasm", |
| }, |
| async: true, |
| deterministicRandom: true, |
| exposeBrowserTest: true, |
| allowUtf16: true, |
| tags: ["Wasm"], |
| }), |
| new WasmEMCCBenchmark({ |
| name: "argon2-wasm", |
| files: [ |
| "./wasm/argon2/build/argon2.js", |
| "./wasm/argon2/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/argon2/build/argon2.wasm", |
| }, |
| iterations: 30, |
| worstCaseCount: 3, |
| deterministicRandom: true, |
| allowUtf16: true, |
| tags: ["Default", "Wasm"], |
| }), |
| // WorkerTests |
| new AsyncBenchmark({ |
| name: "bomb-workers", |
| files: [ |
| "./worker/bomb.js", |
| ], |
| exposeBrowserTest: true, |
| iterations: 80, |
| preload: { |
| rayTrace3D: "./worker/bomb-subtests/3d-raytrace.js", |
| accessNbody: "./worker/bomb-subtests/access-nbody.js", |
| morph3D: "./worker/bomb-subtests/3d-morph.js", |
| cube3D: "./worker/bomb-subtests/3d-cube.js", |
| accessFunnkuch: "./worker/bomb-subtests/access-fannkuch.js", |
| accessBinaryTrees: "./worker/bomb-subtests/access-binary-trees.js", |
| accessNsieve: "./worker/bomb-subtests/access-nsieve.js", |
| bitopsBitwiseAnd: "./worker/bomb-subtests/bitops-bitwise-and.js", |
| bitopsNsieveBits: "./worker/bomb-subtests/bitops-nsieve-bits.js", |
| controlflowRecursive: "./worker/bomb-subtests/controlflow-recursive.js", |
| bitops3BitBitsInByte: "./worker/bomb-subtests/bitops-3bit-bits-in-byte.js", |
| botopsBitsInByte: "./worker/bomb-subtests/bitops-bits-in-byte.js", |
| cryptoAES: "./worker/bomb-subtests/crypto-aes.js", |
| cryptoMD5: "./worker/bomb-subtests/crypto-md5.js", |
| cryptoSHA1: "./worker/bomb-subtests/crypto-sha1.js", |
| dateFormatTofte: "./worker/bomb-subtests/date-format-tofte.js", |
| dateFormatXparb: "./worker/bomb-subtests/date-format-xparb.js", |
| mathCordic: "./worker/bomb-subtests/math-cordic.js", |
| mathPartialSums: "./worker/bomb-subtests/math-partial-sums.js", |
| mathSpectralNorm: "./worker/bomb-subtests/math-spectral-norm.js", |
| stringBase64: "./worker/bomb-subtests/string-base64.js", |
| stringFasta: "./worker/bomb-subtests/string-fasta.js", |
| stringValidateInput: "./worker/bomb-subtests/string-validate-input.js", |
| stringTagcloud: "./worker/bomb-subtests/string-tagcloud.js", |
| stringUnpackCode: "./worker/bomb-subtests/string-unpack-code.js", |
| regexpDNA: "./worker/bomb-subtests/regexp-dna.js", |
| }, |
| tags: ["Default", "WorkerTests"], |
| }), |
| new AsyncBenchmark({ |
| name: "segmentation", |
| files: [ |
| "./worker/segmentation.js", |
| ], |
| preload: { |
| asyncTaskBlob: "./worker/async-task.js", |
| }, |
| iterations: 36, |
| worstCaseCount: 3, |
| tags: ["Default", "WorkerTests"], |
| }), |
| // WSL |
| new WSLBenchmark({ |
| name: "WSL", |
| files: [ |
| "./WSL/Node.js", |
| "./WSL/Type.js", |
| "./WSL/ReferenceType.js", |
| "./WSL/Value.js", |
| "./WSL/Expression.js", |
| "./WSL/Rewriter.js", |
| "./WSL/Visitor.js", |
| "./WSL/CreateLiteral.js", |
| "./WSL/CreateLiteralType.js", |
| "./WSL/PropertyAccessExpression.js", |
| "./WSL/AddressSpace.js", |
| "./WSL/AnonymousVariable.js", |
| "./WSL/ArrayRefType.js", |
| "./WSL/ArrayType.js", |
| "./WSL/Assignment.js", |
| "./WSL/AutoWrapper.js", |
| "./WSL/Block.js", |
| "./WSL/BoolLiteral.js", |
| "./WSL/Break.js", |
| "./WSL/CallExpression.js", |
| "./WSL/CallFunction.js", |
| "./WSL/Check.js", |
| "./WSL/CheckLiteralTypes.js", |
| "./WSL/CheckLoops.js", |
| "./WSL/CheckRecursiveTypes.js", |
| "./WSL/CheckRecursion.js", |
| "./WSL/CheckReturns.js", |
| "./WSL/CheckUnreachableCode.js", |
| "./WSL/CheckWrapped.js", |
| "./WSL/Checker.js", |
| "./WSL/CloneProgram.js", |
| "./WSL/CommaExpression.js", |
| "./WSL/ConstexprFolder.js", |
| "./WSL/ConstexprTypeParameter.js", |
| "./WSL/Continue.js", |
| "./WSL/ConvertPtrToArrayRefExpression.js", |
| "./WSL/DereferenceExpression.js", |
| "./WSL/DoWhileLoop.js", |
| "./WSL/DotExpression.js", |
| "./WSL/DoubleLiteral.js", |
| "./WSL/DoubleLiteralType.js", |
| "./WSL/EArrayRef.js", |
| "./WSL/EBuffer.js", |
| "./WSL/EBufferBuilder.js", |
| "./WSL/EPtr.js", |
| "./WSL/EnumLiteral.js", |
| "./WSL/EnumMember.js", |
| "./WSL/EnumType.js", |
| "./WSL/EvaluationCommon.js", |
| "./WSL/Evaluator.js", |
| "./WSL/ExpressionFinder.js", |
| "./WSL/ExternalOrigin.js", |
| "./WSL/Field.js", |
| "./WSL/FindHighZombies.js", |
| "./WSL/FlattenProtocolExtends.js", |
| "./WSL/FlattenedStructOffsetGatherer.js", |
| "./WSL/FloatLiteral.js", |
| "./WSL/FloatLiteralType.js", |
| "./WSL/FoldConstexprs.js", |
| "./WSL/ForLoop.js", |
| "./WSL/Func.js", |
| "./WSL/FuncDef.js", |
| "./WSL/FuncInstantiator.js", |
| "./WSL/FuncParameter.js", |
| "./WSL/FunctionLikeBlock.js", |
| "./WSL/HighZombieFinder.js", |
| "./WSL/IdentityExpression.js", |
| "./WSL/IfStatement.js", |
| "./WSL/IndexExpression.js", |
| "./WSL/InferTypesForCall.js", |
| "./WSL/Inline.js", |
| "./WSL/Inliner.js", |
| "./WSL/InstantiateImmediates.js", |
| "./WSL/IntLiteral.js", |
| "./WSL/IntLiteralType.js", |
| "./WSL/Intrinsics.js", |
| "./WSL/LateChecker.js", |
| "./WSL/Lexer.js", |
| "./WSL/LexerToken.js", |
| "./WSL/LiteralTypeChecker.js", |
| "./WSL/LogicalExpression.js", |
| "./WSL/LogicalNot.js", |
| "./WSL/LoopChecker.js", |
| "./WSL/MakeArrayRefExpression.js", |
| "./WSL/MakePtrExpression.js", |
| "./WSL/NameContext.js", |
| "./WSL/NameFinder.js", |
| "./WSL/NameResolver.js", |
| "./WSL/NativeFunc.js", |
| "./WSL/NativeFuncInstance.js", |
| "./WSL/NativeType.js", |
| "./WSL/NativeTypeInstance.js", |
| "./WSL/NormalUsePropertyResolver.js", |
| "./WSL/NullLiteral.js", |
| "./WSL/NullType.js", |
| "./WSL/OriginKind.js", |
| "./WSL/OverloadResolutionFailure.js", |
| "./WSL/Parse.js", |
| "./WSL/Prepare.js", |
| "./WSL/Program.js", |
| "./WSL/ProgramWithUnnecessaryThingsRemoved.js", |
| "./WSL/PropertyResolver.js", |
| "./WSL/Protocol.js", |
| "./WSL/ProtocolDecl.js", |
| "./WSL/ProtocolFuncDecl.js", |
| "./WSL/ProtocolRef.js", |
| "./WSL/PtrType.js", |
| "./WSL/ReadModifyWriteExpression.js", |
| "./WSL/RecursionChecker.js", |
| "./WSL/RecursiveTypeChecker.js", |
| "./WSL/ResolveNames.js", |
| "./WSL/ResolveOverloadImpl.js", |
| "./WSL/ResolveProperties.js", |
| "./WSL/ResolveTypeDefs.js", |
| "./WSL/Return.js", |
| "./WSL/ReturnChecker.js", |
| "./WSL/ReturnException.js", |
| "./WSL/StandardLibrary.js", |
| "./WSL/StatementCloner.js", |
| "./WSL/StructLayoutBuilder.js", |
| "./WSL/StructType.js", |
| "./WSL/Substitution.js", |
| "./WSL/SwitchCase.js", |
| "./WSL/SwitchStatement.js", |
| "./WSL/SynthesizeEnumFunctions.js", |
| "./WSL/SynthesizeStructAccessors.js", |
| "./WSL/TrapStatement.js", |
| "./WSL/TypeDef.js", |
| "./WSL/TypeDefResolver.js", |
| "./WSL/TypeOrVariableRef.js", |
| "./WSL/TypeParameterRewriter.js", |
| "./WSL/TypeRef.js", |
| "./WSL/TypeVariable.js", |
| "./WSL/TypeVariableTracker.js", |
| "./WSL/TypedValue.js", |
| "./WSL/UintLiteral.js", |
| "./WSL/UintLiteralType.js", |
| "./WSL/UnificationContext.js", |
| "./WSL/UnreachableCodeChecker.js", |
| "./WSL/VariableDecl.js", |
| "./WSL/VariableRef.js", |
| "./WSL/VisitingSet.js", |
| "./WSL/WSyntaxError.js", |
| "./WSL/WTrapError.js", |
| "./WSL/WTypeError.js", |
| "./WSL/WhileLoop.js", |
| "./WSL/WrapChecker.js", |
| "./WSL/Test.js", |
| ], |
| tags: ["Default", "WSL"], |
| }), |
| // 8bitbench |
| new WasmEMCCBenchmark({ |
| name: "8bitbench-wasm", |
| files: [ |
| "./polyfills/fast-text-encoding/1.0.3/text.js", |
| "./8bitbench/build/rust/pkg/emu_bench.js", |
| "./8bitbench/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./8bitbench/build/rust/pkg/emu_bench_bg.wasm", |
| romBinary: "./8bitbench/build/assets/program.bin", |
| }, |
| iterations: 15, |
| worstCaseCount: 2, |
| tags: ["Default", "Wasm"], |
| }), |
| // zlib-wasm |
| new WasmEMCCBenchmark({ |
| name: "zlib-wasm", |
| files: [ |
| "./wasm/zlib/build/zlib.js", |
| "./wasm/zlib/benchmark.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/zlib/build/zlib.wasm", |
| }, |
| iterations: 40, |
| tags: ["Default", "Wasm"], |
| }), |
| // .NET |
| new AsyncBenchmark({ |
| name: "dotnet-interp-wasm", |
| files: [ |
| "./wasm/dotnet/interp.js", |
| "./wasm/dotnet/benchmark.js", |
| ], |
| preload: dotnetPreloads("interp"), |
| iterations: 10, |
| worstCaseCount: 2, |
| tags: ["Default", "Wasm", "dotnet"], |
| }), |
| new AsyncBenchmark({ |
| name: "dotnet-aot-wasm", |
| files: [ |
| "./wasm/dotnet/aot.js", |
| "./wasm/dotnet/benchmark.js", |
| ], |
| preload: dotnetPreloads("aot"), |
| iterations: 15, |
| worstCaseCount: 2, |
| tags: ["Default", "Wasm", "dotnet"], |
| }), |
| // J2CL |
| new AsyncBenchmark({ |
| name: "j2cl-box2d-wasm", |
| files: [ |
| "./wasm/j2cl-box2d/benchmark.js", |
| "./wasm/j2cl-box2d/build/Box2dBenchmark_j2wasm_entry.js", |
| ], |
| preload: { |
| wasmBinary: "./wasm/j2cl-box2d/build/Box2dBenchmark_j2wasm_binary.wasm", |
| }, |
| iterations: 40, |
| tags: ["Default", "Wasm"], |
| }), |
| ]; |
| |
| |
| const INTL_TESTS = [ |
| "DateTimeFormat", |
| "ListFormat", |
| "RelativeTimeFormat", |
| "NumberFormat", |
| "PluralRules", |
| ]; |
| const INTL_BENCHMARKS = []; |
| for (const test of INTL_TESTS) { |
| const benchmark = new AsyncBenchmark({ |
| name: `${test}-intl`, |
| files: [ |
| "./intl/src/helper.js", |
| `./intl/src/${test}.js`, |
| "./intl/benchmark.js", |
| ], |
| iterations: 2, |
| worstCaseCount: 1, |
| deterministicRandom: true, |
| tags: ["Javascript", "intl"], |
| }); |
| INTL_BENCHMARKS.push(benchmark); |
| } |
| BENCHMARKS.push( |
| new GroupedBenchmark({ |
| name: "intl", |
| tags: ["Javascript", "intl"], |
| }, INTL_BENCHMARKS)); |
| |
| |
| |
| // SunSpider tests |
| const SUNSPIDER_TESTS = [ |
| "3d-cube", |
| "3d-raytrace", |
| "base64", |
| "crypto-aes", |
| "crypto-md5", |
| "crypto-sha1", |
| "date-format-tofte", |
| "date-format-xparb", |
| "n-body", |
| "regex-dna", |
| "string-unpack-code", |
| "tagcloud", |
| ]; |
| let SUNSPIDER_BENCHMARKS = []; |
| for (const test of SUNSPIDER_TESTS) { |
| SUNSPIDER_BENCHMARKS.push(new DefaultBenchmark({ |
| name: `${test}-SP`, |
| files: [ |
| `./SunSpider/${test}.js` |
| ], |
| tags: [], |
| })); |
| } |
| BENCHMARKS.push(new GroupedBenchmark({ |
| name: "Sunspider", |
| tags: ["Default", "SunSpider"], |
| }, SUNSPIDER_BENCHMARKS)) |
| |
| // WTB (Web Tooling Benchmark) tests |
| const WTB_TESTS = { |
| "acorn": true, |
| "babel": true, |
| "babel-minify": true, |
| "babylon": true, |
| "chai": true, |
| "espree": true, |
| "esprima-next": true, |
| // Disabled: Converting ES5 code to ES6+ is no longer a realistic scenario. |
| "lebab": false, |
| "postcss": true, |
| "prettier": true, |
| "source-map": true, |
| }; |
| const WPT_FILES = [ |
| "angular-material-20.1.6.css", |
| "backbone-1.6.1.js", |
| "bootstrap-5.3.7.css", |
| "foundation-6.9.0.css", |
| "jquery-3.7.1.js", |
| "lodash.core-4.17.21.js", |
| "lodash-4.17.4.min.js.map", |
| "mootools-core-1.6.0.js", |
| "preact-8.2.5.js", |
| "preact-10.27.1.min.module.js.map", |
| "redux-5.0.1.min.js", |
| "redux-5.0.1.esm.js", |
| "source-map.min-0.5.7.js.map", |
| "source-map/lib/mappings.wasm", |
| "speedometer-es2015-test-2.0.js", |
| "todomvc/react/app.jsx", |
| "todomvc/react/footer.jsx", |
| "todomvc/react/todoItem.jsx", |
| "todomvc/typescript-angular.ts", |
| "underscore-1.13.7.js", |
| "underscore-1.13.7.min.js.map", |
| "vue-3.5.18.runtime.esm-browser.js", |
| ].reduce((acc, file) => { |
| acc[file] = `./web-tooling-benchmark/third_party/${file}`; |
| return acc |
| }, Object.create(null)); |
| |
| |
| for (const [name, enabled] of Object.entries(WTB_TESTS)) { |
| const tags = ["WTB"]; |
| if (enabled) |
| tags.push("Default"); |
| BENCHMARKS.push(new AsyncBenchmark({ |
| name: `${name}-wtb`, |
| files: [ |
| `./web-tooling-benchmark/dist/${name}.bundle.js`, |
| "./web-tooling-benchmark/benchmark.js", |
| ], |
| preload: { |
| BUNDLE: `./web-tooling-benchmark/dist/${name}.bundle.js`, |
| ...WPT_FILES, |
| }, |
| iterations: 15, |
| worstCaseCount: 2, |
| allowUtf16: true, |
| tags: tags, |
| })); |
| } |
| |
| |
| const benchmarksByName = new Map(); |
| const benchmarksByTag = new Map(); |
| |
| for (const benchmark of BENCHMARKS) { |
| const name = benchmark.name.toLowerCase(); |
| |
| if (benchmarksByName.has(name)) |
| throw new Error(`Duplicate benchmark with name "${name}}"`); |
| else |
| benchmarksByName.set(name, benchmark); |
| |
| for (const tag of benchmark.tags) { |
| if (benchmarksByTag.has(tag)) |
| benchmarksByTag.get(tag).push(benchmark); |
| else |
| benchmarksByTag.set(tag, [benchmark]); |
| } |
| } |
| |
| |
| function processTestList(testList) { |
| let benchmarkNames = []; |
| let benchmarks = []; |
| |
| if (testList instanceof Array) |
| benchmarkNames = testList; |
| else |
| benchmarkNames = testList.split(/[\s,]/); |
| |
| for (let name of benchmarkNames) { |
| name = name.toLowerCase(); |
| if (benchmarksByTag.has(name)) |
| benchmarks = benchmarks.concat(findBenchmarksByTag(name)); |
| else |
| benchmarks.push(findBenchmarkByName(name)); |
| } |
| return benchmarks; |
| } |
| |
| |
| function findBenchmarkByName(name) { |
| const benchmark = benchmarksByName.get(name.toLowerCase()); |
| |
| if (!benchmark) |
| throw new Error(`Couldn't find benchmark named "${name}"`); |
| |
| return benchmark; |
| } |
| |
| |
| function findBenchmarksByTag(tag, excludeTags) { |
| let benchmarks = benchmarksByTag.get(tag.toLowerCase()); |
| if (!benchmarks) { |
| const validTags = Array.from(benchmarksByTag.keys()).join(", "); |
| throw new Error(`Couldn't find tag named: ${tag}.\n Choices are ${validTags}`); |
| } |
| if (excludeTags) { |
| benchmarks = benchmarks.filter(benchmark => { |
| return !benchmark.hasAnyTag(...excludeTags); |
| }); |
| } |
| return benchmarks; |
| } |
| |
| |
| let benchmarks = []; |
| const defaultDisabledTags = []; |
| // FIXME: add better support to run Worker tests in shells. |
| if (!isInBrowser) |
| defaultDisabledTags.push("WorkerTests"); |
| |
| if (JetStreamParams.testList.length) { |
| benchmarks = processTestList(JetStreamParams.testList); |
| } else { |
| benchmarks = findBenchmarksByTag("Default", defaultDisabledTags) |
| } |
| |
| this.JetStream = new Driver(benchmarks); |