@push.rocks/smartproxy 19.6.10 → 19.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,32 +75,46 @@ export class MetricsCollector {
75
75
  const routeThroughput = new Map();
76
76
  const now = Date.now();
77
77
  const windowStart = now - (windowSeconds * 1000);
78
- // Aggregate bytes by route with proper time calculation
78
+ // Aggregate bytes by route - calculate actual bytes transferred in window
79
79
  const routeData = new Map();
80
80
  for (const [_, tracker] of this.connectionByteTrackers) {
81
81
  // Only include connections that were active within the window
82
- if (tracker.lastUpdate > windowStart || tracker.startTime > windowStart) {
83
- // Calculate the actual duration this connection was active within the window
84
- const connectionStart = Math.max(tracker.startTime, windowStart);
85
- const connectionEnd = tracker.lastUpdate;
86
- const durationInWindow = (connectionEnd - connectionStart) / 1000; // Convert to seconds
87
- if (durationInWindow > 0) {
88
- const current = routeData.get(tracker.routeName) || { bytesIn: 0, bytesOut: 0, totalDuration: 0 };
89
- current.bytesIn += tracker.bytesIn;
90
- current.bytesOut += tracker.bytesOut;
91
- current.totalDuration += durationInWindow;
92
- routeData.set(tracker.routeName, current);
82
+ if (tracker.lastUpdate > windowStart) {
83
+ let windowBytesIn = 0;
84
+ let windowBytesOut = 0;
85
+ if (tracker.windowSnapshots && tracker.windowSnapshots.length > 0) {
86
+ // Find the earliest snapshot within or just before the window
87
+ let startSnapshot = { timestamp: tracker.startTime, bytesIn: 0, bytesOut: 0 };
88
+ for (const snapshot of tracker.windowSnapshots) {
89
+ if (snapshot.timestamp <= windowStart) {
90
+ startSnapshot = snapshot;
91
+ }
92
+ else {
93
+ break;
94
+ }
95
+ }
96
+ // Calculate bytes transferred since window start
97
+ windowBytesIn = tracker.bytesIn - startSnapshot.bytesIn;
98
+ windowBytesOut = tracker.bytesOut - startSnapshot.bytesOut;
93
99
  }
100
+ else if (tracker.startTime > windowStart) {
101
+ // Connection started within window, use all its bytes
102
+ windowBytesIn = tracker.bytesIn;
103
+ windowBytesOut = tracker.bytesOut;
104
+ }
105
+ // Add to route totals
106
+ const current = routeData.get(tracker.routeName) || { bytesIn: 0, bytesOut: 0 };
107
+ current.bytesIn += windowBytesIn;
108
+ current.bytesOut += windowBytesOut;
109
+ routeData.set(tracker.routeName, current);
94
110
  }
95
111
  }
96
112
  // Convert to rates (bytes per second)
97
113
  for (const [route, data] of routeData) {
98
- if (data.totalDuration > 0) {
99
- routeThroughput.set(route, {
100
- in: Math.round(data.bytesIn / data.totalDuration),
101
- out: Math.round(data.bytesOut / data.totalDuration)
102
- });
103
- }
114
+ routeThroughput.set(route, {
115
+ in: Math.round(data.bytesIn / windowSeconds),
116
+ out: Math.round(data.bytesOut / windowSeconds)
117
+ });
104
118
  }
105
119
  return routeThroughput;
106
120
  },
@@ -108,32 +122,46 @@ export class MetricsCollector {
108
122
  const ipThroughput = new Map();
109
123
  const now = Date.now();
110
124
  const windowStart = now - (windowSeconds * 1000);
111
- // Aggregate bytes by IP with proper time calculation
125
+ // Aggregate bytes by IP - calculate actual bytes transferred in window
112
126
  const ipData = new Map();
113
127
  for (const [_, tracker] of this.connectionByteTrackers) {
114
128
  // Only include connections that were active within the window
115
- if (tracker.lastUpdate > windowStart || tracker.startTime > windowStart) {
116
- // Calculate the actual duration this connection was active within the window
117
- const connectionStart = Math.max(tracker.startTime, windowStart);
118
- const connectionEnd = tracker.lastUpdate;
119
- const durationInWindow = (connectionEnd - connectionStart) / 1000; // Convert to seconds
120
- if (durationInWindow > 0) {
121
- const current = ipData.get(tracker.remoteIP) || { bytesIn: 0, bytesOut: 0, totalDuration: 0 };
122
- current.bytesIn += tracker.bytesIn;
123
- current.bytesOut += tracker.bytesOut;
124
- current.totalDuration += durationInWindow;
125
- ipData.set(tracker.remoteIP, current);
129
+ if (tracker.lastUpdate > windowStart) {
130
+ let windowBytesIn = 0;
131
+ let windowBytesOut = 0;
132
+ if (tracker.windowSnapshots && tracker.windowSnapshots.length > 0) {
133
+ // Find the earliest snapshot within or just before the window
134
+ let startSnapshot = { timestamp: tracker.startTime, bytesIn: 0, bytesOut: 0 };
135
+ for (const snapshot of tracker.windowSnapshots) {
136
+ if (snapshot.timestamp <= windowStart) {
137
+ startSnapshot = snapshot;
138
+ }
139
+ else {
140
+ break;
141
+ }
142
+ }
143
+ // Calculate bytes transferred since window start
144
+ windowBytesIn = tracker.bytesIn - startSnapshot.bytesIn;
145
+ windowBytesOut = tracker.bytesOut - startSnapshot.bytesOut;
146
+ }
147
+ else if (tracker.startTime > windowStart) {
148
+ // Connection started within window, use all its bytes
149
+ windowBytesIn = tracker.bytesIn;
150
+ windowBytesOut = tracker.bytesOut;
126
151
  }
152
+ // Add to IP totals
153
+ const current = ipData.get(tracker.remoteIP) || { bytesIn: 0, bytesOut: 0 };
154
+ current.bytesIn += windowBytesIn;
155
+ current.bytesOut += windowBytesOut;
156
+ ipData.set(tracker.remoteIP, current);
127
157
  }
128
158
  }
129
159
  // Convert to rates (bytes per second)
130
160
  for (const [ip, data] of ipData) {
131
- if (data.totalDuration > 0) {
132
- ipThroughput.set(ip, {
133
- in: Math.round(data.bytesIn / data.totalDuration),
134
- out: Math.round(data.bytesOut / data.totalDuration)
135
- });
136
- }
161
+ ipThroughput.set(ip, {
162
+ in: Math.round(data.bytesIn / windowSeconds),
163
+ out: Math.round(data.bytesOut / windowSeconds)
164
+ });
137
165
  }
138
166
  return ipThroughput;
139
167
  }
@@ -217,7 +245,8 @@ export class MetricsCollector {
217
245
  bytesIn: 0,
218
246
  bytesOut: 0,
219
247
  startTime: now,
220
- lastUpdate: now
248
+ lastUpdate: now,
249
+ windowSnapshots: [] // Initialize empty snapshots array
221
250
  });
222
251
  // Cleanup old request timestamps
223
252
  if (this.requestTimestamps.length > 5000) {
@@ -242,6 +271,19 @@ export class MetricsCollector {
242
271
  tracker.bytesIn += bytesIn;
243
272
  tracker.bytesOut += bytesOut;
244
273
  tracker.lastUpdate = Date.now();
274
+ // Initialize snapshots array if not present
275
+ if (!tracker.windowSnapshots) {
276
+ tracker.windowSnapshots = [];
277
+ }
278
+ // Add current snapshot - we'll use these for accurate windowed calculations
279
+ tracker.windowSnapshots.push({
280
+ timestamp: Date.now(),
281
+ bytesIn: tracker.bytesIn,
282
+ bytesOut: tracker.bytesOut
283
+ });
284
+ // Keep only snapshots from last 5 minutes to prevent memory growth
285
+ const fiveMinutesAgo = Date.now() - 300000;
286
+ tracker.windowSnapshots = tracker.windowSnapshots.filter(s => s.timestamp > fiveMinutesAgo);
245
287
  }
246
288
  }
247
289
  /**
@@ -312,4 +354,4 @@ export class MetricsCollector {
312
354
  this.stop();
313
355
  }
314
356
  }
315
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy1jb2xsZWN0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L21ldHJpY3MtY29sbGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFRNUMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXBEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGdCQUFnQjtJQW1CM0IsWUFDVSxVQUFzQixFQUM5QixNQUdDO1FBSk8sZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQWhCaEMsbUJBQW1CO1FBQ1gsc0JBQWlCLEdBQWEsRUFBRSxDQUFDO1FBQ2pDLGtCQUFhLEdBQVcsQ0FBQyxDQUFDO1FBRWxDLG9EQUFvRDtRQUM1QywyQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBd0IsQ0FBQztRQXNCakUsb0NBQW9DO1FBQzdCLGdCQUFXLEdBQUc7WUFDbkIsTUFBTSxFQUFFLEdBQVcsRUFBRTtnQkFDbkIsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLGtCQUFrQixFQUFFLENBQUM7WUFDaEUsQ0FBQztZQUVELEtBQUssRUFBRSxHQUFXLEVBQUU7Z0JBQ2xCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsaUJBQWlCLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEUsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO2dCQUVuRSxLQUFLLE1BQU0sTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQkFDcEMsS0FBSyxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQ2xDLENBQUM7Z0JBRUQsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1lBRUQsT0FBTyxFQUFFLEdBQXdCLEVBQUU7Z0JBQ2pDLE1BQU0sV0FBVyxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO2dCQUM5QyxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLGNBQWMsRUFBRSxDQUFDO2dCQUV2RSxLQUFLLE1BQU0sQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLElBQUksV0FBVyxFQUFFLENBQUM7b0JBQ3RDLE1BQU0sU0FBUyxHQUFJLE1BQWMsQ0FBQyxTQUFTO3dCQUMxQixNQUFNLENBQUMsV0FBVyxFQUFFLElBQUk7d0JBQ3hCLFNBQVMsQ0FBQztvQkFFM0IsTUFBTSxPQUFPLEdBQUcsV0FBVyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7b0JBQ2hELFdBQVcsQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFFLE9BQU8sR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDMUMsQ0FBQztnQkFFRCxPQUFPLFdBQVcsQ0FBQztZQUNyQixDQUFDO1lBRUQsSUFBSSxFQUFFLEdBQXdCLEVBQUU7Z0JBQzlCLE1BQU0sUUFBUSxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO2dCQUUzQyxLQUFLLE1BQU0sQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxjQUFjLEVBQUUsRUFBRSxDQUFDO29CQUM3RSxNQUFNLEVBQUUsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDO29CQUMzQixNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQztvQkFDdEMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLEVBQUUsT0FBTyxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUNoQyxDQUFDO2dCQUVELE9BQU8sUUFBUSxDQUFDO1lBQ2xCLENBQUM7WUFFRCxNQUFNLEVBQUUsQ0FBQyxRQUFnQixFQUFFLEVBQXdDLEVBQUU7Z0JBQ25FLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQ3pDLE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLENBQUM7cUJBQ2xDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7cUJBQzNCLEtBQUssQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDO3FCQUNmLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLEtBQUssQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQztZQUMzQyxDQUFDO1NBQ0YsQ0FBQztRQUVGLG9DQUFvQztRQUM3QixlQUFVLEdBQUc7WUFDbEIsT0FBTyxFQUFFLEdBQW9CLEVBQUU7Z0JBQzdCLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMzQyxDQUFDO1lBRUQsTUFBTSxFQUFFLEdBQW9CLEVBQUU7Z0JBQzVCLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUM1QyxDQUFDO1lBRUQsT0FBTyxFQUFFLEdBQW9CLEVBQUU7Z0JBQzdCLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUM1QyxDQUFDO1lBRUQsTUFBTSxFQUFFLENBQUMsT0FBZSxFQUFtQixFQUFFO2dCQUMzQyxPQUFPLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDakQsQ0FBQztZQUVELE9BQU8sRUFBRSxDQUFDLE9BQWUsRUFBa0MsRUFBRTtnQkFDM0QsT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ3BELENBQUM7WUFFRCxPQUFPLEVBQUUsQ0FBQyxnQkFBd0IsRUFBRSxFQUFnQyxFQUFFO2dCQUNwRSxNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsRUFBMkIsQ0FBQztnQkFDM0QsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO2dCQUN2QixNQUFNLFdBQVcsR0FBRyxHQUFHLEdBQUcsQ0FBQyxhQUFhLEdBQUcsSUFBSSxDQUFDLENBQUM7Z0JBRWpELHdEQUF3RDtnQkFDeEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQXdFLENBQUM7Z0JBRWxHLEtBQUssTUFBTSxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsSUFBSSxJQUFJLENBQUMsc0JBQXNCLEVBQUUsQ0FBQztvQkFDdkQsOERBQThEO29CQUM5RCxJQUFJLE9BQU8sQ0FBQyxVQUFVLEdBQUcsV0FBVyxJQUFJLE9BQU8sQ0FBQyxTQUFTLEdBQUcsV0FBVyxFQUFFLENBQUM7d0JBQ3hFLDZFQUE2RTt3QkFDN0UsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFdBQVcsQ0FBQyxDQUFDO3dCQUNqRSxNQUFNLGFBQWEsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDO3dCQUN6QyxNQUFNLGdCQUFnQixHQUFHLENBQUMsYUFBYSxHQUFHLGVBQWUsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDLHFCQUFxQjt3QkFFeEYsSUFBSSxnQkFBZ0IsR0FBRyxDQUFDLEVBQUUsQ0FBQzs0QkFDekIsTUFBTSxPQUFPLEdBQUcsU0FBUyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDLEVBQUUsYUFBYSxFQUFFLENBQUMsRUFBRSxDQUFDOzRCQUNsRyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUM7NEJBQ25DLE9BQU8sQ0FBQyxRQUFRLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQzs0QkFDckMsT0FBTyxDQUFDLGFBQWEsSUFBSSxnQkFBZ0IsQ0FBQzs0QkFDMUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDO3dCQUM1QyxDQUFDO29CQUNILENBQUM7Z0JBQ0gsQ0FBQztnQkFFRCxzQ0FBc0M7Z0JBQ3RDLEtBQUssTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsSUFBSSxTQUFTLEVBQUUsQ0FBQztvQkFDdEMsSUFBSSxJQUFJLENBQUMsYUFBYSxHQUFHLENBQUMsRUFBRSxDQUFDO3dCQUMzQixlQUFlLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRTs0QkFDekIsRUFBRSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDOzRCQUNqRCxHQUFHLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUM7eUJBQ3BELENBQUMsQ0FBQztvQkFDTCxDQUFDO2dCQUNILENBQUM7Z0JBRUQsT0FBTyxlQUFlLENBQUM7WUFDekIsQ0FBQztZQUVELElBQUksRUFBRSxDQUFDLGdCQUF3QixFQUFFLEVBQWdDLEVBQUU7Z0JBQ2pFLE1BQU0sWUFBWSxHQUFHLElBQUksR0FBRyxFQUEyQixDQUFDO2dCQUN4RCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7Z0JBQ3ZCLE1BQU0sV0FBVyxHQUFHLEdBQUcsR0FBRyxDQUFDLGFBQWEsR0FBRyxJQUFJLENBQUMsQ0FBQztnQkFFakQscURBQXFEO2dCQUNyRCxNQUFNLE1BQU0sR0FBRyxJQUFJLEdBQUcsRUFBd0UsQ0FBQztnQkFFL0YsS0FBSyxNQUFNLENBQUMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxJQUFJLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO29CQUN2RCw4REFBOEQ7b0JBQzlELElBQUksT0FBTyxDQUFDLFVBQVUsR0FBRyxXQUFXLElBQUksT0FBTyxDQUFDLFNBQVMsR0FBRyxXQUFXLEVBQUUsQ0FBQzt3QkFDeEUsNkVBQTZFO3dCQUM3RSxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsV0FBVyxDQUFDLENBQUM7d0JBQ2pFLE1BQU0sYUFBYSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUM7d0JBQ3pDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxhQUFhLEdBQUcsZUFBZSxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUMscUJBQXFCO3dCQUV4RixJQUFJLGdCQUFnQixHQUFHLENBQUMsRUFBRSxDQUFDOzRCQUN6QixNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsUUFBUSxFQUFFLENBQUMsRUFBRSxhQUFhLEVBQUUsQ0FBQyxFQUFFLENBQUM7NEJBQzlGLE9BQU8sQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQzs0QkFDbkMsT0FBTyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDOzRCQUNyQyxPQUFPLENBQUMsYUFBYSxJQUFJLGdCQUFnQixDQUFDOzRCQUMxQyxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7d0JBQ3hDLENBQUM7b0JBQ0gsQ0FBQztnQkFDSCxDQUFDO2dCQUVELHNDQUFzQztnQkFDdEMsS0FBSyxNQUFNLENBQUMsRUFBRSxFQUFFLElBQUksQ0FBQyxJQUFJLE1BQU0sRUFBRSxDQUFDO29CQUNoQyxJQUFJLElBQUksQ0FBQyxhQUFhLEdBQUcsQ0FBQyxFQUFFLENBQUM7d0JBQzNCLFlBQVksQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFFOzRCQUNuQixFQUFFLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUM7NEJBQ2pELEdBQUcsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQzt5QkFDcEQsQ0FBQyxDQUFDO29CQUNMLENBQUM7Z0JBQ0gsQ0FBQztnQkFFRCxPQUFPLFlBQVksQ0FBQztZQUN0QixDQUFDO1NBQ0YsQ0FBQztRQUVGLGlDQUFpQztRQUMxQixhQUFRLEdBQUc7WUFDaEIsU0FBUyxFQUFFLEdBQVcsRUFBRTtnQkFDdEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO2dCQUN2QixNQUFNLFlBQVksR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDO2dCQUVoQyx1QkFBdUI7Z0JBQ3ZCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFHLEdBQUcsR0FBRyxLQUFLLENBQUMsQ0FBQztnQkFFL0UsZ0NBQWdDO2dCQUNoQyxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFHLFlBQVksQ0FBQyxDQUFDO2dCQUM5RSxPQUFPLGNBQWMsQ0FBQyxNQUFNLENBQUM7WUFDL0IsQ0FBQztZQUVELFNBQVMsRUFBRSxHQUFXLEVBQUU7Z0JBQ3RCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDdkIsTUFBTSxZQUFZLEdBQUcsR0FBRyxHQUFHLEtBQUssQ0FBQztnQkFFakMsZ0NBQWdDO2dCQUNoQyxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFHLFlBQVksQ0FBQyxDQUFDO2dCQUM5RSxPQUFPLGNBQWMsQ0FBQyxNQUFNLENBQUM7WUFDL0IsQ0FBQztZQUVELEtBQUssRUFBRSxHQUFXLEVBQUU7Z0JBQ2xCLE9BQU8sSUFBSSxDQUFDLGFBQWEsQ0FBQztZQUM1QixDQUFDO1NBQ0YsQ0FBQztRQUVGLHdCQUF3QjtRQUNqQixXQUFNLEdBQUc7WUFDZCxPQUFPLEVBQUUsR0FBVyxFQUFFO2dCQUNwQixJQUFJLEtBQUssR0FBRyxDQUFDLENBQUM7Z0JBRWQsa0NBQWtDO2dCQUNsQyxLQUFLLE1BQU0sQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxjQUFjLEVBQUUsRUFBRSxDQUFDO29CQUM3RSxLQUFLLElBQUksTUFBTSxDQUFDLGFBQWEsQ0FBQztnQkFDaEMsQ0FBQztnQkFFRCx3REFBd0Q7Z0JBRXhELE9BQU8sS0FBSyxDQUFDO1lBQ2YsQ0FBQztZQUVELFFBQVEsRUFBRSxHQUFXLEVBQUU7Z0JBQ3JCLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQztnQkFFZCxrQ0FBa0M7Z0JBQ2xDLEtBQUssTUFBTSxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLGNBQWMsRUFBRSxFQUFFLENBQUM7b0JBQzdFLEtBQUssSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDO2dCQUM1QixDQUFDO2dCQUVELHdEQUF3RDtnQkFFeEQsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1lBRUQsV0FBVyxFQUFFLEdBQVcsRUFBRTtnQkFDeEIsT0FBTyxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ2xDLENBQUM7U0FDRixDQUFDO1FBRUYsbURBQW1EO1FBQzVDLGdCQUFXLEdBQUc7WUFDbkIsa0JBQWtCLEVBQUUsR0FBOEMsRUFBRTtnQkFDbEUsMENBQTBDO2dCQUMxQyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDLEVBQUUsQ0FBQztZQUNwQyxDQUFDO1lBRUQsZ0JBQWdCLEVBQUUsR0FHaEIsRUFBRTtnQkFDRiwwQ0FBMEM7Z0JBQzFDLE9BQU87b0JBQ0wsRUFBRSxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDLEVBQUU7b0JBQzlCLEdBQUcsRUFBRSxFQUFFLEdBQUcsRUFBRSxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxFQUFFO2lCQUNoQyxDQUFDO1lBQ0osQ0FBQztTQUNGLENBQUM7UUE5T0EsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE1BQU0sRUFBRSxnQkFBZ0IsSUFBSSxJQUFJLENBQUM7UUFDekQsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE1BQU0sRUFBRSxnQkFBZ0IsSUFBSSxJQUFJLENBQUM7UUFDekQsSUFBSSxDQUFDLGlCQUFpQixHQUFHLElBQUksaUJBQWlCLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7SUFDeEUsQ0FBQztJQTZPRDs7T0FFRztJQUNJLGFBQWEsQ0FBQyxZQUFvQixFQUFFLFNBQWlCLEVBQUUsUUFBZ0I7UUFDNUUsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDakMsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBRXJCLDhDQUE4QztRQUM5QyxJQUFJLENBQUMsc0JBQXNCLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRTtZQUM1QyxZQUFZO1lBQ1osU0FBUztZQUNULFFBQVE7WUFDUixPQUFPLEVBQUUsQ0FBQztZQUNWLFFBQVEsRUFBRSxDQUFDO1lBQ1gsU0FBUyxFQUFFLEdBQUc7WUFDZCxVQUFVLEVBQUUsR0FBRztTQUNoQixDQUFDLENBQUM7UUFFSCxpQ0FBaUM7UUFDakMsSUFBSSxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxHQUFHLElBQUksRUFBRSxDQUFDO1lBQ3pDLDZEQUE2RDtZQUM3RCxNQUFNLE1BQU0sR0FBRyxHQUFHLEdBQUcsS0FBSyxDQUFDO1lBQzNCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFHLE1BQU0sQ0FBQyxDQUFDO1lBRTFFLDBEQUEwRDtZQUMxRCxJQUFJLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLEdBQUcsSUFBSSxFQUFFLENBQUM7Z0JBQ3pDLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDL0QsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxXQUFXLENBQUMsWUFBb0IsRUFBRSxPQUFlLEVBQUUsUUFBZ0I7UUFDeEUsbUNBQW1DO1FBQ25DLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRXRELHFDQUFxQztRQUNyQyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsc0JBQXNCLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQzlELElBQUksT0FBTyxFQUFFLENBQUM7WUFDWixPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQztZQUMzQixPQUFPLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQztZQUM3QixPQUFPLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNsQyxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksZ0JBQWdCLENBQUMsWUFBb0I7UUFDMUMsSUFBSSxDQUFDLHNCQUFzQixDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUNuRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLO1FBQ1YsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsc0JBQXNCLEVBQUUsQ0FBQztZQUM1QyxNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxDQUFDLENBQUM7UUFDNUUsQ0FBQztRQUVELDBCQUEwQjtRQUMxQixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsV0FBVyxDQUFDLEdBQUcsRUFBRTtZQUN2QyxJQUFJLENBQUMsaUJBQWlCLENBQUMsVUFBVSxFQUFFLENBQUM7WUFFcEMsZ0ZBQWdGO1lBQ2hGLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUM7WUFDbkMsS0FBSyxNQUFNLENBQUMsRUFBRSxFQUFFLE9BQU8sQ0FBQyxJQUFJLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO2dCQUN4RCxJQUFJLE9BQU8sQ0FBQyxVQUFVLEdBQUcsTUFBTSxFQUFFLENBQUM7b0JBQ2hDLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7Z0JBQ3pDLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQyxFQUFFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBRTFCLCtCQUErQjtRQUMvQixJQUFJLENBQUMsc0JBQXNCLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxzQkFBc0IsQ0FBQyxvQkFBb0IsQ0FBQyxTQUFTLENBQUM7WUFDbEcsSUFBSSxFQUFFLENBQUMsTUFBTSxFQUFFLEVBQUU7Z0JBQ2YsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLFdBQVcsRUFBRSxJQUFJLElBQUksU0FBUyxDQUFDO2dCQUN4RCxJQUFJLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFFMUQsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxxQkFBcUIsRUFBRSxDQUFDO29CQUNwRCxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSwyQ0FBMkMsRUFBRTt3QkFDL0QsWUFBWSxFQUFFLE1BQU0sQ0FBQyxFQUFFO3dCQUN2QixRQUFRLEVBQUUsTUFBTSxDQUFDLFFBQVE7d0JBQ3pCLFNBQVM7d0JBQ1QsU0FBUyxFQUFFLFNBQVM7cUJBQ3JCLENBQUMsQ0FBQztnQkFDTCxDQUFDO1lBQ0gsQ0FBQztZQUNELEtBQUssRUFBRSxDQUFDLEdBQUcsRUFBRSxFQUFFO2dCQUNiLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLG9EQUFvRCxFQUFFO29CQUN4RSxLQUFLLEVBQUUsR0FBRyxDQUFDLE9BQU87b0JBQ2xCLFNBQVMsRUFBRSxTQUFTO2lCQUNyQixDQUFDLENBQUM7WUFDTCxDQUFDO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztJQUM1RSxDQUFDO0lBRUQ7O09BRUc7SUFDSSxJQUFJO1FBQ1QsSUFBSSxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUMxQixhQUFhLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7WUFDckMsSUFBSSxDQUFDLGdCQUFnQixHQUFHLFNBQVMsQ0FBQztRQUNwQyxDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsc0JBQXNCLEVBQUUsQ0FBQztZQUNoQyxJQUFJLENBQUMsc0JBQXNCLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDMUMsSUFBSSxDQUFDLHNCQUFzQixHQUFHLFNBQVMsQ0FBQztRQUMxQyxDQUFDO1FBRUQsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztJQUM1RSxDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPO1FBQ1osSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO0lBQ2QsQ0FBQztDQUNGIn0=
357
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy1jb2xsZWN0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L21ldHJpY3MtY29sbGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFRNUMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXBEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGdCQUFnQjtJQW1CM0IsWUFDVSxVQUFzQixFQUM5QixNQUdDO1FBSk8sZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQWhCaEMsbUJBQW1CO1FBQ1gsc0JBQWlCLEdBQWEsRUFBRSxDQUFDO1FBQ2pDLGtCQUFhLEdBQVcsQ0FBQyxDQUFDO1FBRWxDLG9EQUFvRDtRQUM1QywyQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBd0IsQ0FBQztRQXNCakUsb0NBQW9DO1FBQzdCLGdCQUFXLEdBQUc7WUFDbkIsTUFBTSxFQUFFLEdBQVcsRUFBRTtnQkFDbkIsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLGtCQUFrQixFQUFFLENBQUM7WUFDaEUsQ0FBQztZQUVELEtBQUssRUFBRSxHQUFXLEVBQUU7Z0JBQ2xCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsaUJBQWlCLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEUsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO2dCQUVuRSxLQUFLLE1BQU0sTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQkFDcEMsS0FBSyxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQ2xDLENBQUM7Z0JBRUQs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
@@ -104,4 +104,9 @@ export interface IByteTracker {
104
104
  bytesOut: number;
105
105
  startTime: number;
106
106
  lastUpdate: number;
107
+ windowSnapshots?: Array<{
108
+ timestamp: number;
109
+ bytesIn: number;
110
+ bytesOut: number;
111
+ }>;
107
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "19.6.10",
3
+ "version": "19.6.11",
4
4
  "private": false,
5
5
  "description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
6
6
  "main": "dist_ts/index.js",
package/readme.hints.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Byte Tracking and Metrics
4
4
 
5
+ ### Throughput Drift Issue (Fixed)
6
+
7
+ **Problem**: Throughput numbers were gradually increasing over time for long-lived connections.
8
+
9
+ **Root Cause**: The `byRoute()` and `byIP()` methods were dividing cumulative total bytes (since connection start) by the window duration, causing rates to appear higher as connections aged:
10
+ - Hour 1: 1GB total / 60s = 17 MB/s ✓
11
+ - Hour 2: 2GB total / 60s = 34 MB/s ✗ (appears doubled!)
12
+ - Hour 3: 3GB total / 60s = 50 MB/s ✗ (keeps rising!)
13
+
14
+ **Solution**: Implemented snapshot-based byte tracking that calculates actual bytes transferred within each time window:
15
+ - Store periodic snapshots of byte counts with timestamps
16
+ - Calculate delta between window start and end snapshots
17
+ - Divide delta by window duration for accurate throughput
18
+
5
19
  ### What Gets Counted (Network Interface Throughput)
6
20
 
7
21
  The byte tracking is designed to match network interface throughput (what Unifi/network monitoring tools show):
@@ -41,10 +55,13 @@ The byte tracking is designed to match network interface throughput (what Unifi/
41
55
 
42
56
  The metrics system has three layers:
43
57
  1. **Connection Records** (`record.bytesReceived/bytesSent`): Track total bytes per connection
44
- 2. **ThroughputTracker**: Accumulates bytes between samples for rate calculations (bytes/second)
45
- 3. **connectionByteTrackers**: Track bytes per connection with timestamps for per-route/IP metrics
58
+ 2. **ThroughputTracker**: Accumulates bytes between samples for global rate calculations (resets each second)
59
+ 3. **connectionByteTrackers**: Track bytes per connection with snapshots for accurate windowed per-route/IP metrics
46
60
 
47
- Total byte counts come from connection records only, preventing double counting.
61
+ Key features:
62
+ - Global throughput uses sampling with accumulator reset (accurate)
63
+ - Per-route/IP throughput uses snapshots to calculate window-specific deltas (accurate)
64
+ - All byte counting happens exactly once at the data flow point
48
65
 
49
66
  ### Understanding "High" Byte Counts
50
67
 
@@ -124,35 +124,49 @@ export class MetricsCollector implements IMetrics {
124
124
  const now = Date.now();
125
125
  const windowStart = now - (windowSeconds * 1000);
126
126
 
127
- // Aggregate bytes by route with proper time calculation
128
- const routeData = new Map<string, { bytesIn: number; bytesOut: number; totalDuration: number }>();
127
+ // Aggregate bytes by route - calculate actual bytes transferred in window
128
+ const routeData = new Map<string, { bytesIn: number; bytesOut: number }>();
129
129
 
130
130
  for (const [_, tracker] of this.connectionByteTrackers) {
131
131
  // Only include connections that were active within the window
132
- if (tracker.lastUpdate > windowStart || tracker.startTime > windowStart) {
133
- // Calculate the actual duration this connection was active within the window
134
- const connectionStart = Math.max(tracker.startTime, windowStart);
135
- const connectionEnd = tracker.lastUpdate;
136
- const durationInWindow = (connectionEnd - connectionStart) / 1000; // Convert to seconds
132
+ if (tracker.lastUpdate > windowStart) {
133
+ let windowBytesIn = 0;
134
+ let windowBytesOut = 0;
137
135
 
138
- if (durationInWindow > 0) {
139
- const current = routeData.get(tracker.routeName) || { bytesIn: 0, bytesOut: 0, totalDuration: 0 };
140
- current.bytesIn += tracker.bytesIn;
141
- current.bytesOut += tracker.bytesOut;
142
- current.totalDuration += durationInWindow;
143
- routeData.set(tracker.routeName, current);
136
+ if (tracker.windowSnapshots && tracker.windowSnapshots.length > 0) {
137
+ // Find the earliest snapshot within or just before the window
138
+ let startSnapshot = { timestamp: tracker.startTime, bytesIn: 0, bytesOut: 0 };
139
+ for (const snapshot of tracker.windowSnapshots) {
140
+ if (snapshot.timestamp <= windowStart) {
141
+ startSnapshot = snapshot;
142
+ } else {
143
+ break;
144
+ }
145
+ }
146
+
147
+ // Calculate bytes transferred since window start
148
+ windowBytesIn = tracker.bytesIn - startSnapshot.bytesIn;
149
+ windowBytesOut = tracker.bytesOut - startSnapshot.bytesOut;
150
+ } else if (tracker.startTime > windowStart) {
151
+ // Connection started within window, use all its bytes
152
+ windowBytesIn = tracker.bytesIn;
153
+ windowBytesOut = tracker.bytesOut;
144
154
  }
155
+
156
+ // Add to route totals
157
+ const current = routeData.get(tracker.routeName) || { bytesIn: 0, bytesOut: 0 };
158
+ current.bytesIn += windowBytesIn;
159
+ current.bytesOut += windowBytesOut;
160
+ routeData.set(tracker.routeName, current);
145
161
  }
146
162
  }
147
163
 
148
164
  // Convert to rates (bytes per second)
149
165
  for (const [route, data] of routeData) {
150
- if (data.totalDuration > 0) {
151
- routeThroughput.set(route, {
152
- in: Math.round(data.bytesIn / data.totalDuration),
153
- out: Math.round(data.bytesOut / data.totalDuration)
154
- });
155
- }
166
+ routeThroughput.set(route, {
167
+ in: Math.round(data.bytesIn / windowSeconds),
168
+ out: Math.round(data.bytesOut / windowSeconds)
169
+ });
156
170
  }
157
171
 
158
172
  return routeThroughput;
@@ -163,35 +177,49 @@ export class MetricsCollector implements IMetrics {
163
177
  const now = Date.now();
164
178
  const windowStart = now - (windowSeconds * 1000);
165
179
 
166
- // Aggregate bytes by IP with proper time calculation
167
- const ipData = new Map<string, { bytesIn: number; bytesOut: number; totalDuration: number }>();
180
+ // Aggregate bytes by IP - calculate actual bytes transferred in window
181
+ const ipData = new Map<string, { bytesIn: number; bytesOut: number }>();
168
182
 
169
183
  for (const [_, tracker] of this.connectionByteTrackers) {
170
184
  // Only include connections that were active within the window
171
- if (tracker.lastUpdate > windowStart || tracker.startTime > windowStart) {
172
- // Calculate the actual duration this connection was active within the window
173
- const connectionStart = Math.max(tracker.startTime, windowStart);
174
- const connectionEnd = tracker.lastUpdate;
175
- const durationInWindow = (connectionEnd - connectionStart) / 1000; // Convert to seconds
185
+ if (tracker.lastUpdate > windowStart) {
186
+ let windowBytesIn = 0;
187
+ let windowBytesOut = 0;
176
188
 
177
- if (durationInWindow > 0) {
178
- const current = ipData.get(tracker.remoteIP) || { bytesIn: 0, bytesOut: 0, totalDuration: 0 };
179
- current.bytesIn += tracker.bytesIn;
180
- current.bytesOut += tracker.bytesOut;
181
- current.totalDuration += durationInWindow;
182
- ipData.set(tracker.remoteIP, current);
189
+ if (tracker.windowSnapshots && tracker.windowSnapshots.length > 0) {
190
+ // Find the earliest snapshot within or just before the window
191
+ let startSnapshot = { timestamp: tracker.startTime, bytesIn: 0, bytesOut: 0 };
192
+ for (const snapshot of tracker.windowSnapshots) {
193
+ if (snapshot.timestamp <= windowStart) {
194
+ startSnapshot = snapshot;
195
+ } else {
196
+ break;
197
+ }
198
+ }
199
+
200
+ // Calculate bytes transferred since window start
201
+ windowBytesIn = tracker.bytesIn - startSnapshot.bytesIn;
202
+ windowBytesOut = tracker.bytesOut - startSnapshot.bytesOut;
203
+ } else if (tracker.startTime > windowStart) {
204
+ // Connection started within window, use all its bytes
205
+ windowBytesIn = tracker.bytesIn;
206
+ windowBytesOut = tracker.bytesOut;
183
207
  }
208
+
209
+ // Add to IP totals
210
+ const current = ipData.get(tracker.remoteIP) || { bytesIn: 0, bytesOut: 0 };
211
+ current.bytesIn += windowBytesIn;
212
+ current.bytesOut += windowBytesOut;
213
+ ipData.set(tracker.remoteIP, current);
184
214
  }
185
215
  }
186
216
 
187
217
  // Convert to rates (bytes per second)
188
218
  for (const [ip, data] of ipData) {
189
- if (data.totalDuration > 0) {
190
- ipThroughput.set(ip, {
191
- in: Math.round(data.bytesIn / data.totalDuration),
192
- out: Math.round(data.bytesOut / data.totalDuration)
193
- });
194
- }
219
+ ipThroughput.set(ip, {
220
+ in: Math.round(data.bytesIn / windowSeconds),
221
+ out: Math.round(data.bytesOut / windowSeconds)
222
+ });
195
223
  }
196
224
 
197
225
  return ipThroughput;
@@ -294,7 +322,8 @@ export class MetricsCollector implements IMetrics {
294
322
  bytesIn: 0,
295
323
  bytesOut: 0,
296
324
  startTime: now,
297
- lastUpdate: now
325
+ lastUpdate: now,
326
+ windowSnapshots: [] // Initialize empty snapshots array
298
327
  });
299
328
 
300
329
  // Cleanup old request timestamps
@@ -323,6 +352,22 @@ export class MetricsCollector implements IMetrics {
323
352
  tracker.bytesIn += bytesIn;
324
353
  tracker.bytesOut += bytesOut;
325
354
  tracker.lastUpdate = Date.now();
355
+
356
+ // Initialize snapshots array if not present
357
+ if (!tracker.windowSnapshots) {
358
+ tracker.windowSnapshots = [];
359
+ }
360
+
361
+ // Add current snapshot - we'll use these for accurate windowed calculations
362
+ tracker.windowSnapshots.push({
363
+ timestamp: Date.now(),
364
+ bytesIn: tracker.bytesIn,
365
+ bytesOut: tracker.bytesOut
366
+ });
367
+
368
+ // Keep only snapshots from last 5 minutes to prevent memory growth
369
+ const fiveMinutesAgo = Date.now() - 300000;
370
+ tracker.windowSnapshots = tracker.windowSnapshots.filter(s => s.timestamp > fiveMinutesAgo);
326
371
  }
327
372
  }
328
373
 
@@ -109,4 +109,10 @@ export interface IByteTracker {
109
109
  bytesOut: number;
110
110
  startTime: number;
111
111
  lastUpdate: number;
112
+ // Track bytes at window boundaries for rate calculation
113
+ windowSnapshots?: Array<{
114
+ timestamp: number;
115
+ bytesIn: number;
116
+ bytesOut: number;
117
+ }>;
112
118
  }