@adalo/metrics 0.1.16 → 0.1.18
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/metricsClient.d.ts +86 -14
- package/lib/metricsClient.d.ts.map +1 -1
- package/lib/metricsClient.js +166 -37
- package/lib/metricsClient.js.map +1 -1
- package/package.json +1 -1
- package/src/metricsClient.js +172 -70
package/lib/metricsClient.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export class MetricsClient {
|
|
|
15
15
|
* @param {string} [config.pushgatewayPassword] PushGateway password
|
|
16
16
|
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
17
17
|
* @param {boolean} [config.removeAllJobs] Enable to clear all jobs at startup
|
|
18
|
+
* @param {boolean} [config.scripDefaultMetrics] Enable to scip default metrics creation
|
|
19
|
+
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
18
20
|
*/
|
|
19
21
|
constructor(config?: {
|
|
20
22
|
appName?: string | undefined;
|
|
@@ -27,6 +29,8 @@ export class MetricsClient {
|
|
|
27
29
|
pushgatewayPassword?: string | undefined;
|
|
28
30
|
intervalSec?: number | undefined;
|
|
29
31
|
removeAllJobs?: boolean | undefined;
|
|
32
|
+
scripDefaultMetrics?: boolean | undefined;
|
|
33
|
+
startupValidation?: Function | undefined;
|
|
30
34
|
});
|
|
31
35
|
appName: string;
|
|
32
36
|
dynoId: string;
|
|
@@ -37,25 +41,27 @@ export class MetricsClient {
|
|
|
37
41
|
pushgatewayUser: string;
|
|
38
42
|
pushgatewayPassword: string;
|
|
39
43
|
intervalSec: number;
|
|
44
|
+
startupValidation: Function | undefined;
|
|
40
45
|
prefixLogs: string;
|
|
41
|
-
registry:
|
|
46
|
+
get registry(): any;
|
|
42
47
|
defaultLabels: {
|
|
43
48
|
app: string;
|
|
44
49
|
dyno_id: string;
|
|
45
50
|
process_type: string;
|
|
46
51
|
};
|
|
47
52
|
authToken: string;
|
|
48
|
-
gateway: client.Pushgateway<
|
|
53
|
+
gateway: client.Pushgateway<client.RegistryContentType>;
|
|
49
54
|
gauges: {};
|
|
50
55
|
counters: {};
|
|
51
56
|
/** @type {Object<string, function(): number | Promise<number>>} */
|
|
52
57
|
gaugeUpdaters: {
|
|
53
58
|
[x: string]: () => number | Promise<number>;
|
|
54
59
|
};
|
|
60
|
+
customsMetricsTracking: {};
|
|
55
61
|
_lastUsageMicros: number;
|
|
56
62
|
_lastCheckTime: number;
|
|
57
63
|
/**
|
|
58
|
-
* Register default
|
|
64
|
+
* Register all built-in default Gauges and Counters.
|
|
59
65
|
* @private
|
|
60
66
|
*/
|
|
61
67
|
private _initDefaultMetrics;
|
|
@@ -67,16 +73,25 @@ export class MetricsClient {
|
|
|
67
73
|
* @param {string[]} [labelNames]
|
|
68
74
|
* @returns {client.Gauge}
|
|
69
75
|
*/
|
|
70
|
-
createGauge: (name
|
|
76
|
+
createGauge: ({ name, help, updateFn, labelNames, }: string) => client.Gauge;
|
|
71
77
|
/**
|
|
72
|
-
* Create a
|
|
73
|
-
*
|
|
74
|
-
* @param {
|
|
75
|
-
* @param {string}
|
|
76
|
-
* @param {string
|
|
77
|
-
* @
|
|
78
|
+
* Create a Prometheus Counter metric.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} params
|
|
81
|
+
* @param {string} params.name Metric name
|
|
82
|
+
* @param {string} params.help Metric description
|
|
83
|
+
* @param {string[]} [params.labelNames]
|
|
84
|
+
* Optional list of label names. Defaults to this.defaultLabels keys.
|
|
85
|
+
*
|
|
86
|
+
* @returns {function(data?: object, value?: number): void}
|
|
87
|
+
* A trigger function to increment the counter:
|
|
88
|
+
* triggerFn(labels?, incrementValue?)
|
|
78
89
|
*/
|
|
79
|
-
createCounter(name
|
|
90
|
+
createCounter({ name, help, labelNames }: {
|
|
91
|
+
name: string;
|
|
92
|
+
help: string;
|
|
93
|
+
labelNames?: string[] | undefined;
|
|
94
|
+
}): (arg0: data | null, arg1: object, arg2: value | null, arg3: number) => void;
|
|
80
95
|
/**
|
|
81
96
|
* Get CPU usage percent (cgroup-aware)
|
|
82
97
|
* @returns {number}
|
|
@@ -112,13 +127,70 @@ export class MetricsClient {
|
|
|
112
127
|
*/
|
|
113
128
|
pushMetrics: () => Promise<void>;
|
|
114
129
|
/**
|
|
115
|
-
* Start
|
|
130
|
+
* Start periodic metrics collection + push.
|
|
131
|
+
*
|
|
116
132
|
* @param {number} [interval] Interval in seconds
|
|
133
|
+
* @param {function(): void|Promise<void>} [customPushMetics]
|
|
134
|
+
* Optional custom push function. If provided, Prometheus push is skipped.
|
|
117
135
|
*/
|
|
118
|
-
startPush: (interval?: number | undefined) => void;
|
|
136
|
+
startPush: (interval?: number | undefined, customPushMetics?: (() => void | Promise<void>) | undefined) => void;
|
|
119
137
|
cleanup: () => Promise<never>;
|
|
120
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Remove old/stale dyno/instance metrics from PushGateway.
|
|
140
|
+
*
|
|
141
|
+
* Compares existing PushGateway metrics for this job and deletes any instances
|
|
142
|
+
* that do not match the current dynoId.
|
|
143
|
+
*
|
|
144
|
+
* @param {boolean} removeAllJobs If true, performs cleanup; otherwise does nothing
|
|
145
|
+
* @returns {Promise<void>}
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
private _clearOldWorkers;
|
|
149
|
+
/**
|
|
150
|
+
* Delete metrics for this job/instance from PushGateway.
|
|
151
|
+
*
|
|
152
|
+
* @param {Object} [params]
|
|
153
|
+
* @param {string} [params.jobName] Job name (defaults to appName)
|
|
154
|
+
* @param {Object} [params.groupings] Grouping labels
|
|
155
|
+
* @param {string} [params.groupings.process_type] Process type label
|
|
156
|
+
* @param {string} [params.groupings.instance] Instance/dyno ID
|
|
157
|
+
* @returns {Promise<void>}
|
|
158
|
+
*/
|
|
159
|
+
gatewayDelete: (params?: {
|
|
160
|
+
jobName?: string | undefined;
|
|
161
|
+
groupings?: {
|
|
162
|
+
process_type?: string | undefined;
|
|
163
|
+
instance?: string | undefined;
|
|
164
|
+
} | undefined;
|
|
165
|
+
} | undefined) => Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Push metrics to PushGateway.
|
|
168
|
+
*
|
|
169
|
+
* @param {object} [params]
|
|
170
|
+
* @param {string} [params.jobName]
|
|
171
|
+
* @param {object} [params.groupings]
|
|
172
|
+
* @returns {Promise<void>}
|
|
173
|
+
*/
|
|
174
|
+
gatewayPush: (params?: {
|
|
175
|
+
jobName?: string | undefined;
|
|
176
|
+
groupings?: object | undefined;
|
|
177
|
+
} | undefined) => Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Merge the default metric labels (`app`, `dyno_id`, `process_type`)
|
|
180
|
+
* with custom label names.
|
|
181
|
+
*
|
|
182
|
+
* @param {string[]} labels Additional label names
|
|
183
|
+
* @returns {string[]} Combined label names
|
|
184
|
+
*/
|
|
185
|
+
withDefaultLabels: (labels?: string[]) => string[];
|
|
186
|
+
getDefaultLabels: (labels?: any[]) => {
|
|
187
|
+
app: string;
|
|
188
|
+
dyno_id: string;
|
|
189
|
+
process_type: string;
|
|
190
|
+
};
|
|
121
191
|
_setCleanupHandlers: () => void;
|
|
192
|
+
get metricsEnabled(): boolean;
|
|
193
|
+
get metricsLogValues(): boolean;
|
|
122
194
|
}
|
|
123
195
|
import client = require("prom-client");
|
|
124
196
|
//# sourceMappingURL=metricsClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metricsClient.d.ts","sourceRoot":"","sources":["../src/metricsClient.js"],"names":[],"mappings":"AAKA;;;GAGG;AACH;IACE
|
|
1
|
+
{"version":3,"file":"metricsClient.d.ts","sourceRoot":"","sources":["../src/metricsClient.js"],"names":[],"mappings":"AAKA;;;GAGG;AACH;IACE;;;;;;;;;;;;;;OAcG;IACH;QAb2B,OAAO;QACP,MAAM;QACN,WAAW;QACV,OAAO;QACP,SAAS;QACV,cAAc;QACd,eAAe;QACf,mBAAmB;QACnB,WAAW;QACV,aAAa;QACb,mBAAmB;QAClB,iBAAiB;OAgE7C;IA7DC,gBACiE;IACjE,eAAqE;IACrE,oBAG6B;IAC7B,iBAAuE;IACvE,mBAC+D;IAC/D,uBACoE;IACpE,wBACsE;IACtE,4BAGI;IACJ,oBAGI;IACJ,wCAAiD;IAEjD,mBAAyF;IAqf3F,oBAEC;IAlfC;;;;MAIC;IAED,kBAEoB;IACpB,wDAOC;IAED,WAAgB;IAChB,aAAkB;IAElB,mEAAmE;IACnE;YADkB,MAAM,SAAc,MAAM,GAAG,QAAQ,MAAM,CAAC;MACvC;IACvB,2BAAgC;IAChC,yBAAyB;IACzB,uBAAgC;IASlC;;;OAGG;IACH,4BAkDC;IAED;;;;;;;OAOG;IACH,qDANW,MAAM,KAIJ,OAAO,KAAK,CAsBxB;IAED;;;;;;;;;;;;OAYG;IACH;QAT0B,IAAI,EAAnB,MAAM;QACS,IAAI,EAAnB,MAAM;QACY,UAAU;kCAGV,MAAM,4BAAU,MAAM,KAAG,IAAI,CAqBzD;IAED;;;OAGG;IACH,0BAFa,MAAM,CA2BlB;IAED;;;OAGG;IACH,oBAFa,MAAM,CAiBlB;IAED;;;OAGG;IACH,2BAFa,MAAM,CAWlB;IAED;;;OAGG;IACH,2BAFa,MAAM,CAgBlB;IAED;;;OAGG;IACH,cAFa,QAAQ,MAAM,CAAC,CAO3B;IAED;;;OAGG;IACH,oEA0BC;IAED;;OAEG;IACH,iCAmCC;IAED;;;;;;OAMG;IACH,qEAHuB,IAAI,GAAC,QAAQ,IAAI,CAAC,uBA0BxC;IAED,8BAKC;IAED;;;;;;;;;OASG;IACH,yBAuDC;IAED;;;;;;;;;OASG;IACH;;;;;;sBAFa,QAAQ,IAAI,CAAC,CAUzB;IAED;;;;;;;OAOG;IACH;;;sBAFa,QAAQ,IAAI,CAAC,CAUzB;IAED;;;;;;OAMG;IACH,6BAHW,MAAM,EAAE,KACN,MAAM,EAAE,CAIpB;IAED;;;;MAEC;IAED,gCAGC;IAID,8BAEC;IAED,gCAEC;CAKF"}
|
package/lib/metricsClient.js
CHANGED
|
@@ -22,6 +22,8 @@ class MetricsClient {
|
|
|
22
22
|
* @param {string} [config.pushgatewayPassword] PushGateway password
|
|
23
23
|
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
24
24
|
* @param {boolean} [config.removeAllJobs] Enable to clear all jobs at startup
|
|
25
|
+
* @param {boolean} [config.scripDefaultMetrics] Enable to scip default metrics creation
|
|
26
|
+
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
25
27
|
*/
|
|
26
28
|
constructor(config = {}) {
|
|
27
29
|
this.appName = config.appName || process.env.METRICS_APP_NAME || 'unknown-app';
|
|
@@ -33,6 +35,7 @@ class MetricsClient {
|
|
|
33
35
|
this.pushgatewayUser = config.pushgatewayUser || process.env.METRICS_PUSHGATEWAY_USER || '';
|
|
34
36
|
this.pushgatewayPassword = config.pushgatewayPassword || process.env.METRICS_PUSHGATEWAY_PASSWORD || '';
|
|
35
37
|
this.intervalSec = config.intervalSec || parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) || 15;
|
|
38
|
+
this.startupValidation = config.startupValidation;
|
|
36
39
|
this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`;
|
|
37
40
|
this.registry = new client.Registry();
|
|
38
41
|
client.collectDefaultMetrics({
|
|
@@ -57,26 +60,57 @@ class MetricsClient {
|
|
|
57
60
|
|
|
58
61
|
/** @type {Object<string, function(): number | Promise<number>>} */
|
|
59
62
|
this.gaugeUpdaters = {};
|
|
63
|
+
this.customsMetricsTracking = {};
|
|
60
64
|
this._lastUsageMicros = 0;
|
|
61
65
|
this._lastCheckTime = Date.now();
|
|
62
66
|
this._clearOldWorkers(config.removeAllJobs).then(_ => {
|
|
63
|
-
|
|
67
|
+
if (config.scripDefaultMetrics) {
|
|
68
|
+
this._initDefaultMetrics();
|
|
69
|
+
}
|
|
64
70
|
this._setCleanupHandlers();
|
|
65
71
|
});
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
/**
|
|
69
|
-
* Register default
|
|
75
|
+
* Register all built-in default Gauges and Counters.
|
|
70
76
|
* @private
|
|
71
77
|
*/
|
|
72
78
|
_initDefaultMetrics = () => {
|
|
73
|
-
this.createGauge(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.createGauge(
|
|
79
|
-
|
|
79
|
+
this.createGauge({
|
|
80
|
+
name: 'app_process_cpu_usage_percent',
|
|
81
|
+
help: 'Current CPU usage of the Node.js process in percent',
|
|
82
|
+
updateFn: this.getCpuUsagePercent
|
|
83
|
+
});
|
|
84
|
+
this.createGauge({
|
|
85
|
+
name: 'app_available_cpu_count',
|
|
86
|
+
help: 'How many CPU cores are available to this process',
|
|
87
|
+
updateFn: this.getAvailableCPUs
|
|
88
|
+
});
|
|
89
|
+
this.createGauge({
|
|
90
|
+
name: 'app_container_memory_usage_bytes',
|
|
91
|
+
help: 'Current container RAM usage from cgroup',
|
|
92
|
+
updateFn: this.getContainerMemoryUsage
|
|
93
|
+
});
|
|
94
|
+
this.createGauge({
|
|
95
|
+
name: 'app_event_loop_lag_ms',
|
|
96
|
+
help: 'Estimated event loop lag in milliseconds',
|
|
97
|
+
updateFn: this.measureLag
|
|
98
|
+
});
|
|
99
|
+
this.createGauge({
|
|
100
|
+
name: 'app_container_memory_limit_bytes',
|
|
101
|
+
help: 'Max RAM available to container from cgroup (memory.max)',
|
|
102
|
+
updateFn: this.getContainerMemoryLimit
|
|
103
|
+
});
|
|
104
|
+
this.createGauge({
|
|
105
|
+
name: 'app_uptime_seconds',
|
|
106
|
+
help: 'How long the process has been running',
|
|
107
|
+
updateFn: process.uptime
|
|
108
|
+
});
|
|
109
|
+
this.createCounter({
|
|
110
|
+
name: 'app_http_requests_total',
|
|
111
|
+
help: 'Total number of HTTP requests handled by this process',
|
|
112
|
+
labelNames: this.withDefaultLabels(['method', 'route', 'appId', 'databaseId', 'duration', 'requestSize', 'status_code'])
|
|
113
|
+
});
|
|
80
114
|
};
|
|
81
115
|
|
|
82
116
|
/**
|
|
@@ -87,7 +121,12 @@ class MetricsClient {
|
|
|
87
121
|
* @param {string[]} [labelNames]
|
|
88
122
|
* @returns {client.Gauge}
|
|
89
123
|
*/
|
|
90
|
-
createGauge = (
|
|
124
|
+
createGauge = ({
|
|
125
|
+
name,
|
|
126
|
+
help,
|
|
127
|
+
updateFn,
|
|
128
|
+
labelNames = Object.keys(this.defaultLabels)
|
|
129
|
+
}) => {
|
|
91
130
|
if (this.gauges[name]) return this.gauges[name];
|
|
92
131
|
const g = new client.Gauge({
|
|
93
132
|
name,
|
|
@@ -96,19 +135,28 @@ class MetricsClient {
|
|
|
96
135
|
registers: [this.registry]
|
|
97
136
|
});
|
|
98
137
|
this.gauges[name] = g;
|
|
99
|
-
if (typeof updateFn === 'function') this.gaugeUpdaters[name] = updateFn;
|
|
138
|
+
if (updateFn && typeof updateFn === 'function') this.gaugeUpdaters[name] = updateFn;
|
|
100
139
|
return g;
|
|
101
140
|
};
|
|
102
141
|
|
|
103
142
|
/**
|
|
104
|
-
* Create a
|
|
105
|
-
*
|
|
106
|
-
* @param {
|
|
107
|
-
* @param {string}
|
|
108
|
-
* @param {string
|
|
109
|
-
* @
|
|
143
|
+
* Create a Prometheus Counter metric.
|
|
144
|
+
*
|
|
145
|
+
* @param {object} params
|
|
146
|
+
* @param {string} params.name Metric name
|
|
147
|
+
* @param {string} params.help Metric description
|
|
148
|
+
* @param {string[]} [params.labelNames]
|
|
149
|
+
* Optional list of label names. Defaults to this.defaultLabels keys.
|
|
150
|
+
*
|
|
151
|
+
* @returns {function(data?: object, value?: number): void}
|
|
152
|
+
* A trigger function to increment the counter:
|
|
153
|
+
* triggerFn(labels?, incrementValue?)
|
|
110
154
|
*/
|
|
111
|
-
createCounter(
|
|
155
|
+
createCounter({
|
|
156
|
+
name,
|
|
157
|
+
help,
|
|
158
|
+
labelNames = Object.keys(this.defaultLabels)
|
|
159
|
+
}) {
|
|
112
160
|
if (this.counters[name]) return this.counters[name].triggerFn;
|
|
113
161
|
const c = new client.Counter({
|
|
114
162
|
name,
|
|
@@ -244,6 +292,9 @@ class MetricsClient {
|
|
|
244
292
|
try {
|
|
245
293
|
for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {
|
|
246
294
|
try {
|
|
295
|
+
if (!updateFn) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
247
298
|
const result = updateFn();
|
|
248
299
|
const val = result instanceof Promise ? await result : result;
|
|
249
300
|
if (val !== undefined) this.gauges[name].set(this.defaultLabels, val);
|
|
@@ -251,7 +302,7 @@ class MetricsClient {
|
|
|
251
302
|
console.error(`${this.prefixLogs} Failed to update gauge ${name}:`, err);
|
|
252
303
|
}
|
|
253
304
|
}
|
|
254
|
-
await this.
|
|
305
|
+
await this.gatewayPush({
|
|
255
306
|
jobName: this.appName,
|
|
256
307
|
groupings: {
|
|
257
308
|
process_type: this.processType,
|
|
@@ -269,32 +320,48 @@ class MetricsClient {
|
|
|
269
320
|
};
|
|
270
321
|
|
|
271
322
|
/**
|
|
272
|
-
* Start
|
|
323
|
+
* Start periodic metrics collection + push.
|
|
324
|
+
*
|
|
273
325
|
* @param {number} [interval] Interval in seconds
|
|
326
|
+
* @param {function(): void|Promise<void>} [customPushMetics]
|
|
327
|
+
* Optional custom push function. If provided, Prometheus push is skipped.
|
|
274
328
|
*/
|
|
275
|
-
startPush = (interval = this.intervalSec) => {
|
|
329
|
+
startPush = (interval = this.intervalSec, customPushMetics = () => {}) => {
|
|
276
330
|
if (!this.enabled) {
|
|
277
331
|
console.warn(`${this.prefixLogs} Metrics disabled`);
|
|
332
|
+
return;
|
|
278
333
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
334
|
+
if (this.startupValidation && !this.startupValidation()) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (customPushMetics) {
|
|
338
|
+
setInterval(customPushMetics, interval * 1000);
|
|
339
|
+
} else {
|
|
340
|
+
setInterval(() => {
|
|
341
|
+
this.pushMetrics().catch(err => {
|
|
342
|
+
console.error(`${this.prefixLogs} Failed to push metrics:`, err);
|
|
343
|
+
});
|
|
344
|
+
}, interval * 1000);
|
|
345
|
+
}
|
|
346
|
+
console.warn(`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`);
|
|
285
347
|
};
|
|
286
348
|
cleanup = async () => {
|
|
287
349
|
if (this.enabled) {
|
|
288
|
-
await this.
|
|
289
|
-
jobName: this.appName,
|
|
290
|
-
groupings: {
|
|
291
|
-
process_type: this.processType,
|
|
292
|
-
instance: this.dynoId
|
|
293
|
-
}
|
|
294
|
-
});
|
|
350
|
+
await this.gatewayDelete();
|
|
295
351
|
}
|
|
296
352
|
process.exit(0);
|
|
297
353
|
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Remove old/stale dyno/instance metrics from PushGateway.
|
|
357
|
+
*
|
|
358
|
+
* Compares existing PushGateway metrics for this job and deletes any instances
|
|
359
|
+
* that do not match the current dynoId.
|
|
360
|
+
*
|
|
361
|
+
* @param {boolean} removeAllJobs If true, performs cleanup; otherwise does nothing
|
|
362
|
+
* @returns {Promise<void>}
|
|
363
|
+
* @private
|
|
364
|
+
*/
|
|
298
365
|
_clearOldWorkers = async removeAllJobs => {
|
|
299
366
|
if (!removeAllJobs) return;
|
|
300
367
|
try {
|
|
@@ -323,10 +390,8 @@ class MetricsClient {
|
|
|
323
390
|
return;
|
|
324
391
|
}
|
|
325
392
|
for (const instance of instances) {
|
|
326
|
-
await this.
|
|
327
|
-
jobName: this.appName,
|
|
393
|
+
await this.gatewayDelete({
|
|
328
394
|
groupings: {
|
|
329
|
-
process_type: this.processType,
|
|
330
395
|
instance
|
|
331
396
|
}
|
|
332
397
|
});
|
|
@@ -337,10 +402,74 @@ class MetricsClient {
|
|
|
337
402
|
console.error(`${this.prefixLogs} Error deleting old metrics:`, err);
|
|
338
403
|
}
|
|
339
404
|
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Delete metrics for this job/instance from PushGateway.
|
|
408
|
+
*
|
|
409
|
+
* @param {Object} [params]
|
|
410
|
+
* @param {string} [params.jobName] Job name (defaults to appName)
|
|
411
|
+
* @param {Object} [params.groupings] Grouping labels
|
|
412
|
+
* @param {string} [params.groupings.process_type] Process type label
|
|
413
|
+
* @param {string} [params.groupings.instance] Instance/dyno ID
|
|
414
|
+
* @returns {Promise<void>}
|
|
415
|
+
*/
|
|
416
|
+
gatewayDelete = async (params = {}) => {
|
|
417
|
+
return this.gateway.delete({
|
|
418
|
+
jobName: params.jobName || this.appName,
|
|
419
|
+
groupings: {
|
|
420
|
+
process_type: params.groupings?.process_type || this.processType,
|
|
421
|
+
instance: params.groupings?.instance || this.dynoId
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Push metrics to PushGateway.
|
|
428
|
+
*
|
|
429
|
+
* @param {object} [params]
|
|
430
|
+
* @param {string} [params.jobName]
|
|
431
|
+
* @param {object} [params.groupings]
|
|
432
|
+
* @returns {Promise<void>}
|
|
433
|
+
*/
|
|
434
|
+
gatewayPush = async (params = {}) => {
|
|
435
|
+
return this.gateway.push({
|
|
436
|
+
jobName: params.jobName || this.appName,
|
|
437
|
+
groupings: {
|
|
438
|
+
process_type: params.groupings?.process_type || this.processType,
|
|
439
|
+
instance: params.groupings?.instance || this.dynoId
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Merge the default metric labels (`app`, `dyno_id`, `process_type`)
|
|
446
|
+
* with custom label names.
|
|
447
|
+
*
|
|
448
|
+
* @param {string[]} labels Additional label names
|
|
449
|
+
* @returns {string[]} Combined label names
|
|
450
|
+
*/
|
|
451
|
+
withDefaultLabels = (labels = []) => {
|
|
452
|
+
return [...Object.keys(this.defaultLabels), labels];
|
|
453
|
+
};
|
|
454
|
+
getDefaultLabels = (labels = []) => {
|
|
455
|
+
return this.defaultLabels;
|
|
456
|
+
};
|
|
340
457
|
_setCleanupHandlers = () => {
|
|
341
458
|
process.on('SIGINT', this.cleanup);
|
|
342
459
|
process.on('SIGTERM', this.cleanup);
|
|
343
460
|
};
|
|
461
|
+
|
|
462
|
+
// GETTERS
|
|
463
|
+
|
|
464
|
+
get metricsEnabled() {
|
|
465
|
+
return this.enabled;
|
|
466
|
+
}
|
|
467
|
+
get metricsLogValues() {
|
|
468
|
+
return this.logValues;
|
|
469
|
+
}
|
|
470
|
+
get registry() {
|
|
471
|
+
return this.registry;
|
|
472
|
+
}
|
|
344
473
|
}
|
|
345
474
|
module.exports = {
|
|
346
475
|
MetricsClient
|
package/lib/metricsClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metricsClient.js","names":["client","require","fs","os","https","MetricsClient","constructor","config","appName","process","env","METRICS_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","enabled","METRICS_ENABLED","logValues","METRICS_LOG_VALUES","pushgatewayUrl","METRICS_PUSHGATEWAY_URL","pushgatewayUser","METRICS_PUSHGATEWAY_USER","pushgatewayPassword","METRICS_PUSHGATEWAY_PASSWORD","intervalSec","parseInt","METRICS_INTERVAL_SEC","prefixLogs","registry","Registry","collectDefaultMetrics","register","defaultLabels","app","dyno_id","process_type","authToken","Buffer","from","toString","gateway","Pushgateway","headers","Authorization","agent","Agent","keepAlive","gauges","counters","gaugeUpdaters","_lastUsageMicros","_lastCheckTime","Date","now","_clearOldWorkers","removeAllJobs","then","_","_initDefaultMetrics","_setCleanupHandlers","createGauge","getCpuUsagePercent","getAvailableCPUs","getContainerMemoryUsage","measureLag","getContainerMemoryLimit","uptime","createCounter","Object","keys","name","help","updateFn","labelNames","g","Gauge","registers","triggerFn","c","Counter","data","value","inc","stat","readFileSync","match","currentUsage","deltaUsage","deltaTime","cpuMaxPath","existsSync","quotaStr","periodStr","trim","split","cpus","length","memoryUsage","rss","path","val","parsed","totalmem","Promise","resolve","start","setImmediate","countHttpRequestMiddleware","req","res","next","on","route","appId","params","body","query","databaseId","app_http_requests_total","method","status_code","statusCode","duration","requestSize","pushMetrics","entries","result","undefined","set","err","console","error","push","jobName","groupings","instance","values","forEach","counter","reset","metrics","getMetricsAsJSON","log","JSON","stringify","startPush","interval","warn","setInterval","catch","cleanup","delete","exit","url","fetch","Accept","ok","status","text","regex","RegExp","instances","Set","exec","add","size","module","exports"],"sources":["../src/metricsClient.js"],"sourcesContent":["const client = require('prom-client')\nconst fs = require('fs')\nconst os = require('os')\nconst https = require('https')\n\n/**\n * MetricsClient handles Prometheus metrics collection and push.\n * Supports gauges, counters, default metrics, and custom metrics.\n */\nclass MetricsClient {\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.logValues] Log metrics values to console\n * @param {string} [config.pushgatewayUrl] PushGateway URL\n * @param {string} [config.pushgatewayUser] PushGateway username\n * @param {string} [config.pushgatewayPassword] PushGateway password\n * @param {number} [config.intervalSec] Interval in seconds for pushing metrics\n * @param {boolean} [config.removeAllJobs] Enable to clear all jobs at startup\n */\n constructor(config = {}) {\n this.appName =\n config.appName || process.env.METRICS_APP_NAME || 'unknown-app'\n this.dynoId = config.dynoId || process.env.HOSTNAME || 'unknown-dyno'\n this.processType =\n config.processType ||\n process.env.BUILD_DYNO_PROCESS_TYPE ||\n 'undefined_build_dyno_type'\n this.enabled = config.enabled ?? process.env.METRICS_ENABLED === 'true'\n this.logValues =\n config.logValues ?? process.env.METRICS_LOG_VALUES === 'true'\n this.pushgatewayUrl =\n config.pushgatewayUrl || process.env.METRICS_PUSHGATEWAY_URL || ''\n this.pushgatewayUser =\n config.pushgatewayUser || process.env.METRICS_PUSHGATEWAY_USER || ''\n this.pushgatewayPassword =\n config.pushgatewayPassword ||\n process.env.METRICS_PUSHGATEWAY_PASSWORD ||\n ''\n this.intervalSec =\n config.intervalSec ||\n parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||\n 15\n\n this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`\n\n this.registry = new client.Registry()\n client.collectDefaultMetrics({ register: this.registry })\n\n this.defaultLabels = {\n app: this.appName,\n dyno_id: this.dynoId,\n process_type: this.processType,\n }\n\n this.authToken = Buffer.from(\n `${this.pushgatewayUser}:${this.pushgatewayPassword}`\n ).toString('base64')\n this.gateway = new client.Pushgateway(\n this.pushgatewayUrl,\n {\n headers: { Authorization: `Basic ${this.authToken}` },\n agent: new https.Agent({ keepAlive: true }),\n },\n this.registry\n )\n\n this.gauges = {}\n this.counters = {}\n\n /** @type {Object<string, function(): number | Promise<number>>} */\n this.gaugeUpdaters = {}\n this._lastUsageMicros = 0\n this._lastCheckTime = Date.now()\n this._clearOldWorkers(config.removeAllJobs).then(_ => {\n this._initDefaultMetrics()\n this._setCleanupHandlers()\n })\n }\n\n /**\n * Register default gauges and counters.\n * @private\n */\n _initDefaultMetrics = () => {\n this.createGauge(\n 'app_process_cpu_usage_percent',\n 'Current CPU usage of the Node.js process in percent',\n this.getCpuUsagePercent\n )\n this.createGauge(\n 'app_available_cpu_count',\n 'How many CPU cores are available to this process',\n this.getAvailableCPUs\n )\n this.createGauge(\n 'app_container_memory_usage_bytes',\n 'Current container RAM usage from cgroup',\n this.getContainerMemoryUsage\n )\n this.createGauge(\n 'app_event_loop_lag_ms',\n 'Estimated event loop lag in milliseconds',\n this.measureLag\n )\n this.createGauge(\n 'app_container_memory_limit_bytes',\n 'Max RAM available to container from cgroup (memory.max)',\n this.getContainerMemoryLimit\n )\n this.createGauge(\n 'app_uptime_seconds',\n 'How long the process has been running',\n process.uptime\n )\n\n this.createCounter(\n 'app_http_requests_total',\n 'Total number of HTTP requests handled by this process',\n [\n ...Object.keys(this.defaultLabels),\n 'method',\n 'route',\n 'appId',\n 'databaseId',\n 'duration',\n 'requestSize',\n 'status_code',\n ]\n )\n }\n\n /**\n * Create a gauge metric.\n * @param {string} name\n * @param {string} help\n * @param {function(): number|Promise<number>} [updateFn] Optional function returning value\n * @param {string[]} [labelNames]\n * @returns {client.Gauge}\n */\n createGauge = (\n name,\n help,\n updateFn,\n labelNames = Object.keys(this.defaultLabels)\n ) => {\n if (this.gauges[name]) return this.gauges[name]\n\n const g = new client.Gauge({\n name,\n help,\n labelNames,\n registers: [this.registry],\n })\n this.gauges[name] = g\n\n if (typeof updateFn === 'function') this.gaugeUpdaters[name] = updateFn\n\n return g\n }\n\n /**\n * Create a counter metric.\n * Returns a trigger function to increment the counter manually.\n * @param {string} name\n * @param {string} help\n * @param {string[]} [labelNames]\n * @returns {function(data?: object, value?: number): void} triggerFn\n */\n createCounter(name, help, labelNames = Object.keys(this.defaultLabels)) {\n if (this.counters[name]) return this.counters[name].triggerFn\n\n const c = new client.Counter({\n name,\n help,\n labelNames,\n registers: [this.registry],\n })\n this.counters[name] = c\n\n const triggerFn = (data = {}, value = 1) => {\n c.inc({ ...this.defaultLabels, ...data }, value)\n }\n\n this.counters[name].triggerFn = triggerFn\n return triggerFn\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 * Express middleware to count HTTP requests.\n * Increments the `app_http_requests_total` counter.\n */\n countHttpRequestMiddleware = (req, res, next) => {\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 ''\n\n this.counters?.app_http_requests_total?.triggerFn({\n method: req.method,\n route,\n status_code: res.statusCode,\n appId,\n databaseId,\n duration: Date.now() - start,\n requestSize: req.headers['content-length']\n ? parseInt(req.headers['content-length'], 10)\n : 0,\n })\n })\n\n next()\n }\n\n /**\n * Push all gauges and counters to PushGateway and optionally log.\n */\n pushMetrics = async () => {\n try {\n for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {\n try {\n const result = updateFn()\n const val = result instanceof Promise ? await result : result\n if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)\n } catch (err) {\n console.error(\n `${this.prefixLogs} Failed to update gauge ${name}:`,\n err\n )\n }\n }\n\n await this.gateway.push({\n jobName: this.appName,\n groupings: { process_type: this.processType, instance: this.dynoId },\n })\n\n Object.values(this.counters).forEach(counter => counter.reset())\n\n if (this.logValues) {\n const metrics = await this.registry.getMetricsAsJSON()\n console.log(\n `${this.prefixLogs} Metrics:\\n`,\n JSON.stringify(metrics, null, 2)\n )\n }\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n }\n }\n\n /**\n * Start automatic periodic push of metrics.\n * @param {number} [interval] Interval in seconds\n */\n startPush = (interval = this.intervalSec) => {\n if (!this.enabled) {\n console.warn(`${this.prefixLogs} Metrics disabled`)\n }\n\n setInterval(() => {\n this.pushMetrics().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n })\n }, interval * 1000)\n\n console.warn(`${this.prefixLogs} Metrics collection started.`)\n }\n\n cleanup = async () => {\n if (this.enabled) {\n await this.gateway.delete({\n jobName: this.appName,\n groupings: {\n process_type: this.processType,\n instance: this.dynoId,\n },\n })\n }\n process.exit(0)\n }\n\n _clearOldWorkers = async removeAllJobs => {\n if (!removeAllJobs) return\n\n try {\n const url = `${this.pushgatewayUrl}/metrics`\n const res = await fetch(url, {\n headers: {\n Authorization: `Basic ${this.authToken}`,\n Accept: 'text/plain',\n },\n })\n\n if (!res.ok) {\n console.error(\n `${this.prefixLogs} Failed to fetch metrics: ${res.status}`\n )\n return\n }\n\n const text = await res.text()\n\n const regex = new RegExp(\n `(?:instance=\"([^\"]+)\".*job=\"${this.appName}\"|job=\"${this.appName}\".*instance=\"([^\"]+)\")`,\n 'g'\n )\n const instances = new Set()\n let match\n // eslint-disable-next-line no-cond-assign\n while ((match = regex.exec(text)) !== null) {\n const instance = match[1] || match[2]\n if (instance && instance !== this.dynoId) instances.add(instance)\n }\n\n if (instances.size === 0) {\n console.log(`${this.prefixLogs} No old dynos to delete.`)\n return\n }\n\n for (const instance of instances) {\n await this.gateway.delete({\n jobName: this.appName,\n groupings: {\n process_type: this.processType,\n instance\n },\n })\n console.log(\n `${this.prefixLogs} Deleted metrics for old dyno: ${instance}`\n )\n }\n\n console.log(\n `${this.prefixLogs} Cleared all old instances for job ${this.appName}`\n )\n } catch (err) {\n console.error(`${this.prefixLogs} Error deleting old metrics:`, err)\n }\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n}\n\nmodule.exports = { MetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"metricsClient.js","names":["client","require","fs","os","https","MetricsClient","constructor","config","appName","process","env","METRICS_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","enabled","METRICS_ENABLED","logValues","METRICS_LOG_VALUES","pushgatewayUrl","METRICS_PUSHGATEWAY_URL","pushgatewayUser","METRICS_PUSHGATEWAY_USER","pushgatewayPassword","METRICS_PUSHGATEWAY_PASSWORD","intervalSec","parseInt","METRICS_INTERVAL_SEC","startupValidation","prefixLogs","registry","Registry","collectDefaultMetrics","register","defaultLabels","app","dyno_id","process_type","authToken","Buffer","from","toString","gateway","Pushgateway","headers","Authorization","agent","Agent","keepAlive","gauges","counters","gaugeUpdaters","customsMetricsTracking","_lastUsageMicros","_lastCheckTime","Date","now","_clearOldWorkers","removeAllJobs","then","_","scripDefaultMetrics","_initDefaultMetrics","_setCleanupHandlers","createGauge","name","help","updateFn","getCpuUsagePercent","getAvailableCPUs","getContainerMemoryUsage","measureLag","getContainerMemoryLimit","uptime","createCounter","labelNames","withDefaultLabels","Object","keys","g","Gauge","registers","triggerFn","c","Counter","data","value","inc","stat","readFileSync","match","currentUsage","deltaUsage","deltaTime","cpuMaxPath","existsSync","quotaStr","periodStr","trim","split","cpus","length","memoryUsage","rss","path","val","parsed","totalmem","Promise","resolve","start","setImmediate","countHttpRequestMiddleware","req","res","next","on","route","appId","params","body","query","databaseId","app_http_requests_total","method","status_code","statusCode","duration","requestSize","pushMetrics","entries","result","undefined","set","err","console","error","gatewayPush","jobName","groupings","instance","values","forEach","counter","reset","metrics","getMetricsAsJSON","log","JSON","stringify","startPush","interval","customPushMetics","warn","setInterval","catch","cleanup","gatewayDelete","exit","url","fetch","Accept","ok","status","text","regex","RegExp","instances","Set","exec","add","size","delete","push","labels","getDefaultLabels","metricsEnabled","metricsLogValues","module","exports"],"sources":["../src/metricsClient.js"],"sourcesContent":["const client = require('prom-client')\nconst fs = require('fs')\nconst os = require('os')\nconst https = require('https')\n\n/**\n * MetricsClient handles Prometheus metrics collection and push.\n * Supports gauges, counters, default metrics, and custom metrics.\n */\nclass MetricsClient {\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.logValues] Log metrics values to console\n * @param {string} [config.pushgatewayUrl] PushGateway URL\n * @param {string} [config.pushgatewayUser] PushGateway username\n * @param {string} [config.pushgatewayPassword] PushGateway password\n * @param {number} [config.intervalSec] Interval in seconds for pushing metrics\n * @param {boolean} [config.removeAllJobs] Enable to clear all jobs at startup\n * @param {boolean} [config.scripDefaultMetrics] Enable to scip default metrics creation\n * @param {function} [config.startupValidation] Add to validate on start push.\n */\n constructor(config = {}) {\n this.appName =\n config.appName || process.env.METRICS_APP_NAME || 'unknown-app'\n this.dynoId = config.dynoId || process.env.HOSTNAME || 'unknown-dyno'\n this.processType =\n config.processType ||\n process.env.BUILD_DYNO_PROCESS_TYPE ||\n 'undefined_build_dyno_type'\n this.enabled = config.enabled ?? process.env.METRICS_ENABLED === 'true'\n this.logValues =\n config.logValues ?? process.env.METRICS_LOG_VALUES === 'true'\n this.pushgatewayUrl =\n config.pushgatewayUrl || process.env.METRICS_PUSHGATEWAY_URL || ''\n this.pushgatewayUser =\n config.pushgatewayUser || process.env.METRICS_PUSHGATEWAY_USER || ''\n this.pushgatewayPassword =\n config.pushgatewayPassword ||\n process.env.METRICS_PUSHGATEWAY_PASSWORD ||\n ''\n this.intervalSec =\n config.intervalSec ||\n parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||\n 15\n this.startupValidation = config.startupValidation\n\n this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`\n\n this.registry = new client.Registry()\n client.collectDefaultMetrics({ register: this.registry })\n\n this.defaultLabels = {\n app: this.appName,\n dyno_id: this.dynoId,\n process_type: this.processType,\n }\n\n this.authToken = Buffer.from(\n `${this.pushgatewayUser}:${this.pushgatewayPassword}`\n ).toString('base64')\n this.gateway = new client.Pushgateway(\n this.pushgatewayUrl,\n {\n headers: { Authorization: `Basic ${this.authToken}` },\n agent: new https.Agent({ keepAlive: true }),\n },\n this.registry\n )\n\n this.gauges = {}\n this.counters = {}\n\n /** @type {Object<string, function(): number | Promise<number>>} */\n this.gaugeUpdaters = {}\n this.customsMetricsTracking = {}\n this._lastUsageMicros = 0\n this._lastCheckTime = Date.now()\n this._clearOldWorkers(config.removeAllJobs).then(_ => {\n if (config.scripDefaultMetrics) {\n this._initDefaultMetrics()\n }\n this._setCleanupHandlers()\n })\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 this.createCounter({\n name: 'app_http_requests_total',\n help: 'Total number of HTTP requests handled by this process',\n labelNames: this.withDefaultLabels([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\n 'duration',\n 'requestSize',\n 'status_code',\n ]),\n })\n }\n\n /**\n * Create a gauge metric.\n * @param {string} name\n * @param {string} help\n * @param {function(): number|Promise<number>} [updateFn] Optional function returning value\n * @param {string[]} [labelNames]\n * @returns {client.Gauge}\n */\n createGauge = ({\n name,\n help,\n updateFn,\n labelNames = Object.keys(this.defaultLabels),\n }) => {\n if (this.gauges[name]) return this.gauges[name]\n\n const g = new client.Gauge({\n name,\n help,\n labelNames,\n registers: [this.registry],\n })\n this.gauges[name] = g\n\n if (updateFn && typeof updateFn === 'function')\n this.gaugeUpdaters[name] = updateFn\n\n return g\n }\n\n /**\n * Create a Prometheus Counter metric.\n *\n * @param {object} params\n * @param {string} params.name Metric name\n * @param {string} params.help Metric description\n * @param {string[]} [params.labelNames]\n * Optional list of label names. Defaults to this.defaultLabels keys.\n *\n * @returns {function(data?: object, value?: number): void}\n * A trigger function to increment the counter:\n * triggerFn(labels?, incrementValue?)\n */\n createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {\n if (this.counters[name]) return this.counters[name].triggerFn\n\n const c = new client.Counter({\n name,\n help,\n labelNames,\n registers: [this.registry],\n })\n this.counters[name] = c\n\n const triggerFn = (data = {}, value = 1) => {\n c.inc({ ...this.defaultLabels, ...data }, value)\n }\n\n this.counters[name].triggerFn = triggerFn\n return triggerFn\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 * Express middleware to count HTTP requests.\n * Increments the `app_http_requests_total` counter.\n */\n countHttpRequestMiddleware = (req, res, next) => {\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 ''\n\n this.counters?.app_http_requests_total?.triggerFn({\n method: req.method,\n route,\n status_code: res.statusCode,\n appId,\n databaseId,\n duration: Date.now() - start,\n requestSize: req.headers['content-length']\n ? parseInt(req.headers['content-length'], 10)\n : 0,\n })\n })\n\n next()\n }\n\n /**\n * Push all gauges and counters to PushGateway and optionally log.\n */\n pushMetrics = async () => {\n try {\n for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {\n try {\n if (!updateFn) {\n return\n }\n const result = updateFn()\n const val = result instanceof Promise ? await result : result\n if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)\n } catch (err) {\n console.error(\n `${this.prefixLogs} Failed to update gauge ${name}:`,\n err\n )\n }\n }\n\n await this.gatewayPush({\n jobName: this.appName,\n groupings: { process_type: this.processType, instance: this.dynoId },\n })\n\n Object.values(this.counters).forEach(counter => counter.reset())\n\n if (this.logValues) {\n const metrics = await this.registry.getMetricsAsJSON()\n console.log(\n `${this.prefixLogs} Metrics:\\n`,\n JSON.stringify(metrics, null, 2)\n )\n }\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n }\n }\n\n /**\n * Start periodic metrics collection + push.\n *\n * @param {number} [interval] Interval in seconds\n * @param {function(): void|Promise<void>} [customPushMetics]\n * Optional custom push function. If provided, Prometheus push is skipped.\n */\n startPush = (interval = this.intervalSec, customPushMetics = () => {}) => {\n if (!this.enabled) {\n console.warn(`${this.prefixLogs} Metrics disabled`)\n return\n }\n\n if (this.startupValidation && !this.startupValidation()) {\n return\n }\n\n if (customPushMetics) {\n setInterval(customPushMetics, interval * 1000)\n } else {\n setInterval(() => {\n this.pushMetrics().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n })\n }, interval * 1000)\n }\n\n console.warn(\n `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`\n )\n }\n\n cleanup = async () => {\n if (this.enabled) {\n await this.gatewayDelete()\n }\n process.exit(0)\n }\n\n /**\n * Remove old/stale dyno/instance metrics from PushGateway.\n *\n * Compares existing PushGateway metrics for this job and deletes any instances\n * that do not match the current dynoId.\n *\n * @param {boolean} removeAllJobs If true, performs cleanup; otherwise does nothing\n * @returns {Promise<void>}\n * @private\n */\n _clearOldWorkers = async removeAllJobs => {\n if (!removeAllJobs) return\n\n try {\n const url = `${this.pushgatewayUrl}/metrics`\n const res = await fetch(url, {\n headers: {\n Authorization: `Basic ${this.authToken}`,\n Accept: 'text/plain',\n },\n })\n\n if (!res.ok) {\n console.error(\n `${this.prefixLogs} Failed to fetch metrics: ${res.status}`\n )\n return\n }\n\n const text = await res.text()\n\n const regex = new RegExp(\n `(?:instance=\"([^\"]+)\".*job=\"${this.appName}\"|job=\"${this.appName}\".*instance=\"([^\"]+)\")`,\n 'g'\n )\n const instances = new Set()\n let match\n // eslint-disable-next-line no-cond-assign\n while ((match = regex.exec(text)) !== null) {\n const instance = match[1] || match[2]\n if (instance && instance !== this.dynoId) instances.add(instance)\n }\n\n if (instances.size === 0) {\n console.log(`${this.prefixLogs} No old dynos to delete.`)\n return\n }\n\n for (const instance of instances) {\n await this.gatewayDelete({\n groupings: {\n instance,\n },\n })\n console.log(\n `${this.prefixLogs} Deleted metrics for old dyno: ${instance}`\n )\n }\n\n console.log(\n `${this.prefixLogs} Cleared all old instances for job ${this.appName}`\n )\n } catch (err) {\n console.error(`${this.prefixLogs} Error deleting old metrics:`, err)\n }\n }\n\n /**\n * Delete metrics for this job/instance from PushGateway.\n *\n * @param {Object} [params]\n * @param {string} [params.jobName] Job name (defaults to appName)\n * @param {Object} [params.groupings] Grouping labels\n * @param {string} [params.groupings.process_type] Process type label\n * @param {string} [params.groupings.instance] Instance/dyno ID\n * @returns {Promise<void>}\n */\n gatewayDelete = async (params = {}) => {\n return this.gateway.delete({\n jobName: params.jobName || this.appName,\n groupings: {\n process_type: params.groupings?.process_type || this.processType,\n instance: params.groupings?.instance || this.dynoId,\n },\n })\n }\n\n /**\n * Push metrics to PushGateway.\n *\n * @param {object} [params]\n * @param {string} [params.jobName]\n * @param {object} [params.groupings]\n * @returns {Promise<void>}\n */\n gatewayPush = async (params = {}) => {\n return this.gateway.push({\n jobName: params.jobName || this.appName,\n groupings: {\n process_type: params.groupings?.process_type || this.processType,\n instance: params.groupings?.instance || this.dynoId,\n },\n })\n }\n\n /**\n * Merge the default metric labels (`app`, `dyno_id`, `process_type`)\n * with custom label names.\n *\n * @param {string[]} labels Additional label names\n * @returns {string[]} Combined label names\n */\n withDefaultLabels = (labels = []) => {\n return [...Object.keys(this.defaultLabels), labels]\n }\n\n getDefaultLabels = (labels = []) => {\n return this.defaultLabels\n }\n\n _setCleanupHandlers = () => {\n process.on('SIGINT', this.cleanup)\n process.on('SIGTERM', this.cleanup)\n }\n\n // GETTERS\n\n get metricsEnabled() {\n return this.enabled\n }\n\n get metricsLogValues() {\n return this.logValues\n }\n\n get registry() {\n return this.registry\n }\n}\n\nmodule.exports = { MetricsClient }\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
package/src/metricsClient.js
CHANGED
|
@@ -20,6 +20,8 @@ class MetricsClient {
|
|
|
20
20
|
* @param {string} [config.pushgatewayPassword] PushGateway password
|
|
21
21
|
* @param {number} [config.intervalSec] Interval in seconds for pushing metrics
|
|
22
22
|
* @param {boolean} [config.removeAllJobs] Enable to clear all jobs at startup
|
|
23
|
+
* @param {boolean} [config.scripDefaultMetrics] Enable to scip default metrics creation
|
|
24
|
+
* @param {function} [config.startupValidation] Add to validate on start push.
|
|
23
25
|
*/
|
|
24
26
|
constructor(config = {}) {
|
|
25
27
|
this.appName =
|
|
@@ -44,6 +46,7 @@ class MetricsClient {
|
|
|
44
46
|
config.intervalSec ||
|
|
45
47
|
parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||
|
|
46
48
|
15
|
|
49
|
+
this.startupValidation = config.startupValidation
|
|
47
50
|
|
|
48
51
|
this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`
|
|
49
52
|
|
|
@@ -73,55 +76,62 @@ class MetricsClient {
|
|
|
73
76
|
|
|
74
77
|
/** @type {Object<string, function(): number | Promise<number>>} */
|
|
75
78
|
this.gaugeUpdaters = {}
|
|
79
|
+
this.customsMetricsTracking = {}
|
|
76
80
|
this._lastUsageMicros = 0
|
|
77
81
|
this._lastCheckTime = Date.now()
|
|
78
82
|
this._clearOldWorkers(config.removeAllJobs).then(_ => {
|
|
79
|
-
|
|
83
|
+
if (config.scripDefaultMetrics) {
|
|
84
|
+
this._initDefaultMetrics()
|
|
85
|
+
}
|
|
80
86
|
this._setCleanupHandlers()
|
|
81
87
|
})
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
/**
|
|
85
|
-
* Register default
|
|
91
|
+
* Register all built-in default Gauges and Counters.
|
|
86
92
|
* @private
|
|
87
93
|
*/
|
|
88
94
|
_initDefaultMetrics = () => {
|
|
89
|
-
this.createGauge(
|
|
90
|
-
'app_process_cpu_usage_percent',
|
|
91
|
-
'Current CPU usage of the Node.js process in percent',
|
|
92
|
-
this.getCpuUsagePercent
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
'
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
95
|
+
this.createGauge({
|
|
96
|
+
name: 'app_process_cpu_usage_percent',
|
|
97
|
+
help: 'Current CPU usage of the Node.js process in percent',
|
|
98
|
+
updateFn: this.getCpuUsagePercent,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
this.createGauge({
|
|
102
|
+
name: 'app_available_cpu_count',
|
|
103
|
+
help: 'How many CPU cores are available to this process',
|
|
104
|
+
updateFn: this.getAvailableCPUs,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
this.createGauge({
|
|
108
|
+
name: 'app_container_memory_usage_bytes',
|
|
109
|
+
help: 'Current container RAM usage from cgroup',
|
|
110
|
+
updateFn: this.getContainerMemoryUsage,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
this.createGauge({
|
|
114
|
+
name: 'app_event_loop_lag_ms',
|
|
115
|
+
help: 'Estimated event loop lag in milliseconds',
|
|
116
|
+
updateFn: this.measureLag,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
this.createGauge({
|
|
120
|
+
name: 'app_container_memory_limit_bytes',
|
|
121
|
+
help: 'Max RAM available to container from cgroup (memory.max)',
|
|
122
|
+
updateFn: this.getContainerMemoryLimit,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
this.createGauge({
|
|
126
|
+
name: 'app_uptime_seconds',
|
|
127
|
+
help: 'How long the process has been running',
|
|
128
|
+
updateFn: process.uptime,
|
|
129
|
+
})
|
|
119
130
|
|
|
120
|
-
this.createCounter(
|
|
121
|
-
'app_http_requests_total',
|
|
122
|
-
'Total number of HTTP requests handled by this process',
|
|
123
|
-
[
|
|
124
|
-
...Object.keys(this.defaultLabels),
|
|
131
|
+
this.createCounter({
|
|
132
|
+
name: 'app_http_requests_total',
|
|
133
|
+
help: 'Total number of HTTP requests handled by this process',
|
|
134
|
+
labelNames: this.withDefaultLabels([
|
|
125
135
|
'method',
|
|
126
136
|
'route',
|
|
127
137
|
'appId',
|
|
@@ -129,8 +139,8 @@ class MetricsClient {
|
|
|
129
139
|
'duration',
|
|
130
140
|
'requestSize',
|
|
131
141
|
'status_code',
|
|
132
|
-
]
|
|
133
|
-
)
|
|
142
|
+
]),
|
|
143
|
+
})
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
/**
|
|
@@ -141,12 +151,12 @@ class MetricsClient {
|
|
|
141
151
|
* @param {string[]} [labelNames]
|
|
142
152
|
* @returns {client.Gauge}
|
|
143
153
|
*/
|
|
144
|
-
createGauge = (
|
|
154
|
+
createGauge = ({
|
|
145
155
|
name,
|
|
146
156
|
help,
|
|
147
157
|
updateFn,
|
|
148
|
-
labelNames = Object.keys(this.defaultLabels)
|
|
149
|
-
) => {
|
|
158
|
+
labelNames = Object.keys(this.defaultLabels),
|
|
159
|
+
}) => {
|
|
150
160
|
if (this.gauges[name]) return this.gauges[name]
|
|
151
161
|
|
|
152
162
|
const g = new client.Gauge({
|
|
@@ -157,20 +167,26 @@ class MetricsClient {
|
|
|
157
167
|
})
|
|
158
168
|
this.gauges[name] = g
|
|
159
169
|
|
|
160
|
-
if (typeof updateFn === 'function')
|
|
170
|
+
if (updateFn && typeof updateFn === 'function')
|
|
171
|
+
this.gaugeUpdaters[name] = updateFn
|
|
161
172
|
|
|
162
173
|
return g
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
/**
|
|
166
|
-
* Create a
|
|
167
|
-
*
|
|
168
|
-
* @param {
|
|
169
|
-
* @param {string}
|
|
170
|
-
* @param {string
|
|
171
|
-
* @
|
|
177
|
+
* Create a Prometheus Counter metric.
|
|
178
|
+
*
|
|
179
|
+
* @param {object} params
|
|
180
|
+
* @param {string} params.name Metric name
|
|
181
|
+
* @param {string} params.help Metric description
|
|
182
|
+
* @param {string[]} [params.labelNames]
|
|
183
|
+
* Optional list of label names. Defaults to this.defaultLabels keys.
|
|
184
|
+
*
|
|
185
|
+
* @returns {function(data?: object, value?: number): void}
|
|
186
|
+
* A trigger function to increment the counter:
|
|
187
|
+
* triggerFn(labels?, incrementValue?)
|
|
172
188
|
*/
|
|
173
|
-
createCounter(name, help, labelNames = Object.keys(this.defaultLabels)) {
|
|
189
|
+
createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {
|
|
174
190
|
if (this.counters[name]) return this.counters[name].triggerFn
|
|
175
191
|
|
|
176
192
|
const c = new client.Counter({
|
|
@@ -326,6 +342,9 @@ class MetricsClient {
|
|
|
326
342
|
try {
|
|
327
343
|
for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {
|
|
328
344
|
try {
|
|
345
|
+
if (!updateFn) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
329
348
|
const result = updateFn()
|
|
330
349
|
const val = result instanceof Promise ? await result : result
|
|
331
350
|
if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)
|
|
@@ -337,7 +356,7 @@ class MetricsClient {
|
|
|
337
356
|
}
|
|
338
357
|
}
|
|
339
358
|
|
|
340
|
-
await this.
|
|
359
|
+
await this.gatewayPush({
|
|
341
360
|
jobName: this.appName,
|
|
342
361
|
groupings: { process_type: this.processType, instance: this.dynoId },
|
|
343
362
|
})
|
|
@@ -357,36 +376,54 @@ class MetricsClient {
|
|
|
357
376
|
}
|
|
358
377
|
|
|
359
378
|
/**
|
|
360
|
-
* Start
|
|
379
|
+
* Start periodic metrics collection + push.
|
|
380
|
+
*
|
|
361
381
|
* @param {number} [interval] Interval in seconds
|
|
382
|
+
* @param {function(): void|Promise<void>} [customPushMetics]
|
|
383
|
+
* Optional custom push function. If provided, Prometheus push is skipped.
|
|
362
384
|
*/
|
|
363
|
-
startPush = (interval = this.intervalSec) => {
|
|
385
|
+
startPush = (interval = this.intervalSec, customPushMetics = () => {}) => {
|
|
364
386
|
if (!this.enabled) {
|
|
365
387
|
console.warn(`${this.prefixLogs} Metrics disabled`)
|
|
388
|
+
return
|
|
366
389
|
}
|
|
367
390
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
})
|
|
372
|
-
}, interval * 1000)
|
|
391
|
+
if (this.startupValidation && !this.startupValidation()) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
373
394
|
|
|
374
|
-
|
|
395
|
+
if (customPushMetics) {
|
|
396
|
+
setInterval(customPushMetics, interval * 1000)
|
|
397
|
+
} else {
|
|
398
|
+
setInterval(() => {
|
|
399
|
+
this.pushMetrics().catch(err => {
|
|
400
|
+
console.error(`${this.prefixLogs} Failed to push metrics:`, err)
|
|
401
|
+
})
|
|
402
|
+
}, interval * 1000)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.warn(
|
|
406
|
+
`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`
|
|
407
|
+
)
|
|
375
408
|
}
|
|
376
409
|
|
|
377
410
|
cleanup = async () => {
|
|
378
411
|
if (this.enabled) {
|
|
379
|
-
await this.
|
|
380
|
-
jobName: this.appName,
|
|
381
|
-
groupings: {
|
|
382
|
-
process_type: this.processType,
|
|
383
|
-
instance: this.dynoId,
|
|
384
|
-
},
|
|
385
|
-
})
|
|
412
|
+
await this.gatewayDelete()
|
|
386
413
|
}
|
|
387
414
|
process.exit(0)
|
|
388
415
|
}
|
|
389
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Remove old/stale dyno/instance metrics from PushGateway.
|
|
419
|
+
*
|
|
420
|
+
* Compares existing PushGateway metrics for this job and deletes any instances
|
|
421
|
+
* that do not match the current dynoId.
|
|
422
|
+
*
|
|
423
|
+
* @param {boolean} removeAllJobs If true, performs cleanup; otherwise does nothing
|
|
424
|
+
* @returns {Promise<void>}
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
390
427
|
_clearOldWorkers = async removeAllJobs => {
|
|
391
428
|
if (!removeAllJobs) return
|
|
392
429
|
|
|
@@ -426,11 +463,9 @@ class MetricsClient {
|
|
|
426
463
|
}
|
|
427
464
|
|
|
428
465
|
for (const instance of instances) {
|
|
429
|
-
await this.
|
|
430
|
-
jobName: this.appName,
|
|
466
|
+
await this.gatewayDelete({
|
|
431
467
|
groupings: {
|
|
432
|
-
|
|
433
|
-
instance
|
|
468
|
+
instance,
|
|
434
469
|
},
|
|
435
470
|
})
|
|
436
471
|
console.log(
|
|
@@ -446,10 +481,77 @@ class MetricsClient {
|
|
|
446
481
|
}
|
|
447
482
|
}
|
|
448
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Delete metrics for this job/instance from PushGateway.
|
|
486
|
+
*
|
|
487
|
+
* @param {Object} [params]
|
|
488
|
+
* @param {string} [params.jobName] Job name (defaults to appName)
|
|
489
|
+
* @param {Object} [params.groupings] Grouping labels
|
|
490
|
+
* @param {string} [params.groupings.process_type] Process type label
|
|
491
|
+
* @param {string} [params.groupings.instance] Instance/dyno ID
|
|
492
|
+
* @returns {Promise<void>}
|
|
493
|
+
*/
|
|
494
|
+
gatewayDelete = async (params = {}) => {
|
|
495
|
+
return this.gateway.delete({
|
|
496
|
+
jobName: params.jobName || this.appName,
|
|
497
|
+
groupings: {
|
|
498
|
+
process_type: params.groupings?.process_type || this.processType,
|
|
499
|
+
instance: params.groupings?.instance || this.dynoId,
|
|
500
|
+
},
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Push metrics to PushGateway.
|
|
506
|
+
*
|
|
507
|
+
* @param {object} [params]
|
|
508
|
+
* @param {string} [params.jobName]
|
|
509
|
+
* @param {object} [params.groupings]
|
|
510
|
+
* @returns {Promise<void>}
|
|
511
|
+
*/
|
|
512
|
+
gatewayPush = async (params = {}) => {
|
|
513
|
+
return this.gateway.push({
|
|
514
|
+
jobName: params.jobName || this.appName,
|
|
515
|
+
groupings: {
|
|
516
|
+
process_type: params.groupings?.process_type || this.processType,
|
|
517
|
+
instance: params.groupings?.instance || this.dynoId,
|
|
518
|
+
},
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Merge the default metric labels (`app`, `dyno_id`, `process_type`)
|
|
524
|
+
* with custom label names.
|
|
525
|
+
*
|
|
526
|
+
* @param {string[]} labels Additional label names
|
|
527
|
+
* @returns {string[]} Combined label names
|
|
528
|
+
*/
|
|
529
|
+
withDefaultLabels = (labels = []) => {
|
|
530
|
+
return [...Object.keys(this.defaultLabels), labels]
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
getDefaultLabels = (labels = []) => {
|
|
534
|
+
return this.defaultLabels
|
|
535
|
+
}
|
|
536
|
+
|
|
449
537
|
_setCleanupHandlers = () => {
|
|
450
538
|
process.on('SIGINT', this.cleanup)
|
|
451
539
|
process.on('SIGTERM', this.cleanup)
|
|
452
540
|
}
|
|
541
|
+
|
|
542
|
+
// GETTERS
|
|
543
|
+
|
|
544
|
+
get metricsEnabled() {
|
|
545
|
+
return this.enabled
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
get metricsLogValues() {
|
|
549
|
+
return this.logValues
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
get registry() {
|
|
553
|
+
return this.registry
|
|
554
|
+
}
|
|
453
555
|
}
|
|
454
556
|
|
|
455
557
|
module.exports = { MetricsClient }
|