@adalo/metrics 0.0.0-staging.1
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/.env.example +14 -0
- package/.eslintignore +3 -0
- package/.eslintrc +61 -0
- package/.github/pull_request_template.md +14 -0
- package/.github/workflows/code-style.yml +29 -0
- package/.github/workflows/deploy-staging.yml +34 -0
- package/.github/workflows/deploy.yml +29 -0
- package/.github/workflows/tests.yml +17 -0
- package/.idea/codeStyles/Project.xml +101 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/git_toolbox_prj.xml +15 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/prettier.xml +6 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc +10 -0
- package/README-health.md +234 -0
- package/README.md +120 -0
- package/__tests__/metricsRedisClient.test.js +138 -0
- package/babel.config.js +20 -0
- package/lib/health/databaseChecker.d.ts +43 -0
- package/lib/health/databaseChecker.d.ts.map +1 -0
- package/lib/health/databaseChecker.js +189 -0
- package/lib/health/databaseChecker.js.map +1 -0
- package/lib/health/healthCheckCache.d.ts +59 -0
- package/lib/health/healthCheckCache.d.ts.map +1 -0
- package/lib/health/healthCheckCache.js +187 -0
- package/lib/health/healthCheckCache.js.map +1 -0
- package/lib/health/healthCheckClient.d.ts +124 -0
- package/lib/health/healthCheckClient.d.ts.map +1 -0
- package/lib/health/healthCheckClient.js +324 -0
- package/lib/health/healthCheckClient.js.map +1 -0
- package/lib/health/healthCheckUtils.d.ts +52 -0
- package/lib/health/healthCheckUtils.d.ts.map +1 -0
- package/lib/health/healthCheckUtils.js +129 -0
- package/lib/health/healthCheckUtils.js.map +1 -0
- package/lib/health/healthCheckWorker.d.ts +2 -0
- package/lib/health/healthCheckWorker.d.ts.map +1 -0
- package/lib/health/healthCheckWorker.js +70 -0
- package/lib/health/healthCheckWorker.js.map +1 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +105 -0
- package/lib/index.js.map +1 -0
- package/lib/metrics/baseMetricsClient.d.ts +174 -0
- package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
- package/lib/metrics/baseMetricsClient.js +428 -0
- package/lib/metrics/baseMetricsClient.js.map +1 -0
- package/lib/metrics/metricsClient.d.ts +95 -0
- package/lib/metrics/metricsClient.d.ts.map +1 -0
- package/lib/metrics/metricsClient.js +239 -0
- package/lib/metrics/metricsClient.js.map +1 -0
- package/lib/metrics/metricsDatabaseClient.d.ts +74 -0
- package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
- package/lib/metrics/metricsDatabaseClient.js +218 -0
- package/lib/metrics/metricsDatabaseClient.js.map +1 -0
- package/lib/metrics/metricsQueueRedisClient.d.ts +57 -0
- package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
- package/lib/metrics/metricsQueueRedisClient.js +277 -0
- package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
- package/lib/metrics/metricsRedisClient.d.ts +71 -0
- package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
- package/lib/metrics/metricsRedisClient.js +370 -0
- package/lib/metrics/metricsRedisClient.js.map +1 -0
- package/lib/redisUtils.d.ts +53 -0
- package/lib/redisUtils.d.ts.map +1 -0
- package/lib/redisUtils.js +140 -0
- package/lib/redisUtils.js.map +1 -0
- package/package.json +66 -0
- package/scripts/README.md +43 -0
- package/scripts/clearMetrics.js +6 -0
- package/src/health/databaseChecker.js +183 -0
- package/src/health/healthCheckCache.js +216 -0
- package/src/health/healthCheckClient.js +347 -0
- package/src/health/healthCheckUtils.js +125 -0
- package/src/health/healthCheckWorker.js +71 -0
- package/src/index.ts +9 -0
- package/src/metrics/baseMetricsClient.js +494 -0
- package/src/metrics/metricsClient.js +284 -0
- package/src/metrics/metricsDatabaseClient.js +236 -0
- package/src/metrics/metricsQueueRedisClient.js +352 -0
- package/src/metrics/metricsRedisClient.js +417 -0
- package/src/redisUtils.js +155 -0
- package/tsconfig.json +19 -0
- package/tsconfig.types.json +11 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const {
|
|
6
|
+
BaseMetricsClient
|
|
7
|
+
} = require('./baseMetricsClient');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MetricsClient handles Prometheus metrics collection and push.
|
|
11
|
+
* Supports gauges, counters, default metrics, and custom metrics.
|
|
12
|
+
* Extends BaseMetricsClient for common functionality.
|
|
13
|
+
*/
|
|
14
|
+
class MetricsClient extends BaseMetricsClient {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} config
|
|
17
|
+
* @param {string} [config.appName] Name of the application
|
|
18
|
+
* @param {string} [config.dynoId] Dyno/instance ID
|
|
19
|
+
* @param {string} [config.processType] Process type (web, worker, etc.)
|
|
20
|
+
* @param {boolean} [config.enabled] Enable metrics collection
|
|
21
|
+
* @param {boolean} [config.httpMetricsEnabled=false] Enable HTTP request metrics (app_requests_total, app_requests_total_duration)
|
|
22
|
+
* @param {boolean} [config.logValues] Log metrics values to console
|
|
23
|
+
* @param {string} [config.pushgatewayUrl] PushGateway URL
|
|
24
|
+
* @param {string} [config.pushgatewaySecret] PushGateway secret token
|
|
25
|
+
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
26
|
+
* @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
|
|
27
|
+
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
28
|
+
* @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)
|
|
29
|
+
*/
|
|
30
|
+
constructor(config = {}) {
|
|
31
|
+
super(config);
|
|
32
|
+
this.httpMetricsEnabled = config.httpMetricsEnabled ?? process.env.METRICS_HTTP_ENABLED === 'true' ?? false;
|
|
33
|
+
this._lastUsageMicros = 0;
|
|
34
|
+
this._lastCheckTime = Date.now();
|
|
35
|
+
this._initDefaultMetrics();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register all built-in default Gauges and Counters.
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
42
|
+
_initDefaultMetrics = () => {
|
|
43
|
+
this.createGauge({
|
|
44
|
+
name: 'app_process_cpu_usage_percent',
|
|
45
|
+
help: 'Current CPU usage of the Node.js process in percent',
|
|
46
|
+
updateFn: this.getCpuUsagePercent
|
|
47
|
+
});
|
|
48
|
+
this.createGauge({
|
|
49
|
+
name: 'app_available_cpu_count',
|
|
50
|
+
help: 'How many CPU cores are available to this process',
|
|
51
|
+
updateFn: this.getAvailableCPUs
|
|
52
|
+
});
|
|
53
|
+
this.createGauge({
|
|
54
|
+
name: 'app_container_memory_usage_bytes',
|
|
55
|
+
help: 'Current container RAM usage from cgroup',
|
|
56
|
+
updateFn: this.getContainerMemoryUsage
|
|
57
|
+
});
|
|
58
|
+
this.createGauge({
|
|
59
|
+
name: 'app_event_loop_lag_ms',
|
|
60
|
+
help: 'Estimated event loop lag in milliseconds',
|
|
61
|
+
updateFn: this.measureLag
|
|
62
|
+
});
|
|
63
|
+
this.createGauge({
|
|
64
|
+
name: 'app_container_memory_limit_bytes',
|
|
65
|
+
help: 'Max RAM available to container from cgroup (memory.max)',
|
|
66
|
+
updateFn: this.getContainerMemoryLimit
|
|
67
|
+
});
|
|
68
|
+
this.createGauge({
|
|
69
|
+
name: 'app_uptime_seconds',
|
|
70
|
+
help: 'How long the process has been running',
|
|
71
|
+
updateFn: process.uptime
|
|
72
|
+
});
|
|
73
|
+
if (this.httpMetricsEnabled) {
|
|
74
|
+
this.createCounter({
|
|
75
|
+
name: 'app_requests_total',
|
|
76
|
+
help: 'Total number of HTTP requests',
|
|
77
|
+
labelNames: this.withDefaultLabels(['method', 'route', 'appId', 'databaseId', 'status_code'])
|
|
78
|
+
});
|
|
79
|
+
this.createCounter({
|
|
80
|
+
name: 'app_requests_total_duration',
|
|
81
|
+
help: 'Total duration of HTTP requests in milliseconds',
|
|
82
|
+
labelNames: this.withDefaultLabels(['method', 'route', 'appId', 'databaseId', 'status_code'])
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get CPU usage percent (cgroup-aware)
|
|
89
|
+
* @returns {number}
|
|
90
|
+
*/
|
|
91
|
+
getCpuUsagePercent = () => {
|
|
92
|
+
try {
|
|
93
|
+
const stat = fs.readFileSync('/sys/fs/cgroup/cpu.stat', 'utf-8');
|
|
94
|
+
const match = stat.match(/usage_usec (\d+)/);
|
|
95
|
+
if (!match) return 0;
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const currentUsage = parseInt(match[1], 10);
|
|
98
|
+
if (this._lastUsageMicros === 0) {
|
|
99
|
+
this._lastUsageMicros = currentUsage;
|
|
100
|
+
this._lastCheckTime = now;
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
const deltaUsage = currentUsage - this._lastUsageMicros;
|
|
104
|
+
const deltaTime = now - this._lastCheckTime;
|
|
105
|
+
this._lastUsageMicros = currentUsage;
|
|
106
|
+
this._lastCheckTime = now;
|
|
107
|
+
return deltaUsage / (deltaTime * 1000) * 100;
|
|
108
|
+
} catch {
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get available CPU cores.
|
|
115
|
+
* @returns {number}
|
|
116
|
+
*/
|
|
117
|
+
getAvailableCPUs() {
|
|
118
|
+
try {
|
|
119
|
+
const cpuMaxPath = '/sys/fs/cgroup/cpu.max';
|
|
120
|
+
if (fs.existsSync(cpuMaxPath)) {
|
|
121
|
+
const [quotaStr, periodStr] = fs.readFileSync(cpuMaxPath, 'utf8').trim().split(' ');
|
|
122
|
+
if (quotaStr === 'max') return os.cpus().length;
|
|
123
|
+
return parseInt(quotaStr, 10) / parseInt(periodStr, 10);
|
|
124
|
+
}
|
|
125
|
+
return os.cpus().length;
|
|
126
|
+
} catch {
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get container memory usage in bytes.
|
|
133
|
+
* @returns {number}
|
|
134
|
+
*/
|
|
135
|
+
getContainerMemoryUsage() {
|
|
136
|
+
try {
|
|
137
|
+
return parseInt(fs.readFileSync('/sys/fs/cgroup/memory.current', 'utf-8').trim(), 10);
|
|
138
|
+
} catch {
|
|
139
|
+
return process.memoryUsage().rss;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get container memory limit in bytes.
|
|
145
|
+
* @returns {number}
|
|
146
|
+
*/
|
|
147
|
+
getContainerMemoryLimit() {
|
|
148
|
+
try {
|
|
149
|
+
const path = '/sys/fs/cgroup/memory.max';
|
|
150
|
+
if (fs.existsSync(path)) {
|
|
151
|
+
const val = fs.readFileSync(path, 'utf-8').trim();
|
|
152
|
+
if (val !== 'max') {
|
|
153
|
+
const parsed = parseInt(val, 10);
|
|
154
|
+
if (parsed && parsed < os.totalmem()) return parsed;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return os.totalmem();
|
|
158
|
+
} catch {
|
|
159
|
+
return os.totalmem();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Measure event loop lag in ms.
|
|
165
|
+
* @returns {Promise<number>}
|
|
166
|
+
*/
|
|
167
|
+
measureLag() {
|
|
168
|
+
return new Promise(resolve => {
|
|
169
|
+
const start = Date.now();
|
|
170
|
+
setImmediate(() => resolve(Date.now() - start));
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Increment the HTTP requests counter with detailed request information.
|
|
176
|
+
*
|
|
177
|
+
* @param {Object} params - The parameters for the request counter.
|
|
178
|
+
* @param {string} params.method - HTTP method (GET, POST, etc.).
|
|
179
|
+
* @param {string} params.route - The full requested URL or route.
|
|
180
|
+
* @param {number} params.status_code - HTTP response status code.
|
|
181
|
+
* @param {string} [params.appId=''] - Optional application identifier.
|
|
182
|
+
* @param {string} [params.databaseId=''] - Optional database identifier.
|
|
183
|
+
* @param {number} params.duration - Request duration in milliseconds.
|
|
184
|
+
*/
|
|
185
|
+
trackHttpRequest({
|
|
186
|
+
method,
|
|
187
|
+
route,
|
|
188
|
+
status_code,
|
|
189
|
+
appId = '',
|
|
190
|
+
databaseId = '',
|
|
191
|
+
duration
|
|
192
|
+
}) {
|
|
193
|
+
if (!this.httpMetricsEnabled) return;
|
|
194
|
+
this.countersFunctions?.app_requests_total({
|
|
195
|
+
method,
|
|
196
|
+
route,
|
|
197
|
+
status_code,
|
|
198
|
+
appId,
|
|
199
|
+
databaseId
|
|
200
|
+
});
|
|
201
|
+
this.countersFunctions?.app_requests_total_duration({
|
|
202
|
+
method,
|
|
203
|
+
route,
|
|
204
|
+
status_code,
|
|
205
|
+
appId,
|
|
206
|
+
databaseId
|
|
207
|
+
}, duration);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Express middleware to track HTTP requests.
|
|
212
|
+
* Track the `app_requests_total` and `app_requests_total_duration` metric.
|
|
213
|
+
*/
|
|
214
|
+
trackHttpRequestMiddleware = (req, res, next) => {
|
|
215
|
+
if (!this.enabled || !this.httpMetricsEnabled || req.method === 'OPTIONS') {
|
|
216
|
+
next();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const start = Date.now();
|
|
220
|
+
res.on('finish', () => {
|
|
221
|
+
const route = req.route?.path || req.path || 'unknown';
|
|
222
|
+
const appId = req.params?.appId || req.body?.appId || req.query?.appId || '';
|
|
223
|
+
const databaseId = req.params?.databaseId || req.body?.databaseId || req.query?.databaseId || req.params?.datasourceId || req.body?.datasourceId || req.query?.datasourceId || '';
|
|
224
|
+
this.trackHttpRequest({
|
|
225
|
+
method: req.method,
|
|
226
|
+
route,
|
|
227
|
+
status_code: res.statusCode,
|
|
228
|
+
appId,
|
|
229
|
+
databaseId,
|
|
230
|
+
duration: Date.now() - start
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
next();
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
module.exports = {
|
|
237
|
+
MetricsClient
|
|
238
|
+
};
|
|
239
|
+
//# sourceMappingURL=metricsClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsClient.js","names":["fs","require","os","BaseMetricsClient","MetricsClient","constructor","config","httpMetricsEnabled","process","env","METRICS_HTTP_ENABLED","_lastUsageMicros","_lastCheckTime","Date","now","_initDefaultMetrics","createGauge","name","help","updateFn","getCpuUsagePercent","getAvailableCPUs","getContainerMemoryUsage","measureLag","getContainerMemoryLimit","uptime","createCounter","labelNames","withDefaultLabels","stat","readFileSync","match","currentUsage","parseInt","deltaUsage","deltaTime","cpuMaxPath","existsSync","quotaStr","periodStr","trim","split","cpus","length","memoryUsage","rss","path","val","parsed","totalmem","Promise","resolve","start","setImmediate","trackHttpRequest","method","route","status_code","appId","databaseId","duration","countersFunctions","app_requests_total","app_requests_total_duration","trackHttpRequestMiddleware","req","res","next","enabled","on","params","body","query","datasourceId","statusCode","module","exports"],"sources":["../../src/metrics/metricsClient.js"],"sourcesContent":["const fs = require('fs')\nconst os = require('os')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\n\n/**\n * MetricsClient handles Prometheus metrics collection and push.\n * Supports gauges, counters, default metrics, and custom metrics.\n * Extends BaseMetricsClient for common functionality.\n */\nclass MetricsClient extends BaseMetricsClient {\n /**\n * @param {Object} config\n * @param {string} [config.appName] Name of the application\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Process type (web, worker, etc.)\n * @param {boolean} [config.enabled] Enable metrics collection\n * @param {boolean} [config.httpMetricsEnabled=false] Enable HTTP request metrics (app_requests_total, app_requests_total_duration)\n * @param {boolean} [config.logValues] Log metrics values to console\n * @param {string} [config.pushgatewayUrl] PushGateway URL\n * @param {string} [config.pushgatewaySecret] PushGateway secret token\n * @param {number} [config.intervalSec] Interval in seconds for pushing metrics\n * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name\n * @param {function} [config.startupValidation] Add to validate on start push.\n * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)\n */\n constructor(config = {}) {\n super(config)\n\n this.httpMetricsEnabled =\n config.httpMetricsEnabled ??\n process.env.METRICS_HTTP_ENABLED === 'true' ??\n false\n\n this._lastUsageMicros = 0\n this._lastCheckTime = Date.now()\n\n this._initDefaultMetrics()\n }\n\n /**\n * Register all built-in default Gauges and Counters.\n * @private\n */\n _initDefaultMetrics = () => {\n this.createGauge({\n name: 'app_process_cpu_usage_percent',\n help: 'Current CPU usage of the Node.js process in percent',\n updateFn: this.getCpuUsagePercent,\n })\n\n this.createGauge({\n name: 'app_available_cpu_count',\n help: 'How many CPU cores are available to this process',\n updateFn: this.getAvailableCPUs,\n })\n\n this.createGauge({\n name: 'app_container_memory_usage_bytes',\n help: 'Current container RAM usage from cgroup',\n updateFn: this.getContainerMemoryUsage,\n })\n\n this.createGauge({\n name: 'app_event_loop_lag_ms',\n help: 'Estimated event loop lag in milliseconds',\n updateFn: this.measureLag,\n })\n\n this.createGauge({\n name: 'app_container_memory_limit_bytes',\n help: 'Max RAM available to container from cgroup (memory.max)',\n updateFn: this.getContainerMemoryLimit,\n })\n\n this.createGauge({\n name: 'app_uptime_seconds',\n help: 'How long the process has been running',\n updateFn: process.uptime,\n })\n\n if (this.httpMetricsEnabled) {\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabels([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\n 'status_code',\n ]),\n })\n\n this.createCounter({\n name: 'app_requests_total_duration',\n help: 'Total duration of HTTP requests in milliseconds',\n labelNames: this.withDefaultLabels([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\n 'status_code',\n ]),\n })\n }\n }\n\n /**\n * Get CPU usage percent (cgroup-aware)\n * @returns {number}\n */\n getCpuUsagePercent = () => {\n try {\n const stat = fs.readFileSync('/sys/fs/cgroup/cpu.stat', 'utf-8')\n const match = stat.match(/usage_usec (\\d+)/)\n if (!match) return 0\n\n const now = Date.now()\n const currentUsage = parseInt(match[1], 10)\n\n if (this._lastUsageMicros === 0) {\n this._lastUsageMicros = currentUsage\n this._lastCheckTime = now\n return 0\n }\n\n const deltaUsage = currentUsage - this._lastUsageMicros\n const deltaTime = now - this._lastCheckTime\n\n this._lastUsageMicros = currentUsage\n this._lastCheckTime = now\n\n return (deltaUsage / (deltaTime * 1000)) * 100\n } catch {\n return 0\n }\n }\n\n /**\n * Get available CPU cores.\n * @returns {number}\n */\n getAvailableCPUs() {\n try {\n const cpuMaxPath = '/sys/fs/cgroup/cpu.max'\n if (fs.existsSync(cpuMaxPath)) {\n const [quotaStr, periodStr] = fs\n .readFileSync(cpuMaxPath, 'utf8')\n .trim()\n .split(' ')\n if (quotaStr === 'max') return os.cpus().length\n return parseInt(quotaStr, 10) / parseInt(periodStr, 10)\n }\n return os.cpus().length\n } catch {\n return 1\n }\n }\n\n /**\n * Get container memory usage in bytes.\n * @returns {number}\n */\n getContainerMemoryUsage() {\n try {\n return parseInt(\n fs.readFileSync('/sys/fs/cgroup/memory.current', 'utf-8').trim(),\n 10\n )\n } catch {\n return process.memoryUsage().rss\n }\n }\n\n /**\n * Get container memory limit in bytes.\n * @returns {number}\n */\n getContainerMemoryLimit() {\n try {\n const path = '/sys/fs/cgroup/memory.max'\n if (fs.existsSync(path)) {\n const val = fs.readFileSync(path, 'utf-8').trim()\n if (val !== 'max') {\n const parsed = parseInt(val, 10)\n if (parsed && parsed < os.totalmem()) return parsed\n }\n }\n return os.totalmem()\n } catch {\n return os.totalmem()\n }\n }\n\n /**\n * Measure event loop lag in ms.\n * @returns {Promise<number>}\n */\n measureLag() {\n return new Promise(resolve => {\n const start = Date.now()\n setImmediate(() => resolve(Date.now() - start))\n })\n }\n\n /**\n * Increment the HTTP requests counter with detailed request information.\n *\n * @param {Object} params - The parameters for the request counter.\n * @param {string} params.method - HTTP method (GET, POST, etc.).\n * @param {string} params.route - The full requested URL or route.\n * @param {number} params.status_code - HTTP response status code.\n * @param {string} [params.appId=''] - Optional application identifier.\n * @param {string} [params.databaseId=''] - Optional database identifier.\n * @param {number} params.duration - Request duration in milliseconds.\n */\n trackHttpRequest({\n method,\n route,\n status_code,\n appId = '',\n databaseId = '',\n duration,\n }) {\n if (!this.httpMetricsEnabled) return\n\n this.countersFunctions?.app_requests_total({\n method,\n route,\n status_code,\n appId,\n databaseId,\n })\n this.countersFunctions?.app_requests_total_duration(\n {\n method,\n route,\n status_code,\n appId,\n databaseId,\n },\n duration\n )\n }\n\n /**\n * Express middleware to track HTTP requests.\n * Track the `app_requests_total` and `app_requests_total_duration` metric.\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (!this.enabled || !this.httpMetricsEnabled || req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n const appId =\n req.params?.appId || req.body?.appId || req.query?.appId || ''\n const databaseId =\n req.params?.databaseId ||\n req.body?.databaseId ||\n req.query?.databaseId ||\n req.params?.datasourceId ||\n req.body?.datasourceId ||\n req.query?.datasourceId ||\n ''\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n appId,\n databaseId,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { MetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatabaseMetricsClient collects Postgres connection metrics
|
|
3
|
+
* and pushes them to Prometheus Pushgateway.
|
|
4
|
+
*
|
|
5
|
+
* @extends BaseMetricsClient
|
|
6
|
+
*/
|
|
7
|
+
export class DatabaseMetricsClient extends BaseMetricsClient {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Object} options
|
|
10
|
+
* @param {string} options.databaseUrl - Required main database URL
|
|
11
|
+
* @param {string} options.databaseName - Main database name for metrics
|
|
12
|
+
* @param {Object<string, string>} [options.additional_database_urls] - Optional additional DBs, keyed by custom name with URL as value
|
|
13
|
+
* @param {string} [options.appName] - Application name (from BaseMetricsClient)
|
|
14
|
+
* @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)
|
|
15
|
+
* @param {string} [options.processType] - Process type (from BaseMetricsClient)
|
|
16
|
+
* @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)
|
|
17
|
+
* @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)
|
|
18
|
+
* @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)
|
|
19
|
+
* @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)
|
|
20
|
+
* @param {number} [options.intervalSec] - Interval in seconds for pushing metrics
|
|
21
|
+
* @param {boolean} [options.removeOldMetrics] - Remove old metrics by service
|
|
22
|
+
* @param {function} [options.startupValidation] - Function to validate startup
|
|
23
|
+
* @param {boolean} [options.disablePushgateway] - Disable pushing to Pushgateway (use HTTP scraping instead)
|
|
24
|
+
*/
|
|
25
|
+
constructor({ databaseUrl, databaseName, additional_database_urls, ...metricsConfig }?: {
|
|
26
|
+
databaseUrl: string;
|
|
27
|
+
databaseName: string;
|
|
28
|
+
additional_database_urls?: {
|
|
29
|
+
[x: string]: string;
|
|
30
|
+
} | undefined;
|
|
31
|
+
appName?: string | undefined;
|
|
32
|
+
dynoId?: string | undefined;
|
|
33
|
+
processType?: string | undefined;
|
|
34
|
+
enabled?: boolean | undefined;
|
|
35
|
+
logValues?: boolean | undefined;
|
|
36
|
+
pushgatewayUrl?: string | undefined;
|
|
37
|
+
pushgatewaySecret?: string | undefined;
|
|
38
|
+
intervalSec?: number | undefined;
|
|
39
|
+
removeOldMetrics?: boolean | undefined;
|
|
40
|
+
startupValidation?: Function | undefined;
|
|
41
|
+
disablePushgateway?: boolean | undefined;
|
|
42
|
+
});
|
|
43
|
+
databasePools: any[];
|
|
44
|
+
/** Gauge for Database connections */
|
|
45
|
+
databaseConnectionsGauge: import("prom-client").Gauge<string>;
|
|
46
|
+
_addPool: (url: any, dbKey: any) => Pool;
|
|
47
|
+
/**
|
|
48
|
+
* @param {Pool} pool - PG connection pool
|
|
49
|
+
* @returns {Promise<{ current: number, max: number, dbName: string }>}
|
|
50
|
+
*/
|
|
51
|
+
getDBConnectionsAndName: (pool: Pool) => Promise<{
|
|
52
|
+
current: number;
|
|
53
|
+
max: number;
|
|
54
|
+
dbName: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Collect database connection metrics for all configured pools
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
collectDatabaseMetrics: () => Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Push database metrics to Prometheus Pushgateway
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
pushDatabaseMetrics: () => Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Start periodic collection.
|
|
68
|
+
* @param {number} [intervalSec=this.intervalSec] - Interval in seconds
|
|
69
|
+
*/
|
|
70
|
+
startPush: (intervalSec?: number | undefined) => void;
|
|
71
|
+
}
|
|
72
|
+
import { BaseMetricsClient } from "./baseMetricsClient";
|
|
73
|
+
import { Pool } from "pg";
|
|
74
|
+
//# sourceMappingURL=metricsDatabaseClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsDatabaseClient.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsDatabaseClient.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACE;;;;;;;;;;;;;;;;OAgBG;IACH;QAf2B,WAAW,EAA3B,MAAM;QACU,YAAY,EAA5B,MAAM;QAC2B,wBAAwB;;;QACxC,OAAO;QACP,MAAM;QACN,WAAW;QACV,OAAO;QACP,SAAS;QACV,cAAc;QACd,iBAAiB;QACjB,WAAW;QACV,gBAAgB;QACf,iBAAiB;QAClB,kBAAkB;OAkF9C;IAtBC,qBAAuB;IAUvB,qCAAqC;IACrC,8DAQE;IAKJ,yCAKC;IAED;;;OAGG;IACH,gCAHW,IAAI,KACF,QAAQ;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CA4BrE;IAED;;;OAGG;IACH,8BAFa,QAAQ,IAAI,CAAC,CAsBzB;IAED;;;OAGG;IACH,2BAFa,QAAQ,IAAI,CAAC,CAqBzB;IAED;;;OAGG;IACH,sDAMC;CAwBF"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
Pool
|
|
5
|
+
} = require('pg');
|
|
6
|
+
const {
|
|
7
|
+
BaseMetricsClient
|
|
8
|
+
} = require('./baseMetricsClient');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DatabaseMetricsClient collects Postgres connection metrics
|
|
12
|
+
* and pushes them to Prometheus Pushgateway.
|
|
13
|
+
*
|
|
14
|
+
* @extends BaseMetricsClient
|
|
15
|
+
*/
|
|
16
|
+
class DatabaseMetricsClient extends BaseMetricsClient {
|
|
17
|
+
/**
|
|
18
|
+
* @param {Object} options
|
|
19
|
+
* @param {string} options.databaseUrl - Required main database URL
|
|
20
|
+
* @param {string} options.databaseName - Main database name for metrics
|
|
21
|
+
* @param {Object<string, string>} [options.additional_database_urls] - Optional additional DBs, keyed by custom name with URL as value
|
|
22
|
+
* @param {string} [options.appName] - Application name (from BaseMetricsClient)
|
|
23
|
+
* @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)
|
|
24
|
+
* @param {string} [options.processType] - Process type (from BaseMetricsClient)
|
|
25
|
+
* @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)
|
|
26
|
+
* @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)
|
|
27
|
+
* @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)
|
|
28
|
+
* @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)
|
|
29
|
+
* @param {number} [options.intervalSec] - Interval in seconds for pushing metrics
|
|
30
|
+
* @param {boolean} [options.removeOldMetrics] - Remove old metrics by service
|
|
31
|
+
* @param {function} [options.startupValidation] - Function to validate startup
|
|
32
|
+
* @param {boolean} [options.disablePushgateway] - Disable pushing to Pushgateway (use HTTP scraping instead)
|
|
33
|
+
*/
|
|
34
|
+
constructor({
|
|
35
|
+
databaseUrl,
|
|
36
|
+
databaseName,
|
|
37
|
+
additional_database_urls = {},
|
|
38
|
+
...metricsConfig
|
|
39
|
+
} = {}) {
|
|
40
|
+
const intervalSec = metricsConfig.intervalSec || parseInt(process.env.METRICS_DATABASE_INTERVAL_SEC || '', 10) || 60;
|
|
41
|
+
const tmpAppName = metricsConfig.appName || process.env.BUILD_APP_NAME || 'unknown-app';
|
|
42
|
+
const mainDbName = databaseName || `${tmpAppName}_db`;
|
|
43
|
+
const startupValidation = async () => {
|
|
44
|
+
if (!databaseUrl) {
|
|
45
|
+
console.error(`[database-metrics] ❌ METRICS_DATABASE_URL is required`);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const mainPool = new Pool({
|
|
50
|
+
connectionString: databaseUrl
|
|
51
|
+
});
|
|
52
|
+
await mainPool.query('SELECT 1');
|
|
53
|
+
await mainPool.end();
|
|
54
|
+
console.info(`[database-metrics] ✓ Main database OK: ${mainDbName}`);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(`[database-metrics] ❌ Cannot connect to main database: ${err.message}`);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
for (const [dbKey, url] of Object.entries(additional_database_urls)) {
|
|
60
|
+
try {
|
|
61
|
+
const p = new Pool({
|
|
62
|
+
connectionString: url
|
|
63
|
+
});
|
|
64
|
+
await p.query('SELECT 1');
|
|
65
|
+
await p.end();
|
|
66
|
+
console.info(`[database-metrics] ✓ Additional database OK: ${dbKey}`);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(`[database-metrics] ⚠ Skipping additional database: ${dbKey}`);
|
|
69
|
+
console.error(`[database-metrics] ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
console.info(`[database-metrics] Database metrics collection starting`);
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
super({
|
|
76
|
+
...metricsConfig,
|
|
77
|
+
processType: metricsConfig.processType || 'database-metrics',
|
|
78
|
+
intervalSec,
|
|
79
|
+
startupValidation
|
|
80
|
+
});
|
|
81
|
+
this.databasePools = [];
|
|
82
|
+
if (databaseUrl) {
|
|
83
|
+
this._addPool(databaseUrl, mainDbName);
|
|
84
|
+
}
|
|
85
|
+
for (const [dbKey, url] of Object.entries(additional_database_urls)) {
|
|
86
|
+
this._addPool(url, dbKey);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Gauge for Database connections */
|
|
90
|
+
this.databaseConnectionsGauge = this.createGauge({
|
|
91
|
+
name: 'app_database_connections',
|
|
92
|
+
help: 'Postgres database connections',
|
|
93
|
+
labelNames: this.withDefaultLabels(['max_connections', 'database_name', 'db_key'])
|
|
94
|
+
});
|
|
95
|
+
this._setCleanupHandlers();
|
|
96
|
+
}
|
|
97
|
+
_addPool = (url, dbKey) => {
|
|
98
|
+
const pool = new Pool({
|
|
99
|
+
connectionString: url
|
|
100
|
+
});
|
|
101
|
+
pool.dbKey = dbKey;
|
|
102
|
+
this.databasePools.push(pool);
|
|
103
|
+
return pool;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {Pool} pool - PG connection pool
|
|
108
|
+
* @returns {Promise<{ current: number, max: number, dbName: string }>}
|
|
109
|
+
*/
|
|
110
|
+
getDBConnectionsAndName = async pool => {
|
|
111
|
+
try {
|
|
112
|
+
const currentRes = await pool.query('SELECT COUNT(*) AS current FROM pg_stat_activity WHERE datname = current_database()');
|
|
113
|
+
const current = parseInt(currentRes.rows[0]?.current || 0, 10);
|
|
114
|
+
const maxRes = await pool.query("SELECT current_setting('max_connections') AS max");
|
|
115
|
+
const max = parseInt(maxRes.rows[0]?.max || 0, 10);
|
|
116
|
+
let dbName = pool.options?.database;
|
|
117
|
+
if (!dbName && pool.options?.connectionString) {
|
|
118
|
+
try {
|
|
119
|
+
const url = new URL(pool.options.connectionString);
|
|
120
|
+
dbName = url.pathname.replace(/^\//, '');
|
|
121
|
+
} catch {
|
|
122
|
+
dbName = pool.options.connectionString;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
current,
|
|
127
|
+
max,
|
|
128
|
+
dbName
|
|
129
|
+
};
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return {
|
|
132
|
+
current: 0,
|
|
133
|
+
max: 0,
|
|
134
|
+
dbName: pool.options?.database || ''
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Collect database connection metrics for all configured pools
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
*/
|
|
143
|
+
collectDatabaseMetrics = async () => {
|
|
144
|
+
for (const pool of this.databasePools) {
|
|
145
|
+
try {
|
|
146
|
+
const {
|
|
147
|
+
current,
|
|
148
|
+
max,
|
|
149
|
+
dbName
|
|
150
|
+
} = await this.getDBConnectionsAndName(pool);
|
|
151
|
+
this.databaseConnectionsGauge.set({
|
|
152
|
+
...this.getDefaultLabels(),
|
|
153
|
+
database_name: pool.dbKey,
|
|
154
|
+
max_connections: String(max),
|
|
155
|
+
db_key: dbName
|
|
156
|
+
}, current);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
console.warn(`[database-metrics] Failed to collect: ${err.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Push database metrics to Prometheus Pushgateway
|
|
165
|
+
* @returns {Promise<void>}
|
|
166
|
+
*/
|
|
167
|
+
pushDatabaseMetrics = async () => {
|
|
168
|
+
try {
|
|
169
|
+
await this.collectDatabaseMetrics();
|
|
170
|
+
await this.gatewayPush();
|
|
171
|
+
this.clearAllCounters();
|
|
172
|
+
if (this.metricsLogValues) {
|
|
173
|
+
const metricObjects = await this.registry.getMetricsAsJSON();
|
|
174
|
+
console.info(`[database-metrics] Collected DB metrics`, JSON.stringify(metricObjects, null, 2));
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(`[database-metrics] Failed to collect DB metrics: ${error.message}`);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Start periodic collection.
|
|
184
|
+
* @param {number} [intervalSec=this.intervalSec] - Interval in seconds
|
|
185
|
+
*/
|
|
186
|
+
startPush = (intervalSec = this.intervalSec) => {
|
|
187
|
+
this._startPush(intervalSec, () => {
|
|
188
|
+
this.pushDatabaseMetrics().catch(err => {
|
|
189
|
+
console.error(`[database-metrics] Failed to push DB metrics:`, err);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Cleanup database pools and exit process
|
|
196
|
+
* @returns {Promise<void>}
|
|
197
|
+
*/
|
|
198
|
+
cleanup = async () => {
|
|
199
|
+
try {
|
|
200
|
+
if (this.databasePools) {
|
|
201
|
+
for (const pool of this.databasePools) {
|
|
202
|
+
await pool.end();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error('[database-metrics] Error during cleanup:', err);
|
|
207
|
+
}
|
|
208
|
+
process.exit(0);
|
|
209
|
+
};
|
|
210
|
+
_setCleanupHandlers = () => {
|
|
211
|
+
process.on('SIGINT', this.cleanup);
|
|
212
|
+
process.on('SIGTERM', this.cleanup);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
module.exports = {
|
|
216
|
+
DatabaseMetricsClient
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=metricsDatabaseClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsDatabaseClient.js","names":["Pool","require","BaseMetricsClient","DatabaseMetricsClient","constructor","databaseUrl","databaseName","additional_database_urls","metricsConfig","intervalSec","parseInt","process","env","METRICS_DATABASE_INTERVAL_SEC","tmpAppName","appName","BUILD_APP_NAME","mainDbName","startupValidation","console","error","mainPool","connectionString","query","end","info","err","message","dbKey","url","Object","entries","p","processType","databasePools","_addPool","databaseConnectionsGauge","createGauge","name","help","labelNames","withDefaultLabels","_setCleanupHandlers","pool","push","getDBConnectionsAndName","currentRes","current","rows","maxRes","max","dbName","options","database","URL","pathname","replace","collectDatabaseMetrics","set","getDefaultLabels","database_name","max_connections","String","db_key","warn","pushDatabaseMetrics","gatewayPush","clearAllCounters","metricsLogValues","metricObjects","registry","getMetricsAsJSON","JSON","stringify","startPush","_startPush","catch","cleanup","exit","on","module","exports"],"sources":["../../src/metrics/metricsDatabaseClient.js"],"sourcesContent":["const { Pool } = require('pg')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\n\n/**\n * DatabaseMetricsClient collects Postgres connection metrics\n * and pushes them to Prometheus Pushgateway.\n *\n * @extends BaseMetricsClient\n */\nclass DatabaseMetricsClient extends BaseMetricsClient {\n /**\n * @param {Object} options\n * @param {string} options.databaseUrl - Required main database URL\n * @param {string} options.databaseName - Main database name for metrics\n * @param {Object<string, string>} [options.additional_database_urls] - Optional additional DBs, keyed by custom name with URL as value\n * @param {string} [options.appName] - Application name (from BaseMetricsClient)\n * @param {string} [options.dynoId] - Dyno/instance ID (from BaseMetricsClient)\n * @param {string} [options.processType] - Process type (from BaseMetricsClient)\n * @param {boolean} [options.enabled] - Enable metrics collection (from BaseMetricsClient)\n * @param {boolean} [options.logValues] - Log metrics values (from BaseMetricsClient)\n * @param {string} [options.pushgatewayUrl] - PushGateway URL (from BaseMetricsClient)\n * @param {string} [options.pushgatewaySecret] - PushGateway secret token (from BaseMetricsClient)\n * @param {number} [options.intervalSec] - Interval in seconds for pushing metrics\n * @param {boolean} [options.removeOldMetrics] - Remove old metrics by service\n * @param {function} [options.startupValidation] - Function to validate startup\n * @param {boolean} [options.disablePushgateway] - Disable pushing to Pushgateway (use HTTP scraping instead)\n */\n constructor({\n databaseUrl,\n databaseName,\n additional_database_urls = {},\n ...metricsConfig\n } = {}) {\n const intervalSec =\n metricsConfig.intervalSec ||\n parseInt(process.env.METRICS_DATABASE_INTERVAL_SEC || '', 10) ||\n 60\n\n const tmpAppName =\n metricsConfig.appName || process.env.BUILD_APP_NAME || 'unknown-app'\n const mainDbName = databaseName || `${tmpAppName}_db`\n\n const startupValidation = async () => {\n if (!databaseUrl) {\n console.error(`[database-metrics] ❌ METRICS_DATABASE_URL is required`)\n return false\n }\n\n try {\n const mainPool = new Pool({ connectionString: databaseUrl })\n await mainPool.query('SELECT 1')\n await mainPool.end()\n console.info(`[database-metrics] ✓ Main database OK: ${mainDbName}`)\n } catch (err) {\n console.error(\n `[database-metrics] ❌ Cannot connect to main database: ${err.message}`\n )\n return false\n }\n\n for (const [dbKey, url] of Object.entries(additional_database_urls)) {\n try {\n const p = new Pool({ connectionString: url })\n await p.query('SELECT 1')\n await p.end()\n console.info(`[database-metrics] ✓ Additional database OK: ${dbKey}`)\n } catch (err) {\n console.error(\n `[database-metrics] ⚠ Skipping additional database: ${dbKey}`\n )\n console.error(`[database-metrics] ${err.message}`)\n }\n }\n\n console.info(`[database-metrics] Database metrics collection starting`)\n return true\n }\n\n super({\n ...metricsConfig,\n processType: metricsConfig.processType || 'database-metrics',\n intervalSec,\n startupValidation,\n })\n\n this.databasePools = []\n\n if (databaseUrl) {\n this._addPool(databaseUrl, mainDbName)\n }\n\n for (const [dbKey, url] of Object.entries(additional_database_urls)) {\n this._addPool(url, dbKey)\n }\n\n /** Gauge for Database connections */\n this.databaseConnectionsGauge = this.createGauge({\n name: 'app_database_connections',\n help: 'Postgres database connections',\n labelNames: this.withDefaultLabels([\n 'max_connections',\n 'database_name',\n 'db_key',\n ]),\n })\n\n this._setCleanupHandlers()\n }\n\n _addPool = (url, dbKey) => {\n const pool = new Pool({ connectionString: url })\n pool.dbKey = dbKey\n this.databasePools.push(pool)\n return pool\n }\n\n /**\n * @param {Pool} pool - PG connection pool\n * @returns {Promise<{ current: number, max: number, dbName: string }>}\n */\n getDBConnectionsAndName = async pool => {\n try {\n const currentRes = await pool.query(\n 'SELECT COUNT(*) AS current FROM pg_stat_activity WHERE datname = current_database()'\n )\n const current = parseInt(currentRes.rows[0]?.current || 0, 10)\n\n const maxRes = await pool.query(\n \"SELECT current_setting('max_connections') AS max\"\n )\n const max = parseInt(maxRes.rows[0]?.max || 0, 10)\n\n let dbName = pool.options?.database\n if (!dbName && pool.options?.connectionString) {\n try {\n const url = new URL(pool.options.connectionString)\n dbName = url.pathname.replace(/^\\//, '')\n } catch {\n dbName = pool.options.connectionString\n }\n }\n\n return { current, max, dbName }\n } catch (err) {\n return { current: 0, max: 0, dbName: pool.options?.database || '' }\n }\n }\n\n /**\n * Collect database connection metrics for all configured pools\n * @returns {Promise<void>}\n */\n collectDatabaseMetrics = async () => {\n for (const pool of this.databasePools) {\n try {\n const { current, max, dbName } = await this.getDBConnectionsAndName(\n pool\n )\n\n this.databaseConnectionsGauge.set(\n {\n ...this.getDefaultLabels(),\n database_name: pool.dbKey,\n max_connections: String(max),\n db_key: dbName,\n },\n current\n )\n } catch (err) {\n console.warn(`[database-metrics] Failed to collect: ${err.message}`)\n }\n }\n }\n\n /**\n * Push database metrics to Prometheus Pushgateway\n * @returns {Promise<void>}\n */\n pushDatabaseMetrics = async () => {\n try {\n await this.collectDatabaseMetrics()\n await this.gatewayPush()\n this.clearAllCounters()\n\n if (this.metricsLogValues) {\n const metricObjects = await this.registry.getMetricsAsJSON()\n console.info(\n `[database-metrics] Collected DB metrics`,\n JSON.stringify(metricObjects, null, 2)\n )\n }\n } catch (error) {\n console.error(\n `[database-metrics] Failed to collect DB metrics: ${error.message}`\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.pushDatabaseMetrics().catch(err => {\n console.error(`[database-metrics] Failed to push DB metrics:`, err)\n })\n })\n }\n\n /**\n * Cleanup database pools and exit process\n * @returns {Promise<void>}\n */\n cleanup = async () => {\n try {\n if (this.databasePools) {\n for (const pool of this.databasePools) {\n await pool.end()\n }\n }\n } catch (err) {\n console.error('[database-metrics] Error during cleanup:', err)\n }\n\n process.exit(0)\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n}\n\nmodule.exports = { DatabaseMetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueueRedisMetricsClient extends RedisMetricsClient 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
|
+
* Execute a Redis command in a client-type safe way.
|
|
16
|
+
*
|
|
17
|
+
* @param {string[]} args Command args array, e.g. ['LLEN', 'key']
|
|
18
|
+
* @returns {Promise<any>}
|
|
19
|
+
*/
|
|
20
|
+
_send: (args: string[]) => Promise<any>;
|
|
21
|
+
_toNumber: (v: any) => number;
|
|
22
|
+
_getQueueType: (queueName: any) => Promise<"bull" | "bee">;
|
|
23
|
+
_getBullHealth: (queueName: any) => Promise<{
|
|
24
|
+
waiting: number;
|
|
25
|
+
active: number;
|
|
26
|
+
succeeded: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
delayed: number;
|
|
29
|
+
}>;
|
|
30
|
+
_getBeeQueueHealth: (queueName: any) => Promise<{
|
|
31
|
+
waiting: number;
|
|
32
|
+
active: number;
|
|
33
|
+
succeeded: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
delayed: number;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Collect metrics for a single queue and set gauges.
|
|
39
|
+
*
|
|
40
|
+
* Supports:
|
|
41
|
+
* - Bull (default): keys like `bull:<queue>:wait`, `...:active`, `...:completed`, `...:failed`, `...:delayed`
|
|
42
|
+
* - Bee-Queue: keys like `bq:<queue>:waiting`, `...:active`, `...:succeeded`, `...:failed`, `...:delayed`
|
|
43
|
+
*
|
|
44
|
+
* Auto-detects queue type unless `METRICS_QUEUE_TYPE` is set to `bull` or `bee`.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} queueName - Name of the queue
|
|
47
|
+
* @returns {Promise<void>}
|
|
48
|
+
*/
|
|
49
|
+
collectSingleQueueMetrics: (queueName: string) => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Collect metrics for all queues and Redis, then push to Pushgateway
|
|
52
|
+
* @returns {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
collectQueueMetrics: () => Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
import { RedisMetricsClient } from "./metricsRedisClient";
|
|
57
|
+
//# sourceMappingURL=metricsQueueRedisClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsQueueRedisClient.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsQueueRedisClient.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IAgEI,wCAAsD;IACtD,iCAA0C;IAE1C,4DAA4D;IAa5D,0BAA2B;IAE3B,qCAAqC;IACrC,oDAIE;IAKJ;;;;;OAKG;IACH,cAHW,MAAM,EAAE,KACN,QAAQ,GAAG,CAAC,CA6BxB;IAED,8BAGC;IAED,2DA6BC;IAED;;;;;;OAmBC;IAED;;;;;;OAwBC;IAED;;;;;;;;;;;OAWG;IACH,uCAHW,MAAM,KACJ,QAAQ,IAAI,CAAC,CAyCzB;IAED;;;OAGG;IACH,2BAFa,QAAQ,IAAI,CAAC,CAuCzB;CAoCF"}
|