CI / test (push) Successful in 9m54s
Ship remaining roadmap items: encrypted registry vault, peer invite/revoke, Swarm/plugins behind flags, binary streams, engine create validation, deploy rollback, schema validation, fleet/access UI, metrics, fuzz/load/soak tests, systemd packaging, and release tooling. Mark ROADMAP fully complete.
98 lines
2.8 KiB
JavaScript
98 lines
2.8 KiB
JavaScript
/**
|
|
* Formal load test: concurrent HyperDHT RPC pings against a local PeerSession.
|
|
*/
|
|
import test from 'brittle'
|
|
import DHT from 'hyperdht'
|
|
import createTestnet from 'hyperdht/testnet.js'
|
|
import ProtomuxRPC from 'protomux-rpc'
|
|
import { PeerSession } from '../server/rpc/session.js'
|
|
import { registerAllHandlers } from '../server/rpc/register.js'
|
|
import { Methods, PROTOCOL } from '../shared/protocol.js'
|
|
import { encodings } from '../shared/encodings.js'
|
|
|
|
test('load: concurrent pings complete under budget', async (t) => {
|
|
const CONCURRENCY = 20
|
|
const ROUNDS = 5
|
|
const BUDGET_MS = 15_000
|
|
|
|
process.env.PEARDOCK_BROWSE_ROOTS = process.env.PEARDOCK_BROWSE_ROOTS || '/'
|
|
|
|
const testnet = await createTestnet()
|
|
const { bootstrap } = testnet
|
|
const serverDht = new DHT({ bootstrap })
|
|
const keyPair = DHT.keyPair()
|
|
const server = serverDht.createServer()
|
|
|
|
server.on('connection', (socket) => {
|
|
const session = new PeerSession(socket, { serverPublicKey: keyPair.publicKey })
|
|
registerAllHandlers(session)
|
|
})
|
|
await server.listen(keyPair)
|
|
|
|
const clientDht = new DHT({ bootstrap })
|
|
const socket = clientDht.connect(keyPair.publicKey)
|
|
await new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(new Error('connect timeout')), 15000)
|
|
const done = () => {
|
|
clearTimeout(timer)
|
|
resolve()
|
|
}
|
|
socket.once('connect', done)
|
|
socket.once('open', done)
|
|
socket.once('error', (e) => {
|
|
clearTimeout(timer)
|
|
reject(e)
|
|
})
|
|
})
|
|
|
|
const rpc = new ProtomuxRPC(socket, {
|
|
id: keyPair.publicKey,
|
|
protocol: PROTOCOL,
|
|
...encodings,
|
|
})
|
|
|
|
const t0 = Date.now()
|
|
let ok = 0
|
|
let fail = 0
|
|
const latencies = []
|
|
|
|
for (let r = 0; r < ROUNDS; r++) {
|
|
const batch = []
|
|
for (let i = 0; i < CONCURRENCY; i++) {
|
|
batch.push(
|
|
(async () => {
|
|
const start = Date.now()
|
|
try {
|
|
const res = await rpc.request(Methods.ping, {}, encodings)
|
|
if (res?.success) {
|
|
ok += 1
|
|
latencies.push(Date.now() - start)
|
|
} else fail += 1
|
|
} catch {
|
|
fail += 1
|
|
}
|
|
})()
|
|
)
|
|
}
|
|
await Promise.all(batch)
|
|
}
|
|
|
|
const elapsed = Date.now() - t0
|
|
latencies.sort((a, b) => a - b)
|
|
const p99 = latencies[Math.min(latencies.length - 1, Math.floor(latencies.length * 0.99))] || 0
|
|
|
|
t.ok(elapsed < BUDGET_MS, `elapsed ${elapsed}ms < ${BUDGET_MS}ms`)
|
|
t.ok(ok >= CONCURRENCY * ROUNDS * 0.9, `ok=${ok} fail=${fail}`)
|
|
t.ok(p99 < 5000, `p99 ${p99}ms`)
|
|
|
|
console.log(
|
|
`[load] ok=${ok} fail=${fail} elapsed=${elapsed}ms p50=${latencies[Math.floor(latencies.length / 2)] || 0}ms p99=${p99}ms`
|
|
)
|
|
|
|
socket.destroy()
|
|
await server.close()
|
|
await serverDht.destroy()
|
|
await clientDht.destroy()
|
|
await testnet.destroy()
|
|
})
|