Chore: better download stat report

This commit is contained in:
SukkaW
2026-07-19 04:10:41 +08:00
parent 9d5d05e17c
commit 8a8b7c4f3d
4 changed files with 180 additions and 50 deletions

View File

@@ -1,5 +1,6 @@
import picocolors from 'picocolors';
import { createPrettyBits, prettyBandwidth, prettyTraffic } from 'xbits';
import { appendArrayInPlace } from 'foxts/append-array-in-place';
export type ExternalDownloadOutcome = 'winner' | 'aborted' | 'failed';
@@ -9,17 +10,25 @@ export interface ExternalDownloadAttempt {
outcome: ExternalDownloadOutcome,
startedAt: number,
headersAt: number | null,
bodyStartedAt: number | null,
decodedBodyStartedAt: number | null,
encodedBodyStartedAt: number | null,
encodedBodyEndedAt: number | null,
endedAt: number,
bytes: number
decodedBytes: number,
encodedBytes: number,
contentEncoding: string | null
}
export interface ExternalDownloadStatsSnapshot {
attempts: ExternalDownloadAttempt[],
startedAt: number | null,
endedAt: number | null,
totalBytes: number,
usefulBytes: number,
wastedBytes: number,
totalDecodedBytes: number,
usefulDecodedBytes: number,
wastedDecodedBytes: number,
totalEncodedBytes: number,
usefulEncodedBytes: number,
wastedEncodedBytes: number,
winners: number,
aborted: number,
failed: number
@@ -27,11 +36,15 @@ export interface ExternalDownloadStatsSnapshot {
function createEmptySnapshot(): ExternalDownloadStatsSnapshot {
return {
attempts: [],
startedAt: null,
endedAt: null,
totalBytes: 0,
usefulBytes: 0,
wastedBytes: 0,
totalDecodedBytes: 0,
usefulDecodedBytes: 0,
wastedDecodedBytes: 0,
totalEncodedBytes: 0,
usefulEncodedBytes: 0,
wastedEncodedBytes: 0,
winners: 0,
aborted: 0,
failed: 0
@@ -62,36 +75,60 @@ function formatDuration(duration: number | null) {
return duration == null ? 'n/a' : `${duration.toFixed(1)}ms`;
}
function formatCompressionRatio(decodedBytes: number, encodedBytes: number) {
return encodedBytes <= 0 ? 'n/a' : `${(decodedBytes / encodedBytes).toFixed(2)}x`;
}
export function recordExternalDownloadAttempt(attempt: ExternalDownloadAttempt) {
stats.attempts.push(attempt);
stats.startedAt = stats.startedAt == null ? attempt.startedAt : Math.min(stats.startedAt, attempt.startedAt);
stats.endedAt = stats.endedAt == null ? attempt.endedAt : Math.max(stats.endedAt, attempt.endedAt);
stats.totalBytes += attempt.bytes;
stats.totalDecodedBytes += attempt.decodedBytes;
stats.totalEncodedBytes += attempt.encodedBytes;
if (attempt.outcome === 'winner') {
stats.winners++;
stats.usefulBytes += attempt.bytes;
stats.usefulDecodedBytes += attempt.decodedBytes;
stats.usefulEncodedBytes += attempt.encodedBytes;
} else {
stats.wastedBytes += attempt.bytes;
stats.wastedDecodedBytes += attempt.decodedBytes;
stats.wastedEncodedBytes += attempt.encodedBytes;
if (attempt.outcome === 'aborted') {
stats.aborted++;
} else {
stats.failed++;
}
}
}
function printExternalDownloadAttempt(attempt: ExternalDownloadAttempt) {
const ttfb = attempt.headersAt == null ? null : attempt.headersAt - attempt.startedAt;
const queueWait = attempt.headersAt == null || attempt.bodyStartedAt == null
const queueWait = attempt.headersAt == null || attempt.decodedBodyStartedAt == null
? null
: attempt.bodyStartedAt - attempt.headersAt;
const bodyDuration = attempt.bodyStartedAt == null ? null : attempt.endedAt - attempt.bodyStartedAt;
const speed = bodyDuration == null ? 'n/a' : formatDownloadSpeed(attempt.bytes, bodyDuration);
: attempt.decodedBodyStartedAt - attempt.headersAt;
const encodedBodyDuration = attempt.encodedBodyStartedAt == null || attempt.encodedBodyEndedAt == null
? null
: attempt.encodedBodyEndedAt - attempt.encodedBodyStartedAt;
const decodedBodyDuration = attempt.decodedBodyStartedAt == null
? null
: attempt.endedAt - attempt.decodedBodyStartedAt;
const encodedSpeed = encodedBodyDuration == null
? 'n/a'
: formatDownloadSpeed(attempt.encodedBytes, encodedBodyDuration);
const decodedSpeed = decodedBodyDuration == null
? 'n/a'
: formatDownloadSpeed(attempt.decodedBytes, decodedBodyDuration);
console.log(
picocolors.gray('[external download]'),
attempt.kind,
attempt.outcome,
prettyTraffic(attempt.bytes),
speed,
`encoded=${prettyTraffic(attempt.encodedBytes)}`,
encodedSpeed,
`decoded=${prettyTraffic(attempt.decodedBytes)}`,
decodedSpeed,
`ratio=${formatCompressionRatio(attempt.decodedBytes, attempt.encodedBytes)}`,
`encoding=${attempt.contentEncoding ?? 'identity'}`,
`ttfb=${formatDuration(ttfb)}`,
`queue=${formatDuration(queueWait)}`,
attempt.url
@@ -103,15 +140,19 @@ export function mergeExternalDownloadStats(snapshot: ExternalDownloadStatsSnapsh
return;
}
appendArrayInPlace(stats.attempts, snapshot.attempts);
if (snapshot.startedAt != null) {
stats.startedAt = stats.startedAt == null ? snapshot.startedAt : Math.min(stats.startedAt, snapshot.startedAt);
}
if (snapshot.endedAt != null) {
stats.endedAt = stats.endedAt == null ? snapshot.endedAt : Math.max(stats.endedAt, snapshot.endedAt);
}
stats.totalBytes += snapshot.totalBytes;
stats.usefulBytes += snapshot.usefulBytes;
stats.wastedBytes += snapshot.wastedBytes;
stats.totalDecodedBytes += snapshot.totalDecodedBytes;
stats.usefulDecodedBytes += snapshot.usefulDecodedBytes;
stats.wastedDecodedBytes += snapshot.wastedDecodedBytes;
stats.totalEncodedBytes += snapshot.totalEncodedBytes;
stats.usefulEncodedBytes += snapshot.usefulEncodedBytes;
stats.wastedEncodedBytes += snapshot.wastedEncodedBytes;
stats.winners += snapshot.winners;
stats.aborted += snapshot.aborted;
stats.failed += snapshot.failed;
@@ -128,13 +169,21 @@ export function printExternalDownloadStats() {
return;
}
console.log(picocolors.bold('[external downloads]'), `attempts=${stats.attempts.length}`);
stats.attempts
.toSorted((a, b) => a.startedAt - b.startedAt)
.forEach(printExternalDownloadAttempt);
const duration = stats.endedAt - stats.startedAt;
console.log(
picocolors.bold('[external downloads total]'),
`useful=${prettyTraffic(stats.usefulBytes)}`,
`transferred=${prettyTraffic(stats.totalBytes)}`,
`hedge-waste=${prettyTraffic(stats.wastedBytes)}`,
`avg=${formatDownloadSpeed(stats.totalBytes, duration)}`,
`encoded-useful=${prettyTraffic(stats.usefulEncodedBytes)}`,
`encoded-transferred=${prettyTraffic(stats.totalEncodedBytes)}`,
`encoded-hedge-waste=${prettyTraffic(stats.wastedEncodedBytes)}`,
`encoded-avg=${formatDownloadSpeed(stats.totalEncodedBytes, duration)}`,
`decoded-useful=${prettyTraffic(stats.usefulDecodedBytes)}`,
`decoded-avg=${formatDownloadSpeed(stats.totalDecodedBytes, duration)}`,
`ratio=${formatCompressionRatio(stats.totalDecodedBytes, stats.totalEncodedBytes)}`,
`wall=${formatDuration(duration)}`,
`winner=${stats.winners}`,
`aborted=${stats.aborted}`,