Rebuild: prefetch report samples what the connections do
Test and deploy / test (push) Successful in 44s
Test and deploy / deploy (push) Skipped

Once a second: pool connections with a command in flight, loops
fetching/writing/idle; per request GROUP time, XOVER time to the status
line and the transfer, transfers with a silence over 5 s. The old
"connections busy" (fetch time of the chunks finished in the minute)
said 59-75% while all 40 connections were in flight all the time:
dropped.

Homelab: 39-40 of 40 in flight, no idle loops; the status line comes at
once, the whole 8-35 s is the transfer, with data flowing (few gaps
over 5 s): ~3 MB per chunk at 0.1-0.4 MB/s per connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-09 22:40:50 +02:00
1 parent 8475969145
commit 4bf526ab6e
3 files changed
+109 -16

No files matched your search

+27 -3
View File
@@ -40,12 +40,26 @@ export class XoverClient {
private wake?: () => void
private failure?: Error
compressed = false
/**
* The last xoverRaw's timing, ms: until the status line came (the server
* thinking), from there to the end (the transfer), and the longest silence
* during the transfer.
*/
lastTiming = { replyMs: 0, bodyMs: 0, longestGapMs: 0 }
private measuring = false
private lastDataAt = 0
private longestGap = 0
private constructor(
private sock: net.Socket,
private timeoutMs: number
) {
sock.on('data', (chunk: Buffer) => {
if (this.measuring) {
const now = performance.now()
this.longestGap = Math.max(this.longestGap, now - this.lastDataAt)
this.lastDataAt = now
}
this.chunks.push(chunk)
this.length += chunk.length
this.wake?.()
@@ -119,12 +133,22 @@ export class XoverClient {
async xoverRaw(from: number, to: number): Promise<Buffer> {
return this.timed(async () => {
const sentAt = performance.now()
const reply = await this.command(`XOVER ${from}-${to}`)
const repliedAt = performance.now()
if (reply.startsWith('420') || reply.startsWith('423')) return deflateSync(Buffer.alloc(0))
await this.expect(reply, ['224'])
if (reply.includes('COMPRESS=GZIP')) return (await this.compressedRaw(false)).raw
// Plain replies (the relay's default): compressed off the event loop, at the fastest level.
return deflateAsync(Buffer.from(await this.plainBody()), { level: 1 })
this.measuring = true
this.lastDataAt = repliedAt
this.longestGap = 0
try {
if (reply.includes('COMPRESS=GZIP')) return (await this.compressedRaw(false)).raw
// Plain replies (the relay's default): compressed off the event loop, at the fastest level.
return deflateAsync(Buffer.from(await this.plainBody()), { level: 1 })
} finally {
this.measuring = false
this.lastTiming = { replyMs: repliedAt - sentAt, bodyMs: performance.now() - repliedAt, longestGapMs: this.longestGap }
}
})
}