master-zapier-plan-draft/packages/verae-nats-cluster/scripts/latency.mjs
George Lambert fef7590c27
Some checks are pending
offline / test (push) Waiting to run
Add a NATS cluster message-speed bench (throughput and delay).
Client LXC 510 against nats-a/b/c at several core and JetStream r=3 loads.
2026-09-12 00:56:48 -04:00

105 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Pub→sub round trip through the cluster (two connections).
* Usage: NATS_URL=... node latency.mjs [count] [payloadBytes] [publishers] [ping|flood]
* ping = sequential publish-wait (one-message RTT)
* flood = publish the batch then drain (queueing under burst)
*/
import { connect, headers } from "nats";
const url = process.env.NATS_URL || "nats://10.10.10.21:4222";
const count = Number(process.argv[2] || 5000);
const size = Number(process.argv[3] || 128);
const pubs = Number(process.argv[4] || 1);
const mode = process.argv[5] || "flood";
const servers = url.split(",").map((s) => s.trim());
const subject = `bench.lat.${process.pid}`;
const payload = new Uint8Array(size);
function pct(sorted, p) {
if (!sorted.length) return 0;
const i = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
return sorted[i];
}
const samples = [];
if (mode === "ping") {
const subNc = await connect({ servers, name: "lat-sub" });
const pubNc = await connect({ servers, name: "lat-pub" });
let resolveOne = null;
const sub = subNc.subscribe(subject, { max: count });
const consume = (async () => {
for await (const m of sub) {
const sent = Number(m.headers?.get("t") || 0);
samples.push(Number(process.hrtime.bigint() / 1000n) - sent);
resolveOne?.();
}
})();
await subNc.flush();
for (let i = 0; i < count; i++) {
const got = new Promise((r) => {
resolveOne = r;
});
const h = headers();
h.set("t", String(process.hrtime.bigint() / 1000n));
pubNc.publish(subject, payload, { headers: h });
await got;
}
await consume;
await pubNc.close();
await subNc.close();
} else {
const subNc = await connect({ servers, name: "lat-sub" });
const sub = subNc.subscribe(subject, { max: count });
const done = (async () => {
for await (const m of sub) {
const sent = Number(m.headers?.get("t") || 0);
if (sent) samples.push(Number(process.hrtime.bigint() / 1000n) - sent);
}
})();
await subNc.flush();
const per = Math.ceil(count / pubs);
const publishers = [];
for (let p = 0; p < pubs; p++) {
publishers.push(
(async () => {
const nc = await connect({ servers, name: `lat-pub-${p}` });
const n = p === pubs - 1 ? count - per * (pubs - 1) : per;
for (let i = 0; i < n; i++) {
const h = headers();
h.set("t", String(process.hrtime.bigint() / 1000n));
nc.publish(subject, payload, { headers: h });
}
await nc.flush();
await nc.close();
})(),
);
}
await Promise.all(publishers);
await done;
await subNc.close();
}
samples.sort((a, b) => a - b);
const sum = samples.reduce((a, b) => a + b, 0);
const us = (n) => `${(n / 1000).toFixed(3)}ms`;
console.log(
JSON.stringify({
count: samples.length,
pubs,
size,
mode,
min_us: samples[0],
avg_us: Math.round(sum / samples.length),
p50_us: pct(samples, 50),
p90_us: pct(samples, 90),
p99_us: pct(samples, 99),
max_us: samples[samples.length - 1],
min: us(samples[0]),
avg: us(sum / samples.length),
p50: us(pct(samples, 50)),
p90: us(pct(samples, 90)),
p99: us(pct(samples, 99)),
max: us(samples[samples.length - 1]),
}),
);