bench: Cloud monitoring
This commit is contained in:
223
scripts/cloud_bench_results.mjs
Normal file
223
scripts/cloud_bench_results.mjs
Normal file
@@ -0,0 +1,223 @@
|
||||
// cloud_bench_results.mjs — benchmark output parsing and result aggregation.
|
||||
//
|
||||
// Extracted from cloud_bench_orchestrate.mjs to keep the orchestrator focused
|
||||
// on provisioning and execution flow.
|
||||
|
||||
export function parseNostrBenchSections(output) {
|
||||
const lines = output.split(/\r?\n/);
|
||||
let section = null;
|
||||
const parsed = {};
|
||||
|
||||
for (const lineRaw of lines) {
|
||||
const line = lineRaw.trim();
|
||||
const header = line.match(/^==>\s+nostr-bench\s+(connect|echo|event|req)\s+/);
|
||||
if (header) {
|
||||
section = header[1];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!line.startsWith("{")) continue;
|
||||
|
||||
try {
|
||||
const json = JSON.parse(line);
|
||||
if (section) {
|
||||
parsed[section] = json;
|
||||
}
|
||||
} catch {
|
||||
// ignore noisy non-json lines
|
||||
}
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function mean(values) {
|
||||
const valid = values.filter((v) => Number.isFinite(v));
|
||||
if (valid.length === 0) return NaN;
|
||||
return valid.reduce((a, b) => a + b, 0) / valid.length;
|
||||
}
|
||||
|
||||
export function sum(values) {
|
||||
const valid = values.filter((v) => Number.isFinite(v));
|
||||
if (valid.length === 0) return NaN;
|
||||
return valid.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
export function throughputFromSection(section) {
|
||||
const elapsedMs = Number(section?.elapsed ?? NaN);
|
||||
const complete = Number(section?.message_stats?.complete ?? NaN);
|
||||
const totalBytes = Number(section?.message_stats?.size ?? NaN);
|
||||
|
||||
const cumulativeTps =
|
||||
Number.isFinite(elapsedMs) && elapsedMs > 0 && Number.isFinite(complete)
|
||||
? complete / (elapsedMs / 1000)
|
||||
: NaN;
|
||||
|
||||
const cumulativeMibs =
|
||||
Number.isFinite(elapsedMs) && elapsedMs > 0 && Number.isFinite(totalBytes)
|
||||
? totalBytes / (1024 * 1024) / (elapsedMs / 1000)
|
||||
: NaN;
|
||||
|
||||
const sampleTps = Number(section?.tps ?? NaN);
|
||||
const sampleMibs = Number(section?.size ?? NaN);
|
||||
|
||||
return {
|
||||
tps: Number.isFinite(cumulativeTps) ? cumulativeTps : sampleTps,
|
||||
mibs: Number.isFinite(cumulativeMibs) ? cumulativeMibs : sampleMibs,
|
||||
};
|
||||
}
|
||||
|
||||
export function metricFromSections(sections) {
|
||||
const connect = sections?.connect?.connect_stats?.success_time || {};
|
||||
const echo = throughputFromSection(sections?.echo || {});
|
||||
const event = throughputFromSection(sections?.event || {});
|
||||
const req = throughputFromSection(sections?.req || {});
|
||||
|
||||
return {
|
||||
connect_avg_ms: Number(connect.avg ?? NaN),
|
||||
connect_max_ms: Number(connect.max ?? NaN),
|
||||
echo_tps: echo.tps,
|
||||
echo_mibs: echo.mibs,
|
||||
event_tps: event.tps,
|
||||
event_mibs: event.mibs,
|
||||
req_tps: req.tps,
|
||||
req_mibs: req.mibs,
|
||||
};
|
||||
}
|
||||
|
||||
export function summariseFlatResults(results) {
|
||||
const byServer = new Map();
|
||||
|
||||
for (const runEntry of results) {
|
||||
const serverName = runEntry.target;
|
||||
if (!byServer.has(serverName)) {
|
||||
byServer.set(serverName, []);
|
||||
}
|
||||
|
||||
const clientSamples = (runEntry.clients || [])
|
||||
.filter((clientResult) => clientResult.status === "ok")
|
||||
.map((clientResult) => metricFromSections(clientResult.sections || {}));
|
||||
|
||||
if (clientSamples.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byServer.get(serverName).push({
|
||||
connect_avg_ms: mean(clientSamples.map((s) => s.connect_avg_ms)),
|
||||
connect_max_ms: mean(clientSamples.map((s) => s.connect_max_ms)),
|
||||
echo_tps: sum(clientSamples.map((s) => s.echo_tps)),
|
||||
echo_mibs: sum(clientSamples.map((s) => s.echo_mibs)),
|
||||
event_tps: sum(clientSamples.map((s) => s.event_tps)),
|
||||
event_mibs: sum(clientSamples.map((s) => s.event_mibs)),
|
||||
req_tps: sum(clientSamples.map((s) => s.req_tps)),
|
||||
req_mibs: sum(clientSamples.map((s) => s.req_mibs)),
|
||||
});
|
||||
}
|
||||
|
||||
const metricKeys = [
|
||||
"connect_avg_ms",
|
||||
"connect_max_ms",
|
||||
"echo_tps",
|
||||
"echo_mibs",
|
||||
"event_tps",
|
||||
"event_mibs",
|
||||
"req_tps",
|
||||
"req_mibs",
|
||||
];
|
||||
|
||||
const out = {};
|
||||
for (const [serverName, runSamples] of byServer.entries()) {
|
||||
const summary = {};
|
||||
for (const key of metricKeys) {
|
||||
summary[key] = mean(runSamples.map((s) => s[key]));
|
||||
}
|
||||
out[serverName] = summary;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function summarisePhasedResults(results) {
|
||||
const byServer = new Map();
|
||||
|
||||
for (const entry of results) {
|
||||
if (!byServer.has(entry.target)) byServer.set(entry.target, []);
|
||||
const phases = entry.phases;
|
||||
if (!phases) continue;
|
||||
|
||||
const sample = {};
|
||||
|
||||
// connect
|
||||
const connectClients = (phases.connect?.clients || [])
|
||||
.filter((c) => c.status === "ok")
|
||||
.map((c) => metricFromSections(c.sections || {}));
|
||||
if (connectClients.length > 0) {
|
||||
sample.connect_avg_ms = mean(connectClients.map((s) => s.connect_avg_ms));
|
||||
sample.connect_max_ms = mean(connectClients.map((s) => s.connect_max_ms));
|
||||
}
|
||||
|
||||
// echo
|
||||
const echoClients = (phases.echo?.clients || [])
|
||||
.filter((c) => c.status === "ok")
|
||||
.map((c) => metricFromSections(c.sections || {}));
|
||||
if (echoClients.length > 0) {
|
||||
sample.echo_tps = sum(echoClients.map((s) => s.echo_tps));
|
||||
sample.echo_mibs = sum(echoClients.map((s) => s.echo_mibs));
|
||||
}
|
||||
|
||||
// Per-level req and event metrics
|
||||
for (const level of ["empty", "warm", "hot"]) {
|
||||
const phase = phases[level];
|
||||
if (!phase) continue;
|
||||
|
||||
const reqClients = (phase.req?.clients || [])
|
||||
.filter((c) => c.status === "ok")
|
||||
.map((c) => metricFromSections(c.sections || {}));
|
||||
if (reqClients.length > 0) {
|
||||
sample[`req_${level}_tps`] = sum(reqClients.map((s) => s.req_tps));
|
||||
sample[`req_${level}_mibs`] = sum(reqClients.map((s) => s.req_mibs));
|
||||
}
|
||||
|
||||
const eventClients = (phase.event?.clients || [])
|
||||
.filter((c) => c.status === "ok")
|
||||
.map((c) => metricFromSections(c.sections || {}));
|
||||
if (eventClients.length > 0) {
|
||||
sample[`event_${level}_tps`] = sum(eventClients.map((s) => s.event_tps));
|
||||
sample[`event_${level}_mibs`] = sum(eventClients.map((s) => s.event_mibs));
|
||||
}
|
||||
}
|
||||
|
||||
byServer.get(entry.target).push(sample);
|
||||
}
|
||||
|
||||
const out = {};
|
||||
for (const [name, samples] of byServer.entries()) {
|
||||
if (samples.length === 0) continue;
|
||||
const allKeys = new Set(samples.flatMap((s) => Object.keys(s)));
|
||||
const summary = {};
|
||||
for (const key of allKeys) {
|
||||
summary[key] = mean(samples.map((s) => s[key]).filter((v) => v !== undefined));
|
||||
}
|
||||
out[name] = summary;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function summariseServersFromResults(results) {
|
||||
const isPhased = results.some((r) => r.mode === "phased");
|
||||
return isPhased ? summarisePhasedResults(results) : summariseFlatResults(results);
|
||||
}
|
||||
|
||||
// Count events successfully written by event benchmarks across all clients.
|
||||
export function countEventsWritten(clientResults) {
|
||||
let total = 0;
|
||||
for (const cr of clientResults) {
|
||||
if (cr.status !== "ok") continue;
|
||||
const eventSection = cr.sections?.event;
|
||||
if (eventSection?.message_stats?.complete) {
|
||||
total += Number(eventSection.message_stats.complete) || 0;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
Reference in New Issue
Block a user