@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
package/lib/index.d.ts
CHANGED
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,sBAAsB,CAAA;AACpC,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA"}
|
package/lib/index.js
CHANGED
|
@@ -25,6 +25,17 @@ Object.keys(_metricsRedisClient).forEach(function (key) {
|
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
|
+
var _metricsQueueRedisClient = require("./metricsQueueRedisClient");
|
|
29
|
+
Object.keys(_metricsQueueRedisClient).forEach(function (key) {
|
|
30
|
+
if (key === "default" || key === "__esModule") return;
|
|
31
|
+
if (key in exports && exports[key] === _metricsQueueRedisClient[key]) return;
|
|
32
|
+
Object.defineProperty(exports, key, {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function () {
|
|
35
|
+
return _metricsQueueRedisClient[key];
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
28
39
|
var _redisUtils = require("./redisUtils");
|
|
29
40
|
Object.keys(_redisUtils).forEach(function (key) {
|
|
30
41
|
if (key === "default" || key === "__esModule") return;
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_metricsClient","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_metricsRedisClient","_redisUtils"],"sources":["../src/index.ts"],"sourcesContent":["export * from './metricsClient'\nexport * from './metricsRedisClient'\nexport * from './redisUtils'\n"],"mappings":";;;;;AAAA,IAAAA,cAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,cAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,cAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,cAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,mBAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,mBAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,mBAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,mBAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,
|
|
1
|
+
{"version":3,"file":"index.js","names":["_metricsClient","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_metricsRedisClient","_metricsQueueRedisClient","_redisUtils"],"sources":["../src/index.ts"],"sourcesContent":["export * from './metricsClient'\nexport * from './metricsRedisClient'\nexport * from './metricsQueueRedisClient'\nexport * from './redisUtils'\n"],"mappings":";;;;;AAAA,IAAAA,cAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,cAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,cAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,cAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,mBAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,mBAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,mBAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,mBAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,wBAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,wBAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,wBAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,wBAAA,CAAAN,GAAA;IAAA;EAAA;AAAA;AACA,IAAAO,WAAA,GAAAX,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAS,WAAA,EAAAR,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,WAAA,CAAAP,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAG,WAAA,CAAAP,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueueRedisMetricsClient extends MetricsClient to collect
|
|
3
|
+
* Redis and Bee Queue metrics periodically and push them to Prometheus Pushgateway.
|
|
4
|
+
*
|
|
5
|
+
* @extends RedisMetricsClient
|
|
6
|
+
*/
|
|
7
|
+
export class QueueRedisMetricsClient extends RedisMetricsClient {
|
|
8
|
+
getConfiguredQueueNames: () => string[];
|
|
9
|
+
startupValidation: () => boolean;
|
|
10
|
+
/** Cache for queue objects to avoid multiple connections */
|
|
11
|
+
queueCache: Map<any, any>;
|
|
12
|
+
/** Gauge for queue jobs by status */
|
|
13
|
+
queueJobsGauge: import("prom-client").Gauge<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Collect metrics for a single Bee Queue and set gauges
|
|
16
|
+
* @param {string} queueName - Name of the queue
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
*/
|
|
19
|
+
collectSingleQueueMetrics: (queueName: string) => Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Collect metrics for all queues and Redis, then push to Pushgateway
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
collectQueueMetrics: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
import { RedisMetricsClient } from "./metricsRedisClient";
|
|
27
|
+
//# sourceMappingURL=metricsQueueRedisClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsQueueRedisClient.d.ts","sourceRoot":"","sources":["../src/metricsQueueRedisClient.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IAgEI,wCAAsD;IACtD,iCAA0C;IAE1C,4DAA4D;IAC5D,0BAA2B;IAE3B,qCAAqC;IACrC,oDAIE;IAKJ;;;;OAIG;IACH,uCAHW,MAAM,KACJ,QAAQ,IAAI,CAAC,CAkDzB;IAED;;;OAGG;IACH,2BAFa,QAAQ,IAAI,CAAC,CAsCzB;CAmCF"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const Queue = require('bee-queue');
|
|
4
|
+
const {
|
|
5
|
+
RedisMetricsClient
|
|
6
|
+
} = require('.');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* QueueRedisMetricsClient extends MetricsClient to collect
|
|
10
|
+
* Redis and Bee Queue metrics periodically and push them to Prometheus Pushgateway.
|
|
11
|
+
*
|
|
12
|
+
* @extends RedisMetricsClient
|
|
13
|
+
*/
|
|
14
|
+
class QueueRedisMetricsClient extends RedisMetricsClient {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} options
|
|
17
|
+
* @param {any} options.redisClient - Redis client instance (required)
|
|
18
|
+
* @param {string} [options.appName] - Application name (from MetricsClient)
|
|
19
|
+
* @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
|
|
20
|
+
* @param {string} [options.processType] - Process type (from MetricsClient)
|
|
21
|
+
* @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
|
|
22
|
+
* @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
|
|
23
|
+
* @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
|
|
24
|
+
* @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
|
|
25
|
+
* @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)
|
|
26
|
+
* @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)
|
|
27
|
+
* @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)
|
|
28
|
+
* @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)
|
|
29
|
+
*/
|
|
30
|
+
constructor({
|
|
31
|
+
redisClient,
|
|
32
|
+
metricsConfig = {}
|
|
33
|
+
} = {}) {
|
|
34
|
+
const getConfiguredQueueNames = () => {
|
|
35
|
+
if (!process.env.METRICS_APP_REDIS_BQ) {
|
|
36
|
+
throw new Error('No queues configured for monitoring. Set METRICS_APP_REDIS_BQ with comma-separated queue names');
|
|
37
|
+
}
|
|
38
|
+
const allQueues = process.env.METRICS_APP_REDIS_BQ.split(',').map(q => q.trim()).filter(Boolean);
|
|
39
|
+
if (allQueues.length === 0) {
|
|
40
|
+
throw new Error('METRICS_APP_REDIS_BQ is empty or contains only whitespace. ' + 'Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"');
|
|
41
|
+
}
|
|
42
|
+
return [...new Set(allQueues)];
|
|
43
|
+
};
|
|
44
|
+
const startupValidation = () => {
|
|
45
|
+
try {
|
|
46
|
+
const queueNames = getConfiguredQueueNames();
|
|
47
|
+
console.info(`[queue-metrics] Queue & Redis metrics collection starting for ${queueNames.length} queues`);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`[queue-metrics] ❌ Cannot start: ${error.message}`);
|
|
51
|
+
console.error(`[queue-metrics] 💡 Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`);
|
|
52
|
+
console.error(`[queue-metrics] 💡 Optional: METRICS_QUEUE_INTERVAL_SEC="10"`);
|
|
53
|
+
console.error(`[queue-metrics] Skipping queue metrics collection`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
super({
|
|
58
|
+
...metricsConfig,
|
|
59
|
+
redisClient,
|
|
60
|
+
startupValidation
|
|
61
|
+
});
|
|
62
|
+
this.getConfiguredQueueNames = getConfiguredQueueNames;
|
|
63
|
+
this.startupValidation = startupValidation;
|
|
64
|
+
|
|
65
|
+
/** Cache for queue objects to avoid multiple connections */
|
|
66
|
+
this.queueCache = new Map();
|
|
67
|
+
|
|
68
|
+
/** Gauge for queue jobs by status */
|
|
69
|
+
this.queueJobsGauge = this.createGauge({
|
|
70
|
+
name: 'app_queue_jobs_count',
|
|
71
|
+
help: 'Number of app jobs in the queue by status',
|
|
72
|
+
labelNames: this.withDefaultLabels(['queue_name', 'status'])
|
|
73
|
+
});
|
|
74
|
+
this._setCleanupHandlers();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Collect metrics for a single Bee Queue and set gauges
|
|
79
|
+
* @param {string} queueName - Name of the queue
|
|
80
|
+
* @returns {Promise<void>}
|
|
81
|
+
*/
|
|
82
|
+
collectSingleQueueMetrics = async queueName => {
|
|
83
|
+
try {
|
|
84
|
+
if (!this.queueCache.has(queueName)) {
|
|
85
|
+
this.queueCache.set(queueName, new Queue(queueName, {
|
|
86
|
+
redis: this.redisClient,
|
|
87
|
+
isWorker: false,
|
|
88
|
+
getEvents: false,
|
|
89
|
+
sendEvents: false
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
const queue = this.queueCache.get(queueName);
|
|
93
|
+
const health = await queue.checkHealth();
|
|
94
|
+
const labels = {
|
|
95
|
+
...this.getDefaultLabels(),
|
|
96
|
+
queue_name: queueName
|
|
97
|
+
};
|
|
98
|
+
this.queueJobsGauge.set({
|
|
99
|
+
...labels,
|
|
100
|
+
status: 'waiting'
|
|
101
|
+
}, health.waiting || 0);
|
|
102
|
+
this.queueJobsGauge.set({
|
|
103
|
+
...labels,
|
|
104
|
+
status: 'active'
|
|
105
|
+
}, health.active || 0);
|
|
106
|
+
this.queueJobsGauge.set({
|
|
107
|
+
...labels,
|
|
108
|
+
status: 'succeeded'
|
|
109
|
+
}, health.succeeded || 0);
|
|
110
|
+
this.queueJobsGauge.set({
|
|
111
|
+
...labels,
|
|
112
|
+
status: 'failed'
|
|
113
|
+
}, health.failed || 0);
|
|
114
|
+
this.queueJobsGauge.set({
|
|
115
|
+
...labels,
|
|
116
|
+
status: 'delayed'
|
|
117
|
+
}, health.delayed || 0);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.warn(`[queue-metrics] Failed to collect metrics for queue ${queueName}:`, error.message);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Collect metrics for all queues and Redis, then push to Pushgateway
|
|
125
|
+
* @returns {Promise<void>}
|
|
126
|
+
*/
|
|
127
|
+
collectQueueMetrics = async () => {
|
|
128
|
+
try {
|
|
129
|
+
const queueNames = this.getConfiguredQueueNames();
|
|
130
|
+
await this.collectRedisMetrics();
|
|
131
|
+
await Promise.allSettled(queueNames.map(queueName => this.collectSingleQueueMetrics(queueName)));
|
|
132
|
+
await this.gatewayPush();
|
|
133
|
+
if (this.metricsLogValues) {
|
|
134
|
+
const metricObjects = await this.registry.getMetricsAsJSON();
|
|
135
|
+
console.info(`[queue-metrics] Collected metrics for ${queueNames.length} queues:`, JSON.stringify(metricObjects, null, 2));
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error.message?.includes('No queues configured') || error.message?.includes('METRICS_APP_REDIS_BQ')) {
|
|
139
|
+
console.error(`[queue-metrics] ❌ Configuration error: ${error.message}`);
|
|
140
|
+
console.error(`[queue-metrics] 💡 Example config: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`);
|
|
141
|
+
} else {
|
|
142
|
+
console.error(`[queue-metrics] Failed to collect queue metrics: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Start periodic collection.
|
|
150
|
+
* @param {number} [intervalSec=this.intervalSec] - Interval in seconds
|
|
151
|
+
*/
|
|
152
|
+
startPush = (intervalSec = this.intervalSec) => {
|
|
153
|
+
this._startPush(intervalSec, () => {
|
|
154
|
+
this.collectQueueMetrics().catch(err => {
|
|
155
|
+
console.error(`[queue-metrics] Failed to collect queue & Redis metrics:`, err);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
_setCleanupHandlers = () => {
|
|
160
|
+
process.on('SIGINT', this.cleanup);
|
|
161
|
+
process.on('SIGTERM', this.cleanup);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Cleanup queues and Redis client
|
|
166
|
+
*/
|
|
167
|
+
cleanup = async () => {
|
|
168
|
+
for (const [queueName, queue] of this.queueCache) {
|
|
169
|
+
try {
|
|
170
|
+
await queue.close();
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.error(`[queue-metrics] Error closing queue ${queueName}:`, err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
process.exit(0);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
module.exports = {
|
|
179
|
+
QueueRedisMetricsClient
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=metricsQueueRedisClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsQueueRedisClient.js","names":["Queue","require","RedisMetricsClient","QueueRedisMetricsClient","constructor","redisClient","metricsConfig","getConfiguredQueueNames","process","env","METRICS_APP_REDIS_BQ","Error","allQueues","split","map","q","trim","filter","Boolean","length","Set","startupValidation","queueNames","console","info","error","message","queueCache","Map","queueJobsGauge","createGauge","name","help","labelNames","withDefaultLabels","_setCleanupHandlers","collectSingleQueueMetrics","queueName","has","set","redis","isWorker","getEvents","sendEvents","queue","get","health","checkHealth","labels","getDefaultLabels","queue_name","status","waiting","active","succeeded","failed","delayed","warn","collectQueueMetrics","collectRedisMetrics","Promise","allSettled","gatewayPush","metricsLogValues","metricObjects","registry","getMetricsAsJSON","JSON","stringify","includes","startPush","intervalSec","_startPush","catch","err","on","cleanup","close","exit","module","exports"],"sources":["../src/metricsQueueRedisClient.js"],"sourcesContent":["const Queue = require('bee-queue')\nconst { RedisMetricsClient } = require('.')\n\n/**\n * QueueRedisMetricsClient extends MetricsClient to collect\n * Redis and Bee Queue metrics periodically and push them to Prometheus Pushgateway.\n *\n * @extends RedisMetricsClient\n */\nclass QueueRedisMetricsClient extends RedisMetricsClient {\n /**\n * @param {Object} options\n * @param {any} options.redisClient - Redis client instance (required)\n * @param {string} [options.appName] - Application name (from MetricsClient)\n * @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)\n * @param {string} [options.processType] - Process type (from MetricsClient)\n * @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)\n * @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)\n * @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)\n * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)\n * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)\n * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)\n * @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)\n * @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)\n */\n constructor({ redisClient, metricsConfig = {} } = {}) {\n const getConfiguredQueueNames = () => {\n if (!process.env.METRICS_APP_REDIS_BQ) {\n throw new Error(\n 'No queues configured for monitoring. Set METRICS_APP_REDIS_BQ with comma-separated queue names'\n )\n }\n\n const allQueues = process.env.METRICS_APP_REDIS_BQ.split(',')\n .map(q => q.trim())\n .filter(Boolean)\n\n if (allQueues.length === 0) {\n throw new Error(\n 'METRICS_APP_REDIS_BQ is empty or contains only whitespace. ' +\n 'Example: METRICS_APP_REDIS_BQ=\"adalo-compile,adalo-migrations\"'\n )\n }\n\n return [...new Set(allQueues)]\n }\n\n const startupValidation = () => {\n try {\n const queueNames = getConfiguredQueueNames()\n console.info(\n `[queue-metrics] Queue & Redis metrics collection starting for ${queueNames.length} queues`\n )\n return true\n } catch (error) {\n console.error(`[queue-metrics] ❌ Cannot start: ${error.message}`)\n console.error(\n `[queue-metrics] 💡 Example: METRICS_APP_REDIS_BQ=\"adalo-compile,adalo-migrations\"`\n )\n console.error(\n `[queue-metrics] 💡 Optional: METRICS_QUEUE_INTERVAL_SEC=\"10\"`\n )\n console.error(`[queue-metrics] Skipping queue metrics collection`)\n return false\n }\n }\n\n super({\n ...metricsConfig,\n redisClient,\n startupValidation,\n })\n\n this.getConfiguredQueueNames = getConfiguredQueueNames\n this.startupValidation = startupValidation\n\n /** Cache for queue objects to avoid multiple connections */\n this.queueCache = new Map()\n\n /** Gauge for queue jobs by status */\n this.queueJobsGauge = this.createGauge({\n name: 'app_queue_jobs_count',\n help: 'Number of app jobs in the queue by status',\n labelNames: this.withDefaultLabels(['queue_name', 'status']),\n })\n\n this._setCleanupHandlers()\n }\n\n /**\n * Collect metrics for a single Bee Queue and set gauges\n * @param {string} queueName - Name of the queue\n * @returns {Promise<void>}\n */\n collectSingleQueueMetrics = async queueName => {\n try {\n if (!this.queueCache.has(queueName)) {\n this.queueCache.set(\n queueName,\n new Queue(queueName, {\n redis: this.redisClient,\n isWorker: false,\n getEvents: false,\n sendEvents: false,\n })\n )\n }\n\n const queue = this.queueCache.get(queueName)\n const health = await queue.checkHealth()\n\n const labels = {\n ...this.getDefaultLabels(),\n queue_name: queueName,\n }\n\n this.queueJobsGauge.set(\n { ...labels, status: 'waiting' },\n health.waiting || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'active' },\n health.active || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'succeeded' },\n health.succeeded || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'failed' },\n health.failed || 0\n )\n this.queueJobsGauge.set(\n { ...labels, status: 'delayed' },\n health.delayed || 0\n )\n } catch (error) {\n console.warn(\n `[queue-metrics] Failed to collect metrics for queue ${queueName}:`,\n error.message\n )\n }\n }\n\n /**\n * Collect metrics for all queues and Redis, then push to Pushgateway\n * @returns {Promise<void>}\n */\n collectQueueMetrics = async () => {\n try {\n const queueNames = this.getConfiguredQueueNames()\n\n await this.collectRedisMetrics()\n await Promise.allSettled(\n queueNames.map(queueName => this.collectSingleQueueMetrics(queueName))\n )\n\n await this.gatewayPush()\n\n if (this.metricsLogValues) {\n const metricObjects = await this.registry.getMetricsAsJSON()\n console.info(\n `[queue-metrics] Collected metrics for ${queueNames.length} queues:`,\n JSON.stringify(metricObjects, null, 2)\n )\n }\n } catch (error) {\n if (\n error.message?.includes('No queues configured') ||\n error.message?.includes('METRICS_APP_REDIS_BQ')\n ) {\n console.error(\n `[queue-metrics] ❌ Configuration error: ${error.message}`\n )\n console.error(\n `[queue-metrics] 💡 Example config: METRICS_APP_REDIS_BQ=\"adalo-compile,adalo-migrations\"`\n )\n } else {\n console.error(\n `[queue-metrics] Failed to collect queue metrics: ${error.message}`\n )\n }\n throw error\n }\n }\n\n /**\n * Start periodic collection.\n * @param {number} [intervalSec=this.intervalSec] - Interval in seconds\n */\n startPush = (intervalSec = this.intervalSec) => {\n this._startPush(intervalSec, () => {\n this.collectQueueMetrics().catch(err => {\n console.error(\n `[queue-metrics] Failed to collect queue & Redis metrics:`,\n err\n )\n })\n })\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n\n /**\n * Cleanup queues and Redis client\n */\n cleanup = async () => {\n for (const [queueName, queue] of this.queueCache) {\n try {\n await queue.close()\n } catch (err) {\n console.error(`[queue-metrics] Error closing queue ${queueName}:`, err)\n }\n }\n process.exit(0)\n }\n}\n\nmodule.exports = { QueueRedisMetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RedisMetricsClient extends MetricsClient to collect
|
|
3
|
-
* Redis
|
|
3
|
+
* Redis metrics periodically and push them to Prometheus Pushgateway.
|
|
4
4
|
*
|
|
5
5
|
* @extends MetricsClient
|
|
6
6
|
*/
|
|
7
7
|
export class RedisMetricsClient extends MetricsClient {
|
|
8
8
|
/**
|
|
9
|
-
* @param {Object} options
|
|
9
|
+
* @param {Object} options
|
|
10
10
|
* @param {any} options.redisClient - Redis client instance (required)
|
|
11
|
-
* @param {
|
|
11
|
+
* @param {string} [options.appName] - Application name (from MetricsClient)
|
|
12
|
+
* @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
|
|
13
|
+
* @param {string} [options.processType] - Process type (from MetricsClient)
|
|
14
|
+
* @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
|
|
15
|
+
* @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
|
|
16
|
+
* @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
|
|
17
|
+
* @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
|
|
18
|
+
* @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)
|
|
19
|
+
* @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)
|
|
20
|
+
* @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)
|
|
21
|
+
* @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)
|
|
12
22
|
*/
|
|
13
|
-
constructor({ redisClient, metricsConfig }?: {
|
|
23
|
+
constructor({ redisClient, ...metricsConfig }?: {
|
|
14
24
|
redisClient: any;
|
|
15
|
-
|
|
25
|
+
appName?: string | undefined;
|
|
26
|
+
dynoId?: string | undefined;
|
|
27
|
+
processType?: string | undefined;
|
|
28
|
+
enabled?: boolean | undefined;
|
|
29
|
+
logValues?: boolean | undefined;
|
|
30
|
+
pushgatewayUrl?: string | undefined;
|
|
31
|
+
pushgatewaySecret?: string | undefined;
|
|
32
|
+
intervalSec?: number | undefined;
|
|
33
|
+
removeOldMetrics?: boolean | undefined;
|
|
34
|
+
scripDefaultMetrics?: boolean | undefined;
|
|
35
|
+
startupValidation?: Function | undefined;
|
|
16
36
|
});
|
|
17
|
-
|
|
18
|
-
startupValidation: () => boolean;
|
|
19
|
-
/** Redis client used for queue & Redis metrics */
|
|
37
|
+
/** Redis client used for metrics */
|
|
20
38
|
redisClient: any;
|
|
21
|
-
/** Cache for queue objects to avoid multiple connections */
|
|
22
|
-
queueCache: Map<any, any>;
|
|
23
|
-
/** Gauge for queue jobs by status */
|
|
24
|
-
queueJobsGauge: import("prom-client").Gauge<string>;
|
|
25
39
|
/** Gauge for Redis client connections */
|
|
26
40
|
redisConnectionsGauge: import("prom-client").Gauge<string>;
|
|
27
41
|
/** Gauge for Redis memory usage */
|
|
@@ -34,16 +48,10 @@ export class RedisMetricsClient extends MetricsClient {
|
|
|
34
48
|
*/
|
|
35
49
|
collectRedisMetrics: () => Promise<void>;
|
|
36
50
|
/**
|
|
37
|
-
* Collect metrics for
|
|
38
|
-
* @param {string} queueName - Name of the queue
|
|
51
|
+
* Collect metrics for all Redis and push to Prometheus Pushgateway
|
|
39
52
|
* @returns {Promise<void>}
|
|
40
53
|
*/
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Collect metrics for all queues and Redis, then push to Pushgateway
|
|
44
|
-
* @returns {Promise<void>}
|
|
45
|
-
*/
|
|
46
|
-
collectQueueMetrics: () => Promise<void>;
|
|
54
|
+
pushRedisMetrics: () => Promise<void>;
|
|
47
55
|
/**
|
|
48
56
|
* Start periodic collection.
|
|
49
57
|
* @param {number} [intervalSec=this.intervalSec] - Interval in seconds
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metricsRedisClient.d.ts","sourceRoot":"","sources":["../src/metricsRedisClient.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metricsRedisClient.d.ts","sourceRoot":"","sources":["../src/metricsRedisClient.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;;;;;;;;;;OAcG;IACH;QAbwB,WAAW,EAAxB,GAAG;QACc,OAAO;QACP,MAAM;QACN,WAAW;QACV,OAAO;QACP,SAAS;QACV,cAAc;QACd,iBAAiB;QACjB,WAAW;QACV,gBAAgB;QAChB,mBAAmB;QAClB,iBAAiB;OAwC9C;IAzBC,oCAAoC;IACpC,iBAA8B;IAE9B,yCAAyC;IACzC,2DAIE;IAEF,mCAAmC;IACnC,sDAIE;IAEF,sCAAsC;IACtC,qDAIE;IAKJ;;;OAGG;IACH,2BAFa,QAAQ,IAAI,CAAC,CAiEzB;IAED;;;OAGG;IACH,wBAFa,QAAQ,IAAI,CAAC,CAoBzB;IAED;;;OAGG;IACH,sDAMC;CAkBF"}
|
|
@@ -1,73 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const Queue = require('bee-queue');
|
|
4
3
|
const {
|
|
5
4
|
MetricsClient
|
|
6
5
|
} = require('.');
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* RedisMetricsClient extends MetricsClient to collect
|
|
10
|
-
* Redis
|
|
9
|
+
* Redis metrics periodically and push them to Prometheus Pushgateway.
|
|
11
10
|
*
|
|
12
11
|
* @extends MetricsClient
|
|
13
12
|
*/
|
|
14
13
|
class RedisMetricsClient extends MetricsClient {
|
|
15
14
|
/**
|
|
16
|
-
* @param {Object} options
|
|
15
|
+
* @param {Object} options
|
|
17
16
|
* @param {any} options.redisClient - Redis client instance (required)
|
|
18
|
-
* @param {
|
|
17
|
+
* @param {string} [options.appName] - Application name (from MetricsClient)
|
|
18
|
+
* @param {string} [options.dynoId] - Dyno/instance ID (from MetricsClient)
|
|
19
|
+
* @param {string} [options.processType] - Process type (from MetricsClient)
|
|
20
|
+
* @param {boolean} [options.enabled] - Enable metrics collection (from MetricsClient)
|
|
21
|
+
* @param {boolean} [options.logValues] - Log metrics values (from MetricsClient)
|
|
22
|
+
* @param {string} [options.pushgatewayUrl] - PushGateway URL (from MetricsClient)
|
|
23
|
+
* @param {string} [options.pushgatewaySecret] - PushGateway secret token (from MetricsClient)
|
|
24
|
+
* @param {number} [options.intervalSec] - Interval in seconds for pushing metrics (from MetricsClient)
|
|
25
|
+
* @param {boolean} [options.removeOldMetrics] - Remove old metrics by service (from MetricsClient)
|
|
26
|
+
* @param {boolean} [options.scripDefaultMetrics] - Skip default metrics creation (from MetricsClient)
|
|
27
|
+
* @param {function} [options.startupValidation] - Function to validate startup (from MetricsClient)
|
|
19
28
|
*/
|
|
20
29
|
constructor({
|
|
21
30
|
redisClient,
|
|
22
|
-
metricsConfig
|
|
31
|
+
...metricsConfig
|
|
23
32
|
} = {}) {
|
|
24
|
-
const
|
|
25
|
-
const getConfiguredQueueNames = () => {
|
|
26
|
-
if (!process.env.METRICS_APP_REDIS_BQ) {
|
|
27
|
-
throw new Error('No queues configured for monitoring. Set METRICS_APP_REDIS_BQ with comma-separated queue names');
|
|
28
|
-
}
|
|
29
|
-
const allQueues = process.env.METRICS_APP_REDIS_BQ.split(',').map(q => q.trim()).filter(Boolean);
|
|
30
|
-
if (allQueues.length === 0) {
|
|
31
|
-
throw new Error('METRICS_APP_REDIS_BQ is empty or contains only whitespace. ' + 'Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"');
|
|
32
|
-
}
|
|
33
|
-
return [...new Set(allQueues)];
|
|
34
|
-
};
|
|
35
|
-
const startupValidation = () => {
|
|
36
|
-
try {
|
|
37
|
-
const queueNames = getConfiguredQueueNames();
|
|
38
|
-
console.info(`[queue-metrics] Queue & Redis metrics collection starting for ${queueNames.length} queues`);
|
|
39
|
-
return true;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error(`[queue-metrics] ❌ Cannot start: ${error.message}`);
|
|
42
|
-
console.error(`[queue-metrics] 💡 Example: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`);
|
|
43
|
-
console.error(`[queue-metrics] 💡 Optional: METRICS_QUEUE_INTERVAL_SEC="10"`);
|
|
44
|
-
console.error(`[queue-metrics] Skipping queue metrics collection`);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
33
|
+
const intervalSec = metricsConfig.intervalSec || parseInt(process.env.METRICS_QUEUE_INTERVAL_SEC || '', 10) || 5;
|
|
48
34
|
super({
|
|
49
35
|
...metricsConfig,
|
|
50
36
|
scripDefaultMetrics: true,
|
|
51
|
-
processType: metricsConfig.processType || '
|
|
52
|
-
intervalSec
|
|
53
|
-
startupValidation
|
|
37
|
+
processType: metricsConfig.processType || 'redis-metrics',
|
|
38
|
+
intervalSec
|
|
54
39
|
});
|
|
55
|
-
this.getConfiguredQueueNames = getConfiguredQueueNames;
|
|
56
|
-
this.startupValidation = startupValidation;
|
|
57
40
|
|
|
58
|
-
/** Redis client used for
|
|
41
|
+
/** Redis client used for metrics */
|
|
59
42
|
this.redisClient = redisClient;
|
|
60
43
|
|
|
61
|
-
/** Cache for queue objects to avoid multiple connections */
|
|
62
|
-
this.queueCache = new Map();
|
|
63
|
-
|
|
64
|
-
/** Gauge for queue jobs by status */
|
|
65
|
-
this.queueJobsGauge = this.createGauge({
|
|
66
|
-
name: 'app_queue_jobs_count',
|
|
67
|
-
help: 'Number of app jobs in the queue by status',
|
|
68
|
-
labelNames: this.withDefaultLabels(['queue_name', 'status'])
|
|
69
|
-
});
|
|
70
|
-
|
|
71
44
|
/** Gauge for Redis client connections */
|
|
72
45
|
this.redisConnectionsGauge = this.createGauge({
|
|
73
46
|
name: 'app_redis_connections_count',
|
|
@@ -138,72 +111,19 @@ class RedisMetricsClient extends MetricsClient {
|
|
|
138
111
|
};
|
|
139
112
|
|
|
140
113
|
/**
|
|
141
|
-
* Collect metrics for
|
|
142
|
-
* @param {string} queueName - Name of the queue
|
|
114
|
+
* Collect metrics for all Redis and push to Prometheus Pushgateway
|
|
143
115
|
* @returns {Promise<void>}
|
|
144
116
|
*/
|
|
145
|
-
|
|
117
|
+
pushRedisMetrics = async () => {
|
|
146
118
|
try {
|
|
147
|
-
if (!this.queueCache.has(queueName)) {
|
|
148
|
-
this.queueCache.set(queueName, new Queue(queueName, {
|
|
149
|
-
redis: this.redisClient,
|
|
150
|
-
isWorker: false,
|
|
151
|
-
getEvents: false,
|
|
152
|
-
sendEvents: false
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
const queue = this.queueCache.get(queueName);
|
|
156
|
-
const health = await queue.checkHealth();
|
|
157
|
-
const labels = {
|
|
158
|
-
...this.getDefaultLabels(),
|
|
159
|
-
queue_name: queueName
|
|
160
|
-
};
|
|
161
|
-
this.queueJobsGauge.set({
|
|
162
|
-
...labels,
|
|
163
|
-
status: 'waiting'
|
|
164
|
-
}, health.waiting || 0);
|
|
165
|
-
this.queueJobsGauge.set({
|
|
166
|
-
...labels,
|
|
167
|
-
status: 'active'
|
|
168
|
-
}, health.active || 0);
|
|
169
|
-
this.queueJobsGauge.set({
|
|
170
|
-
...labels,
|
|
171
|
-
status: 'succeeded'
|
|
172
|
-
}, health.succeeded || 0);
|
|
173
|
-
this.queueJobsGauge.set({
|
|
174
|
-
...labels,
|
|
175
|
-
status: 'failed'
|
|
176
|
-
}, health.failed || 0);
|
|
177
|
-
this.queueJobsGauge.set({
|
|
178
|
-
...labels,
|
|
179
|
-
status: 'delayed'
|
|
180
|
-
}, health.delayed || 0);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
console.warn(`[queue-metrics] Failed to collect metrics for queue ${queueName}:`, error.message);
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Collect metrics for all queues and Redis, then push to Pushgateway
|
|
188
|
-
* @returns {Promise<void>}
|
|
189
|
-
*/
|
|
190
|
-
collectQueueMetrics = async () => {
|
|
191
|
-
try {
|
|
192
|
-
const queueNames = this.getConfiguredQueueNames();
|
|
193
119
|
await this.collectRedisMetrics();
|
|
194
|
-
await Promise.allSettled(queueNames.map(queueName => this.collectSingleQueueMetrics(queueName)));
|
|
195
120
|
await this.gatewayPush();
|
|
196
121
|
if (this.metricsLogValues) {
|
|
197
122
|
const metricObjects = await this.registry.getMetricsAsJSON();
|
|
198
|
-
console.info(`[
|
|
123
|
+
console.info(`[redis-metrics] Collected metrics for Redis`, JSON.stringify(metricObjects, null, 2));
|
|
199
124
|
}
|
|
200
125
|
} catch (error) {
|
|
201
|
-
|
|
202
|
-
console.error(`[queue-metrics] ❌ Configuration error: ${error.message}`);
|
|
203
|
-
console.error(`[queue-metrics] 💡 Example config: METRICS_APP_REDIS_BQ="adalo-compile,adalo-migrations"`);
|
|
204
|
-
} else {
|
|
205
|
-
console.error(`[queue-metrics] Failed to collect queue metrics: ${error.message}`);
|
|
206
|
-
}
|
|
126
|
+
console.error(`[redis-metrics] Failed to collect Redis metrics: ${error.message}`);
|
|
207
127
|
throw error;
|
|
208
128
|
}
|
|
209
129
|
};
|
|
@@ -214,32 +134,27 @@ class RedisMetricsClient extends MetricsClient {
|
|
|
214
134
|
*/
|
|
215
135
|
startPush = (intervalSec = this.intervalSec) => {
|
|
216
136
|
this._startPush(intervalSec, () => {
|
|
217
|
-
this.
|
|
218
|
-
console.error(`[
|
|
137
|
+
this.pushRedisMetrics().catch(err => {
|
|
138
|
+
console.error(`[redis-metrics] Failed to push Redis metrics:`, err);
|
|
219
139
|
});
|
|
220
140
|
});
|
|
221
141
|
};
|
|
222
|
-
_setCleanupHandlers = () => {
|
|
223
|
-
process.on('SIGINT', this.cleanup);
|
|
224
|
-
process.on('SIGTERM', this.cleanup);
|
|
225
|
-
};
|
|
226
142
|
|
|
227
143
|
/**
|
|
228
|
-
* Cleanup
|
|
144
|
+
* Cleanup Redis client
|
|
229
145
|
*/
|
|
230
146
|
cleanup = async () => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.error(`[queue-metrics] Error closing queue ${queueName}:`, err);
|
|
236
|
-
}
|
|
147
|
+
try {
|
|
148
|
+
this.redisClient?.quit();
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error('[redis-metrics] Error closing Redis client:', err);
|
|
237
151
|
}
|
|
238
|
-
|
|
239
|
-
// Close Redis connection
|
|
240
|
-
this.redisClient?.quit();
|
|
241
152
|
process.exit(0);
|
|
242
153
|
};
|
|
154
|
+
_setCleanupHandlers = () => {
|
|
155
|
+
process.on('SIGINT', this.cleanup);
|
|
156
|
+
process.on('SIGTERM', this.cleanup);
|
|
157
|
+
};
|
|
243
158
|
}
|
|
244
159
|
module.exports = {
|
|
245
160
|
RedisMetricsClient
|