@push.rocks/smartproxy 19.6.11 → 19.6.12
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.
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +2 -0
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +52 -99
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +0 -5
- package/package.json +1 -1
- package/readme.hints.md +14 -9
- package/ts/proxies/smart-proxy/metrics-collector.ts +55 -105
- package/ts/proxies/smart-proxy/models/metrics-types.ts +2 -8
|
@@ -6,6 +6,8 @@ import type { IMetrics, IThroughputData, IThroughputHistoryPoint } from './model
|
|
|
6
6
|
export declare class MetricsCollector implements IMetrics {
|
|
7
7
|
private smartProxy;
|
|
8
8
|
private throughputTracker;
|
|
9
|
+
private routeThroughputTrackers;
|
|
10
|
+
private ipThroughputTrackers;
|
|
9
11
|
private requestTimestamps;
|
|
10
12
|
private totalRequests;
|
|
11
13
|
private connectionByteTrackers;
|
|
@@ -7,6 +7,8 @@ import { logger } from '../../core/utils/logger.js';
|
|
|
7
7
|
export class MetricsCollector {
|
|
8
8
|
constructor(smartProxy, config) {
|
|
9
9
|
this.smartProxy = smartProxy;
|
|
10
|
+
this.routeThroughputTrackers = new Map();
|
|
11
|
+
this.ipThroughputTrackers = new Map();
|
|
10
12
|
// Request tracking
|
|
11
13
|
this.requestTimestamps = [];
|
|
12
14
|
this.totalRequests = 0;
|
|
@@ -71,98 +73,26 @@ export class MetricsCollector {
|
|
|
71
73
|
history: (seconds) => {
|
|
72
74
|
return this.throughputTracker.getHistory(seconds);
|
|
73
75
|
},
|
|
74
|
-
byRoute: (windowSeconds =
|
|
76
|
+
byRoute: (windowSeconds = 1) => {
|
|
75
77
|
const routeThroughput = new Map();
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Only include connections that were active within the window
|
|
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;
|
|
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);
|
|
78
|
+
// Get throughput from each route's dedicated tracker
|
|
79
|
+
for (const [route, tracker] of this.routeThroughputTrackers) {
|
|
80
|
+
const rate = tracker.getRate(windowSeconds);
|
|
81
|
+
if (rate.in > 0 || rate.out > 0) {
|
|
82
|
+
routeThroughput.set(route, rate);
|
|
110
83
|
}
|
|
111
84
|
}
|
|
112
|
-
// Convert to rates (bytes per second)
|
|
113
|
-
for (const [route, data] of routeData) {
|
|
114
|
-
routeThroughput.set(route, {
|
|
115
|
-
in: Math.round(data.bytesIn / windowSeconds),
|
|
116
|
-
out: Math.round(data.bytesOut / windowSeconds)
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
85
|
return routeThroughput;
|
|
120
86
|
},
|
|
121
|
-
byIP: (windowSeconds =
|
|
87
|
+
byIP: (windowSeconds = 1) => {
|
|
122
88
|
const ipThroughput = new Map();
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Only include connections that were active within the window
|
|
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;
|
|
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);
|
|
89
|
+
// Get throughput from each IP's dedicated tracker
|
|
90
|
+
for (const [ip, tracker] of this.ipThroughputTrackers) {
|
|
91
|
+
const rate = tracker.getRate(windowSeconds);
|
|
92
|
+
if (rate.in > 0 || rate.out > 0) {
|
|
93
|
+
ipThroughput.set(ip, rate);
|
|
157
94
|
}
|
|
158
95
|
}
|
|
159
|
-
// Convert to rates (bytes per second)
|
|
160
|
-
for (const [ip, data] of ipData) {
|
|
161
|
-
ipThroughput.set(ip, {
|
|
162
|
-
in: Math.round(data.bytesIn / windowSeconds),
|
|
163
|
-
out: Math.round(data.bytesOut / windowSeconds)
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
96
|
return ipThroughput;
|
|
167
97
|
}
|
|
168
98
|
};
|
|
@@ -245,8 +175,7 @@ export class MetricsCollector {
|
|
|
245
175
|
bytesIn: 0,
|
|
246
176
|
bytesOut: 0,
|
|
247
177
|
startTime: now,
|
|
248
|
-
lastUpdate: now
|
|
249
|
-
windowSnapshots: [] // Initialize empty snapshots array
|
|
178
|
+
lastUpdate: now
|
|
250
179
|
});
|
|
251
180
|
// Cleanup old request timestamps
|
|
252
181
|
if (this.requestTimestamps.length > 5000) {
|
|
@@ -271,19 +200,20 @@ export class MetricsCollector {
|
|
|
271
200
|
tracker.bytesIn += bytesIn;
|
|
272
201
|
tracker.bytesOut += bytesOut;
|
|
273
202
|
tracker.lastUpdate = Date.now();
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
203
|
+
// Update per-route throughput tracker
|
|
204
|
+
let routeTracker = this.routeThroughputTrackers.get(tracker.routeName);
|
|
205
|
+
if (!routeTracker) {
|
|
206
|
+
routeTracker = new ThroughputTracker(this.retentionSeconds);
|
|
207
|
+
this.routeThroughputTrackers.set(tracker.routeName, routeTracker);
|
|
208
|
+
}
|
|
209
|
+
routeTracker.recordBytes(bytesIn, bytesOut);
|
|
210
|
+
// Update per-IP throughput tracker
|
|
211
|
+
let ipTracker = this.ipThroughputTrackers.get(tracker.remoteIP);
|
|
212
|
+
if (!ipTracker) {
|
|
213
|
+
ipTracker = new ThroughputTracker(this.retentionSeconds);
|
|
214
|
+
this.ipThroughputTrackers.set(tracker.remoteIP, ipTracker);
|
|
277
215
|
}
|
|
278
|
-
|
|
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);
|
|
216
|
+
ipTracker.recordBytes(bytesIn, bytesOut);
|
|
287
217
|
}
|
|
288
218
|
}
|
|
289
219
|
/**
|
|
@@ -301,7 +231,16 @@ export class MetricsCollector {
|
|
|
301
231
|
}
|
|
302
232
|
// Start periodic sampling
|
|
303
233
|
this.samplingInterval = setInterval(() => {
|
|
234
|
+
// Sample global throughput
|
|
304
235
|
this.throughputTracker.takeSample();
|
|
236
|
+
// Sample per-route throughput
|
|
237
|
+
for (const [_, tracker] of this.routeThroughputTrackers) {
|
|
238
|
+
tracker.takeSample();
|
|
239
|
+
}
|
|
240
|
+
// Sample per-IP throughput
|
|
241
|
+
for (const [_, tracker] of this.ipThroughputTrackers) {
|
|
242
|
+
tracker.takeSample();
|
|
243
|
+
}
|
|
305
244
|
// Clean up old connection trackers (connections closed more than 5 minutes ago)
|
|
306
245
|
const cutoff = Date.now() - 300000;
|
|
307
246
|
for (const [id, tracker] of this.connectionByteTrackers) {
|
|
@@ -309,6 +248,20 @@ export class MetricsCollector {
|
|
|
309
248
|
this.connectionByteTrackers.delete(id);
|
|
310
249
|
}
|
|
311
250
|
}
|
|
251
|
+
// Clean up unused route trackers
|
|
252
|
+
const activeRoutes = new Set(Array.from(this.connectionByteTrackers.values()).map(t => t.routeName));
|
|
253
|
+
for (const [route, _] of this.routeThroughputTrackers) {
|
|
254
|
+
if (!activeRoutes.has(route)) {
|
|
255
|
+
this.routeThroughputTrackers.delete(route);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Clean up unused IP trackers
|
|
259
|
+
const activeIPs = new Set(Array.from(this.connectionByteTrackers.values()).map(t => t.remoteIP));
|
|
260
|
+
for (const [ip, _] of this.ipThroughputTrackers) {
|
|
261
|
+
if (!activeIPs.has(ip)) {
|
|
262
|
+
this.ipThroughputTrackers.delete(ip);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
312
265
|
}, this.sampleIntervalMs);
|
|
313
266
|
// Subscribe to new connections
|
|
314
267
|
this.connectionSubscription = this.smartProxy.routeConnectionHandler.newConnectionSubject.subscribe({
|
|
@@ -354,4 +307,4 @@ export class MetricsCollector {
|
|
|
354
307
|
this.stop();
|
|
355
308
|
}
|
|
356
309
|
}
|
|
357
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy1jb2xsZWN0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L21ldHJpY3MtY29sbGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFRNUMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXBEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGdCQUFnQjtJQW1CM0IsWUFDVSxVQUFzQixFQUM5QixNQUdDO1FBSk8sZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQWhCaEMsbUJBQW1CO1FBQ1gsc0JBQWlCLEdBQWEsRUFBRSxDQUFDO1FBQ2pDLGtCQUFhLEdBQVcsQ0FBQyxDQUFDO1FBRWxDLG9EQUFvRDtRQUM1QywyQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBd0IsQ0FBQztRQXNCakUsb0NBQW9DO1FBQzdCLGdCQUFXLEdBQUc7WUFDbkIsTUFBTSxFQUFFLEdBQVcsRUFBRTtnQkFDbkIsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLGtCQUFrQixFQUFFLENBQUM7WUFDaEUsQ0FBQztZQUVELEtBQUssRUFBRSxHQUFXLEVBQUU7Z0JBQ2xCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsaUJBQWlCLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEUsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO2dCQUVuRSxLQUFLLE1BQU0sTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQkFDcEMsS0FBSyxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBQ2xDLENBQUM7Z0JBRUQs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
|
|
310
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy1jb2xsZWN0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L21ldHJpY3MtY29sbGVjdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFRNUMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDNUQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXBEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGdCQUFnQjtJQXFCM0IsWUFDVSxVQUFzQixFQUM5QixNQUdDO1FBSk8sZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQW5CeEIsNEJBQXVCLEdBQUcsSUFBSSxHQUFHLEVBQTZCLENBQUM7UUFDL0QseUJBQW9CLEdBQUcsSUFBSSxHQUFHLEVBQTZCLENBQUM7UUFFcEUsbUJBQW1CO1FBQ1gsc0JBQWlCLEdBQWEsRUFBRSxDQUFDO1FBQ2pDLGtCQUFhLEdBQVcsQ0FBQyxDQUFDO1FBRWxDLG9EQUFvRDtRQUM1QywyQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBd0IsQ0FBQztRQXNCakUsb0NBQW9DO1FBQzdCLGdCQUFXLEdBQUc7WUFDbkIsTUFBTSxFQUFFLEdBQVcsRUFBRTtnQkFDbkIsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDLGtCQUFrQixFQUFFLENBQUM7WUFDaEUsQ0FBQztZQUVELEtBQUssRUFBRSxHQUFXLEVBQUU7Z0JBQ2xCLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsaUJBQWlCLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEUsSUFBSSxLQUFLLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO2dCQUVuRSxLQUFLLE1BQU0sTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLEVB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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "19.6.
|
|
3
|
+
"version": "19.6.12",
|
|
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
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
- Hour 2: 2GB total / 60s = 34 MB/s ✗ (appears doubled!)
|
|
12
12
|
- Hour 3: 3GB total / 60s = 50 MB/s ✗ (keeps rising!)
|
|
13
13
|
|
|
14
|
-
**Solution**: Implemented
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
14
|
+
**Solution**: Implemented dedicated ThroughputTracker instances for each route and IP address:
|
|
15
|
+
- Each route and IP gets its own throughput tracker with per-second sampling
|
|
16
|
+
- Samples are taken every second and stored in a circular buffer
|
|
17
|
+
- Rate calculations use actual samples within the requested window
|
|
18
|
+
- Default window is now 1 second for real-time accuracy
|
|
18
19
|
|
|
19
20
|
### What Gets Counted (Network Interface Throughput)
|
|
20
21
|
|
|
@@ -53,15 +54,19 @@ The byte tracking is designed to match network interface throughput (what Unifi/
|
|
|
53
54
|
|
|
54
55
|
### Metrics Architecture
|
|
55
56
|
|
|
56
|
-
The metrics system has
|
|
57
|
+
The metrics system has multiple layers:
|
|
57
58
|
1. **Connection Records** (`record.bytesReceived/bytesSent`): Track total bytes per connection
|
|
58
|
-
2. **ThroughputTracker**: Accumulates bytes between samples for
|
|
59
|
-
3. **
|
|
59
|
+
2. **Global ThroughputTracker**: Accumulates bytes between samples for overall rate calculations
|
|
60
|
+
3. **Per-Route ThroughputTrackers**: Dedicated tracker for each route with per-second sampling
|
|
61
|
+
4. **Per-IP ThroughputTrackers**: Dedicated tracker for each IP with per-second sampling
|
|
62
|
+
5. **connectionByteTrackers**: Track cumulative bytes and metadata for active connections
|
|
60
63
|
|
|
61
64
|
Key features:
|
|
62
|
-
-
|
|
63
|
-
-
|
|
65
|
+
- All throughput trackers sample every second (1Hz)
|
|
66
|
+
- Each tracker maintains a circular buffer of samples (default: 1 hour retention)
|
|
67
|
+
- Rate calculations are accurate for any requested window (default: 1 second)
|
|
64
68
|
- All byte counting happens exactly once at the data flow point
|
|
69
|
+
- Unused route/IP trackers are automatically cleaned up when connections close
|
|
65
70
|
|
|
66
71
|
### Understanding "High" Byte Counts
|
|
67
72
|
|
|
@@ -15,6 +15,8 @@ import { logger } from '../../core/utils/logger.js';
|
|
|
15
15
|
export class MetricsCollector implements IMetrics {
|
|
16
16
|
// Throughput tracking
|
|
17
17
|
private throughputTracker: ThroughputTracker;
|
|
18
|
+
private routeThroughputTrackers = new Map<string, ThroughputTracker>();
|
|
19
|
+
private ipThroughputTrackers = new Map<string, ThroughputTracker>();
|
|
18
20
|
|
|
19
21
|
// Request tracking
|
|
20
22
|
private requestTimestamps: number[] = [];
|
|
@@ -119,109 +121,31 @@ export class MetricsCollector implements IMetrics {
|
|
|
119
121
|
return this.throughputTracker.getHistory(seconds);
|
|
120
122
|
},
|
|
121
123
|
|
|
122
|
-
byRoute: (windowSeconds: number =
|
|
124
|
+
byRoute: (windowSeconds: number = 1): Map<string, IThroughputData> => {
|
|
123
125
|
const routeThroughput = new Map<string, IThroughputData>();
|
|
124
|
-
const now = Date.now();
|
|
125
|
-
const windowStart = now - (windowSeconds * 1000);
|
|
126
|
-
|
|
127
|
-
// Aggregate bytes by route - calculate actual bytes transferred in window
|
|
128
|
-
const routeData = new Map<string, { bytesIn: number; bytesOut: number }>();
|
|
129
126
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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;
|
|
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);
|
|
127
|
+
// Get throughput from each route's dedicated tracker
|
|
128
|
+
for (const [route, tracker] of this.routeThroughputTrackers) {
|
|
129
|
+
const rate = tracker.getRate(windowSeconds);
|
|
130
|
+
if (rate.in > 0 || rate.out > 0) {
|
|
131
|
+
routeThroughput.set(route, rate);
|
|
161
132
|
}
|
|
162
133
|
}
|
|
163
134
|
|
|
164
|
-
// Convert to rates (bytes per second)
|
|
165
|
-
for (const [route, data] of routeData) {
|
|
166
|
-
routeThroughput.set(route, {
|
|
167
|
-
in: Math.round(data.bytesIn / windowSeconds),
|
|
168
|
-
out: Math.round(data.bytesOut / windowSeconds)
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
135
|
return routeThroughput;
|
|
173
136
|
},
|
|
174
137
|
|
|
175
|
-
byIP: (windowSeconds: number =
|
|
138
|
+
byIP: (windowSeconds: number = 1): Map<string, IThroughputData> => {
|
|
176
139
|
const ipThroughput = new Map<string, IThroughputData>();
|
|
177
|
-
const now = Date.now();
|
|
178
|
-
const windowStart = now - (windowSeconds * 1000);
|
|
179
140
|
|
|
180
|
-
//
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (tracker.lastUpdate > windowStart) {
|
|
186
|
-
let windowBytesIn = 0;
|
|
187
|
-
let windowBytesOut = 0;
|
|
188
|
-
|
|
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;
|
|
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);
|
|
141
|
+
// Get throughput from each IP's dedicated tracker
|
|
142
|
+
for (const [ip, tracker] of this.ipThroughputTrackers) {
|
|
143
|
+
const rate = tracker.getRate(windowSeconds);
|
|
144
|
+
if (rate.in > 0 || rate.out > 0) {
|
|
145
|
+
ipThroughput.set(ip, rate);
|
|
214
146
|
}
|
|
215
147
|
}
|
|
216
148
|
|
|
217
|
-
// Convert to rates (bytes per second)
|
|
218
|
-
for (const [ip, data] of ipData) {
|
|
219
|
-
ipThroughput.set(ip, {
|
|
220
|
-
in: Math.round(data.bytesIn / windowSeconds),
|
|
221
|
-
out: Math.round(data.bytesOut / windowSeconds)
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
149
|
return ipThroughput;
|
|
226
150
|
}
|
|
227
151
|
};
|
|
@@ -322,8 +246,7 @@ export class MetricsCollector implements IMetrics {
|
|
|
322
246
|
bytesIn: 0,
|
|
323
247
|
bytesOut: 0,
|
|
324
248
|
startTime: now,
|
|
325
|
-
lastUpdate: now
|
|
326
|
-
windowSnapshots: [] // Initialize empty snapshots array
|
|
249
|
+
lastUpdate: now
|
|
327
250
|
});
|
|
328
251
|
|
|
329
252
|
// Cleanup old request timestamps
|
|
@@ -353,21 +276,21 @@ export class MetricsCollector implements IMetrics {
|
|
|
353
276
|
tracker.bytesOut += bytesOut;
|
|
354
277
|
tracker.lastUpdate = Date.now();
|
|
355
278
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
279
|
+
// Update per-route throughput tracker
|
|
280
|
+
let routeTracker = this.routeThroughputTrackers.get(tracker.routeName);
|
|
281
|
+
if (!routeTracker) {
|
|
282
|
+
routeTracker = new ThroughputTracker(this.retentionSeconds);
|
|
283
|
+
this.routeThroughputTrackers.set(tracker.routeName, routeTracker);
|
|
359
284
|
}
|
|
285
|
+
routeTracker.recordBytes(bytesIn, bytesOut);
|
|
360
286
|
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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);
|
|
287
|
+
// Update per-IP throughput tracker
|
|
288
|
+
let ipTracker = this.ipThroughputTrackers.get(tracker.remoteIP);
|
|
289
|
+
if (!ipTracker) {
|
|
290
|
+
ipTracker = new ThroughputTracker(this.retentionSeconds);
|
|
291
|
+
this.ipThroughputTrackers.set(tracker.remoteIP, ipTracker);
|
|
292
|
+
}
|
|
293
|
+
ipTracker.recordBytes(bytesIn, bytesOut);
|
|
371
294
|
}
|
|
372
295
|
}
|
|
373
296
|
|
|
@@ -388,8 +311,19 @@ export class MetricsCollector implements IMetrics {
|
|
|
388
311
|
|
|
389
312
|
// Start periodic sampling
|
|
390
313
|
this.samplingInterval = setInterval(() => {
|
|
314
|
+
// Sample global throughput
|
|
391
315
|
this.throughputTracker.takeSample();
|
|
392
316
|
|
|
317
|
+
// Sample per-route throughput
|
|
318
|
+
for (const [_, tracker] of this.routeThroughputTrackers) {
|
|
319
|
+
tracker.takeSample();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Sample per-IP throughput
|
|
323
|
+
for (const [_, tracker] of this.ipThroughputTrackers) {
|
|
324
|
+
tracker.takeSample();
|
|
325
|
+
}
|
|
326
|
+
|
|
393
327
|
// Clean up old connection trackers (connections closed more than 5 minutes ago)
|
|
394
328
|
const cutoff = Date.now() - 300000;
|
|
395
329
|
for (const [id, tracker] of this.connectionByteTrackers) {
|
|
@@ -397,6 +331,22 @@ export class MetricsCollector implements IMetrics {
|
|
|
397
331
|
this.connectionByteTrackers.delete(id);
|
|
398
332
|
}
|
|
399
333
|
}
|
|
334
|
+
|
|
335
|
+
// Clean up unused route trackers
|
|
336
|
+
const activeRoutes = new Set(Array.from(this.connectionByteTrackers.values()).map(t => t.routeName));
|
|
337
|
+
for (const [route, _] of this.routeThroughputTrackers) {
|
|
338
|
+
if (!activeRoutes.has(route)) {
|
|
339
|
+
this.routeThroughputTrackers.delete(route);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Clean up unused IP trackers
|
|
344
|
+
const activeIPs = new Set(Array.from(this.connectionByteTrackers.values()).map(t => t.remoteIP));
|
|
345
|
+
for (const [ip, _] of this.ipThroughputTrackers) {
|
|
346
|
+
if (!activeIPs.has(ip)) {
|
|
347
|
+
this.ipThroughputTrackers.delete(ip);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
400
350
|
}, this.sampleIntervalMs);
|
|
401
351
|
|
|
402
352
|
// Subscribe to new connections
|
|
@@ -49,8 +49,8 @@ export interface IMetrics {
|
|
|
49
49
|
average(): IThroughputData; // Last 60 seconds
|
|
50
50
|
custom(seconds: number): IThroughputData;
|
|
51
51
|
history(seconds: number): Array<IThroughputHistoryPoint>;
|
|
52
|
-
byRoute(windowSeconds?: number): Map<string, IThroughputData>;
|
|
53
|
-
byIP(windowSeconds?: number): Map<string, IThroughputData>;
|
|
52
|
+
byRoute(windowSeconds?: number): Map<string, IThroughputData>; // Default: 1 second
|
|
53
|
+
byIP(windowSeconds?: number): Map<string, IThroughputData>; // Default: 1 second
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
// Request metrics
|
|
@@ -109,10 +109,4 @@ 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
|
-
}>;
|
|
118
112
|
}
|