@@ -30,23 +30,91 @@ import { ERRORS, StorageBackendError } from '@internal/errors'
30
30
import { getConfig } from '../../config'
31
31
import Agent , { HttpsAgent } from 'agentkeepalive'
32
32
import { Readable } from 'stream'
33
+ import {
34
+ HttpPoolErrorGauge ,
35
+ HttpPoolFreeSocketsGauge ,
36
+ HttpPoolPendingRequestsGauge ,
37
+ HttpPoolSocketsGauge ,
38
+ } from '@internal/monitoring/metrics'
39
+
40
+ const { storageS3MaxSockets, region } = getConfig ( )
41
+
42
+ const watchers : NodeJS . Timeout [ ] = [ ]
33
43
34
- const { storageS3MaxSockets } = getConfig ( )
44
+ process . once ( 'SIGTERM' , ( ) => {
45
+ watchers . forEach ( ( watcher ) => {
46
+ clearInterval ( watcher )
47
+ } )
48
+ } )
35
49
36
50
/**
37
51
* Creates an agent for the given protocol
38
- * @param protocol
52
+ * @param name
39
53
*/
40
- export function createAgent ( protocol : 'http' | 'https' ) {
54
+ export function createAgent ( name : string ) {
41
55
const agentOptions = {
42
56
maxSockets : storageS3MaxSockets ,
43
57
keepAlive : true ,
44
58
keepAliveMsecs : 1000 ,
59
+ freeSocketTimeout : 1000 * 15 ,
60
+ }
61
+
62
+ const httpAgent = new Agent ( agentOptions )
63
+ const httpsAgent = new HttpsAgent ( agentOptions )
64
+
65
+ if ( httpsAgent ) {
66
+ const watcher = setInterval ( ( ) => {
67
+ const httpStatus = httpAgent . getCurrentStatus ( )
68
+ const httpsStatus = httpsAgent . getCurrentStatus ( )
69
+ updateHttpPoolMetrics ( name , 'http' , httpStatus )
70
+ updateHttpPoolMetrics ( name , 'https' , httpsStatus )
71
+ } , 5000 )
72
+
73
+ watchers . push ( watcher )
74
+ }
75
+
76
+ return { httpAgent, httpsAgent }
77
+ }
78
+
79
+ // Function to update Prometheus metrics based on the current status of the agent
80
+ function updateHttpPoolMetrics ( name : string , protocol : string , status : Agent . AgentStatus ) : void {
81
+ // Calculate the number of busy sockets by iterating over the `sockets` object
82
+ let busySocketCount = 0
83
+ for ( const host in status . sockets ) {
84
+ if ( status . sockets . hasOwnProperty ( host ) ) {
85
+ busySocketCount += status . sockets [ host ]
86
+ }
45
87
}
46
88
47
- return protocol === 'http'
48
- ? { httpAgent : new Agent ( agentOptions ) }
49
- : { httpsAgent : new HttpsAgent ( agentOptions ) }
89
+ // Calculate the number of free sockets by iterating over the `freeSockets` object
90
+ let freeSocketCount = 0
91
+ for ( const host in status . freeSockets ) {
92
+ if ( status . freeSockets . hasOwnProperty ( host ) ) {
93
+ freeSocketCount += status . freeSockets [ host ]
94
+ }
95
+ }
96
+
97
+ // Calculate the number of pending requests by iterating over the `requests` object
98
+ let pendingRequestCount = 0
99
+ for ( const host in status . requests ) {
100
+ if ( status . requests . hasOwnProperty ( host ) ) {
101
+ pendingRequestCount += status . requests [ host ]
102
+ }
103
+ }
104
+
105
+ // Update the metrics with calculated values
106
+ HttpPoolSocketsGauge . set ( { name, region, protocol } , busySocketCount )
107
+ HttpPoolFreeSocketsGauge . set ( { name, region, protocol } , freeSocketCount )
108
+ HttpPoolPendingRequestsGauge . set ( { name, region } , pendingRequestCount )
109
+ HttpPoolErrorGauge . set ( { name, region, type : 'socket_error' , protocol } , status . errorSocketCount )
110
+ HttpPoolErrorGauge . set (
111
+ { name, region, type : 'timeout_socket_error' , protocol } ,
112
+ status . timeoutSocketCount
113
+ )
114
+ HttpPoolErrorGauge . set (
115
+ { name, region, type : 'create_socket_error' , protocol } ,
116
+ status . createSocketErrorCount
117
+ )
50
118
}
51
119
52
120
export interface S3ClientOptions {
@@ -56,7 +124,7 @@ export interface S3ClientOptions {
56
124
accessKey ?: string
57
125
secretKey ?: string
58
126
role ?: string
59
- httpAgent ?: { httpAgent : Agent } | { httpsAgent : HttpsAgent }
127
+ httpAgent ?: { httpAgent : Agent ; httpsAgent : HttpsAgent }
60
128
requestTimeout ?: number
61
129
downloadTimeout ?: number
62
130
uploadTimeout ?: number
@@ -75,18 +143,21 @@ export class S3Backend implements StorageBackendAdapter {
75
143
// Default client for API operations
76
144
this . client = this . createS3Client ( {
77
145
...options ,
146
+ name : 's3_default' ,
78
147
requestTimeout : options . requestTimeout ,
79
148
} )
80
149
81
150
// Upload client exclusively for upload operations
82
151
this . uploadClient = this . createS3Client ( {
83
152
...options ,
153
+ name : 's3_upload' ,
84
154
requestTimeout : options . uploadTimeout ,
85
155
} )
86
156
87
157
// Download client exclusively for download operations
88
158
this . downloadClient = this . createS3Client ( {
89
159
...options ,
160
+ name : 's3_download' ,
90
161
requestTimeout : options . downloadTimeout ,
91
162
} )
92
163
}
@@ -144,14 +215,16 @@ export class S3Backend implements StorageBackendAdapter {
144
215
* @param body
145
216
* @param contentType
146
217
* @param cacheControl
218
+ * @param signal
147
219
*/
148
220
async uploadObject (
149
221
bucketName : string ,
150
222
key : string ,
151
223
version : string | undefined ,
152
224
body : NodeJS . ReadableStream ,
153
225
contentType : string ,
154
- cacheControl : string
226
+ cacheControl : string ,
227
+ signal ?: AbortSignal
155
228
) : Promise < ObjectMetadata > {
156
229
try {
157
230
const paralellUploadS3 = new Upload ( {
@@ -166,6 +239,14 @@ export class S3Backend implements StorageBackendAdapter {
166
239
} ,
167
240
} )
168
241
242
+ signal ?. addEventListener (
243
+ 'abort' ,
244
+ ( ) => {
245
+ paralellUploadS3 . abort ( )
246
+ } ,
247
+ { once : true }
248
+ )
249
+
169
250
const data = ( await paralellUploadS3 . done ( ) ) as CompleteMultipartUploadCommandOutput
170
251
171
252
const metadata = await this . headObject ( bucketName , key , version )
@@ -451,10 +532,8 @@ export class S3Backend implements StorageBackendAdapter {
451
532
}
452
533
}
453
534
454
- protected createS3Client ( options : S3ClientOptions ) {
455
- const storageS3Protocol = options . endpoint ?. includes ( 'http://' ) ? 'http' : 'https'
456
-
457
- const agent = options . httpAgent ? options . httpAgent : createAgent ( storageS3Protocol )
535
+ protected createS3Client ( options : S3ClientOptions & { name : string } ) {
536
+ const agent = options . httpAgent ?? createAgent ( options . name )
458
537
459
538
const params : S3ClientConfig = {
460
539
region : options . region ,
0 commit comments