fix: SpeedTracker uses monotonic bytes + 15s smoothing for accurate speed display
Speed display was wildly inaccurate during parallel multipart uploads because: 1. Parallel chunk progress events caused non-monotonic byte counts 2. The 5-second smoothing window was too short for bursty uploads 3. Speed dropped to near-zero between chunk boundaries Fixes: - Enforce monotonic byte tracking via _peak guard - Extend sliding window from 5s to 15s - Cache last good speed value for display continuity - Require >= 1s of data before calculating (was 0.5s)
This commit is contained in:
parent
5113adb635
commit
2e3ac69f97
1 changed files with 16 additions and 6 deletions
|
|
@ -1135,28 +1135,38 @@ async function startUpload() {
|
|||
// Falls back to server-proxied chunked upload if presigned fails.
|
||||
// ============================================================
|
||||
// Upload speed tracker — smoothed speed + ETA
|
||||
// Uses monotonically-increasing cumulative bytes with a 15-second sliding
|
||||
// window so that parallel chunk uploads don't cause wild speed fluctuations.
|
||||
class SpeedTracker {
|
||||
constructor(totalBytes) {
|
||||
this.total = totalBytes;
|
||||
this.startTime = Date.now();
|
||||
this.samples = []; // {time, bytes}
|
||||
this.samples = []; // {time, bytes} — cumulative bytes only
|
||||
this.uploaded = 0;
|
||||
this._peak = 0; // highest cumulative value seen (monotonic guard)
|
||||
this._lastSpeed = 0; // cache last good speed for display continuity
|
||||
}
|
||||
update(bytes) {
|
||||
// Enforce monotonic: with parallel chunks, in-flight progress can
|
||||
// temporarily report a lower total when a new chunk starts from 0.
|
||||
if (bytes < this._peak) return;
|
||||
this._peak = bytes;
|
||||
this.uploaded = bytes;
|
||||
const now = Date.now();
|
||||
this.samples.push({ time: now, bytes });
|
||||
// Keep last 5 seconds of samples for smoothing
|
||||
const cutoff = now - 5000;
|
||||
// 15-second sliding window — long enough to smooth out inter-chunk gaps
|
||||
const cutoff = now - 15000;
|
||||
this.samples = this.samples.filter(s => s.time >= cutoff);
|
||||
}
|
||||
speed() {
|
||||
if (this.samples.length < 2) return 0;
|
||||
if (this.samples.length < 2) return this._lastSpeed;
|
||||
const first = this.samples[0];
|
||||
const last = this.samples[this.samples.length - 1];
|
||||
const dt = (last.time - first.time) / 1000;
|
||||
if (dt < 0.5) return 0;
|
||||
return (last.bytes - first.bytes) / dt;
|
||||
if (dt < 1) return this._lastSpeed; // need >= 1 s of data
|
||||
const s = (last.bytes - first.bytes) / dt;
|
||||
if (s > 0) this._lastSpeed = s;
|
||||
return this._lastSpeed;
|
||||
}
|
||||
eta() {
|
||||
const s = this.speed();
|
||||
Loading…
Reference in a new issue