@devilsdev/rag-pipeline-utils 2.2.1 → 2.3.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.
- package/README.md +244 -11
- package/contracts/contract-schema.json +189 -0
- package/dist/index.cjs +19 -0
- package/dist/index.mjs +15 -1
- package/package.json +3 -2
- package/src/cli/commands/wizard.js +627 -0
- package/src/cli/input-sanitization-middleware.js +356 -0
- package/src/cli/templates/development.template.json +64 -0
- package/src/cli/templates/minimal.template.json +33 -0
- package/src/cli/templates/production.template.json +104 -0
- package/src/cli/templates/testing.template.json +45 -0
- package/src/config/connection-pool-config.json +259 -0
- package/src/config/memory-optimization.js +414 -0
- package/src/config/redaction-patterns.json +150 -0
- package/src/config/sanitization-rules.json +323 -0
- package/src/core/plugin-registry.js +266 -20
- package/src/dag/core/error-context.js +391 -0
- package/src/dag/core/execution-scheduler.js +350 -0
- package/src/dag/core/topology-validation.js +415 -0
- package/src/dag/dag-engine.js +26 -424
- package/src/dev/README.md +126 -0
- package/src/dev/dev-server.js +356 -0
- package/src/dev/examples/hot-reload-usage.js +355 -0
- package/src/dev/hot-reload.js +807 -0
- package/src/enterprise/sso-integration.js +67 -25
- package/src/index.js +19 -0
- package/src/observability/event-logger.js +62 -2
- package/src/plugins/optimized-openai-embedder.js +169 -0
- package/src/security/jwt-config.js +431 -0
- package/src/security/jwt-validator.js +701 -0
- package/src/utils/api-client-pool-integration.js +436 -0
- package/src/utils/batch-processor.js +710 -0
- package/src/utils/connection-pool.js +605 -0
- package/src/utils/contract-validator.js +460 -0
- package/src/utils/error-formatter.js +603 -0
- package/src/utils/examples/error-formatter-usage.js +351 -0
- package/src/utils/input-sanitizer.js +967 -0
- package/src/utils/memory-monitor.js +800 -0
- package/src/utils/secure-logger-config.js +352 -0
- package/src/utils/secure-logger.js +576 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Connection Pool Manager
|
|
5
|
+
*
|
|
6
|
+
* High-performance HTTP/HTTPS connection pooling for external API calls.
|
|
7
|
+
* Provides connection reuse, keep-alive, and intelligent pool management
|
|
8
|
+
* to reduce latency and improve throughput.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Connection reuse with keep-alive
|
|
12
|
+
* - Configurable pool size limits
|
|
13
|
+
* - Timeout management
|
|
14
|
+
* - Pool utilization metrics
|
|
15
|
+
* - Graceful degradation
|
|
16
|
+
* - Per-host connection limits
|
|
17
|
+
*
|
|
18
|
+
* @module utils/connection-pool
|
|
19
|
+
* @since 2.3.0
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const http = require('http');
|
|
23
|
+
const https = require('https');
|
|
24
|
+
const { EventEmitter } = require('events');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Connection Pool Configuration
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_CONFIG = {
|
|
30
|
+
// Pool size limits
|
|
31
|
+
maxSockets: 50, // Maximum total sockets
|
|
32
|
+
maxFreeSockets: 10, // Maximum idle sockets
|
|
33
|
+
maxSocketsPerHost: 10, // Maximum sockets per host
|
|
34
|
+
|
|
35
|
+
// Timeout settings (milliseconds)
|
|
36
|
+
timeout: 30000, // Socket timeout (30s)
|
|
37
|
+
freeSocketTimeout: 15000, // Idle socket timeout (15s)
|
|
38
|
+
keepAliveTimeout: 60000, // Keep-alive timeout (60s)
|
|
39
|
+
|
|
40
|
+
// Keep-alive settings
|
|
41
|
+
keepAlive: true,
|
|
42
|
+
keepAliveMsecs: 1000,
|
|
43
|
+
|
|
44
|
+
// Scheduling algorithm
|
|
45
|
+
scheduling: 'lifo', // 'lifo' or 'fifo'
|
|
46
|
+
|
|
47
|
+
// Monitoring
|
|
48
|
+
trackMetrics: true,
|
|
49
|
+
metricsInterval: 60000, // Metrics collection interval (1 minute)
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Connection Pool Manager Class
|
|
54
|
+
*/
|
|
55
|
+
class ConnectionPoolManager extends EventEmitter {
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
super();
|
|
58
|
+
|
|
59
|
+
this.config = {
|
|
60
|
+
...DEFAULT_CONFIG,
|
|
61
|
+
...options,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// HTTP and HTTPS agents
|
|
65
|
+
this.httpAgent = null;
|
|
66
|
+
this.httpsAgent = null;
|
|
67
|
+
|
|
68
|
+
// Pool state
|
|
69
|
+
this.pools = new Map(); // host -> pool state
|
|
70
|
+
this.activeConnections = 0;
|
|
71
|
+
this.totalConnections = 0;
|
|
72
|
+
|
|
73
|
+
// Metrics
|
|
74
|
+
this.metrics = {
|
|
75
|
+
totalRequests: 0,
|
|
76
|
+
cachedConnections: 0,
|
|
77
|
+
newConnections: 0,
|
|
78
|
+
connectionReuse: 0,
|
|
79
|
+
timeouts: 0,
|
|
80
|
+
errors: 0,
|
|
81
|
+
avgResponseTime: 0,
|
|
82
|
+
totalResponseTime: 0,
|
|
83
|
+
poolExhausted: 0,
|
|
84
|
+
connectionsCreated: 0,
|
|
85
|
+
connectionsDestroyed: 0,
|
|
86
|
+
currentPoolSize: 0,
|
|
87
|
+
peakPoolSize: 0,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Performance tracking
|
|
91
|
+
this.requestTimings = [];
|
|
92
|
+
this.maxTimingsSamples = 1000;
|
|
93
|
+
|
|
94
|
+
// Initialize agents
|
|
95
|
+
this._initializeAgents();
|
|
96
|
+
|
|
97
|
+
// Start metrics collection
|
|
98
|
+
if (this.config.trackMetrics) {
|
|
99
|
+
this._startMetricsCollection();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Initialize HTTP and HTTPS agents with connection pooling
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
_initializeAgents() {
|
|
108
|
+
// HTTP Agent configuration
|
|
109
|
+
const httpConfig = {
|
|
110
|
+
keepAlive: this.config.keepAlive,
|
|
111
|
+
keepAliveMsecs: this.config.keepAliveMsecs,
|
|
112
|
+
maxSockets: this.config.maxSockets,
|
|
113
|
+
maxFreeSockets: this.config.maxFreeSockets,
|
|
114
|
+
timeout: this.config.timeout,
|
|
115
|
+
freeSocketTimeout: this.config.freeSocketTimeout,
|
|
116
|
+
scheduling: this.config.scheduling,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// HTTPS Agent configuration (inherits from HTTP)
|
|
120
|
+
const httpsConfig = {
|
|
121
|
+
...httpConfig,
|
|
122
|
+
// Additional HTTPS-specific settings
|
|
123
|
+
rejectUnauthorized: true,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Create agents
|
|
127
|
+
this.httpAgent = new http.Agent(httpConfig);
|
|
128
|
+
this.httpsAgent = new https.Agent(httpsConfig);
|
|
129
|
+
|
|
130
|
+
// Instrument agents for metrics
|
|
131
|
+
this._instrumentAgent(this.httpAgent, 'http');
|
|
132
|
+
this._instrumentAgent(this.httpsAgent, 'https');
|
|
133
|
+
|
|
134
|
+
this.emit('agents_initialized', {
|
|
135
|
+
http: this.httpAgent,
|
|
136
|
+
https: this.httpsAgent,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Instrument agent for metrics collection
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_instrumentAgent(agent, protocol) {
|
|
145
|
+
// Track socket creation
|
|
146
|
+
const originalCreateConnection = agent.createConnection;
|
|
147
|
+
agent.createConnection = (...args) => {
|
|
148
|
+
this.metrics.connectionsCreated++;
|
|
149
|
+
this.metrics.newConnections++;
|
|
150
|
+
this.activeConnections++;
|
|
151
|
+
this.totalConnections++;
|
|
152
|
+
|
|
153
|
+
// Update peak pool size
|
|
154
|
+
if (this.activeConnections > this.metrics.peakPoolSize) {
|
|
155
|
+
this.metrics.peakPoolSize = this.activeConnections;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.emit('connection_created', {
|
|
159
|
+
protocol,
|
|
160
|
+
active: this.activeConnections,
|
|
161
|
+
total: this.totalConnections,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return originalCreateConnection.apply(agent, args);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Track socket lifecycle
|
|
168
|
+
agent.on('free', (socket, options) => {
|
|
169
|
+
this.activeConnections--;
|
|
170
|
+
this.metrics.cachedConnections++;
|
|
171
|
+
|
|
172
|
+
this.emit('connection_freed', {
|
|
173
|
+
protocol,
|
|
174
|
+
host: options.host,
|
|
175
|
+
port: options.port,
|
|
176
|
+
active: this.activeConnections,
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Track socket removal
|
|
181
|
+
const originalRemoveSocket = agent.removeSocket;
|
|
182
|
+
agent.removeSocket = function (socket, options) {
|
|
183
|
+
this.metrics.connectionsDestroyed++;
|
|
184
|
+
this.activeConnections = Math.max(0, this.activeConnections - 1);
|
|
185
|
+
|
|
186
|
+
this.emit('connection_removed', {
|
|
187
|
+
protocol,
|
|
188
|
+
host: options?.host,
|
|
189
|
+
active: this.activeConnections,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return originalRemoveSocket.call(agent, socket, options);
|
|
193
|
+
}.bind(this);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get HTTP agent
|
|
198
|
+
*
|
|
199
|
+
* @returns {http.Agent} HTTP agent with connection pooling
|
|
200
|
+
*/
|
|
201
|
+
getHttpAgent() {
|
|
202
|
+
return this.httpAgent;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get HTTPS agent
|
|
207
|
+
*
|
|
208
|
+
* @returns {https.Agent} HTTPS agent with connection pooling
|
|
209
|
+
*/
|
|
210
|
+
getHttpsAgent() {
|
|
211
|
+
return this.httpsAgent;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get agent for URL
|
|
216
|
+
*
|
|
217
|
+
* @param {string} url - URL to get agent for
|
|
218
|
+
* @returns {http.Agent|https.Agent} Appropriate agent
|
|
219
|
+
*/
|
|
220
|
+
getAgentForUrl(url) {
|
|
221
|
+
const protocol = url.startsWith('https') ? 'https' : 'http';
|
|
222
|
+
return protocol === 'https' ? this.httpsAgent : this.httpAgent;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Track request timing
|
|
227
|
+
*
|
|
228
|
+
* @param {number} duration - Request duration in milliseconds
|
|
229
|
+
*/
|
|
230
|
+
trackRequest(duration) {
|
|
231
|
+
this.metrics.totalRequests++;
|
|
232
|
+
this.metrics.totalResponseTime += duration;
|
|
233
|
+
this.metrics.avgResponseTime =
|
|
234
|
+
this.metrics.totalResponseTime / this.metrics.totalRequests;
|
|
235
|
+
|
|
236
|
+
// Store timing sample (with circular buffer)
|
|
237
|
+
this.requestTimings.push(duration);
|
|
238
|
+
if (this.requestTimings.length > this.maxTimingsSamples) {
|
|
239
|
+
this.requestTimings.shift();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Check for connection reuse
|
|
243
|
+
const reuseRate =
|
|
244
|
+
this.metrics.cachedConnections / Math.max(1, this.metrics.totalRequests);
|
|
245
|
+
if (reuseRate > 0) {
|
|
246
|
+
this.metrics.connectionReuse = reuseRate * 100; // Percentage
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.emit('request_completed', {
|
|
250
|
+
duration,
|
|
251
|
+
avgResponseTime: this.metrics.avgResponseTime,
|
|
252
|
+
connectionReuse: this.metrics.connectionReuse,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Track request error
|
|
258
|
+
*
|
|
259
|
+
* @param {Error} error - Request error
|
|
260
|
+
* @param {string} type - Error type (timeout, network, etc.)
|
|
261
|
+
*/
|
|
262
|
+
trackError(error, type = 'error') {
|
|
263
|
+
this.metrics.errors++;
|
|
264
|
+
|
|
265
|
+
if (type === 'timeout') {
|
|
266
|
+
this.metrics.timeouts++;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.emit('request_error', {
|
|
270
|
+
error: error.message,
|
|
271
|
+
type,
|
|
272
|
+
totalErrors: this.metrics.errors,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Track pool exhaustion
|
|
278
|
+
*/
|
|
279
|
+
trackPoolExhaustion() {
|
|
280
|
+
this.metrics.poolExhausted++;
|
|
281
|
+
|
|
282
|
+
this.emit('pool_exhausted', {
|
|
283
|
+
maxSockets: this.config.maxSockets,
|
|
284
|
+
active: this.activeConnections,
|
|
285
|
+
exhaustedCount: this.metrics.poolExhausted,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get current pool metrics
|
|
291
|
+
*
|
|
292
|
+
* @returns {Object} Current metrics
|
|
293
|
+
*/
|
|
294
|
+
getMetrics() {
|
|
295
|
+
// Calculate percentiles from timing samples
|
|
296
|
+
const sortedTimings = [...this.requestTimings].sort((a, b) => a - b);
|
|
297
|
+
const p50 = sortedTimings[Math.floor(sortedTimings.length * 0.5)] || 0;
|
|
298
|
+
const p95 = sortedTimings[Math.floor(sortedTimings.length * 0.95)] || 0;
|
|
299
|
+
const p99 = sortedTimings[Math.floor(sortedTimings.length * 0.99)] || 0;
|
|
300
|
+
|
|
301
|
+
// Get agent statistics
|
|
302
|
+
const httpSockets = this._getAgentStats(this.httpAgent);
|
|
303
|
+
const httpsSockets = this._getAgentStats(this.httpsAgent);
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
...this.metrics,
|
|
307
|
+
currentPoolSize: this.activeConnections,
|
|
308
|
+
poolUtilization: (this.activeConnections / this.config.maxSockets) * 100,
|
|
309
|
+
connectionReuseRate: this.metrics.connectionReuse,
|
|
310
|
+
|
|
311
|
+
// Response time percentiles
|
|
312
|
+
responseTime: {
|
|
313
|
+
avg: this.metrics.avgResponseTime,
|
|
314
|
+
p50,
|
|
315
|
+
p95,
|
|
316
|
+
p99,
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// Agent-specific stats
|
|
320
|
+
agents: {
|
|
321
|
+
http: httpSockets,
|
|
322
|
+
https: httpsSockets,
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
// Pool configuration
|
|
326
|
+
config: {
|
|
327
|
+
maxSockets: this.config.maxSockets,
|
|
328
|
+
maxFreeSockets: this.config.maxFreeSockets,
|
|
329
|
+
timeout: this.config.timeout,
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get agent statistics
|
|
336
|
+
* @private
|
|
337
|
+
*/
|
|
338
|
+
_getAgentStats(agent) {
|
|
339
|
+
const sockets = agent.sockets;
|
|
340
|
+
const freeSockets = agent.freeSockets;
|
|
341
|
+
const requests = agent.requests;
|
|
342
|
+
|
|
343
|
+
let totalSockets = 0;
|
|
344
|
+
let totalFreeSockets = 0;
|
|
345
|
+
let totalRequests = 0;
|
|
346
|
+
|
|
347
|
+
// Count sockets per host
|
|
348
|
+
for (const [, socketArray] of Object.entries(sockets)) {
|
|
349
|
+
totalSockets += socketArray.length;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Count free sockets per host
|
|
353
|
+
for (const [, socketArray] of Object.entries(freeSockets)) {
|
|
354
|
+
totalFreeSockets += socketArray.length;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Count pending requests per host
|
|
358
|
+
for (const [, requestArray] of Object.entries(requests)) {
|
|
359
|
+
totalRequests += requestArray.length;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
active: totalSockets,
|
|
364
|
+
idle: totalFreeSockets,
|
|
365
|
+
pending: totalRequests,
|
|
366
|
+
total: totalSockets + totalFreeSockets,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get pool status
|
|
372
|
+
*
|
|
373
|
+
* @returns {Object} Pool status information
|
|
374
|
+
*/
|
|
375
|
+
getStatus() {
|
|
376
|
+
const metrics = this.getMetrics();
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
healthy: metrics.poolUtilization < 90,
|
|
380
|
+
utilization: metrics.poolUtilization,
|
|
381
|
+
activeConnections: this.activeConnections,
|
|
382
|
+
maxConnections: this.config.maxSockets,
|
|
383
|
+
connectionReuse: metrics.connectionReuseRate,
|
|
384
|
+
avgResponseTime: metrics.avgResponseTime,
|
|
385
|
+
errors: metrics.errors,
|
|
386
|
+
timeouts: metrics.timeouts,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Reset metrics
|
|
392
|
+
*/
|
|
393
|
+
resetMetrics() {
|
|
394
|
+
const currentPoolSize = this.activeConnections;
|
|
395
|
+
const peakPoolSize = this.metrics.peakPoolSize;
|
|
396
|
+
|
|
397
|
+
this.metrics = {
|
|
398
|
+
totalRequests: 0,
|
|
399
|
+
cachedConnections: 0,
|
|
400
|
+
newConnections: 0,
|
|
401
|
+
connectionReuse: 0,
|
|
402
|
+
timeouts: 0,
|
|
403
|
+
errors: 0,
|
|
404
|
+
avgResponseTime: 0,
|
|
405
|
+
totalResponseTime: 0,
|
|
406
|
+
poolExhausted: 0,
|
|
407
|
+
connectionsCreated: 0,
|
|
408
|
+
connectionsDestroyed: 0,
|
|
409
|
+
currentPoolSize,
|
|
410
|
+
peakPoolSize,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
this.requestTimings = [];
|
|
414
|
+
|
|
415
|
+
this.emit('metrics_reset');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Start periodic metrics collection
|
|
420
|
+
* @private
|
|
421
|
+
*/
|
|
422
|
+
_startMetricsCollection() {
|
|
423
|
+
this.metricsInterval = setInterval(() => {
|
|
424
|
+
const metrics = this.getMetrics();
|
|
425
|
+
|
|
426
|
+
this.emit('metrics_collected', metrics);
|
|
427
|
+
|
|
428
|
+
// Warn if pool utilization is high
|
|
429
|
+
if (metrics.poolUtilization > 80) {
|
|
430
|
+
this.emit('high_utilization', {
|
|
431
|
+
utilization: metrics.poolUtilization,
|
|
432
|
+
active: this.activeConnections,
|
|
433
|
+
max: this.config.maxSockets,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Warn if error rate is high
|
|
438
|
+
const errorRate =
|
|
439
|
+
(metrics.errors / Math.max(1, metrics.totalRequests)) * 100;
|
|
440
|
+
if (errorRate > 5) {
|
|
441
|
+
this.emit('high_error_rate', {
|
|
442
|
+
errorRate,
|
|
443
|
+
errors: metrics.errors,
|
|
444
|
+
requests: metrics.totalRequests,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}, this.config.metricsInterval);
|
|
448
|
+
|
|
449
|
+
// Don't block process exit
|
|
450
|
+
if (this.metricsInterval.unref) {
|
|
451
|
+
this.metricsInterval.unref();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Destroy all connections and cleanup
|
|
457
|
+
*/
|
|
458
|
+
destroy() {
|
|
459
|
+
// Stop metrics collection
|
|
460
|
+
if (this.metricsInterval) {
|
|
461
|
+
clearInterval(this.metricsInterval);
|
|
462
|
+
this.metricsInterval = null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Destroy agents
|
|
466
|
+
if (this.httpAgent) {
|
|
467
|
+
this.httpAgent.destroy();
|
|
468
|
+
this.httpAgent = null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (this.httpsAgent) {
|
|
472
|
+
this.httpsAgent.destroy();
|
|
473
|
+
this.httpsAgent = null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Clear state
|
|
477
|
+
this.pools.clear();
|
|
478
|
+
this.activeConnections = 0;
|
|
479
|
+
this.requestTimings = [];
|
|
480
|
+
|
|
481
|
+
this.emit('destroyed');
|
|
482
|
+
this.removeAllListeners();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Get connection pool report
|
|
487
|
+
*
|
|
488
|
+
* @returns {string} Human-readable pool report
|
|
489
|
+
*/
|
|
490
|
+
getReport() {
|
|
491
|
+
const metrics = this.getMetrics();
|
|
492
|
+
const status = this.getStatus();
|
|
493
|
+
|
|
494
|
+
return `
|
|
495
|
+
Connection Pool Report
|
|
496
|
+
======================
|
|
497
|
+
Status: ${status.healthy ? 'Healthy ✓' : 'Warning ⚠'}
|
|
498
|
+
Pool Utilization: ${status.utilization.toFixed(1)}%
|
|
499
|
+
Active Connections: ${status.activeConnections} / ${status.maxConnections}
|
|
500
|
+
Connection Reuse Rate: ${status.connectionReuse.toFixed(1)}%
|
|
501
|
+
|
|
502
|
+
Performance Metrics
|
|
503
|
+
-------------------
|
|
504
|
+
Total Requests: ${metrics.totalRequests}
|
|
505
|
+
Avg Response Time: ${metrics.avgResponseTime.toFixed(2)}ms
|
|
506
|
+
P50: ${metrics.responseTime.p50.toFixed(2)}ms
|
|
507
|
+
P95: ${metrics.responseTime.p95.toFixed(2)}ms
|
|
508
|
+
P99: ${metrics.responseTime.p99.toFixed(2)}ms
|
|
509
|
+
|
|
510
|
+
Connection Stats
|
|
511
|
+
----------------
|
|
512
|
+
New Connections: ${metrics.newConnections}
|
|
513
|
+
Cached Connections: ${metrics.cachedConnections}
|
|
514
|
+
Connections Created: ${metrics.connectionsCreated}
|
|
515
|
+
Connections Destroyed: ${metrics.connectionsDestroyed}
|
|
516
|
+
Peak Pool Size: ${metrics.peakPoolSize}
|
|
517
|
+
|
|
518
|
+
Errors
|
|
519
|
+
------
|
|
520
|
+
Total Errors: ${metrics.errors}
|
|
521
|
+
Timeouts: ${metrics.timeouts}
|
|
522
|
+
Pool Exhausted: ${metrics.poolExhausted}
|
|
523
|
+
|
|
524
|
+
Agent Stats
|
|
525
|
+
-----------
|
|
526
|
+
HTTP: Active: ${metrics.agents.http.active}, Idle: ${metrics.agents.http.idle}, Pending: ${metrics.agents.http.pending}
|
|
527
|
+
HTTPS: Active: ${metrics.agents.https.active}, Idle: ${metrics.agents.https.idle}, Pending: ${metrics.agents.https.pending}
|
|
528
|
+
`.trim();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Global connection pool instance
|
|
534
|
+
*/
|
|
535
|
+
let globalPool = null;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get or create global connection pool
|
|
539
|
+
*
|
|
540
|
+
* @param {Object} options - Pool configuration options
|
|
541
|
+
* @returns {ConnectionPoolManager} Global pool instance
|
|
542
|
+
*/
|
|
543
|
+
function getGlobalPool(options = {}) {
|
|
544
|
+
if (!globalPool) {
|
|
545
|
+
globalPool = new ConnectionPoolManager(options);
|
|
546
|
+
}
|
|
547
|
+
return globalPool;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Reset global connection pool
|
|
552
|
+
*/
|
|
553
|
+
function resetGlobalPool() {
|
|
554
|
+
if (globalPool) {
|
|
555
|
+
globalPool.destroy();
|
|
556
|
+
globalPool = null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Create fetch wrapper with connection pooling
|
|
562
|
+
*
|
|
563
|
+
* @param {ConnectionPoolManager} pool - Connection pool manager
|
|
564
|
+
* @returns {Function} Fetch function with pooling
|
|
565
|
+
*/
|
|
566
|
+
function createPooledFetch(pool) {
|
|
567
|
+
return async function pooledFetch(url, options = {}) {
|
|
568
|
+
const agent = pool.getAgentForUrl(url);
|
|
569
|
+
const startTime = Date.now();
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
// Add agent to fetch options
|
|
573
|
+
const fetchOptions = {
|
|
574
|
+
...options,
|
|
575
|
+
agent,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// Make request (assuming fetch or node-fetch is available)
|
|
579
|
+
const response = await fetch(url, fetchOptions);
|
|
580
|
+
|
|
581
|
+
// Track successful request
|
|
582
|
+
const duration = Date.now() - startTime;
|
|
583
|
+
pool.trackRequest(duration);
|
|
584
|
+
|
|
585
|
+
return response;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
// Track error
|
|
588
|
+
const errorType =
|
|
589
|
+
error.code === 'ETIMEDOUT' || error.code === 'ESOCKETTIMEDOUT'
|
|
590
|
+
? 'timeout'
|
|
591
|
+
: 'error';
|
|
592
|
+
pool.trackError(error, errorType);
|
|
593
|
+
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
module.exports = {
|
|
600
|
+
ConnectionPoolManager,
|
|
601
|
+
getGlobalPool,
|
|
602
|
+
resetGlobalPool,
|
|
603
|
+
createPooledFetch,
|
|
604
|
+
DEFAULT_CONFIG,
|
|
605
|
+
};
|