@push.rocks/smartproxy 19.6.2 → 19.6.7
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/connection-manager.d.ts +4 -7
- package/dist_ts/proxies/smart-proxy/connection-manager.js +22 -22
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +4 -3
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +9 -9
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +68 -56
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +226 -176
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +94 -48
- package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +4 -4
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +6 -6
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +4 -7
- package/dist_ts/proxies/smart-proxy/port-manager.js +6 -9
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -15
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +128 -128
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/security-manager.js +9 -9
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +20 -13
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +16 -13
- package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +36 -0
- package/dist_ts/proxies/smart-proxy/throughput-tracker.js +117 -0
- package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +4 -3
- package/dist_ts/proxies/smart-proxy/timeout-manager.js +16 -16
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/tls-manager.js +12 -12
- package/package.json +8 -17
- package/readme.hints.md +0 -897
- package/readme.md +960 -54
- package/readme.plan.md +301 -562
- package/ts/proxies/smart-proxy/connection-manager.ts +23 -21
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +9 -8
- package/ts/proxies/smart-proxy/metrics-collector.ts +277 -189
- package/ts/proxies/smart-proxy/models/interfaces.ts +7 -0
- package/ts/proxies/smart-proxy/models/metrics-types.ts +93 -41
- package/ts/proxies/smart-proxy/nftables-manager.ts +5 -5
- package/ts/proxies/smart-proxy/port-manager.ts +6 -14
- package/ts/proxies/smart-proxy/route-connection-handler.ts +136 -136
- package/ts/proxies/smart-proxy/security-manager.ts +8 -8
- package/ts/proxies/smart-proxy/smart-proxy.ts +26 -35
- package/ts/proxies/smart-proxy/throughput-tracker.ts +144 -0
- package/ts/proxies/smart-proxy/timeout-manager.ts +16 -15
- package/ts/proxies/smart-proxy/tls-manager.ts +11 -11
- package/readme.connections.md +0 -724
- package/readme.delete.md +0 -187
- package/readme.memory-leaks-fixed.md +0 -45
- package/readme.metrics.md +0 -591
- package/readme.monitoring.md +0 -202
- package/readme.proxy-chain-summary.md +0 -112
- package/readme.proxy-protocol-example.md +0 -462
- package/readme.proxy-protocol.md +0 -415
- package/readme.routing.md +0 -341
- package/readme.websocket-keepalive-config.md +0 -140
- package/readme.websocket-keepalive-fix.md +0 -63
|
@@ -1,258 +1,341 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { SmartProxy } from './smart-proxy.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
IMetrics,
|
|
5
|
+
IThroughputData,
|
|
6
|
+
IThroughputHistoryPoint,
|
|
7
|
+
IByteTracker
|
|
8
|
+
} from './models/metrics-types.js';
|
|
9
|
+
import { ThroughputTracker } from './throughput-tracker.js';
|
|
4
10
|
import { logger } from '../../core/utils/logger.js';
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
|
-
* Collects and
|
|
13
|
+
* Collects and provides metrics for SmartProxy with clean API
|
|
8
14
|
*/
|
|
9
|
-
export class MetricsCollector implements
|
|
10
|
-
//
|
|
11
|
-
private
|
|
12
|
-
private readonly RPS_WINDOW_SIZE = 60000; // 1 minute window
|
|
13
|
-
private readonly MAX_TIMESTAMPS = 5000; // Maximum timestamps to keep
|
|
15
|
+
export class MetricsCollector implements IMetrics {
|
|
16
|
+
// Throughput tracking
|
|
17
|
+
private throughputTracker: ThroughputTracker;
|
|
14
18
|
|
|
15
|
-
//
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
connectionsByRoute?: Map<string, number>;
|
|
19
|
-
connectionsByIP?: Map<string, number>;
|
|
20
|
-
} = { timestamp: 0 };
|
|
19
|
+
// Request tracking
|
|
20
|
+
private requestTimestamps: number[] = [];
|
|
21
|
+
private totalRequests: number = 0;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// Connection byte tracking for per-route/IP metrics
|
|
24
|
+
private connectionByteTrackers = new Map<string, IByteTracker>();
|
|
23
25
|
|
|
24
|
-
//
|
|
26
|
+
// Subscriptions
|
|
27
|
+
private samplingInterval?: NodeJS.Timeout;
|
|
25
28
|
private connectionSubscription?: plugins.smartrx.rxjs.Subscription;
|
|
26
29
|
|
|
30
|
+
// Configuration
|
|
31
|
+
private readonly sampleIntervalMs: number;
|
|
32
|
+
private readonly retentionSeconds: number;
|
|
33
|
+
|
|
27
34
|
constructor(
|
|
28
|
-
private smartProxy: SmartProxy
|
|
35
|
+
private smartProxy: SmartProxy,
|
|
36
|
+
config?: {
|
|
37
|
+
sampleIntervalMs?: number;
|
|
38
|
+
retentionSeconds?: number;
|
|
39
|
+
}
|
|
29
40
|
) {
|
|
30
|
-
|
|
41
|
+
this.sampleIntervalMs = config?.sampleIntervalMs || 1000;
|
|
42
|
+
this.retentionSeconds = config?.retentionSeconds || 3600;
|
|
43
|
+
this.throughputTracker = new ThroughputTracker(this.retentionSeconds);
|
|
31
44
|
}
|
|
32
45
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get connection counts grouped by route name
|
|
42
|
-
*/
|
|
43
|
-
public getConnectionsByRoute(): Map<string, number> {
|
|
44
|
-
const now = Date.now();
|
|
45
|
-
|
|
46
|
-
// Return cached value if fresh
|
|
47
|
-
if (this.cachedMetrics.connectionsByRoute &&
|
|
48
|
-
now - this.cachedMetrics.timestamp < this.CACHE_TTL) {
|
|
49
|
-
return new Map(this.cachedMetrics.connectionsByRoute);
|
|
50
|
-
}
|
|
46
|
+
// Connection metrics implementation
|
|
47
|
+
public connections = {
|
|
48
|
+
active: (): number => {
|
|
49
|
+
return this.smartProxy.connectionManager.getConnectionCount();
|
|
50
|
+
},
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
total: (): number => {
|
|
53
|
+
const stats = this.smartProxy.connectionManager.getTerminationStats();
|
|
54
|
+
let total = this.smartProxy.connectionManager.getConnectionCount();
|
|
55
|
+
|
|
56
|
+
for (const reason in stats.incoming) {
|
|
57
|
+
total += stats.incoming[reason];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return total;
|
|
61
|
+
},
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
byRoute: (): Map<string, number> => {
|
|
64
|
+
const routeCounts = new Map<string, number>();
|
|
65
|
+
const connections = this.smartProxy.connectionManager.getConnections();
|
|
66
|
+
|
|
67
|
+
for (const [_, record] of connections) {
|
|
68
|
+
const routeName = (record as any).routeName ||
|
|
69
|
+
record.routeConfig?.name ||
|
|
70
|
+
'unknown';
|
|
71
|
+
|
|
72
|
+
const current = routeCounts.get(routeName) || 0;
|
|
73
|
+
routeCounts.set(routeName, current + 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return routeCounts;
|
|
77
|
+
},
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const routeName = (record as any).routeName ||
|
|
66
|
-
record.routeConfig?.name ||
|
|
67
|
-
(record.routeConfig as any)?.routeName ||
|
|
68
|
-
'unknown';
|
|
79
|
+
byIP: (): Map<string, number> => {
|
|
80
|
+
const ipCounts = new Map<string, number>();
|
|
69
81
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
hasRouteConfig: !!record.routeConfig,
|
|
75
|
-
routeConfigName: record.routeConfig?.name,
|
|
76
|
-
routeConfigKeys: record.routeConfig ? Object.keys(record.routeConfig) : [],
|
|
77
|
-
component: 'metrics'
|
|
78
|
-
});
|
|
82
|
+
for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
|
|
83
|
+
const ip = record.remoteIP;
|
|
84
|
+
const current = ipCounts.get(ip) || 0;
|
|
85
|
+
ipCounts.set(ip, current + 1);
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
88
|
+
return ipCounts;
|
|
89
|
+
},
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
topIPs: (limit: number = 10): Array<{ ip: string; count: number }> => {
|
|
92
|
+
const ipCounts = this.connections.byIP();
|
|
93
|
+
return Array.from(ipCounts.entries())
|
|
94
|
+
.sort((a, b) => b[1] - a[1])
|
|
95
|
+
.slice(0, limit)
|
|
96
|
+
.map(([ip, count]) => ({ ip, count }));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
// Throughput metrics implementation
|
|
101
|
+
public throughput = {
|
|
102
|
+
instant: (): IThroughputData => {
|
|
103
|
+
return this.throughputTracker.getRate(1);
|
|
104
|
+
},
|
|
96
105
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return new Map(this.cachedMetrics.connectionsByIP);
|
|
101
|
-
}
|
|
106
|
+
recent: (): IThroughputData => {
|
|
107
|
+
return this.throughputTracker.getRate(10);
|
|
108
|
+
},
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const ip = record.remoteIP;
|
|
107
|
-
const current = ipCounts.get(ip) || 0;
|
|
108
|
-
ipCounts.set(ip, current + 1);
|
|
109
|
-
}
|
|
110
|
+
average: (): IThroughputData => {
|
|
111
|
+
return this.throughputTracker.getRate(60);
|
|
112
|
+
},
|
|
110
113
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return new Map(ipCounts);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get the total number of connections since proxy start
|
|
119
|
-
*/
|
|
120
|
-
public getTotalConnections(): number {
|
|
121
|
-
// Get from termination stats
|
|
122
|
-
const stats = this.smartProxy.connectionManager.getTerminationStats();
|
|
123
|
-
let total = this.smartProxy.connectionManager.getConnectionCount(); // Add active connections
|
|
114
|
+
custom: (seconds: number): IThroughputData => {
|
|
115
|
+
return this.throughputTracker.getRate(seconds);
|
|
116
|
+
},
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
118
|
+
history: (seconds: number): Array<IThroughputHistoryPoint> => {
|
|
119
|
+
return this.throughputTracker.getHistory(seconds);
|
|
120
|
+
},
|
|
129
121
|
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
byRoute: (windowSeconds: number = 60): Map<string, IThroughputData> => {
|
|
123
|
+
const routeThroughput = new Map<string, IThroughputData>();
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
const windowStart = now - (windowSeconds * 1000);
|
|
126
|
+
|
|
127
|
+
// Aggregate bytes by route from trackers
|
|
128
|
+
const routeBytes = new Map<string, { in: number; out: number }>();
|
|
129
|
+
|
|
130
|
+
for (const [_, tracker] of this.connectionByteTrackers) {
|
|
131
|
+
if (tracker.lastUpdate > windowStart) {
|
|
132
|
+
const current = routeBytes.get(tracker.routeName) || { in: 0, out: 0 };
|
|
133
|
+
current.in += tracker.bytesIn;
|
|
134
|
+
current.out += tracker.bytesOut;
|
|
135
|
+
routeBytes.set(tracker.routeName, current);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Convert to rates
|
|
140
|
+
for (const [route, bytes] of routeBytes) {
|
|
141
|
+
routeThroughput.set(route, {
|
|
142
|
+
in: Math.round(bytes.in / windowSeconds),
|
|
143
|
+
out: Math.round(bytes.out / windowSeconds)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return routeThroughput;
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
byIP: (windowSeconds: number = 60): Map<string, IThroughputData> => {
|
|
151
|
+
const ipThroughput = new Map<string, IThroughputData>();
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
const windowStart = now - (windowSeconds * 1000);
|
|
154
|
+
|
|
155
|
+
// Aggregate bytes by IP from trackers
|
|
156
|
+
const ipBytes = new Map<string, { in: number; out: number }>();
|
|
157
|
+
|
|
158
|
+
for (const [_, tracker] of this.connectionByteTrackers) {
|
|
159
|
+
if (tracker.lastUpdate > windowStart) {
|
|
160
|
+
const current = ipBytes.get(tracker.remoteIP) || { in: 0, out: 0 };
|
|
161
|
+
current.in += tracker.bytesIn;
|
|
162
|
+
current.out += tracker.bytesOut;
|
|
163
|
+
ipBytes.set(tracker.remoteIP, current);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Convert to rates
|
|
168
|
+
for (const [ip, bytes] of ipBytes) {
|
|
169
|
+
ipThroughput.set(ip, {
|
|
170
|
+
in: Math.round(bytes.in / windowSeconds),
|
|
171
|
+
out: Math.round(bytes.out / windowSeconds)
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return ipThroughput;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
132
178
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
179
|
+
// Request metrics implementation
|
|
180
|
+
public requests = {
|
|
181
|
+
perSecond: (): number => {
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
const oneSecondAgo = now - 1000;
|
|
184
|
+
|
|
185
|
+
// Clean old timestamps
|
|
186
|
+
this.requestTimestamps = this.requestTimestamps.filter(ts => ts > now - 60000);
|
|
187
|
+
|
|
188
|
+
// Count requests in last second
|
|
189
|
+
const recentRequests = this.requestTimestamps.filter(ts => ts > oneSecondAgo);
|
|
190
|
+
return recentRequests.length;
|
|
191
|
+
},
|
|
139
192
|
|
|
140
|
-
|
|
141
|
-
|
|
193
|
+
perMinute: (): number => {
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
const oneMinuteAgo = now - 60000;
|
|
196
|
+
|
|
197
|
+
// Count requests in last minute
|
|
198
|
+
const recentRequests = this.requestTimestamps.filter(ts => ts > oneMinuteAgo);
|
|
199
|
+
return recentRequests.length;
|
|
200
|
+
},
|
|
142
201
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
202
|
+
total: (): number => {
|
|
203
|
+
return this.totalRequests;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
147
206
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
207
|
+
// Totals implementation
|
|
208
|
+
public totals = {
|
|
209
|
+
bytesIn: (): number => {
|
|
210
|
+
let total = 0;
|
|
211
|
+
|
|
212
|
+
// Sum from all active connections
|
|
213
|
+
for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
|
|
214
|
+
total += record.bytesReceived;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// TODO: Add historical data from terminated connections
|
|
218
|
+
|
|
219
|
+
return total;
|
|
220
|
+
},
|
|
154
221
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
222
|
+
bytesOut: (): number => {
|
|
223
|
+
let total = 0;
|
|
224
|
+
|
|
225
|
+
// Sum from all active connections
|
|
226
|
+
for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
|
|
227
|
+
total += record.bytesSent;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// TODO: Add historical data from terminated connections
|
|
231
|
+
|
|
232
|
+
return total;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
connections: (): number => {
|
|
236
|
+
return this.connections.total();
|
|
160
237
|
}
|
|
161
|
-
}
|
|
238
|
+
};
|
|
162
239
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
240
|
+
// Percentiles implementation (placeholder for now)
|
|
241
|
+
public percentiles = {
|
|
242
|
+
connectionDuration: (): { p50: number; p95: number; p99: number } => {
|
|
243
|
+
// TODO: Implement percentile calculations
|
|
244
|
+
return { p50: 0, p95: 0, p99: 0 };
|
|
245
|
+
},
|
|
169
246
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
247
|
+
bytesTransferred: (): {
|
|
248
|
+
in: { p50: number; p95: number; p99: number };
|
|
249
|
+
out: { p50: number; p95: number; p99: number };
|
|
250
|
+
} => {
|
|
251
|
+
// TODO: Implement percentile calculations
|
|
252
|
+
return {
|
|
253
|
+
in: { p50: 0, p95: 0, p99: 0 },
|
|
254
|
+
out: { p50: 0, p95: 0, p99: 0 }
|
|
255
|
+
};
|
|
174
256
|
}
|
|
175
|
-
|
|
176
|
-
return { bytesIn, bytesOut };
|
|
177
|
-
}
|
|
257
|
+
};
|
|
178
258
|
|
|
179
259
|
/**
|
|
180
|
-
*
|
|
260
|
+
* Record a new request
|
|
181
261
|
*/
|
|
182
|
-
public
|
|
262
|
+
public recordRequest(connectionId: string, routeName: string, remoteIP: string): void {
|
|
183
263
|
const now = Date.now();
|
|
184
|
-
|
|
185
|
-
|
|
264
|
+
this.requestTimestamps.push(now);
|
|
265
|
+
this.totalRequests++;
|
|
186
266
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
recentBytesIn += record.bytesReceived / rate;
|
|
197
|
-
recentBytesOut += record.bytesSent / rate;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
267
|
+
// Initialize byte tracker for this connection
|
|
268
|
+
this.connectionByteTrackers.set(connectionId, {
|
|
269
|
+
connectionId,
|
|
270
|
+
routeName,
|
|
271
|
+
remoteIP,
|
|
272
|
+
bytesIn: 0,
|
|
273
|
+
bytesOut: 0,
|
|
274
|
+
lastUpdate: now
|
|
275
|
+
});
|
|
200
276
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
277
|
+
// Cleanup old request timestamps (keep last minute only)
|
|
278
|
+
if (this.requestTimestamps.length > 1000) {
|
|
279
|
+
const cutoff = now - 60000;
|
|
280
|
+
this.requestTimestamps = this.requestTimestamps.filter(ts => ts > cutoff);
|
|
281
|
+
}
|
|
205
282
|
}
|
|
206
283
|
|
|
207
284
|
/**
|
|
208
|
-
*
|
|
285
|
+
* Record bytes transferred for a connection
|
|
209
286
|
*/
|
|
210
|
-
public
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.sort((a, b) => b[1] - a[1])
|
|
214
|
-
.slice(0, limit)
|
|
215
|
-
.map(([ip, connections]) => ({ ip, connections }));
|
|
287
|
+
public recordBytes(connectionId: string, bytesIn: number, bytesOut: number): void {
|
|
288
|
+
// Update global throughput tracker
|
|
289
|
+
this.throughputTracker.recordBytes(bytesIn, bytesOut);
|
|
216
290
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const ipCounts = this.getConnectionsByIP();
|
|
225
|
-
const currentConnections = ipCounts.get(ip) || 0;
|
|
226
|
-
return currentConnections >= maxConnectionsPerIP;
|
|
291
|
+
// Update connection-specific tracker
|
|
292
|
+
const tracker = this.connectionByteTrackers.get(connectionId);
|
|
293
|
+
if (tracker) {
|
|
294
|
+
tracker.bytesIn += bytesIn;
|
|
295
|
+
tracker.bytesOut += bytesOut;
|
|
296
|
+
tracker.lastUpdate = Date.now();
|
|
297
|
+
}
|
|
227
298
|
}
|
|
228
299
|
|
|
229
300
|
/**
|
|
230
|
-
* Clean up
|
|
301
|
+
* Clean up tracking for a closed connection
|
|
231
302
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.requestTimestamps = this.requestTimestamps.filter(ts => ts > cutoff);
|
|
303
|
+
public removeConnection(connectionId: string): void {
|
|
304
|
+
this.connectionByteTrackers.delete(connectionId);
|
|
235
305
|
}
|
|
236
306
|
|
|
237
307
|
/**
|
|
238
|
-
* Start the metrics collector
|
|
308
|
+
* Start the metrics collector
|
|
239
309
|
*/
|
|
240
310
|
public start(): void {
|
|
241
311
|
if (!this.smartProxy.routeConnectionHandler) {
|
|
242
312
|
throw new Error('MetricsCollector: RouteConnectionHandler not available');
|
|
243
313
|
}
|
|
244
314
|
|
|
245
|
-
//
|
|
315
|
+
// Start periodic sampling
|
|
316
|
+
this.samplingInterval = setInterval(() => {
|
|
317
|
+
this.throughputTracker.takeSample();
|
|
318
|
+
|
|
319
|
+
// Clean up old connection trackers (connections closed more than 5 minutes ago)
|
|
320
|
+
const cutoff = Date.now() - 300000;
|
|
321
|
+
for (const [id, tracker] of this.connectionByteTrackers) {
|
|
322
|
+
if (tracker.lastUpdate < cutoff) {
|
|
323
|
+
this.connectionByteTrackers.delete(id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}, this.sampleIntervalMs);
|
|
327
|
+
|
|
328
|
+
// Subscribe to new connections
|
|
246
329
|
this.connectionSubscription = this.smartProxy.routeConnectionHandler.newConnectionSubject.subscribe({
|
|
247
330
|
next: (record) => {
|
|
248
|
-
|
|
331
|
+
const routeName = record.routeConfig?.name || 'unknown';
|
|
332
|
+
this.recordRequest(record.id, routeName, record.remoteIP);
|
|
249
333
|
|
|
250
|
-
// Optional: Log connection details
|
|
251
334
|
if (this.smartProxy.settings?.enableDetailedLogging) {
|
|
252
335
|
logger.log('debug', `MetricsCollector: New connection recorded`, {
|
|
253
336
|
connectionId: record.id,
|
|
254
337
|
remoteIP: record.remoteIP,
|
|
255
|
-
routeName
|
|
338
|
+
routeName,
|
|
256
339
|
component: 'metrics'
|
|
257
340
|
});
|
|
258
341
|
}
|
|
@@ -269,9 +352,14 @@ export class MetricsCollector implements IProxyStatsExtended {
|
|
|
269
352
|
}
|
|
270
353
|
|
|
271
354
|
/**
|
|
272
|
-
* Stop the metrics collector
|
|
355
|
+
* Stop the metrics collector
|
|
273
356
|
*/
|
|
274
357
|
public stop(): void {
|
|
358
|
+
if (this.samplingInterval) {
|
|
359
|
+
clearInterval(this.samplingInterval);
|
|
360
|
+
this.samplingInterval = undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
275
363
|
if (this.connectionSubscription) {
|
|
276
364
|
this.connectionSubscription.unsubscribe();
|
|
277
365
|
this.connectionSubscription = undefined;
|
|
@@ -281,7 +369,7 @@ export class MetricsCollector implements IProxyStatsExtended {
|
|
|
281
369
|
}
|
|
282
370
|
|
|
283
371
|
/**
|
|
284
|
-
* Alias for stop() for
|
|
372
|
+
* Alias for stop() for compatibility
|
|
285
373
|
*/
|
|
286
374
|
public destroy(): void {
|
|
287
375
|
this.stop();
|
|
@@ -105,6 +105,13 @@ export interface ISmartProxyOptions {
|
|
|
105
105
|
useHttpProxy?: number[]; // Array of ports to forward to HttpProxy
|
|
106
106
|
httpProxyPort?: number; // Port where HttpProxy is listening (default: 8443)
|
|
107
107
|
|
|
108
|
+
// Metrics configuration
|
|
109
|
+
metrics?: {
|
|
110
|
+
enabled?: boolean;
|
|
111
|
+
sampleIntervalMs?: number;
|
|
112
|
+
retentionSeconds?: number;
|
|
113
|
+
};
|
|
114
|
+
|
|
108
115
|
/**
|
|
109
116
|
* Global ACME configuration options for SmartProxy
|
|
110
117
|
*
|