132 lines
4.1 KiB
JavaScript
132 lines
4.1 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|reconnect]
|
|
* ping = sequential publish-wait on persistent sockets (one-message RTT)
|
|
* flood = publish the batch then drain (queueing under burst)
|
|
* reconnect = connect, one publish, wait, close — measures handshake tax
|
|
*/
|
|
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 === "reconnect") {
|
|
const subNc = await connect({ servers, name: "lat-sub" });
|
|
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 pubNc = await connect({ servers, name: `lat-re-${i}` });
|
|
const h = headers();
|
|
h.set("t", String(process.hrtime.bigint() / 1000n));
|
|
pubNc.publish(subject, payload, { headers: h });
|
|
await pubNc.flush();
|
|
await got;
|
|
await pubNc.close();
|
|
}
|
|
await consume;
|
|
await subNc.close();
|
|
} else 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]),
|
|
}),
|
|
);
|