@push.rocks/smartproxy 19.5.25 → 19.6.0

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.
@@ -673,4 +673,52 @@ if (!record.connectionClosed && record.outgoing && record.bytesReceived > 0 && r
673
673
  - Connection is older than 60 seconds
674
674
  - Both sockets are still alive (not destroyed)
675
675
 
676
- This complements the zombie detection by handling cases where sockets remain technically alive but the connection is effectively dead.
676
+ This complements the zombie detection by handling cases where sockets remain technically alive but the connection is effectively dead.
677
+
678
+ ## 🚨 CRITICAL FIX: Cleanup Queue Bug (January 2025)
679
+
680
+ ### Critical Bug Found
681
+ The cleanup queue had a severe bug that caused connection accumulation when more than 100 connections needed cleanup:
682
+
683
+ ```typescript
684
+ // BUG: This cleared the ENTIRE queue after processing only the first batch!
685
+ const toCleanup = Array.from(this.cleanupQueue).slice(0, this.cleanupBatchSize);
686
+ this.cleanupQueue.clear(); // ❌ This discarded all connections beyond the first 100!
687
+ ```
688
+
689
+ ### Fix Implemented
690
+ ```typescript
691
+ // Now only removes the connections being processed
692
+ const toCleanup = Array.from(this.cleanupQueue).slice(0, this.cleanupBatchSize);
693
+ for (const connectionId of toCleanup) {
694
+ this.cleanupQueue.delete(connectionId); // ✅ Only remove what we process
695
+ const record = this.connectionRecords.get(connectionId);
696
+ if (record) {
697
+ this.cleanupConnection(record, record.incomingTerminationReason || 'normal');
698
+ }
699
+ }
700
+ ```
701
+
702
+ ### Impact
703
+ - **Before**: If 150 connections needed cleanup, only the first 100 would be processed and the remaining 50 would accumulate forever
704
+ - **After**: All connections are properly cleaned up in batches
705
+
706
+ ### Additional Improvements
707
+
708
+ 1. **Faster Inactivity Checks**: Reduced from 30s to 10s intervals
709
+ - Zombies and stuck connections are detected 3x faster
710
+ - Reduces the window for accumulation
711
+
712
+ 2. **Duplicate Prevention**: Added check in queueCleanup to prevent processing already-closed connections
713
+ - Prevents unnecessary work
714
+ - Ensures connections are only cleaned up once
715
+
716
+ ### Summary of All Fixes
717
+
718
+ 1. **Connection Timeout** (already documented) - Prevents accumulation when backends are unreachable
719
+ 2. **Zombie Detection** - Cleans up connections with destroyed sockets
720
+ 3. **Stuck Connection Detection** - Cleans up connections to hanging backends
721
+ 4. **Cleanup Queue Bug** - Ensures ALL connections get cleaned up, not just the first 100
722
+ 5. **Faster Detection** - Reduced check interval from 30s to 10s
723
+
724
+ These fixes combined should prevent connection accumulation in all known scenarios.
package/readme.md CHANGED
@@ -919,6 +919,124 @@ Available helper functions:
919
919
  })
