144 lines
4.1 KiB
TypeScript
144 lines
4.1 KiB
TypeScript
![]() |
import type { IThroughputSample, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js';
|
||
|
|
||
|
/**
|
||
|
* Tracks throughput data using time-series sampling
|
||
|
*/
|
||
|
export class ThroughputTracker {
|
||
|
private samples: IThroughputSample[] = [];
|
||
|
private readonly maxSamples: number;
|
||
|
private accumulatedBytesIn: number = 0;
|
||
|
private accumulatedBytesOut: number = 0;
|
||
|
private lastSampleTime: number = 0;
|
||
|
|
||
|
constructor(retentionSeconds: number = 3600) {
|
||
|
// Keep samples for the retention period at 1 sample per second
|
||
|
this.maxSamples = retentionSeconds;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Record bytes transferred (called on every data transfer)
|
||
|
*/
|
||
|
public recordBytes(bytesIn: number, bytesOut: number): void {
|
||
|
this.accumulatedBytesIn += bytesIn;
|
||
|
this.accumulatedBytesOut += bytesOut;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Take a sample of accumulated bytes (called every second)
|
||
|
*/
|
||
|
public takeSample(): void {
|
||
|
const now = Date.now();
|
||
|
|
||
|
// Record accumulated bytes since last sample
|
||
|
this.samples.push({
|
||
|
timestamp: now,
|
||
|
bytesIn: this.accumulatedBytesIn,
|
||
|
bytesOut: this.accumulatedBytesOut
|
||
|
});
|
||
|
|
||
|
// Reset accumulators
|
||
|
this.accumulatedBytesIn = 0;
|
||
|
this.accumulatedBytesOut = 0;
|
||
|
this.lastSampleTime = now;
|
||
|
|
||
|
// Maintain circular buffer - remove oldest samples
|
||
|
if (this.samples.length > this.maxSamples) {
|
||
|
this.samples.shift();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get throughput rate over specified window (bytes per second)
|
||
|
*/
|
||
|
public getRate(windowSeconds: number): IThroughputData {
|
||
|
if (this.samples.length === 0) {
|
||
|
return { in: 0, out: 0 };
|
||
|
}
|
||
|
|
||
|
const now = Date.now();
|
||
|
const windowStart = now - (windowSeconds * 1000);
|
||
|
|
||
|
// Find samples within the window
|
||
|
const relevantSamples = this.samples.filter(s => s.timestamp > windowStart);
|
||
|
|
||
|
if (relevantSamples.length === 0) {
|
||
|
return { in: 0, out: 0 };
|
||
|
}
|
||
|
|
||
|
// Sum bytes in the window
|
||
|
const totalBytesIn = relevantSamples.reduce((sum, s) => sum + s.bytesIn, 0);
|
||
|
const totalBytesOut = relevantSamples.reduce((sum, s) => sum + s.bytesOut, 0);
|
||
|
|
||
|
// Calculate actual window duration (might be less than requested if not enough data)
|
||
|
const actualWindowSeconds = Math.min(
|
||
|
windowSeconds,
|
||
|
(now - relevantSamples[0].timestamp) / 1000
|
||
|
);
|
||
|
|
||
|
// Avoid division by zero
|
||
|
if (actualWindowSeconds === 0) {
|
||
|
return { in: 0, out: 0 };
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
in: Math.round(totalBytesIn / actualWindowSeconds),
|
||
|
out: Math.round(totalBytesOut / actualWindowSeconds)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get throughput history for specified duration
|
||
|
*/
|
||
|
public getHistory(durationSeconds: number): IThroughputHistoryPoint[] {
|
||
|
const now = Date.now();
|
||
|
const startTime = now - (durationSeconds * 1000);
|
||
|
|
||
|
// Filter samples within duration
|
||
|
const relevantSamples = this.samples.filter(s => s.timestamp > startTime);
|
||
|
|
||
|
// Convert to history points with per-second rates
|
||
|
const history: IThroughputHistoryPoint[] = [];
|
||
|
|
||
|
for (let i = 0; i < relevantSamples.length; i++) {
|
||
|
const sample = relevantSamples[i];
|
||
|
|
||
|
// For the first sample or samples after gaps, we can't calculate rate
|
||
|
if (i === 0 || sample.timestamp - relevantSamples[i - 1].timestamp > 2000) {
|
||
|
history.push({
|
||
|
timestamp: sample.timestamp,
|
||
|
in: sample.bytesIn,
|
||
|
out: sample.bytesOut
|
||
|
});
|
||
|
} else {
|
||
|
// Calculate rate based on time since previous sample
|
||
|
const prevSample = relevantSamples[i - 1];
|
||
|
const timeDelta = (sample.timestamp - prevSample.timestamp) / 1000;
|
||
|
|
||
|
history.push({
|
||
|
timestamp: sample.timestamp,
|
||
|
in: Math.round(sample.bytesIn / timeDelta),
|
||
|
out: Math.round(sample.bytesOut / timeDelta)
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return history;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clear all samples
|
||
|
*/
|
||
|
public clear(): void {
|
||
|
this.samples = [];
|
||
|
this.accumulatedBytesIn = 0;
|
||
|
this.accumulatedBytesOut = 0;
|
||
|
this.lastSampleTime = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get sample count for debugging
|
||
|
*/
|
||
|
public getSampleCount(): number {
|
||
|
return this.samples.length;
|
||
|
}
|
||
|
}
|