Files
Surge_by_SukkaW/Build/lib/download-stats.ts
2026-07-19 03:07:21 +08:00

144 lines
4.1 KiB
TypeScript

import picocolors from 'picocolors';
import { createPrettyBits, prettyBandwidth, prettyTraffic } from 'xbits';
export type ExternalDownloadOutcome = 'winner' | 'aborted' | 'failed';
export interface ExternalDownloadAttempt {
url: string,
kind: 'primary' | 'fallback',
outcome: ExternalDownloadOutcome,
startedAt: number,
headersAt: number | null,
bodyStartedAt: number | null,
endedAt: number,
bytes: number
}
export interface ExternalDownloadStatsSnapshot {
startedAt: number | null,
endedAt: number | null,
totalBytes: number,
usefulBytes: number,
wastedBytes: number,
winners: number,
aborted: number,
failed: number
}
function createEmptySnapshot(): ExternalDownloadStatsSnapshot {
return {
startedAt: null,
endedAt: null,
totalBytes: 0,
usefulBytes: 0,
wastedBytes: 0,
winners: 0,
aborted: 0,
failed: 0
};
}
let stats = createEmptySnapshot();
const prettyBinaryByteSpeed = createPrettyBits({
bits: false,
binary: true,
speed: true,
largeK: true
});
export function downloadTimestamp() {
return performance.timeOrigin + performance.now();
}
export function formatDownloadSpeed(bytes: number, duration: number) {
if (duration <= 0) {
return 'n/a';
}
const bytesPerSecond = bytes / duration * 1000;
return `${prettyBinaryByteSpeed(bytesPerSecond)} (${prettyBandwidth(bytesPerSecond * 8)})`;
}
function formatDuration(duration: number | null) {
return duration == null ? 'n/a' : `${duration.toFixed(1)}ms`;
}
export function recordExternalDownloadAttempt(attempt: ExternalDownloadAttempt) {
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;
if (attempt.outcome === 'winner') {
stats.winners++;
stats.usefulBytes += attempt.bytes;
} else {
stats.wastedBytes += attempt.bytes;
if (attempt.outcome === 'aborted') {
stats.aborted++;
} else {
stats.failed++;
}
}
const ttfb = attempt.headersAt == null ? null : attempt.headersAt - attempt.startedAt;
const queueWait = attempt.headersAt == null || attempt.bodyStartedAt == 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);
console.log(
picocolors.gray('[external download]'),
attempt.kind,
attempt.outcome,
prettyTraffic(attempt.bytes),
speed,
`ttfb=${formatDuration(ttfb)}`,
`queue=${formatDuration(queueWait)}`,
attempt.url
);
}
export function mergeExternalDownloadStats(snapshot: ExternalDownloadStatsSnapshot | undefined) {
if (!snapshot) {
return;
}
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.winners += snapshot.winners;
stats.aborted += snapshot.aborted;
stats.failed += snapshot.failed;
}
export function takeExternalDownloadStats() {
const snapshot = stats;
stats = createEmptySnapshot();
return snapshot;
}
export function printExternalDownloadStats() {
if (stats.startedAt == null || stats.endedAt == null) {
return;
}
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)}`,
`wall=${formatDuration(duration)}`,
`winner=${stats.winners}`,
`aborted=${stats.aborted}`,
`failed=${stats.failed}`
);
}