/** * Does the news server answer XOVER faster for some eras of a group than for * others? Each connection fetches chunks round-robin from several starting * points (eras) of one group, at the same time, so every era sees the same * server load; per era: seconds per command, articles/s per connection, * compressed bytes per article (LOG.md 2026-10-09 (6)). * * node --env-file=.env --import=@poppinss/ts-exec tools/xover_eras.ts ,,… [--connections 2] [--seconds 600] [--chunk 100000] */ import { unzipSync } from 'node:zlib' import { XoverClient } from '#lib/nntp/index' const [group, starts] = process.argv.slice(2) const flag = (name: string, fallback: number) => { const i = process.argv.indexOf(name) return i === -1 ? fallback : Number(process.argv[i + 1]) } const connections = flag('--connections', 2) const seconds = flag('--seconds', 600) const chunk = flag('--chunk', 100_000) const eras = starts.split(',').map((s) => ({ start: Number(s), next: Number(s), posted: '', chunks: [] as { ms: number; articles: number; bytes: number }[] })) const until = Date.now() + seconds * 1000 async function connection(index: number) { const env = process.env const client = await XoverClient.connect({ host: env.NNTP_HOST!, port: Number(env.NNTP_PORT ?? 563), user: env.NNTP_USER, password: env.NNTP_PASSWORD, secure: (env.NNTP_SECURE ?? 'true') !== 'false', compress: true, timeoutMs: 300_000, }) await client.group(group) for (let step = index; Date.now() < until; step++) { const era = eras[step % eras.length] const from = era.next era.next += chunk const at = performance.now() const raw = await client.xoverRaw(from, from + chunk - 1) const ms = performance.now() - at const text = unzipSync(raw).toString('utf8') const lines = text.split('\n').filter((l) => l.includes('\t')) if (!era.posted && lines[0]) era.posted = lines[0].split('\t')[3] ?? '' era.chunks.push({ ms, articles: lines.length, bytes: raw.length }) console.log(JSON.stringify({ connection: index, start: era.start, from, seconds: +(ms / 1000).toFixed(2), articles: lines.length, bytes: raw.length })) } client.close() } await Promise.all(Array.from({ length: connections }, (_, i) => connection(i))) for (const era of eras) { const ms = era.chunks.reduce((n, c) => n + c.ms, 0) const articles = era.chunks.reduce((n, c) => n + c.articles, 0) const bytes = era.chunks.reduce((n, c) => n + c.bytes, 0) const sorted = era.chunks.map((c) => c.ms).sort((a, b) => a - b) const median = sorted[Math.floor(sorted.length / 2)] ?? 0 console.log( `${group} from ${era.start} (${era.posted}): ${era.chunks.length} chunks, ` + `${(ms / era.chunks.length / 1000).toFixed(2)} s a chunk (median ${(median / 1000).toFixed(2)}), ` + `${Math.round(articles / (ms / 1000)).toLocaleString('en')} articles/s per connection, ${(bytes / Math.max(1, articles)).toFixed(1)} B an article` ) }