Add scoped CLI benchmark metadata

This commit is contained in:
vorotamoroz
2026-07-09 01:47:34 +00:00
parent 7d26f0ae35
commit af72cac4e3
6 changed files with 593 additions and 118 deletions
+134 -28
View File
@@ -1,8 +1,18 @@
import { TempDir } from "./helpers/temp.ts";
import { applyRemoteSyncSettings, initSettingsFile } from "./helpers/settings.ts";
import {
applyRemoteSyncSettings,
initSettingsFile,
} from "./helpers/settings.ts";
import { assertFilesEqual, runCliOrFail } from "./helpers/cli.ts";
import { createCouchdbDatabase, startCouchdb, stopCouchdb } from "./helpers/docker.ts";
import { createDeterministicDataset, type DatasetEntry } from "./helpers/dataset.ts";
import {
createCouchdbDatabase,
startCouchdb,
stopCouchdb,
} from "./helpers/docker.ts";
import {
createDeterministicDataset,
type DatasetEntry,
} from "./helpers/dataset.ts";
type BenchmarkConfig = {
caseName: string;
@@ -26,6 +36,8 @@ type BenchmarkConfig = {
simulationTier: string;
networkProfile: string;
networkModel: string;
measurementScope: string;
limitations: string[];
};
function readEnvString(name: string, fallback: string): string {
@@ -54,6 +66,30 @@ function readEnvBool(name: string, fallback: boolean): boolean {
return /^(1|true|yes|on)$/i.test(raw.trim());
}
function readEnvStringArray(name: string, fallback: string[]): string[] {
const raw = Deno.env.get(name)?.trim();
if (!raw) {
return fallback;
}
try {
const parsed = JSON.parse(raw);
if (
Array.isArray(parsed) &&
parsed.every((item) => typeof item === "string")
) {
return parsed;
}
} catch {
// Fall through to pipe-separated parsing for hand-written invocations.
}
return raw
.split("|")
.map((item) => item.trim())
.filter((item) => item.length > 0);
}
function nowMs(): number {
return performance.now();
}
@@ -76,26 +112,57 @@ function formatBytes(value: number): string {
function buildConfig(): BenchmarkConfig {
return {
caseName: readEnvString("BENCH_CASE", "couchdb-baseline"),
couchdbBackendUri: readEnvString("BENCH_COUCHDB_BACKEND_URI", "http://127.0.0.1:5989"),
couchdbProxyUri: readEnvString("BENCH_COUCHDB_URI", "http://127.0.0.1:15989"),
couchdbUser: readEnvString("BENCH_COUCHDB_USER", readEnvString("username", "admin")),
couchdbPassword: readEnvString("BENCH_COUCHDB_PASSWORD", readEnvString("password", "password")),
couchdbDbname: readEnvString("BENCH_COUCHDB_DBNAME", `bench-couchdb-${Date.now()}`),
couchdbBackendUri: readEnvString(
"BENCH_COUCHDB_BACKEND_URI",
"http://127.0.0.1:5989",
),
couchdbProxyUri: readEnvString(
"BENCH_COUCHDB_URI",
"http://127.0.0.1:15989",
),
couchdbUser: readEnvString(
"BENCH_COUCHDB_USER",
readEnvString("username", "admin"),
),
couchdbPassword: readEnvString(
"BENCH_COUCHDB_PASSWORD",
readEnvString("password", "password"),
),
couchdbDbname: readEnvString(
"BENCH_COUCHDB_DBNAME",
`bench-couchdb-${Date.now()}`,
),
datasetDirName: readEnvString("BENCH_DATASET_DIR", "bench-dataset"),
datasetSeed: readEnvString("BENCH_SEED", "livesync-benchmark-seed"),
mdFileCount: Math.floor(readEnvNumber("BENCH_MD_FILE_COUNT", 1500)),
mdMinSizeBytes: Math.floor(readEnvNumber("BENCH_MD_MIN_SIZE_BYTES", 1024)),
mdMaxSizeBytes: Math.floor(readEnvNumber("BENCH_MD_MAX_SIZE_BYTES", 20 * 1024)),
mdMinSizeBytes: Math.floor(
readEnvNumber("BENCH_MD_MIN_SIZE_BYTES", 1024),
),
mdMaxSizeBytes: Math.floor(
readEnvNumber("BENCH_MD_MAX_SIZE_BYTES", 20 * 1024),
),
binFileCount: Math.floor(readEnvNumber("BENCH_BIN_FILE_COUNT", 500)),
binSizeBytes: Math.floor(readEnvNumber("BENCH_BIN_SIZE_BYTES", 100 * 1024)),
binSizeBytes: Math.floor(
readEnvNumber("BENCH_BIN_SIZE_BYTES", 100 * 1024),
),
syncTimeoutSeconds: readEnvNumber("BENCH_SYNC_TIMEOUT", 240),
requestedRttMs: Math.floor(readEnvNumber("BENCH_COUCHDB_RTT_MS", 50)),
passphrase: readEnvString("BENCH_PASSPHRASE", `bench-${Date.now()}`),
encrypt: readEnvBool("BENCH_ENCRYPT", true),
managedCouchdb: readEnvBool("BENCH_COUCHDB_MANAGED", true),
simulationTier: readEnvString("BENCH_SIMULATION_TIER", "1"),
networkProfile: readEnvString("BENCH_NETWORK_PROFILE", "http-latency-proxy"),
networkProfile: readEnvString(
"BENCH_NETWORK_PROFILE",
"http-latency-proxy",
),
networkModel: readEnvString("BENCH_NETWORK_MODEL", "local-http-proxy"),
measurementScope: readEnvString(
"BENCH_MEASUREMENT_SCOPE",
"Two one-shot synchronisation phases through a CouchDB-compatible remote-store path.",
),
limitations: readEnvStringArray("BENCH_LIMITATIONS_JSON", [
"This benchmark result is scoped to the configured dataset, remote store, and network model.",
]),
};
}
@@ -130,7 +197,9 @@ type ProxyHandle = {
note: string;
};
function startCouchdbProxy(options: { backendUri: string; proxyUri: string; requestedRttMs: number }): ProxyHandle {
function startCouchdbProxy(
options: { backendUri: string; proxyUri: string; requestedRttMs: number },
): ProxyHandle {
const backend = new URL(options.backendUri);
const proxy = new URL(options.proxyUri);
const halfDelayMs = Math.max(1, Math.floor(options.requestedRttMs / 2));
@@ -182,12 +251,13 @@ function startCouchdbProxy(options: { backendUri: string; proxyUri: string; requ
statusText: upstream.statusText,
headers: responseHeaders,
});
}
},
);
return {
applied: true,
note: `local reverse proxy on ${proxy.origin} with ${halfDelayMs}ms pre-forward delay`,
note:
`local reverse proxy on ${proxy.origin} with ${halfDelayMs}ms pre-forward delay`,
stop: async () => {
controller.abort();
await listener.finished.catch(() => {});
@@ -211,14 +281,21 @@ async function main(): Promise<void> {
await initSettingsFile(settingsB);
if (config.managedCouchdb) {
await startCouchdb(config.couchdbBackendUri, config.couchdbUser, config.couchdbPassword, config.couchdbDbname);
await startCouchdb(
config.couchdbBackendUri,
config.couchdbUser,
config.couchdbPassword,
config.couchdbDbname,
);
} else {
console.log(`[INFO] using externally managed CouchDB: ${config.couchdbBackendUri}`);
console.log(
`[INFO] using externally managed CouchDB: ${config.couchdbBackendUri}`,
);
await createCouchdbDatabase(
config.couchdbBackendUri,
config.couchdbUser,
config.couchdbPassword,
config.couchdbDbname
config.couchdbDbname,
);
}
@@ -275,12 +352,21 @@ async function main(): Promise<void> {
const sampleFiles = pickSampleFiles(seedFiles.entries);
for (const sample of sampleFiles) {
const pulledPath = workDir.join(`pulled-${sample.relativePath.split("/").join("_")}`);
await runCliOrFail(vaultB, "--settings", settingsB, "pull", sample.relativePath, pulledPath);
const pulledPath = workDir.join(
`pulled-${sample.relativePath.split("/").join("_")}`,
);
await runCliOrFail(
vaultB,
"--settings",
settingsB,
"pull",
sample.relativePath,
pulledPath,
);
await assertFilesEqual(
sample.absolutePath,
pulledPath,
`sample file mismatch after CouchDB sync: ${sample.relativePath}`
`sample file mismatch after CouchDB sync: ${sample.relativePath}`,
);
}
@@ -294,6 +380,8 @@ async function main(): Promise<void> {
simulationTier: config.simulationTier,
networkProfile: config.networkProfile,
networkModel: config.networkModel,
measurementScope: config.measurementScope,
limitations: config.limitations,
rttRequestedMs: config.requestedRttMs,
proxyApplied: proxy.applied,
proxyNote: proxy.note,
@@ -306,22 +394,40 @@ async function main(): Promise<void> {
mirrorElapsedMs: Number(mirrorElapsed.toFixed(1)),
syncAElapsedMs: Number(syncAElapsed.toFixed(1)),
syncBElapsedMs: Number(syncBElapsed.toFixed(1)),
totalSyncElapsedMs: Number((syncAElapsed + syncBElapsed).toFixed(1)),
throughputBytesPerSec: Number((seedFiles.totalBytes / ((syncAElapsed + syncBElapsed) / 1000)).toFixed(2)),
totalSyncElapsedMs: Number(
(syncAElapsed + syncBElapsed).toFixed(1),
),
throughputBytesPerSec: Number(
(seedFiles.totalBytes / ((syncAElapsed + syncBElapsed) / 1000))
.toFixed(
2,
),
),
throughputMiBPerSec: Number(
(seedFiles.totalBytes / ((syncAElapsed + syncBElapsed) / 1000) / 1024 / 1024).toFixed(4)
(seedFiles.totalBytes / ((syncAElapsed + syncBElapsed) / 1000) /
1024 /
1024).toFixed(4),
),
};
if (resultPath) {
await Deno.writeTextFile(resultPath, JSON.stringify(result, null, 2));
await Deno.writeTextFile(
resultPath,
JSON.stringify(result, null, 2),
);
}
console.log(JSON.stringify(result, null, 2));
console.error(
`[Benchmark] couchdb mirrored ${seedFiles.totalFiles} files (${formatBytes(seedFiles.totalBytes)}) in ${formatMs(
mirrorElapsed
)}, synced in ${formatMs(syncAElapsed + syncBElapsed)} (${result.throughputBytesPerSec} B/s, ${result.throughputMiBPerSec} MiB/s)`
`[Benchmark] couchdb mirrored ${seedFiles.totalFiles} files (${
formatBytes(seedFiles.totalBytes)
}) in ${
formatMs(
mirrorElapsed,
)
}, synced in ${
formatMs(syncAElapsed + syncBElapsed)
} (${result.throughputBytesPerSec} B/s, ${result.throughputMiBPerSec} MiB/s)`,
);
} finally {
await proxy.stop();