fix(metrics): improve metrics
This commit is contained in:
144
ts/proxies/smart-proxy/throughput-tracker.ts
Normal file
144
ts/proxies/smart-proxy/throughput-tracker.ts
Normal file
@ -0,0 +1,144 @@
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user