920
920
  ```
921
921
 
922
+ ## Metrics and Monitoring
923
+
924
+ SmartProxy includes a comprehensive metrics collection system that provides real-time insights into proxy performance, connection statistics, and throughput data.
925
+
926
+ ### Getting Metrics
927
+
928
+ ```typescript
929
+ const proxy = new SmartProxy({ /* config */ });
930
+ await proxy.start();
931
+
932
+ // Access metrics through the getStats() method
933
+ const stats = proxy.getStats();
934
+
935
+ // Get current active connections
936
+ console.log(`Active connections: ${stats.getActiveConnections()}`);
937
+
938
+ // Get total connections since start
939
+ console.log(`Total connections: ${stats.getTotalConnections()}`);
940
+
941
+ // Get requests per second (RPS)
942
+ console.log(`Current RPS: ${stats.getRequestsPerSecond()}`);
943
+
944
+ // Get throughput data
945
+ const throughput = stats.getThroughput();
946
+ console.log(`Bytes received: ${throughput.bytesIn}`);
947
+ console.log(`Bytes sent: ${throughput.bytesOut}`);
948
+
949
+ // Get connections by route
950
+ const routeConnections = stats.getConnectionsByRoute();
951
+ for (const [route, count] of routeConnections) {
952
+ console.log(`Route ${route}: ${count} connections`);
953
+ }
954
+
955
+ // Get connections by IP address
956
+ const ipConnections = stats.getConnectionsByIP();
957
+ for (const [ip, count] of ipConnections) {
958
+ console.log(`IP ${ip}: ${count} connections`);
959
+ }
960
+ ```
961
+
962
+ ### Available Metrics
963
+
964
+ The `IProxyStats` interface provides the following methods:
965
+
966
+ - `getActiveConnections()`: Current number of active connections
967
+ - `getTotalConnections()`: Total connections handled since proxy start
968
+ - `getRequestsPerSecond()`: Current requests per second (1-minute average)
969
+ - `getThroughput()`: Total bytes transferred (in/out)
970
+ - `getConnectionsByRoute()`: Connection count per route
971
+ - `getConnectionsByIP()`: Connection count per client IP
972
+
973
+ ### Monitoring Example
974
+
975
+ ```typescript
976
+ // Create a monitoring loop
977
+ setInterval(() => {
978
+ const stats = proxy.getStats();
979
+
980
+ // Log key metrics
981
+ console.log({
982
+ timestamp: new Date().toISOString(),
983
+ activeConnections: stats.getActiveConnections(),
984
+ rps: stats.getRequestsPerSecond(),
985
+ throughput: stats.getThroughput()
986
+ });
987
+
988
+ // Check for high connection counts from specific IPs
989
+ const ipConnections = stats.getConnectionsByIP();
990
+ for (const [ip, count] of ipConnections) {
991
+ if (count > 100) {
992
+ console.warn(`High connection count from ${ip}: ${count}`);
993
+ }
994
+ }
995
+ }, 10000); // Every 10 seconds
996
+ ```
997
+
998
+ ### Exporting Metrics
999
+
1000
+ You can export metrics in various formats for external monitoring systems:
1001
+
1002
+ ```typescript
1003
+ // Export as JSON
1004
+ app.get('/metrics.json', (req, res) => {
1005
+ const stats = proxy.getStats();
1006
+ res.json({
1007
+ activeConnections: stats.getActiveConnections(),
1008
+ totalConnections: stats.getTotalConnections(),
1009
+ requestsPerSecond: stats.getRequestsPerSecond(),
1010
+ throughput: stats.getThroughput(),
1011
+ connectionsByRoute: Object.fromEntries(stats.getConnectionsByRoute()),
1012
+ connectionsByIP: Object.fromEntries(stats.getConnectionsByIP())
1013
+ });
1014
+ });
1015
+
1016
+ // Export as Prometheus format
1017
+ app.get('/metrics', (req, res) => {
1018
+ const stats = proxy.getStats();
1019
+ res.set('Content-Type', 'text/plain');
1020
+ res.send(`
1021
+ # HELP smartproxy_active_connections Current active connections
1022
+ # TYPE smartproxy_active_connections gauge
1023
+ smartproxy_active_connections ${stats.getActiveConnections()}
1024
+
1025
+ # HELP smartproxy_requests_per_second Current requests per second
1026
+ # TYPE smartproxy_requests_per_second gauge
1027
+ smartproxy_requests_per_second ${stats.getRequestsPerSecond()}
1028
+
1029
+ # HELP smartproxy_bytes_in Total bytes received
1030
+ # TYPE smartproxy_bytes_in counter
1031
+ smartproxy_bytes_in ${stats.getThroughput().bytesIn}
1032
+
1033
+ # HELP smartproxy_bytes_out Total bytes sent
1034
+ # TYPE smartproxy_bytes_out counter
1035
+ smartproxy_bytes_out ${stats.getThroughput().bytesOut}
1036
+ `);
1037
+ });
1038
+ ```
1039
+
922
1040
  ## Other Components
923
1041
 
924
1042
  While SmartProxy provides a unified API for most needs, you can also use individual components: