Source code

Revision control

Copy as Markdown

Other Tools

import { BenchmarkRunner } from "./benchmark-runner.mjs";
import { params } from "./params.mjs";
import { Suites } from "./tests.mjs";
class InteractiveBenchmarkRunner extends BenchmarkRunner {
_stepPromise = undefined;
_stepPromiseResolve = undefined;
_isRunning = false;
_isStepping = false;
constructor(suites, iterationCount) {
super(suites);
this._client = this._createClient();
if (!Number.isInteger(iterationCount) || iterationCount <= 0)
throw Error("iterationCount must be a positive integer.");
this._iterationCount = iterationCount;
}
_createClient() {
return {
willStartFirstIteration: this._start.bind(this),
willRunTest: this._testStart.bind(this),
didRunTest: this._testDone.bind(this),
didRunSuites: this._iterationDone.bind(this),
didFinishLastIteration: this._done.bind(this),
};
}
_start() {
if (this._isRunning)
throw Error("Runner was not stopped before starting;");
this._isRunning = true;
if (this._isStepping)
this._stepPromise = this._newStepPromise();
}
_step() {
if (!this._stepPromise) {
// Allow switching to stepping mid-run.
this._stepPromise = this._newStepPromise();
} else {
const resolve = this._stepPromiseResolve;
this._stepPromise = this._newStepPromise();
resolve();
}
}
_newStepPromise() {
return new Promise((resolve) => {
this._stepPromiseResolve = resolve;
});
}
_testStart(suite, test) {
test.anchor.classList.add("running");
}
async _testDone(suite, test) {
const classList = test.anchor.classList;
classList.remove("running");
classList.add("ran");
if (this._isStepping)
await this._stepPromise;
}
_iterationDone(measuredValues) {
let results = "";
for (const suiteName in measuredValues.tests) {
let suiteResults = measuredValues.tests[suiteName];
for (const testName in suiteResults.tests) {
let testResults = suiteResults.tests[testName];
for (const subtestName in testResults.tests)
results += `${suiteName} : ${testName} : ${subtestName}: ${testResults.tests[subtestName]} ms\n`;
}
results += `${suiteName} : ${suiteResults.total} ms\n`;
}
results += `Arithmetic Mean : ${measuredValues.mean} ms\n`;
results += `Geometric Mean : ${measuredValues.geomean} ms\n`;
results += `Total : ${measuredValues.total} ms\n`;
results += `Score : ${measuredValues.score} rpm\n`;
if (!results)
return;
const pre = document.createElement("pre");
document.body.appendChild(pre);
pre.textContent = results;
}
_done() {
this.isRunning = false;
}
runStep() {
this._isStepping = true;
if (!this._isRunning)
this.runMultipleIterations(this._iterationCount);
else
this._step();
}
runSuites() {
if (this._isRunning) {
if (this._isStepping) {
// Switch to continuous running only if we've been stepping.
this._isStepping = false;
this._step();
}
} else {
this._isStepping = false;
this.runMultipleIterations(this._iterationCount);
}
}
}
// Expose Suites/BenchmarkRunner for backwards compatibility
globalThis.BenchmarkRunner = InteractiveBenchmarkRunner;
function formatTestName(suiteName, testName) {
return suiteName + (testName ? `/${testName}` : "");
}
function createUIForSuites(suites, onStep, onRunSuites) {
const control = document.createElement("nav");
const ol = document.createElement("ol");
const checkboxes = [];
for (let suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
const suite = suites[suiteIndex];
const li = document.createElement("li");
const checkbox = document.createElement("input");
checkbox.id = suite.name;
checkbox.type = "checkbox";
checkbox.checked = !suite.disabled;
checkbox.onchange = () => {
suite.disabled = !checkbox.checked;
};
checkbox.onchange();
checkboxes.push(checkbox);
li.appendChild(checkbox);
var label = document.createElement("label");
label.appendChild(document.createTextNode(formatTestName(suite.name)));
li.appendChild(label);
label.htmlFor = checkbox.id;
const testList = document.createElement("ol");
for (let testIndex = 0; testIndex < suite.tests.length; testIndex++) {
const testItem = document.createElement("li");
const test = suite.tests[testIndex];
const anchor = document.createElement("a");
anchor.id = `${suite.name}-${test.name}`;
test.anchor = anchor;
anchor.appendChild(document.createTextNode(formatTestName(suite.name, test.name)));
testItem.appendChild(anchor);
testList.appendChild(testItem);
}
li.appendChild(testList);
ol.appendChild(li);
}
control.appendChild(ol);
let button = document.createElement("button");
button.textContent = "Step";
button.onclick = onStep;
control.appendChild(button);
button = document.createElement("button");
button.textContent = "Run";
button.id = "runSuites";
button.onclick = onRunSuites;
control.appendChild(button);
button = document.createElement("button");
button.textContent = "Select all";
button.onclick = () => {
for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
suites[suiteIndex].disabled = false;
checkboxes[suiteIndex].checked = true;
}
};
control.appendChild(button);
button = document.createElement("button");
button.textContent = "Unselect all";
button.onclick = () => {
for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
suites[suiteIndex].disabled = true;
checkboxes[suiteIndex].checked = false;
}
};
control.appendChild(button);
return control;
}
function startTest() {
if (params.suites.length > 0 || params.tags.length > 0)
Suites.enable(params.suites, params.tags);
const interactiveRunner = new window.BenchmarkRunner(Suites, params.iterationCount);
if (!(interactiveRunner instanceof InteractiveBenchmarkRunner))
throw Error("window.BenchmarkRunner must be a subclass of InteractiveBenchmarkRunner");
// Don't call step while step is already executing.
document.body.appendChild(createUIForSuites(Suites, interactiveRunner.runStep.bind(interactiveRunner), interactiveRunner.runSuites.bind(interactiveRunner)));
if (params.startAutomatically)
document.getElementById("runSuites").click();
}
window.addEventListener("load", startTest);