@push.rocks/smartproxy 19.5.26 → 19.6.1
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/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/proxies/http-proxy/function-cache.d.ts +5 -0
- package/dist_ts/proxies/http-proxy/function-cache.js +19 -2
- package/dist_ts/proxies/http-proxy/http-proxy.js +5 -1
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +5 -0
- package/dist_ts/proxies/http-proxy/request-handler.js +27 -2
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +80 -0
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +239 -0
- package/dist_ts/proxies/smart-proxy/models/index.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/models/index.js +2 -1
- package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +55 -0
- package/dist_ts/proxies/smart-proxy/models/metrics-types.js +2 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +2 -2
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +14 -6
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +12 -2
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +17 -1
- package/package.json +18 -8
- package/readme.md +118 -0
- package/readme.memory-leaks-fixed.md +45 -0
- package/readme.metrics.md +591 -0
- package/ts/plugins.ts +2 -0
- package/ts/proxies/http-proxy/function-cache.ts +21 -1
- package/ts/proxies/http-proxy/http-proxy.ts +5 -0
- package/ts/proxies/http-proxy/request-handler.ts +32 -1
- package/ts/proxies/smart-proxy/metrics-collector.ts +289 -0
- package/ts/proxies/smart-proxy/models/index.ts +1 -0
- package/ts/proxies/smart-proxy/models/metrics-types.ts +54 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +18 -5
- package/ts/proxies/smart-proxy/smart-proxy.ts +27 -2
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { SmartProxy } from './smart-proxy.js';
|
|
3
|
+
import type { IProxyStats, IProxyStatsExtended } from './models/metrics-types.js';
|
|
4
|
+
import { logger } from '../../core/utils/logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Collects and computes metrics for SmartProxy on-demand
|
|
8
|
+
*/
|
|
9
|
+
export class MetricsCollector implements IProxyStatsExtended {
|
|
10
|
+
// RPS tracking (the only state we need to maintain)
|
|
11
|
+
private requestTimestamps: number[] = [];
|
|
12
|
+
private readonly RPS_WINDOW_SIZE = 60000; // 1 minute window
|
|
13
|
+
private readonly MAX_TIMESTAMPS = 5000; // Maximum timestamps to keep
|
|
14
|
+
|
|
15
|
+
// Optional caching for performance
|
|
16
|
+
private cachedMetrics: {
|
|
17
|
+
timestamp: number;
|
|
18
|
+
connectionsByRoute?: Map<string, number>;
|
|
19
|
+
connectionsByIP?: Map<string, number>;
|
|
20
|
+
} = { timestamp: 0 };
|
|
21
|
+
|
|
22
|
+
private readonly CACHE_TTL = 1000; // 1 second cache
|
|
23
|
+
|
|
24
|
+
// RxJS subscription for connection events
|
|
25
|
+
private connectionSubscription?: plugins.smartrx.rxjs.Subscription;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private smartProxy: SmartProxy
|
|
29
|
+
) {
|
|
30
|
+
// Subscription will be set up in start() method
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the current number of active connections
|
|
35
|
+
*/
|
|
36
|
+
public getActiveConnections(): number {
|
|
37
|
+
return this.smartProxy.connectionManager.getConnectionCount();
|
|
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
|
+
}
|
|
51
|
+
|
|
52
|
+
// Compute fresh value
|
|
53
|
+
const routeCounts = new Map<string, number>();
|
|
54
|
+
const connections = this.smartProxy.connectionManager.getConnections();
|
|
55
|
+
|
|
56
|
+
if (this.smartProxy.settings?.enableDetailedLogging) {
|
|
57
|
+
logger.log('debug', `MetricsCollector: Computing route connections`, {
|
|
58
|
+
totalConnections: connections.size,
|
|
59
|
+
component: 'metrics'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const [_, record] of connections) {
|
|
64
|
+
// Try different ways to get the route name
|
|
65
|
+
const routeName = (record as any).routeName ||
|
|
66
|
+
record.routeConfig?.name ||
|
|
67
|
+
(record.routeConfig as any)?.routeName ||
|
|
68
|
+
'unknown';
|
|
69
|
+
|
|
70
|
+
if (this.smartProxy.settings?.enableDetailedLogging) {
|
|
71
|
+
logger.log('debug', `MetricsCollector: Connection route info`, {
|
|
72
|
+
connectionId: record.id,
|
|
73
|
+
routeName,
|
|
74
|
+
hasRouteConfig: !!record.routeConfig,
|
|
75
|
+
routeConfigName: record.routeConfig?.name,
|
|
76
|
+
routeConfigKeys: record.routeConfig ? Object.keys(record.routeConfig) : [],
|
|
77
|
+
component: 'metrics'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const current = routeCounts.get(routeName) || 0;
|
|
82
|
+
routeCounts.set(routeName, current + 1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Cache and return
|
|
86
|
+
this.cachedMetrics.connectionsByRoute = routeCounts;
|
|
87
|
+
this.cachedMetrics.timestamp = now;
|
|
88
|
+
return new Map(routeCounts);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get connection counts grouped by IP address
|
|
93
|
+
*/
|
|
94
|
+
public getConnectionsByIP(): Map<string, number> {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
|
|
97
|
+
// Return cached value if fresh
|
|
98
|
+
if (this.cachedMetrics.connectionsByIP &&
|
|
99
|
+
now - this.cachedMetrics.timestamp < this.CACHE_TTL) {
|
|
100
|
+
return new Map(this.cachedMetrics.connectionsByIP);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Compute fresh value
|
|
104
|
+
const ipCounts = new Map<string, number>();
|
|
105
|
+
for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
|
|
106
|
+
const ip = record.remoteIP;
|
|
107
|
+
const current = ipCounts.get(ip) || 0;
|
|
108
|
+
ipCounts.set(ip, current + 1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Cache and return
|
|
112
|
+
this.cachedMetrics.connectionsByIP = ipCounts;
|
|
113
|
+
this.cachedMetrics.timestamp = now;
|
|
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
|
|
124
|
+
|
|
125
|
+
// Add all terminated connections
|
|
126
|
+
for (const reason in stats.incoming) {
|
|
127
|
+
total += stats.incoming[reason];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return total;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the current requests per second rate
|
|
135
|
+
*/
|
|
136
|
+
public getRequestsPerSecond(): number {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const windowStart = now - this.RPS_WINDOW_SIZE;
|
|
139
|
+
|
|
140
|
+
// Clean old timestamps
|
|
141
|
+
this.requestTimestamps = this.requestTimestamps.filter(ts => ts > windowStart);
|
|
142
|
+
|
|
143
|
+
// Calculate RPS based on window
|
|
144
|
+
const requestsInWindow = this.requestTimestamps.length;
|
|
145
|
+
return requestsInWindow / (this.RPS_WINDOW_SIZE / 1000);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Record a new request for RPS tracking
|
|
150
|
+
*/
|
|
151
|
+
public recordRequest(): void {
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
this.requestTimestamps.push(now);
|
|
154
|
+
|
|
155
|
+
// Prevent unbounded growth - clean up more aggressively
|
|
156
|
+
if (this.requestTimestamps.length > this.MAX_TIMESTAMPS) {
|
|
157
|
+
// Keep only timestamps within the window
|
|
158
|
+
const cutoff = now - this.RPS_WINDOW_SIZE;
|
|
159
|
+
this.requestTimestamps = this.requestTimestamps.filter(ts => ts > cutoff);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get total throughput (bytes transferred)
|
|
165
|
+
*/
|
|
166
|
+
public getThroughput(): { bytesIn: number; bytesOut: number } {
|
|
167
|
+
let bytesIn = 0;
|
|
168
|
+
let bytesOut = 0;
|
|
169
|
+
|
|
170
|
+
// Sum bytes from all active connections
|
|
171
|
+
for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
|
|
172
|
+
bytesIn += record.bytesReceived;
|
|
173
|
+
bytesOut += record.bytesSent;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { bytesIn, bytesOut };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get throughput rate (bytes per second) for last minute
|
|
181
|
+
*/
|
|
182
|
+
public getThroughputRate(): { bytesInPerSec: number; bytesOutPerSec: number } {
|
|
183
|
+
const now = Date.now();
|
|
184
|
+
let recentBytesIn = 0;
|
|
185
|
+
let recentBytesOut = 0;
|
|
186
|
+
|
|
187
|
+
// Calculate bytes transferred in last minute from active connections
|
|
188
|
+
for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
|
|
189
|
+
const connectionAge = now - record.incomingStartTime;
|
|
190
|
+
if (connectionAge < 60000) { // Connection started within last minute
|
|
191
|
+
recentBytesIn += record.bytesReceived;
|
|
192
|
+
recentBytesOut += record.bytesSent;
|
|
193
|
+
} else {
|
|
194
|
+
// For older connections, estimate rate based on average
|
|
195
|
+
const rate = connectionAge / 60000;
|
|
196
|
+
recentBytesIn += record.bytesReceived / rate;
|
|
197
|
+
recentBytesOut += record.bytesSent / rate;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
bytesInPerSec: Math.round(recentBytesIn / 60),
|
|
203
|
+
bytesOutPerSec: Math.round(recentBytesOut / 60)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get top IPs by connection count
|
|
209
|
+
*/
|
|
210
|
+
public getTopIPs(limit: number = 10): Array<{ ip: string; connections: number }> {
|
|
211
|
+
const ipCounts = this.getConnectionsByIP();
|
|
212
|
+
const sorted = Array.from(ipCounts.entries())
|
|
213
|
+
.sort((a, b) => b[1] - a[1])
|
|
214
|
+
.slice(0, limit)
|
|
215
|
+
.map(([ip, connections]) => ({ ip, connections }));
|
|
216
|
+
|
|
217
|
+
return sorted;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check if an IP has reached the connection limit
|
|
222
|
+
*/
|
|
223
|
+
public isIPBlocked(ip: string, maxConnectionsPerIP: number): boolean {
|
|
224
|
+
const ipCounts = this.getConnectionsByIP();
|
|
225
|
+
const currentConnections = ipCounts.get(ip) || 0;
|
|
226
|
+
return currentConnections >= maxConnectionsPerIP;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Clean up old request timestamps
|
|
231
|
+
*/
|
|
232
|
+
private cleanupOldRequests(): void {
|
|
233
|
+
const cutoff = Date.now() - this.RPS_WINDOW_SIZE;
|
|
234
|
+
this.requestTimestamps = this.requestTimestamps.filter(ts => ts > cutoff);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Start the metrics collector and set up subscriptions
|
|
239
|
+
*/
|
|
240
|
+
public start(): void {
|
|
241
|
+
if (!this.smartProxy.routeConnectionHandler) {
|
|
242
|
+
throw new Error('MetricsCollector: RouteConnectionHandler not available');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Subscribe to the newConnectionSubject from RouteConnectionHandler
|
|
246
|
+
this.connectionSubscription = this.smartProxy.routeConnectionHandler.newConnectionSubject.subscribe({
|
|
247
|
+
next: (record) => {
|
|
248
|
+
this.recordRequest();
|
|
249
|
+
|
|
250
|
+
// Optional: Log connection details
|
|
251
|
+
if (this.smartProxy.settings?.enableDetailedLogging) {
|
|
252
|
+
logger.log('debug', `MetricsCollector: New connection recorded`, {
|
|
253
|
+
connectionId: record.id,
|
|
254
|
+
remoteIP: record.remoteIP,
|
|
255
|
+
routeName: record.routeConfig?.name || 'unknown',
|
|
256
|
+
component: 'metrics'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
error: (err) => {
|
|
261
|
+
logger.log('error', `MetricsCollector: Error in connection subscription`, {
|
|
262
|
+
error: err.message,
|
|
263
|
+
component: 'metrics'
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
logger.log('debug', 'MetricsCollector started', { component: 'metrics' });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Stop the metrics collector and clean up resources
|
|
273
|
+
*/
|
|
274
|
+
public stop(): void {
|
|
275
|
+
if (this.connectionSubscription) {
|
|
276
|
+
this.connectionSubscription.unsubscribe();
|
|
277
|
+
this.connectionSubscription = undefined;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
logger.log('debug', 'MetricsCollector stopped', { component: 'metrics' });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Alias for stop() for backward compatibility
|
|
285
|
+
*/
|
|
286
|
+
public destroy(): void {
|
|
287
|
+
this.stop();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for proxy statistics and metrics
|
|
3
|
+
*/
|
|
4
|
+
export interface IProxyStats {
|
|
5
|
+
/**
|
|
6
|
+
* Get the current number of active connections
|
|
7
|
+
*/
|
|
8
|
+
getActiveConnections(): number;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get connection counts grouped by route name
|
|
12
|
+
*/
|
|
13
|
+
getConnectionsByRoute(): Map<string, number>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get connection counts grouped by IP address
|
|
17
|
+
*/
|
|
18
|
+
getConnectionsByIP(): Map<string, number>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the total number of connections since proxy start
|
|
22
|
+
*/
|
|
23
|
+
getTotalConnections(): number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the current requests per second rate
|
|
27
|
+
*/
|
|
28
|
+
getRequestsPerSecond(): number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get total throughput (bytes transferred)
|
|
32
|
+
*/
|
|
33
|
+
getThroughput(): { bytesIn: number; bytesOut: number };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extended interface for additional metrics helpers
|
|
38
|
+
*/
|
|
39
|
+
export interface IProxyStatsExtended extends IProxyStats {
|
|
40
|
+
/**
|
|
41
|
+
* Get throughput rate (bytes per second) for last minute
|
|
42
|
+
*/
|
|
43
|
+
getThroughputRate(): { bytesInPerSec: number; bytesOutPerSec: number };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get top IPs by connection count
|
|
47
|
+
*/
|
|
48
|
+
getTopIPs(limit?: number): Array<{ ip: string; connections: number }>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if an IP has reached the connection limit
|
|
52
|
+
*/
|
|
53
|
+
isIPBlocked(ip: string, maxConnectionsPerIP: number): boolean;
|
|
54
|
+
}
|
|
@@ -10,7 +10,7 @@ import { TlsManager } from './tls-manager.js';
|
|
|
10
10
|
import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
11
11
|
import { TimeoutManager } from './timeout-manager.js';
|
|
12
12
|
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
13
|
-
import { cleanupSocket,
|
|
13
|
+
import { cleanupSocket, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
14
14
|
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
15
15
|
import { getUnderlyingSocket } from '../../core/models/socket-types.js';
|
|
16
16
|
import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
|
|
@@ -21,8 +21,12 @@ import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
|
|
|
21
21
|
export class RouteConnectionHandler {
|
|
22
22
|
private settings: ISmartProxyOptions;
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
|
|
24
|
+
// Note: Route context caching was considered but not implemented
|
|
25
|
+
// as route contexts are lightweight and should be created fresh
|
|
26
|
+
// for each connection to ensure accurate context data
|
|
27
|
+
|
|
28
|
+
// RxJS Subject for new connections
|
|
29
|
+
public newConnectionSubject = new plugins.smartrx.rxjs.Subject<IConnectionRecord>();
|
|
26
30
|
|
|
27
31
|
constructor(
|
|
28
32
|
settings: ISmartProxyOptions,
|
|
@@ -35,6 +39,7 @@ export class RouteConnectionHandler {
|
|
|
35
39
|
) {
|
|
36
40
|
this.settings = settings;
|
|
37
41
|
}
|
|
42
|
+
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Create a route context object for port and host mapping functions
|
|
@@ -110,6 +115,9 @@ export class RouteConnectionHandler {
|
|
|
110
115
|
// Connection was rejected due to limit - socket already destroyed by connection manager
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
118
|
+
|
|
119
|
+
// Emit new connection event
|
|
120
|
+
this.newConnectionSubject.next(record);
|
|
113
121
|
const connectionId = record.id;
|
|
114
122
|
|
|
115
123
|
// Apply socket optimizations (apply to underlying socket)
|
|
@@ -640,6 +648,9 @@ export class RouteConnectionHandler {
|
|
|
640
648
|
): void {
|
|
641
649
|
const connectionId = record.id;
|
|
642
650
|
const action = route.action as IRouteAction;
|
|
651
|
+
|
|
652
|
+
// Store the route config in the connection record for metrics and other uses
|
|
653
|
+
record.routeConfig = route;
|
|
643
654
|
|
|
644
655
|
// Check if this route uses NFTables for forwarding
|
|
645
656
|
if (action.forwardingEngine === 'nftables') {
|
|
@@ -720,8 +731,7 @@ export class RouteConnectionHandler {
|
|
|
720
731
|
routeId: route.id,
|
|
721
732
|
});
|
|
722
733
|
|
|
723
|
-
//
|
|
724
|
-
this.routeContextCache.set(connectionId, routeContext);
|
|
734
|
+
// Note: Route contexts are not cached to ensure fresh data for each connection
|
|
725
735
|
|
|
726
736
|
// Determine host using function or static value
|
|
727
737
|
let targetHost: string | string[];
|
|
@@ -957,6 +967,9 @@ export class RouteConnectionHandler {
|
|
|
957
967
|
): Promise<void> {
|
|
958
968
|
const connectionId = record.id;
|
|
959
969
|
|
|
970
|
+
// Store the route config in the connection record for metrics and other uses
|
|
971
|
+
record.routeConfig = route;
|
|
972
|
+
|
|
960
973
|
if (!route.action.socketHandler) {
|
|
961
974
|
logger.log('error', 'socket-handler action missing socketHandler function', {
|
|
962
975
|
connectionId,
|
|
@@ -27,6 +27,10 @@ import { Mutex } from './utils/mutex.js';
|
|
|
27
27
|
// Import ACME state manager
|
|
28
28
|
import { AcmeStateManager } from './acme-state-manager.js';
|
|
29
29
|
|
|
30
|
+
// Import metrics collector
|
|
31
|
+
import { MetricsCollector } from './metrics-collector.js';
|
|
32
|
+
import type { IProxyStats } from './models/metrics-types.js';
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* SmartProxy - Pure route-based API
|
|
32
36
|
*
|
|
@@ -47,13 +51,13 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
47
51
|
private isShuttingDown: boolean = false;
|
|
48
52
|
|
|
49
53
|
// Component managers
|
|
50
|
-
|
|
54
|
+
public connectionManager: ConnectionManager;
|
|
51
55
|
private securityManager: SecurityManager;
|
|
52
56
|
private tlsManager: TlsManager;
|
|
53
57
|
private httpProxyBridge: HttpProxyBridge;
|
|
54
58
|
private timeoutManager: TimeoutManager;
|
|
55
59
|
public routeManager: RouteManager; // Made public for route management
|
|
56
|
-
|
|
60
|
+
public routeConnectionHandler: RouteConnectionHandler; // Made public for metrics
|
|
57
61
|
private nftablesManager: NFTablesManager;
|
|
58
62
|
|
|
59
63
|
// Certificate manager for ACME and static certificates
|
|
@@ -64,6 +68,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
64
68
|
private routeUpdateLock: any = null; // Will be initialized as AsyncMutex
|
|
65
69
|
private acmeStateManager: AcmeStateManager;
|
|
66
70
|
|
|
71
|
+
// Metrics collector
|
|
72
|
+
private metricsCollector: MetricsCollector;
|
|
73
|
+
|
|
67
74
|
// Track port usage across route updates
|
|
68
75
|
private portUsageMap: Map<number, Set<string>> = new Map();
|
|
69
76
|
|
|
@@ -204,6 +211,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
204
211
|
|
|
205
212
|
// Initialize ACME state manager
|
|
206
213
|
this.acmeStateManager = new AcmeStateManager();
|
|
214
|
+
|
|
215
|
+
// Initialize metrics collector with reference to this SmartProxy instance
|
|
216
|
+
this.metricsCollector = new MetricsCollector(this);
|
|
207
217
|
}
|
|
208
218
|
|
|
209
219
|
/**
|
|
@@ -383,6 +393,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
383
393
|
logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
|
|
384
394
|
await this.certManager.provisionAllCertificates();
|
|
385
395
|
}
|
|
396
|
+
|
|
397
|
+
// Start the metrics collector now that all components are initialized
|
|
398
|
+
this.metricsCollector.start();
|
|
386
399
|
|
|
387
400
|
// Set up periodic connection logging and inactivity checks
|
|
388
401
|
this.connectionLogger = setInterval(() => {
|
|
@@ -508,6 +521,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
508
521
|
|
|
509
522
|
// Clear ACME state manager
|
|
510
523
|
this.acmeStateManager.clear();
|
|
524
|
+
|
|
525
|
+
// Stop metrics collector
|
|
526
|
+
this.metricsCollector.stop();
|
|
511
527
|
|
|
512
528
|
logger.log('info', 'SmartProxy shutdown complete.');
|
|
513
529
|
}
|
|
@@ -905,6 +921,15 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
905
921
|
return this.certManager.getCertificateStatus(routeName);
|
|
906
922
|
}
|
|
907
923
|
|
|
924
|
+
/**
|
|
925
|
+
* Get proxy statistics and metrics
|
|
926
|
+
*
|
|
927
|
+
* @returns IProxyStats interface with various metrics methods
|
|
928
|
+
*/
|
|
929
|
+
public getStats(): IProxyStats {
|
|
930
|
+
return this.metricsCollector;
|
|
931
|
+
}
|
|
932
|
+
|
|
908
933
|
/**
|
|
909
934
|
* Validates if a domain name is valid for certificate issuance
|
|
910
935
|
*/
|