@adalo/metrics 0.1.56 → 0.1.58
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/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -1
- package/lib/metricsQueueRedisClient.d.ts +27 -0
- package/lib/metricsQueueRedisClient.d.ts.map +1 -0
- package/lib/metricsQueueRedisClient.js +181 -0
- package/lib/metricsQueueRedisClient.js.map +1 -0
- package/lib/metricsRedisClient.d.ts +28 -20
- package/lib/metricsRedisClient.d.ts.map +1 -1
- package/lib/metricsRedisClient.js +33 -118
- package/lib/metricsRedisClient.js.map +1 -1
- package/lib/redisUtils.d.ts +13 -1
- package/lib/redisUtils.d.ts.map +1 -1
- package/lib/redisUtils.js +47 -2
- package/lib/redisUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/metricsQueueRedisClient.js +222 -0
- package/src/metricsRedisClient.js +36 -163
- package/src/redisUtils.js +58 -2
|
@@ -1,89 +1,43 @@
|
|
|
1
|
-
const Queue = require('bee-queue')
|
|
2
1
|
const { MetricsClient } = require('.')
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* RedisMetricsClient extends MetricsClient to collect
|
|
6
|
-
* Redis
|
|
5
|
+
* Redis metrics periodically and push them to Prometheus Pushgateway.
|
|
7
6
|
*
|
|
8
7
|
* @extends MetricsClient
|
|
9
8
|
*/
|
|
10
9
|
class RedisMetricsClient extends MetricsClient {
|
|
11
10
|
/**
|
|
12
|
-
* @param {Object} options
|
|
11
|
+
* @param {Object} options
|
|
13
12
|
* @param {any} options.redisClient - Redis client instance (required)
|
|
14
|
-
* @param {
|
|
13
|
+
* @param {string} [options.appName] - Application name (from MetricsClient)
|
|
14
|
+
* @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
|
|
15
|
+
* @param {string} [options.processType] - Process type (from MetricsClient)
|
|
16
|
+
* @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
|
|
17
|
+
* @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
|
|
18
|
+
* @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
|
|
19
|
+
* @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
|
|
20
|
+
* @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)
|
|
21
|
+
* @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)
|
|
22
|
+
* @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)
|
|
23
|
+
* @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)
|
|
15
24
|
*/
|
|
16
|
-
constructor({ redisClient, metricsConfig
|
|
17
|
-
const
|
|
25
|
+
constructor({ redisClient, ...metricsConfig } = {}) {
|
|
26
|
+
const intervalSec =
|
|
18
27
|
metricsConfig.intervalSec ||
|
|
19
28
|
parseInt(process.env.METRICS_QUEUE_INTERVAL_SEC || '', 10) ||
|
|
20
29
|
5
|
|
21
30
|
|
|
22
|
-
const getConfiguredQueueNames = () => {
|
|
23
|
-
if (!process.env.METRICS_APP_REDIS_BQ) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
'No queues configured for monitoring. Set METRICS_APP_REDIS_BQ with comma-separated queue names'
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const allQueues = process.env.METRICS_APP_REDIS_BQ.split(',')
|
|
30
|
-
.map(q => q.trim())
|
|
31
|
-
.filter(Boolean)
|
|
32
|
-
|
|
33
|
-
if (allQueues.length === 0) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
'METRICS_APP_REDIS_BQ is empty or contains only whitespace. ' +
|
|
36
|
-
'Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"'
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return [...new Set(allQueues)]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const startupValidation = () => {
|
|
44
|
-
try {
|
|
45
|
-
const queueNames = getConfiguredQueueNames()
|
|
46
|
-
console.info(
|
|
47
|
-
`[queue-metrics] Queue & Redis metrics collection starting for ${queueNames.length} queues`
|
|
48
|
-
)
|
|
49
|
-
return true
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error(`[queue-metrics] ❌ Cannot start: ${error.message}`)
|
|
52
|
-
console.error(
|
|
53
|
-
`[queue-metrics] 💡 Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`
|
|
54
|
-
)
|
|
55
|
-
console.error(
|
|
56
|
-
`[queue-metrics] 💡 Optional: METRICS_QUEUE_INTERVAL_SEC="10"`
|
|
57
|
-
)
|
|
58
|
-
console.error(`[queue-metrics] Skipping queue metrics collection`)
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
31
|
super({
|
|
64
32
|
...metricsConfig,
|
|
65
33
|
scripDefaultMetrics: true,
|
|
66
|
-
processType: metricsConfig.processType || '
|
|
67
|
-
intervalSec
|
|
68
|
-
startupValidation,
|
|
34
|
+
processType: metricsConfig.processType || 'redis-metrics',
|
|
35
|
+
intervalSec,
|
|
69
36
|
})
|
|
70
37
|
|
|
71
|
-
|
|
72
|
-
this.startupValidation = startupValidation
|
|
73
|
-
|
|
74
|
-
/** Redis client used for queue & Redis metrics */
|
|
38
|
+
/** Redis client used for metrics */
|
|
75
39
|
this.redisClient = redisClient
|
|
76
40
|
|
|
77
|
-
/** Cache for queue objects to avoid multiple connections */
|
|
78
|
-
this.queueCache = new Map()
|
|
79
|
-
|
|
80
|
-
/** Gauge for queue jobs by status */
|
|
81
|
-
this.queueJobsGauge = this.createGauge({
|
|
82
|
-
name: 'app_queue_jobs_count',
|
|
83
|
-
help: 'Number of app jobs in the queue by status',
|
|
84
|
-
labelNames: this.withDefaultLabels(['queue_name', 'status']),
|
|
85
|
-
})
|
|
86
|
-
|
|
87
41
|
/** Gauge for Redis client connections */
|
|
88
42
|
this.redisConnectionsGauge = this.createGauge({
|
|
89
43
|
name: 'app_redis_connections_count',
|
|
@@ -178,98 +132,25 @@ class RedisMetricsClient extends MetricsClient {
|
|
|
178
132
|
}
|
|
179
133
|
|
|
180
134
|
/**
|
|
181
|
-
* Collect metrics for
|
|
182
|
-
* @param {string} queueName - Name of the queue
|
|
135
|
+
* Collect metrics for all Redis and push to Prometheus Pushgateway
|
|
183
136
|
* @returns {Promise<void>}
|
|
184
137
|
*/
|
|
185
|
-
|
|
138
|
+
pushRedisMetrics = async () => {
|
|
186
139
|
try {
|
|
187
|
-
if (!this.queueCache.has(queueName)) {
|
|
188
|
-
this.queueCache.set(
|
|
189
|
-
queueName,
|
|
190
|
-
new Queue(queueName, {
|
|
191
|
-
redis: this.redisClient,
|
|
192
|
-
isWorker: false,
|
|
193
|
-
getEvents: false,
|
|
194
|
-
sendEvents: false,
|
|
195
|
-
})
|
|
196
|
-
)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const queue = this.queueCache.get(queueName)
|
|
200
|
-
const health = await queue.checkHealth()
|
|
201
|
-
|
|
202
|
-
const labels = {
|
|
203
|
-
...this.getDefaultLabels(),
|
|
204
|
-
queue_name: queueName,
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
this.queueJobsGauge.set(
|
|
208
|
-
{ ...labels, status: 'waiting' },
|
|
209
|
-
health.waiting || 0
|
|
210
|
-
)
|
|
211
|
-
this.queueJobsGauge.set(
|
|
212
|
-
{ ...labels, status: 'active' },
|
|
213
|
-
health.active || 0
|
|
214
|
-
)
|
|
215
|
-
this.queueJobsGauge.set(
|
|
216
|
-
{ ...labels, status: 'succeeded' },
|
|
217
|
-
health.succeeded || 0
|
|
218
|
-
)
|
|
219
|
-
this.queueJobsGauge.set(
|
|
220
|
-
{ ...labels, status: 'failed' },
|
|
221
|
-
health.failed || 0
|
|
222
|
-
)
|
|
223
|
-
this.queueJobsGauge.set(
|
|
224
|
-
{ ...labels, status: 'delayed' },
|
|
225
|
-
health.delayed || 0
|
|
226
|
-
)
|
|
227
|
-
} catch (error) {
|
|
228
|
-
console.warn(
|
|
229
|
-
`[queue-metrics] Failed to collect metrics for queue ${queueName}:`,
|
|
230
|
-
error.message
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Collect metrics for all queues and Redis, then push to Pushgateway
|
|
237
|
-
* @returns {Promise<void>}
|
|
238
|
-
*/
|
|
239
|
-
collectQueueMetrics = async () => {
|
|
240
|
-
try {
|
|
241
|
-
const queueNames = this.getConfiguredQueueNames()
|
|
242
|
-
|
|
243
140
|
await this.collectRedisMetrics()
|
|
244
|
-
await Promise.allSettled(
|
|
245
|
-
queueNames.map(queueName => this.collectSingleQueueMetrics(queueName))
|
|
246
|
-
)
|
|
247
|
-
|
|
248
141
|
await this.gatewayPush()
|
|
249
142
|
|
|
250
143
|
if (this.metricsLogValues) {
|
|
251
144
|
const metricObjects = await this.registry.getMetricsAsJSON()
|
|
252
145
|
console.info(
|
|
253
|
-
`[
|
|
146
|
+
`[redis-metrics] Collected metrics for Redis`,
|
|
254
147
|
JSON.stringify(metricObjects, null, 2)
|
|
255
148
|
)
|
|
256
149
|
}
|
|
257
150
|
} catch (error) {
|
|
258
|
-
|
|
259
|
-
error.message
|
|
260
|
-
|
|
261
|
-
) {
|
|
262
|
-
console.error(
|
|
263
|
-
`[queue-metrics] ❌ Configuration error: ${error.message}`
|
|
264
|
-
)
|
|
265
|
-
console.error(
|
|
266
|
-
`[queue-metrics] 💡 Example config: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`
|
|
267
|
-
)
|
|
268
|
-
} else {
|
|
269
|
-
console.error(
|
|
270
|
-
`[queue-metrics] Failed to collect queue metrics: ${error.message}`
|
|
271
|
-
)
|
|
272
|
-
}
|
|
151
|
+
console.error(
|
|
152
|
+
`[redis-metrics] Failed to collect Redis metrics: ${error.message}`
|
|
153
|
+
)
|
|
273
154
|
throw error
|
|
274
155
|
}
|
|
275
156
|
}
|
|
@@ -280,36 +161,28 @@ class RedisMetricsClient extends MetricsClient {
|
|
|
280
161
|
*/
|
|
281
162
|
startPush = (intervalSec = this.intervalSec) => {
|
|
282
163
|
this._startPush(intervalSec, () => {
|
|
283
|
-
this.
|
|
284
|
-
console.error(
|
|
285
|
-
`[queue-metrics] Failed to collect queue & Redis metrics:`,
|
|
286
|
-
err
|
|
287
|
-
)
|
|
164
|
+
this.pushRedisMetrics().catch(err => {
|
|
165
|
+
console.error(`[redis-metrics] Failed to push Redis metrics:`, err)
|
|
288
166
|
})
|
|
289
167
|
})
|
|
290
168
|
}
|
|
291
169
|
|
|
292
|
-
_setCleanupHandlers = () => {
|
|
293
|
-
process.on('SIGINT', this.cleanup)
|
|
294
|
-
process.on('SIGTERM', this.cleanup)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
170
|
/**
|
|
298
|
-
* Cleanup
|
|
171
|
+
* Cleanup Redis client
|
|
299
172
|
*/
|
|
300
173
|
cleanup = async () => {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
console.error(`[queue-metrics] Error closing queue ${queueName}:`, err)
|
|
306
|
-
}
|
|
174
|
+
try {
|
|
175
|
+
this.redisClient?.quit()
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error('[redis-metrics] Error closing Redis client:', err)
|
|
307
178
|
}
|
|
308
|
-
|
|
309
|
-
// Close Redis connection
|
|
310
|
-
this.redisClient?.quit()
|
|
311
179
|
process.exit(0)
|
|
312
180
|
}
|
|
181
|
+
|
|
182
|
+
_setCleanupHandlers = () => {
|
|
183
|
+
process.on('SIGINT', this.cleanup)
|
|
184
|
+
process.on('SIGTERM', this.cleanup)
|
|
185
|
+
}
|
|
313
186
|
}
|
|
314
187
|
|
|
315
188
|
module.exports = { RedisMetricsClient }
|
package/src/redisUtils.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @param {string} [defaultAppName='undefined-app'] - Fallback app name if METRICS_APP_NAME is not set.
|
|
5
5
|
* @returns {(client: import('redis').RedisClient, name: string) => void}
|
|
6
6
|
*/
|
|
7
|
-
const
|
|
7
|
+
const createSetClientNameForRedisV3 = (defaultAppName = 'undefined-app') => {
|
|
8
8
|
return (client, name) => {
|
|
9
9
|
const appName = process.env.METRICS_APP_NAME || defaultAppName
|
|
10
10
|
const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'
|
|
@@ -27,4 +27,60 @@ const createSetClientName = (defaultAppName = 'undefined-app') => {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Creates a function to set Redis client name for ioredis
|
|
32
|
+
* @param {string} defaultAppName
|
|
33
|
+
* @returns {(client: Redis, name: string) => void}
|
|
34
|
+
*/
|
|
35
|
+
const createSetClientNameForIoredis = (defaultAppName = 'undefined-app') => {
|
|
36
|
+
return (client, name) => {
|
|
37
|
+
const appName = process.env.METRICS_APP_NAME || defaultAppName
|
|
38
|
+
const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'
|
|
39
|
+
|
|
40
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
41
|
+
client.once('connect', () => {
|
|
42
|
+
client
|
|
43
|
+
.call('CLIENT', ['SETNAME', `${appName}:${dyno}:${name}`])
|
|
44
|
+
.then(() => {
|
|
45
|
+
console.log(`Connected to Redis for pid:${process.pid} (${name})`)
|
|
46
|
+
})
|
|
47
|
+
.catch(err => {
|
|
48
|
+
console.error(`Failed to set client name for ${name}:`, err)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates a function to set Redis client name for node-redis v4
|
|
57
|
+
* @param {string} defaultAppName
|
|
58
|
+
* @returns {(client: ReturnType<createClient>, name: string) => void}
|
|
59
|
+
*/
|
|
60
|
+
const createSetClientNameForRedisV4 = (defaultAppName = 'undefined-app') => {
|
|
61
|
+
return (client, name) => {
|
|
62
|
+
const appName = process.env.METRICS_APP_NAME || defaultAppName
|
|
63
|
+
const dyno = process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined-dyno'
|
|
64
|
+
|
|
65
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
66
|
+
client.on('ready', async () => {
|
|
67
|
+
try {
|
|
68
|
+
await client.sendCommand([
|
|
69
|
+
'CLIENT',
|
|
70
|
+
'SETNAME',
|
|
71
|
+
`${appName}:${dyno}:${name}`,
|
|
72
|
+
])
|
|
73
|
+
console.log(`Connected to Redis for pid:${process.pid} (${name})`)
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`Failed to set client name for ${name}:`, err)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
createSetClientNameForRedisV3,
|
|
84
|
+
createSetClientNameForRedisV4,
|
|
85
|
+
createSetClientNameForIoredis,
|
|
86
|
+
}
|