@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.
Files changed (52) hide show
  1. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +4 -7
  2. package/dist_ts/proxies/smart-proxy/connection-manager.js +22 -22
  3. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +4 -3
  4. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +9 -9
  5. package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +68 -56
  6. package/dist_ts/proxies/smart-proxy/metrics-collector.js +226 -176
  7. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -0
  8. package/dist_ts/proxies/smart-proxy/models/metrics-types.d.ts +94 -48
  9. package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +4 -4
  10. package/dist_ts/proxies/smart-proxy/nftables-manager.js +6 -6
  11. package/dist_ts/proxies/smart-proxy/port-manager.d.ts +4 -7
  12. package/dist_ts/proxies/smart-proxy/port-manager.js +6 -9
  13. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -15
  14. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +128 -128
  15. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +3 -3
  16. package/dist_ts/proxies/smart-proxy/security-manager.js +9 -9
  17. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +20 -13
  18. package/dist_ts/proxies/smart-proxy/smart-proxy.js +16 -13
  19. package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +36 -0
  20. package/dist_ts/proxies/smart-proxy/throughput-tracker.js +117 -0
  21. package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +4 -3
  22. package/dist_ts/proxies/smart-proxy/timeout-manager.js +16 -16
  23. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +3 -3
  24. package/dist_ts/proxies/smart-proxy/tls-manager.js +12 -12
  25. package/package.json +8 -17
  26. package/readme.hints.md +0 -897
  27. package/readme.md +960 -54
  28. package/readme.plan.md +301 -562
  29. package/ts/proxies/smart-proxy/connection-manager.ts +23 -21
  30. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +9 -8
  31. package/ts/proxies/smart-proxy/metrics-collector.ts +277 -189
  32. package/ts/proxies/smart-proxy/models/interfaces.ts +7 -0
  33. package/ts/proxies/smart-proxy/models/metrics-types.ts +93 -41
  34. package/ts/proxies/smart-proxy/nftables-manager.ts +5 -5
  35. package/ts/proxies/smart-proxy/port-manager.ts +6 -14
  36. package/ts/proxies/smart-proxy/route-connection-handler.ts +136 -136
  37. package/ts/proxies/smart-proxy/security-manager.ts +8 -8
  38. package/ts/proxies/smart-proxy/smart-proxy.ts +26 -35
  39. package/ts/proxies/smart-proxy/throughput-tracker.ts +144 -0
  40. package/ts/proxies/smart-proxy/timeout-manager.ts +16 -15
  41. package/ts/proxies/smart-proxy/tls-manager.ts +11 -11
  42. package/readme.connections.md +0 -724
  43. package/readme.delete.md +0 -187
  44. package/readme.memory-leaks-fixed.md +0 -45
  45. package/readme.metrics.md +0 -591
  46. package/readme.monitoring.md +0 -202
  47. package/readme.proxy-chain-summary.md +0 -112
  48. package/readme.proxy-protocol-example.md +0 -462
  49. package/readme.proxy-protocol.md +0 -415
  50. package/readme.routing.md +0 -341
  51. package/readme.websocket-keepalive-config.md +0 -140
  52. 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 { IProxyStats, IProxyStatsExtended } from './models/metrics-types.js';
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 computes metrics for SmartProxy on-demand
13
+ * Collects and provides metrics for SmartProxy with clean API
8
14
  */
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
15
+ export class MetricsCollector implements IMetrics {
16
+ // Throughput tracking
17
+ private throughputTracker: ThroughputTracker;
14
18
 
15
- // Optional caching for performance
16
- private cachedMetrics: {
17
- timestamp: number;
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
- private readonly CACHE_TTL = 1000; // 1 second cache
23
+ // Connection byte tracking for per-route/IP metrics
24
+ private connectionByteTrackers = new Map<string, IByteTracker>();
23
25
 
24
- // RxJS subscription for connection events
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
- // Subscription will be set up in start() method
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
- * 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
- }
46
+ // Connection metrics implementation
47
+ public connections = {
48
+ active: (): number => {
49
+ return this.smartProxy.connectionManager.getConnectionCount();
50
+ },
51
51
 
52
- // Compute fresh value
53
- const routeCounts = new Map<string, number>();
54
- const connections = this.smartProxy.connectionManager.getConnections();
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
- if (this.smartProxy.settings?.enableDetailedLogging) {
57
- logger.log('debug', `MetricsCollector: Computing route connections`, {
58
- totalConnections: connections.size,
59
- component: 'metrics'
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
- 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';
79
+ byIP: (): Map<string, number> => {
80
+ const ipCounts = new Map<string, number>();
69
81
 
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
- });
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
- const current = routeCounts.get(routeName) || 0;
82
- routeCounts.set(routeName, current + 1);
83
- }
88
+ return ipCounts;
89
+ },
84
90
 
85
- // Cache and return
86
- this.cachedMetrics.connectionsByRoute = routeCounts;
87
- this.cachedMetrics.timestamp = now;
88
- return new Map(routeCounts);
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
- * Get connection counts grouped by IP address
93
- */
94
- public getConnectionsByIP(): Map<string, number> {
95
- const now = Date.now();
100
+ // Throughput metrics implementation
101
+ public throughput = {
102
+ instant: (): IThroughputData => {
103
+ return this.throughputTracker.getRate(1);
104
+ },
96
105
 
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
- }
106
+ recent: (): IThroughputData => {
107
+ return this.throughputTracker.getRate(10);
108
+ },
102
109
 
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
+ average: (): IThroughputData => {
111
+ return this.throughputTracker.getRate(60);
112
+ },
110
113
 
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
114
+ custom: (seconds: number): IThroughputData => {
115
+ return this.throughputTracker.getRate(seconds);
116
+ },
124
117
 
125
- // Add all terminated connections
126
- for (const reason in stats.incoming) {
127
- total += stats.incoming[reason];
128
- }
118
+ history: (seconds: number): Array<IThroughputHistoryPoint> => {
119
+ return this.throughputTracker.getHistory(seconds);
120
+ },
129
121
 
130
- return total;
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
- * 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;
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
- // Clean old timestamps
141
- this.requestTimestamps = this.requestTimestamps.filter(ts => ts > windowStart);
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
- // Calculate RPS based on window
144
- const requestsInWindow = this.requestTimestamps.length;
145
- return requestsInWindow / (this.RPS_WINDOW_SIZE / 1000);
146
- }
202
+ total: (): number => {
203
+ return this.totalRequests;
204
+ }
205
+ };
147
206
 
148
- /**
149
- * Record a new request for RPS tracking
150
- */
151
- public recordRequest(): void {
152
- const now = Date.now();
153
- this.requestTimestamps.push(now);
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
- // 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);
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
- * Get total throughput (bytes transferred)
165
- */
166
- public getThroughput(): { bytesIn: number; bytesOut: number } {
167
- let bytesIn = 0;
168
- let bytesOut = 0;
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
- // Sum bytes from all active connections
171
- for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
172
- bytesIn += record.bytesReceived;
173
- bytesOut += record.bytesSent;
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
- * Get throughput rate (bytes per second) for last minute
260
+ * Record a new request
181
261
  */
182
- public getThroughputRate(): { bytesInPerSec: number; bytesOutPerSec: number } {
262
+ public recordRequest(connectionId: string, routeName: string, remoteIP: string): void {
183
263
  const now = Date.now();
184
- let recentBytesIn = 0;
185
- let recentBytesOut = 0;
264
+ this.requestTimestamps.push(now);
265
+ this.totalRequests++;
186
266
 
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
- }
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
- return {
202
- bytesInPerSec: Math.round(recentBytesIn / 60),
203
- bytesOutPerSec: Math.round(recentBytesOut / 60)
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
- * Get top IPs by connection count
285
+ * Record bytes transferred for a connection
209
286
  */
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 }));
287
+ public recordBytes(connectionId: string, bytesIn: number, bytesOut: number): void {
288
+ // Update global throughput tracker
289
+ this.throughputTracker.recordBytes(bytesIn, bytesOut);
216
290
 
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;
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 old request timestamps
301
+ * Clean up tracking for a closed connection
231
302
  */
232
- private cleanupOldRequests(): void {
233
- const cutoff = Date.now() - this.RPS_WINDOW_SIZE;
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 and set up subscriptions
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
- // Subscribe to the newConnectionSubject from RouteConnectionHandler
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
- this.recordRequest();
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: record.routeConfig?.name || 'unknown',
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 and clean up resources
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 backward compatibility
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
  *