anubis/web/js/bench.ts
Xe Iaso 2011b83a44
chore: port client-side JS to TypeScript (#1100)
* chore(challenge/preact): port to typescript

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(js/algorithms): port to typescript

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(js/worker): port to typescript

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(web): fix TypeScript build logic

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(web): port bench.mjs to typescript

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(web): port main.mjs to typescript

Signed-off-by: Xe Iaso <me@xeiaso.net>

* Update metadata

check-spelling run (pull_request) for Xe/use-typescript

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>

* fix(js/algorithms/fast): handle old browsers

Closes #1082

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
2025-09-11 10:03:10 -04:00

151 lines
4.7 KiB
TypeScript

import algorithms from "./algorithms";
const defaultDifficulty = 4;
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
const difficultyInput: HTMLInputElement = document.getElementById("difficulty-input") as HTMLInputElement;
const algorithmSelect: HTMLSelectElement = document.getElementById("algorithm-select") as HTMLSelectElement;
const compareSelect: HTMLSelectElement = document.getElementById("compare-select") as HTMLSelectElement;
const header: HTMLTableRowElement = document.getElementById("table-header") as HTMLTableRowElement;
const headerCompare: HTMLTableSectionElement = document.getElementById("table-header-compare") as HTMLTableSectionElement;
const results: HTMLTableRowElement = document.getElementById("results") as HTMLTableRowElement;
const setupControls = () => {
if (defaultDifficulty == null) {
return;
}
difficultyInput.value = defaultDifficulty.toString();
for (const alg of Object.keys(algorithms)) {
const option1 = document.createElement("option");
algorithmSelect?.append(option1);
const option2 = document.createElement("option");
compareSelect.append(option2);
option1.value = option1.innerText = option2.value = option2.innerText = alg;
}
};
const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
if (!(difficulty >= 1)) {
throw new Error(`Invalid difficulty: ${difficulty}`);
}
const process = algorithms[algorithm];
if (process == null) {
throw new Error(`Unknown algorithm: ${algorithm}`);
}
const rawChallenge = new Uint8Array(32);
crypto.getRandomValues(rawChallenge);
const challenge = Array.from(rawChallenge)
.map((c) => c.toString(16).padStart(2, "0"))
.join("");
const t0 = performance.now();
const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal);
const t1 = performance.now();
console.log({ hash, nonce });
stats.time += t1 - t0;
stats.iters += nonce;
return { time: t1 - t0, nonce };
};
const stats = { time: 0, iters: 0 };
const comparison = { time: 0, iters: 0 };
const updateStatus = () => {
const mainRate = stats.iters / stats.time;
const compareRate = comparison.iters / comparison.time;
if (Number.isFinite(mainRate)) {
status.innerText = `Average hashrate: ${mainRate.toFixed(3)}kH/s`;
if (Number.isFinite(compareRate)) {
const change = ((mainRate - compareRate) / mainRate) * 100;
status.innerText += ` vs ${compareRate.toFixed(3)}kH/s (${change.toFixed(2)}% change)`;
}
} else {
status.innerText = "Benchmarking...";
}
};
const tableCell = (text) => {
const td = document.createElement("td");
td.innerText = text;
td.style.padding = "0 0.25rem";
return td;
};
const benchmarkLoop = async (controller) => {
const difficulty = difficultyInput.value;
const algorithm = algorithmSelect.value;
const compareAlgorithm = compareSelect.value;
updateStatus();
try {
const { time, nonce } = await benchmarkTrial(
stats,
difficulty,
algorithm,
controller.signal,
);
const tr = document.createElement("tr");
tr.style.display = "contents";
tr.append(tableCell(`${time}ms`), tableCell(nonce));
// auto-scroll to new rows
const atBottom =
results.scrollHeight - results.clientHeight <= results.scrollTop;
results.append(tr);
if (atBottom) {
results.scrollTop = results.scrollHeight - results.clientHeight;
}
updateStatus();
if (compareAlgorithm !== "NONE") {
const { time, nonce } = await benchmarkTrial(
comparison,
difficulty,
compareAlgorithm,
controller.signal,
);
tr.append(tableCell(`${time}ms`), tableCell(nonce));
}
} catch (e) {
if (e !== false) {
status.innerText = e;
}
return;
}
await benchmarkLoop(controller);
};
let controller: AbortController | null = null;
const reset = () => {
stats.time = stats.iters = 0;
comparison.time = comparison.iters = 0;
results.innerHTML = status.innerText = "";
const table = results.parentElement as HTMLElement;
if (compareSelect.value !== "NONE") {
table.style.gridTemplateColumns = "repeat(4,auto)";
header.style.display = "none";
headerCompare.style.display = "contents";
} else {
table.style.gridTemplateColumns = "repeat(2,auto)";
header.style.display = "contents";
headerCompare.style.display = "none";
}
if (controller != null) {
controller.abort();
}
controller = new AbortController();
void benchmarkLoop(controller);
};
setupControls();
difficultyInput.addEventListener("change", reset);
algorithmSelect.addEventListener("change", reset);
compareSelect.addEventListener("change", reset);
reset();