@adalo/metrics 0.1.17 → 0.1.19

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.
@@ -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: client.Registry<"text/plain; version=0.0.4; charset=utf-8">;
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<"text/plain; version=0.0.4; charset=utf-8">;
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 gauges and counters.
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: string, help: string, updateFn?: (() => number | Promise<number>) | undefined, labelNames?: string[] | undefined) => client.Gauge;
76
+ createGauge: ({ name, help, updateFn, labelNames, }: string) => client.Gauge;
71
77
  /**
72
- * Create a counter metric.
73
- * Returns a trigger function to increment the counter manually.
74
- * @param {string} name
75
- * @param {string} help
76
- * @param {string[]} [labelNames]
77
- * @returns {function(data?: object, value?: number): void} triggerFn
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: string, help: string, labelNames?: string[] | undefined): (arg0: data | null, arg1: object, arg2: value | null, arg3: number) => void;
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 automatic periodic push of metrics.
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
- _clearOldWorkers: (removeAllJobs: any) => Promise<void>;
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;;;;;;;;;;;;OAYG;IACH;QAX2B,OAAO;QACP,MAAM;QACN,WAAW;QACV,OAAO;QACP,SAAS;QACV,cAAc;QACd,eAAe;QACf,mBAAmB;QACnB,WAAW;QACV,aAAa;OA4DxC;IAzDC,gBACiE;IACjE,eAAqE;IACrE,oBAG6B;IAC7B,iBAAuE;IACvE,mBAC+D;IAC/D,uBACoE;IACpE,wBACsE;IACtE,4BAGI;IACJ,oBAGI;IAEJ,mBAAyF;IAEzF,sEAAqC;IAGrC;;;;MAIC;IAED,kBAEoB;IACpB,wEAOC;IAED,WAAgB;IAChB,aAAkB;IAElB,mEAAmE;IACnE;YADkB,MAAM,SAAc,MAAM,GAAG,QAAQ,MAAM,CAAC;MACvC;IACvB,yBAAyB;IACzB,uBAAgC;IAOlC;;;OAGG;IACH,4BA8CC;IAED;;;;;;;OAOG;IACH,oBANW,MAAM,QACN,MAAM,oBACM,MAAM,GAAC,QAAQ,MAAM,CAAC,qDAEhC,OAAO,KAAK,CAqBxB;IAED;;;;;;;OAOG;IACH,oBALW,MAAM,QACN,MAAM,gEAEY,MAAM,4BAAU,MAAM,KAAG,IAAI,CAmBzD;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,iCAgCC;IAED;;;OAGG;IACH,mDAYC;IAED,8BAWC;IAED,wDAyDC;IAED,gCAGC;CACF"}
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;IAsf3F,oBAEC;IAnfC;;;;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,CAuBxB;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"}
@@ -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
- this._initDefaultMetrics();
67
+ if (config.scripDefaultMetrics) {
68
+ this._initDefaultMetrics();
69
+ }
64
70
  this._setCleanupHandlers();
65
71
  });
66
72
  }
67
73
 
68
74
  /**
69
- * Register default gauges and counters.
75
+ * Register all built-in default Gauges and Counters.
70
76
  * @private
71
77
  */
72
78
  _initDefaultMetrics = () => {
73
- this.createGauge('app_process_cpu_usage_percent', 'Current CPU usage of the Node.js process in percent', this.getCpuUsagePercent);
74
- this.createGauge('app_available_cpu_count', 'How many CPU cores are available to this process', this.getAvailableCPUs);
75
- this.createGauge('app_container_memory_usage_bytes', 'Current container RAM usage from cgroup', this.getContainerMemoryUsage);
76
- this.createGauge('app_event_loop_lag_ms', 'Estimated event loop lag in milliseconds', this.measureLag);
77
- this.createGauge('app_container_memory_limit_bytes', 'Max RAM available to container from cgroup (memory.max)', this.getContainerMemoryLimit);
78
- this.createGauge('app_uptime_seconds', 'How long the process has been running', process.uptime);
79
- this.createCounter('app_http_requests_total', 'Total number of HTTP requests handled by this process', [...Object.keys(this.defaultLabels), 'method', 'route', 'appId', 'databaseId', 'duration', 'requestSize', 'status_code']);
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 = (name, help, updateFn, labelNames = Object.keys(this.defaultLabels)) => {
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,30 @@ 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') {
139
+ this.gaugeUpdaters[name] = updateFn;
140
+ }
100
141
  return g;
101
142
  };
102
143
 
103
144
  /**
104
- * Create a counter metric.
105
- * Returns a trigger function to increment the counter manually.
106
- * @param {string} name
107
- * @param {string} help
108
- * @param {string[]} [labelNames]
109
- * @returns {function(data?: object, value?: number): void} triggerFn
145
+ * Create a Prometheus Counter metric.
146
+ *
147
+ * @param {object} params
148
+ * @param {string} params.name Metric name
149
+ * @param {string} params.help Metric description
150
+ * @param {string[]} [params.labelNames]
151
+ * Optional list of label names. Defaults to this.defaultLabels keys.
152
+ *
153
+ * @returns {function(data?: object, value?: number): void}
154
+ * A trigger function to increment the counter:
155
+ * triggerFn(labels?, incrementValue?)
110
156
  */
111
- createCounter(name, help, labelNames = Object.keys(this.defaultLabels)) {
157
+ createCounter({
158
+ name,
159
+ help,
160
+ labelNames = Object.keys(this.defaultLabels)
161
+ }) {
112
162
  if (this.counters[name]) return this.counters[name].triggerFn;
113
163
  const c = new client.Counter({
114
164
  name,
@@ -244,6 +294,9 @@ class MetricsClient {
244
294
  try {
245
295
  for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {
246
296
  try {
297
+ if (!updateFn) {
298
+ return;
299
+ }
247
300
  const result = updateFn();
248
301
  const val = result instanceof Promise ? await result : result;
249
302
  if (val !== undefined) this.gauges[name].set(this.defaultLabels, val);
@@ -251,7 +304,7 @@ class MetricsClient {
251
304
  console.error(`${this.prefixLogs} Failed to update gauge ${name}:`, err);
252
305
  }
253
306
  }
254
- await this.gateway.push({
307
+ await this.gatewayPush({
255
308
  jobName: this.appName,
256
309
  groupings: {
257
310
  process_type: this.processType,
@@ -269,32 +322,48 @@ class MetricsClient {
269
322
  };
270
323
 
271
324
  /**
272
- * Start automatic periodic push of metrics.
325
+ * Start periodic metrics collection + push.
326
+ *
273
327
  * @param {number} [interval] Interval in seconds
328
+ * @param {function(): void|Promise<void>} [customPushMetics]
329
+ * Optional custom push function. If provided, Prometheus push is skipped.
274
330
  */
275
- startPush = (interval = this.intervalSec) => {
331
+ startPush = (interval = this.intervalSec, customPushMetics = () => {}) => {
276
332
  if (!this.enabled) {
277
333
  console.warn(`${this.prefixLogs} Metrics disabled`);
334
+ return;
278
335
  }
279
- setInterval(() => {
280
- this.pushMetrics().catch(err => {
281
- console.error(`${this.prefixLogs} Failed to push metrics:`, err);
282
- });
283
- }, interval * 1000);
284
- console.warn(`${this.prefixLogs} Metrics collection started.`);
336
+ if (this.startupValidation && !this.startupValidation()) {
337
+ return;
338
+ }
339
+ if (customPushMetics) {
340
+ setInterval(customPushMetics, interval * 1000);
341
+ } else {
342
+ setInterval(() => {
343
+ this.pushMetrics().catch(err => {
344
+ console.error(`${this.prefixLogs} Failed to push metrics:`, err);
345
+ });
346
+ }, interval * 1000);
347
+ }
348
+ console.warn(`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`);
285
349
  };
286
350
  cleanup = async () => {
287
351
  if (this.enabled) {
288
- await this.gateway.delete({
289
- jobName: this.appName,
290
- groupings: {
291
- process_type: this.processType,
292
- instance: this.dynoId
293
- }
294
- });
352
+ await this.gatewayDelete();
295
353
  }
296
354
  process.exit(0);
297
355
  };
356
+
357
+ /**
358
+ * Remove old/stale dyno/instance metrics from PushGateway.
359
+ *
360
+ * Compares existing PushGateway metrics for this job and deletes any instances
361
+ * that do not match the current dynoId.
362
+ *
363
+ * @param {boolean} removeAllJobs If true, performs cleanup; otherwise does nothing
364
+ * @returns {Promise<void>}
365
+ * @private
366
+ */
298
367
  _clearOldWorkers = async removeAllJobs => {
299
368
  if (!removeAllJobs) return;
300
369
  try {
@@ -323,10 +392,8 @@ class MetricsClient {
323
392
  return;
324
393
  }
325
394
  for (const instance of instances) {
326
- await this.gateway.delete({
327
- jobName: this.appName,
395
+ await this.gatewayDelete({
328
396
  groupings: {
329
- process_type: this.processType,
330
397
  instance
331
398
  }
332
399
  });
@@ -337,10 +404,74 @@ class MetricsClient {
337
404
  console.error(`${this.prefixLogs} Error deleting old metrics:`, err);
338
405
  }
339
406
  };
407
+
408
+ /**
409
+ * Delete metrics for this job/instance from PushGateway.
410
+ *
411
+ * @param {Object} [params]
412
+ * @param {string} [params.jobName] Job name (defaults to appName)
413
+ * @param {Object} [params.groupings] Grouping labels
414
+ * @param {string} [params.groupings.process_type] Process type label
415
+ * @param {string} [params.groupings.instance] Instance/dyno ID
416
+ * @returns {Promise<void>}
417
+ */
418
+ gatewayDelete = async (params = {}) => {
419
+ return this.gateway.delete({
420
+ jobName: params.jobName || this.appName,
421
+ groupings: {
422
+ process_type: params.groupings?.process_type || this.processType,
423
+ instance: params.groupings?.instance || this.dynoId
424
+ }
425
+ });
426
+ };
427
+
428
+ /**
429
+ * Push metrics to PushGateway.
430
+ *
431
+ * @param {object} [params]
432
+ * @param {string} [params.jobName]
433
+ * @param {object} [params.groupings]
434
+ * @returns {Promise<void>}
435
+ */
436
+ gatewayPush = async (params = {}) => {
437
+ return this.gateway.push({
438
+ jobName: params.jobName || this.appName,
439
+ groupings: {
440
+ process_type: params.groupings?.process_type || this.processType,
441
+ instance: params.groupings?.instance || this.dynoId
442
+ }
443
+ });
444
+ };
445
+
446
+ /**
447
+ * Merge the default metric labels (`app`, `dyno_id`, `process_type`)
448
+ * with custom label names.
449
+ *
450
+ * @param {string[]} labels Additional label names
451
+ * @returns {string[]} Combined label names
452
+ */
453
+ withDefaultLabels = (labels = []) => {
454
+ return [...Object.keys(this.defaultLabels), labels];
455
+ };
456
+ getDefaultLabels = (labels = []) => {
457
+ return this.defaultLabels;
458
+ };
340
459
  _setCleanupHandlers = () => {
341
460
  process.on('SIGINT', this.cleanup);
342
461
  process.on('SIGTERM', this.cleanup);
343
462
  };
463
+
464
+ // GETTERS
465
+
466
+ get metricsEnabled() {
467
+ return this.enabled;
468
+ }
469
+ get metricsLogValues() {
470
+ return this.logValues;
471
+ }
472
+ get registry() {
473
+ return this.registry;
474
+ }
344
475
  }
345
476
  module.exports = {
346
477
  MetricsClient
@@ -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\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":";;AAAA,MAAMA,MAAM,GAAGC,OAAO,CAAC,aAAa,CAAC;AACrC,MAAMC,EAAE,GAAGD,OAAO,CAAC,IAAI,CAAC;AACxB,MAAME,EAAE,GAAGF,OAAO,CAAC,IAAI,CAAC;AACxB,MAAMG,KAAK,GAAGH,OAAO,CAAC,OAAO,CAAC;;AAE9B;AACA;AACA;AACA;AACA,MAAMI,aAAa,CAAC;EAClB;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAACC,MAAM,GAAG,CAAC,CAAC,EAAE;IACvB,IAAI,CAACC,OAAO,GACVD,MAAM,CAACC,OAAO,IAAIC,OAAO,CAACC,GAAG,CAACC,gBAAgB,IAAI,aAAa;IACjE,IAAI,CAACC,MAAM,GAAGL,MAAM,CAACK,MAAM,IAAIH,OAAO,CAACC,GAAG,CAACG,QAAQ,IAAI,cAAc;IACrE,IAAI,CAACC,WAAW,GACdP,MAAM,CAACO,WAAW,IAClBL,OAAO,CAACC,GAAG,CAACK,uBAAuB,IACnC,2BAA2B;IAC7B,IAAI,CAACC,OAAO,GAAGT,MAAM,CAACS,OAAO,IAAIP,OAAO,CAACC,GAAG,CAACO,eAAe,KAAK,MAAM;IACvE,IAAI,CAACC,SAAS,GACZX,MAAM,CAACW,SAAS,IAAIT,OAAO,CAACC,GAAG,CAACS,kBAAkB,KAAK,MAAM;IAC/D,IAAI,CAACC,cAAc,GACjBb,MAAM,CAACa,cAAc,IAAIX,OAAO,CAACC,GAAG,CAACW,uBAAuB,IAAI,EAAE;IACpE,IAAI,CAACC,eAAe,GAClBf,MAAM,CAACe,eAAe,IAAIb,OAAO,CAACC,GAAG,CAACa,wBAAwB,IAAI,EAAE;IACtE,IAAI,CAACC,mBAAmB,GACtBjB,MAAM,CAACiB,mBAAmB,IAC1Bf,OAAO,CAACC,GAAG,CAACe,4BAA4B,IACxC,EAAE;IACJ,IAAI,CAACC,WAAW,GACdnB,MAAM,CAACmB,WAAW,IAClBC,QAAQ,CAAClB,OAAO,CAACC,GAAG,CAACkB,oBAAoB,IAAI,EAAE,EAAE,EAAE,CAAC,IACpD,EAAE;IACJ,IAAI,CAACC,iBAAiB,GAAGtB,MAAM,CAACsB,iBAAiB;IAEjD,IAAI,CAACC,UAAU,GAAG,IAAI,IAAI,CAAChB,WAAW,MAAM,IAAI,CAACN,OAAO,MAAM,IAAI,CAACI,MAAM,gBAAgB;IAEzF,IAAI,CAACmB,QAAQ,GAAG,IAAI/B,MAAM,CAACgC,QAAQ,CAAC,CAAC;IACrChC,MAAM,CAACiC,qBAAqB,CAAC;MAAEC,QAAQ,EAAE,IAAI,CAACH;IAAS,CAAC,CAAC;IAEzD,IAAI,CAACI,aAAa,GAAG;MACnBC,GAAG,EAAE,IAAI,CAAC5B,OAAO;MACjB6B,OAAO,EAAE,IAAI,CAACzB,MAAM;MACpB0B,YAAY,EAAE,IAAI,CAACxB;IACrB,CAAC;IAED,IAAI,CAACyB,SAAS,GAAGC,MAAM,CAACC,IAAI,CAC1B,GAAG,IAAI,CAACnB,eAAe,IAAI,IAAI,CAACE,mBAAmB,EACrD,CAAC,CAACkB,QAAQ,CAAC,QAAQ,CAAC;IACpB,IAAI,CAACC,OAAO,GAAG,IAAI3C,MAAM,CAAC4C,WAAW,CACnC,IAAI,CAACxB,cAAc,EACnB;MACEyB,OAAO,EAAE;QAAEC,aAAa,EAAE,SAAS,IAAI,CAACP,SAAS;MAAG,CAAC;MACrDQ,KAAK,EAAE,IAAI3C,KAAK,CAAC4C,KAAK,CAAC;QAAEC,SAAS,EAAE;MAAK,CAAC;IAC5C,CAAC,EACD,IAAI,CAAClB,QACP,CAAC;IAED,IAAI,CAACmB,MAAM,GAAG,CAAC,CAAC;IAChB,IAAI,CAACC,QAAQ,GAAG,CAAC,CAAC;;IAElB;IACA,IAAI,CAACC,aAAa,GAAG,CAAC,CAAC;IACvB,IAAI,CAACC,sBAAsB,GAAG,CAAC,CAAC;IAChC,IAAI,CAACC,gBAAgB,GAAG,CAAC;IACzB,IAAI,CAACC,cAAc,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;IAChC,IAAI,CAACC,gBAAgB,CAACnD,MAAM,CAACoD,aAAa,CAAC,CAACC,IAAI,CAACC,CAAC,IAAI;MACpD,IAAItD,MAAM,CAACuD,mBAAmB,EAAE;QAC9B,IAAI,CAACC,mBAAmB,CAAC,CAAC;MAC5B;MACA,IAAI,CAACC,mBAAmB,CAAC,CAAC;IAC5B,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;EACED,mBAAmB,GAAGA,CAAA,KAAM;IAC1B,IAAI,CAACE,WAAW,CAAC;MACfC,IAAI,EAAE,+BAA+B;MACrCC,IAAI,EAAE,qDAAqD;MAC3DC,QAAQ,EAAE,IAAI,CAACC;IACjB,CAAC,CAAC;IAEF,IAAI,CAACJ,WAAW,CAAC;MACfC,IAAI,EAAE,yBAAyB;MAC/BC,IAAI,EAAE,kDAAkD;MACxDC,QAAQ,EAAE,IAAI,CAACE;IACjB,CAAC,CAAC;IAEF,IAAI,CAACL,WAAW,CAAC;MACfC,IAAI,EAAE,kCAAkC;MACxCC,IAAI,EAAE,yCAAyC;MAC/CC,QAAQ,EAAE,IAAI,CAACG;IACjB,CAAC,CAAC;IAEF,IAAI,CAACN,WAAW,CAAC;MACfC,IAAI,EAAE,uBAAuB;MAC7BC,IAAI,EAAE,0CAA0C;MAChDC,QAAQ,EAAE,IAAI,CAACI;IACjB,CAAC,CAAC;IAEF,IAAI,CAACP,WAAW,CAAC;MACfC,IAAI,EAAE,kCAAkC;MACxCC,IAAI,EAAE,yDAAyD;MAC/DC,QAAQ,EAAE,IAAI,CAACK;IACjB,CAAC,CAAC;IAEF,IAAI,CAACR,WAAW,CAAC;MACfC,IAAI,EAAE,oBAAoB;MAC1BC,IAAI,EAAE,uCAAuC;MAC7CC,QAAQ,EAAE3D,OAAO,CAACiE;IACpB,CAAC,CAAC;IAEF,IAAI,CAACC,aAAa,CAAC;MACjBT,IAAI,EAAE,yBAAyB;MAC/BC,IAAI,EAAE,uDAAuD;MAC7DS,UAAU,EAAE,IAAI,CAACC,iBAAiB,CAAC,CACjC,QAAQ,EACR,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,aAAa,EACb,aAAa,CACd;IACH,CAAC,CAAC;EACJ,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;EACEZ,WAAW,GAAGA,CAAC;IACbC,IAAI;IACJC,IAAI;IACJC,QAAQ;IACRQ,UAAU,GAAGE,MAAM,CAACC,IAAI,CAAC,IAAI,CAAC5C,aAAa;EAC7C,CAAC,KAAK;IACJ,IAAI,IAAI,CAACe,MAAM,CAACgB,IAAI,CAAC,EAAE,OAAO,IAAI,CAAChB,MAAM,CAACgB,IAAI,CAAC;IAE/C,MAAMc,CAAC,GAAG,IAAIhF,MAAM,CAACiF,KAAK,CAAC;MACzBf,IAAI;MACJC,IAAI;MACJS,UAAU;MACVM,SAAS,EAAE,CAAC,IAAI,CAACnD,QAAQ;IAC3B,CAAC,CAAC;IACF,IAAI,CAACmB,MAAM,CAACgB,IAAI,CAAC,GAAGc,CAAC;IAErB,IAAIZ,QAAQ,IAAI,OAAOA,QAAQ,KAAK,UAAU,EAAE;MAC9C,IAAI,CAAChB,aAAa,CAACc,IAAI,CAAC,GAAGE,QAAQ;IACrC;IAEA,OAAOY,CAAC;EACV,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEL,aAAaA,CAAC;IAAET,IAAI;IAAEC,IAAI;IAAES,UAAU,GAAGE,MAAM,CAACC,IAAI,CAAC,IAAI,CAAC5C,aAAa;EAAE,CAAC,EAAE;IAC1E,IAAI,IAAI,CAACgB,QAAQ,CAACe,IAAI,CAAC,EAAE,OAAO,IAAI,CAACf,QAAQ,CAACe,IAAI,CAAC,CAACiB,SAAS;IAE7D,MAAMC,CAAC,GAAG,IAAIpF,MAAM,CAACqF,OAAO,CAAC;MAC3BnB,IAAI;MACJC,IAAI;MACJS,UAAU;MACVM,SAAS,EAAE,CAAC,IAAI,CAACnD,QAAQ;IAC3B,CAAC,CAAC;IACF,IAAI,CAACoB,QAAQ,CAACe,IAAI,CAAC,GAAGkB,CAAC;IAEvB,MAAMD,SAAS,GAAGA,CAACG,IAAI,GAAG,CAAC,CAAC,EAAEC,KAAK,GAAG,CAAC,KAAK;MAC1CH,CAAC,CAACI,GAAG,CAAC;QAAE,GAAG,IAAI,CAACrD,aAAa;QAAE,GAAGmD;MAAK,CAAC,EAAEC,KAAK,CAAC;IAClD,CAAC;IAED,IAAI,CAACpC,QAAQ,CAACe,IAAI,CAAC,CAACiB,SAAS,GAAGA,SAAS;IACzC,OAAOA,SAAS;EAClB;;EAEA;AACF;AACA;AACA;EACEd,kBAAkB,GAAGA,CAAA,KAAM;IACzB,IAAI;MACF,MAAMoB,IAAI,GAAGvF,EAAE,CAACwF,YAAY,CAAC,yBAAyB,EAAE,OAAO,CAAC;MAChE,MAAMC,KAAK,GAAGF,IAAI,CAACE,KAAK,CAAC,kBAAkB,CAAC;MAC5C,IAAI,CAACA,KAAK,EAAE,OAAO,CAAC;MAEpB,MAAMlC,GAAG,GAAGD,IAAI,CAACC,GAAG,CAAC,CAAC;MACtB,MAAMmC,YAAY,GAAGjE,QAAQ,CAACgE,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;MAE3C,IAAI,IAAI,CAACrC,gBAAgB,KAAK,CAAC,EAAE;QAC/B,IAAI,CAACA,gBAAgB,GAAGsC,YAAY;QACpC,IAAI,CAACrC,cAAc,GAAGE,GAAG;QACzB,OAAO,CAAC;MACV;MAEA,MAAMoC,UAAU,GAAGD,YAAY,GAAG,IAAI,CAACtC,gBAAgB;MACvD,MAAMwC,SAAS,GAAGrC,GAAG,GAAG,IAAI,CAACF,cAAc;MAE3C,IAAI,CAACD,gBAAgB,GAAGsC,YAAY;MACpC,IAAI,CAACrC,cAAc,GAAGE,GAAG;MAEzB,OAAQoC,UAAU,IAAIC,SAAS,GAAG,IAAI,CAAC,GAAI,GAAG;IAChD,CAAC,CAAC,MAAM;MACN,OAAO,CAAC;IACV;EACF,CAAC;;EAED;AACF;AACA;AACA;EACExB,gBAAgBA,CAAA,EAAG;IACjB,IAAI;MACF,MAAMyB,UAAU,GAAG,wBAAwB;MAC3C,IAAI7F,EAAE,CAAC8F,UAAU,CAACD,UAAU,CAAC,EAAE;QAC7B,MAAM,CAACE,QAAQ,EAAEC,SAAS,CAAC,GAAGhG,EAAE,CAC7BwF,YAAY,CAACK,UAAU,EAAE,MAAM,CAAC,CAChCI,IAAI,CAAC,CAAC,CACNC,KAAK,CAAC,GAAG,CAAC;QACb,IAAIH,QAAQ,KAAK,KAAK,EAAE,OAAO9F,EAAE,CAACkG,IAAI,CAAC,CAAC,CAACC,MAAM;QAC/C,OAAO3E,QAAQ,CAACsE,QAAQ,EAAE,EAAE,CAAC,GAAGtE,QAAQ,CAACuE,SAAS,EAAE,EAAE,CAAC;MACzD;MACA,OAAO/F,EAAE,CAACkG,IAAI,CAAC,CAAC,CAACC,MAAM;IACzB,CAAC,CAAC,MAAM;MACN,OAAO,CAAC;IACV;EACF;;EAEA;AACF;AACA;AACA;EACE/B,uBAAuBA,CAAA,EAAG;IACxB,IAAI;MACF,OAAO5C,QAAQ,CACbzB,EAAE,CAACwF,YAAY,CAAC,+BAA+B,EAAE,OAAO,CAAC,CAACS,IAAI,CAAC,CAAC,EAChE,EACF,CAAC;IACH,CAAC,CAAC,MAAM;MACN,OAAO1F,OAAO,CAAC8F,WAAW,CAAC,CAAC,CAACC,GAAG;IAClC;EACF;;EAEA;AACF;AACA;AACA;EACE/B,uBAAuBA,CAAA,EAAG;IACxB,IAAI;MACF,MAAMgC,IAAI,GAAG,2BAA2B;MACxC,IAAIvG,EAAE,CAAC8F,UAAU,CAACS,IAAI,CAAC,EAAE;QACvB,MAAMC,GAAG,GAAGxG,EAAE,CAACwF,YAAY,CAACe,IAAI,EAAE,OAAO,CAAC,CAACN,IAAI,CAAC,CAAC;QACjD,IAAIO,GAAG,KAAK,KAAK,EAAE;UACjB,MAAMC,MAAM,GAAGhF,QAAQ,CAAC+E,GAAG,EAAE,EAAE,CAAC;UAChC,IAAIC,MAAM,IAAIA,MAAM,GAAGxG,EAAE,CAACyG,QAAQ,CAAC,CAAC,EAAE,OAAOD,MAAM;QACrD;MACF;MACA,OAAOxG,EAAE,CAACyG,QAAQ,CAAC,CAAC;IACtB,CAAC,CAAC,MAAM;MACN,OAAOzG,EAAE,CAACyG,QAAQ,CAAC,CAAC;IACtB;EACF;;EAEA;AACF;AACA;AACA;EACEpC,UAAUA,CAAA,EAAG;IACX,OAAO,IAAIqC,OAAO,CAACC,OAAO,IAAI;MAC5B,MAAMC,KAAK,GAAGvD,IAAI,CAACC,GAAG,CAAC,CAAC;MACxBuD,YAAY,CAAC,MAAMF,OAAO,CAACtD,IAAI,CAACC,GAAG,CAAC,CAAC,GAAGsD,KAAK,CAAC,CAAC;IACjD,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;EACEE,0BAA0B,GAAGA,CAACC,GAAG,EAAEC,GAAG,EAAEC,IAAI,KAAK;IAC/C,MAAML,KAAK,GAAGvD,IAAI,CAACC,GAAG,CAAC,CAAC;IACxB0D,GAAG,CAACE,EAAE,CAAC,QAAQ,EAAE,MAAM;MACrB,MAAMC,KAAK,GAAGJ,GAAG,CAACI,KAAK,EAAEb,IAAI,IAAIS,GAAG,CAACT,IAAI,IAAI,SAAS;MACtD,MAAMc,KAAK,GACTL,GAAG,CAACM,MAAM,EAAED,KAAK,IAAIL,GAAG,CAACO,IAAI,EAAEF,KAAK,IAAIL,GAAG,CAACQ,KAAK,EAAEH,KAAK,IAAI,EAAE;MAChE,MAAMI,UAAU,GACdT,GAAG,CAACM,MAAM,EAAEG,UAAU,IACtBT,GAAG,CAACO,IAAI,EAAEE,UAAU,IACpBT,GAAG,CAACQ,KAAK,EAAEC,UAAU,IACrB,EAAE;MAEJ,IAAI,CAACxE,QAAQ,EAAEyE,uBAAuB,EAAEzC,SAAS,CAAC;QAChD0C,MAAM,EAAEX,GAAG,CAACW,MAAM;QAClBP,KAAK;QACLQ,WAAW,EAAEX,GAAG,CAACY,UAAU;QAC3BR,KAAK;QACLI,UAAU;QACVK,QAAQ,EAAExE,IAAI,CAACC,GAAG,CAAC,CAAC,GAAGsD,KAAK;QAC5BkB,WAAW,EAAEf,GAAG,CAACrE,OAAO,CAAC,gBAAgB,CAAC,GACtClB,QAAQ,CAACuF,GAAG,CAACrE,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,GAC3C;MACN,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFuE,IAAI,CAAC,CAAC;EACR,CAAC;;EAED;AACF;AACA;EACEc,WAAW,GAAG,MAAAA,CAAA,KAAY;IACxB,IAAI;MACF,KAAK,MAAM,CAAChE,IAAI,EAAEE,QAAQ,CAAC,IAAIU,MAAM,CAACqD,OAAO,CAAC,IAAI,CAAC/E,aAAa,CAAC,EAAE;QACjE,IAAI;UACF,IAAI,CAACgB,QAAQ,EAAE;YACb;UACF;UACA,MAAMgE,MAAM,GAAGhE,QAAQ,CAAC,CAAC;UACzB,MAAMsC,GAAG,GAAG0B,MAAM,YAAYvB,OAAO,GAAG,MAAMuB,MAAM,GAAGA,MAAM;UAC7D,IAAI1B,GAAG,KAAK2B,SAAS,EAAE,IAAI,CAACnF,MAAM,CAACgB,IAAI,CAAC,CAACoE,GAAG,CAAC,IAAI,CAACnG,aAAa,EAAEuE,GAAG,CAAC;QACvE,CAAC,CAAC,OAAO6B,GAAG,EAAE;UACZC,OAAO,CAACC,KAAK,CACX,GAAG,IAAI,CAAC3G,UAAU,2BAA2BoC,IAAI,GAAG,EACpDqE,GACF,CAAC;QACH;MACF;MAEA,MAAM,IAAI,CAACG,WAAW,CAAC;QACrBC,OAAO,EAAE,IAAI,CAACnI,OAAO;QACrBoI,SAAS,EAAE;UAAEtG,YAAY,EAAE,IAAI,CAACxB,WAAW;UAAE+H,QAAQ,EAAE,IAAI,CAACjI;QAAO;MACrE,CAAC,CAAC;MAEFkE,MAAM,CAACgE,MAAM,CAAC,IAAI,CAAC3F,QAAQ,CAAC,CAAC4F,OAAO,CAACC,OAAO,IAAIA,OAAO,CAACC,KAAK,CAAC,CAAC,CAAC;MAEhE,IAAI,IAAI,CAAC/H,SAAS,EAAE;QAClB,MAAMgI,OAAO,GAAG,MAAM,IAAI,CAACnH,QAAQ,CAACoH,gBAAgB,CAAC,CAAC;QACtDX,OAAO,CAACY,GAAG,CACT,GAAG,IAAI,CAACtH,UAAU,aAAa,EAC/BuH,IAAI,CAACC,SAAS,CAACJ,OAAO,EAAE,IAAI,EAAE,CAAC,CACjC,CAAC;MACH;IACF,CAAC,CAAC,OAAOX,GAAG,EAAE;MACZC,OAAO,CAACC,KAAK,CAAC,GAAG,IAAI,CAAC3G,UAAU,0BAA0B,EAAEyG,GAAG,CAAC;IAClE;EACF,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;EACEgB,SAAS,GAAGA,CAACC,QAAQ,GAAG,IAAI,CAAC9H,WAAW,EAAE+H,gBAAgB,GAAGA,CAAA,KAAM,CAAC,CAAC,KAAK;IACxE,IAAI,CAAC,IAAI,CAACzI,OAAO,EAAE;MACjBwH,OAAO,CAACkB,IAAI,CAAC,GAAG,IAAI,CAAC5H,UAAU,mBAAmB,CAAC;MACnD;IACF;IAEA,IAAI,IAAI,CAACD,iBAAiB,IAAI,CAAC,IAAI,CAACA,iBAAiB,CAAC,CAAC,EAAE;MACvD;IACF;IAEA,IAAI4H,gBAAgB,EAAE;MACpBE,WAAW,CAACF,gBAAgB,EAAED,QAAQ,GAAG,IAAI,CAAC;IAChD,CAAC,MAAM;MACLG,WAAW,CAAC,MAAM;QAChB,IAAI,CAACzB,WAAW,CAAC,CAAC,CAAC0B,KAAK,CAACrB,GAAG,IAAI;UAC9BC,OAAO,CAACC,KAAK,CAAC,GAAG,IAAI,CAAC3G,UAAU,0BAA0B,EAAEyG,GAAG,CAAC;QAClE,CAAC,CAAC;MACJ,CAAC,EAAEiB,QAAQ,GAAG,IAAI,CAAC;IACrB;IAEAhB,OAAO,CAACkB,IAAI,CACV,GAAG,IAAI,CAAC5H,UAAU,2CAA2C,IAAI,CAACJ,WAAW,IAC/E,CAAC;EACH,CAAC;EAEDmI,OAAO,GAAG,MAAAA,CAAA,KAAY;IACpB,IAAI,IAAI,CAAC7I,OAAO,EAAE;MAChB,MAAM,IAAI,CAAC8I,aAAa,CAAC,CAAC;IAC5B;IACArJ,OAAO,CAACsJ,IAAI,CAAC,CAAC,CAAC;EACjB,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACErG,gBAAgB,GAAG,MAAMC,aAAa,IAAI;IACxC,IAAI,CAACA,aAAa,EAAE;IAEpB,IAAI;MACF,MAAMqG,GAAG,GAAG,GAAG,IAAI,CAAC5I,cAAc,UAAU;MAC5C,MAAM+F,GAAG,GAAG,MAAM8C,KAAK,CAACD,GAAG,EAAE;QAC3BnH,OAAO,EAAE;UACPC,aAAa,EAAE,SAAS,IAAI,CAACP,SAAS,EAAE;UACxC2H,MAAM,EAAE;QACV;MACF,CAAC,CAAC;MAEF,IAAI,CAAC/C,GAAG,CAACgD,EAAE,EAAE;QACX3B,OAAO,CAACC,KAAK,CACX,GAAG,IAAI,CAAC3G,UAAU,6BAA6BqF,GAAG,CAACiD,MAAM,EAC3D,CAAC;QACD;MACF;MAEA,MAAMC,IAAI,GAAG,MAAMlD,GAAG,CAACkD,IAAI,CAAC,CAAC;MAE7B,MAAMC,KAAK,GAAG,IAAIC,MAAM,CACtB,+BAA+B,IAAI,CAAC/J,OAAO,UAAU,IAAI,CAACA,OAAO,wBAAwB,EACzF,GACF,CAAC;MACD,MAAMgK,SAAS,GAAG,IAAIC,GAAG,CAAC,CAAC;MAC3B,IAAI9E,KAAK;MACT;MACA,OAAO,CAACA,KAAK,GAAG2E,KAAK,CAACI,IAAI,CAACL,IAAI,CAAC,MAAM,IAAI,EAAE;QAC1C,MAAMxB,QAAQ,GAAGlD,KAAK,CAAC,CAAC,CAAC,IAAIA,KAAK,CAAC,CAAC,CAAC;QACrC,IAAIkD,QAAQ,IAAIA,QAAQ,KAAK,IAAI,CAACjI,MAAM,EAAE4J,SAAS,CAACG,GAAG,CAAC9B,QAAQ,CAAC;MACnE;MAEA,IAAI2B,SAAS,CAACI,IAAI,KAAK,CAAC,EAAE;QACxBpC,OAAO,CAACY,GAAG,CAAC,GAAG,IAAI,CAACtH,UAAU,0BAA0B,CAAC;QACzD;MACF;MAEA,KAAK,MAAM+G,QAAQ,IAAI2B,SAAS,EAAE;QAChC,MAAM,IAAI,CAACV,aAAa,CAAC;UACvBlB,SAAS,EAAE;YACTC;UACF;QACF,CAAC,CAAC;QACFL,OAAO,CAACY,GAAG,CACT,GAAG,IAAI,CAACtH,UAAU,kCAAkC+G,QAAQ,EAC9D,CAAC;MACH;MAEAL,OAAO,CAACY,GAAG,CACT,GAAG,IAAI,CAACtH,UAAU,sCAAsC,IAAI,CAACtB,OAAO,EACtE,CAAC;IACH,CAAC,CAAC,OAAO+H,GAAG,EAAE;MACZC,OAAO,CAACC,KAAK,CAAC,GAAG,IAAI,CAAC3G,UAAU,8BAA8B,EAAEyG,GAAG,CAAC;IACtE;EACF,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEuB,aAAa,GAAG,MAAAA,CAAOtC,MAAM,GAAG,CAAC,CAAC,KAAK;IACrC,OAAO,IAAI,CAAC7E,OAAO,CAACkI,MAAM,CAAC;MACzBlC,OAAO,EAAEnB,MAAM,CAACmB,OAAO,IAAI,IAAI,CAACnI,OAAO;MACvCoI,SAAS,EAAE;QACTtG,YAAY,EAAEkF,MAAM,CAACoB,SAAS,EAAEtG,YAAY,IAAI,IAAI,CAACxB,WAAW;QAChE+H,QAAQ,EAAErB,MAAM,CAACoB,SAAS,EAAEC,QAAQ,IAAI,IAAI,CAACjI;MAC/C;IACF,CAAC,CAAC;EACJ,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;EACE8H,WAAW,GAAG,MAAAA,CAAOlB,MAAM,GAAG,CAAC,CAAC,KAAK;IACnC,OAAO,IAAI,CAAC7E,OAAO,CAACmI,IAAI,CAAC;MACvBnC,OAAO,EAAEnB,MAAM,CAACmB,OAAO,IAAI,IAAI,CAACnI,OAAO;MACvCoI,SAAS,EAAE;QACTtG,YAAY,EAAEkF,MAAM,CAACoB,SAAS,EAAEtG,YAAY,IAAI,IAAI,CAACxB,WAAW;QAChE+H,QAAQ,EAAErB,MAAM,CAACoB,SAAS,EAAEC,QAAQ,IAAI,IAAI,CAACjI;MAC/C;IACF,CAAC,CAAC;EACJ,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;EACEiE,iBAAiB,GAAGA,CAACkG,MAAM,GAAG,EAAE,KAAK;IACnC,OAAO,CAAC,GAAGjG,MAAM,CAACC,IAAI,CAAC,IAAI,CAAC5C,aAAa,CAAC,EAAE4I,MAAM,CAAC;EACrD,CAAC;EAEDC,gBAAgB,GAAGA,CAACD,MAAM,GAAG,EAAE,KAAK;IAClC,OAAO,IAAI,CAAC5I,aAAa;EAC3B,CAAC;EAED6B,mBAAmB,GAAGA,CAAA,KAAM;IAC1BvD,OAAO,CAAC4G,EAAE,CAAC,QAAQ,EAAE,IAAI,CAACwC,OAAO,CAAC;IAClCpJ,OAAO,CAAC4G,EAAE,CAAC,SAAS,EAAE,IAAI,CAACwC,OAAO,CAAC;EACrC,CAAC;;EAED;;EAEA,IAAIoB,cAAcA,CAAA,EAAG;IACnB,OAAO,IAAI,CAACjK,OAAO;EACrB;EAEA,IAAIkK,gBAAgBA,CAAA,EAAG;IACrB,OAAO,IAAI,CAAChK,SAAS;EACvB;EAEA,IAAIa,QAAQA,CAAA,EAAG;IACb,OAAO,IAAI,CAACA,QAAQ;EACtB;AACF;AAEAoJ,MAAM,CAACC,OAAO,GAAG;EAAE/K;AAAc,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adalo/metrics",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Reusable metrics utilities for Node.js and Laravel apps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -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
- this._initDefaultMetrics()
83
+ if (config.scripDefaultMetrics) {
84
+ this._initDefaultMetrics()
85
+ }
80
86
  this._setCleanupHandlers()
81
87
  })
82
88
  }
83
89
 
84
90
  /**
85
- * Register default gauges and counters.
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
- this.createGauge(
95
- 'app_available_cpu_count',
96
- 'How many CPU cores are available to this process',
97
- this.getAvailableCPUs
98
- )
99
- this.createGauge(
100
- 'app_container_memory_usage_bytes',
101
- 'Current container RAM usage from cgroup',
102
- this.getContainerMemoryUsage
103
- )
104
- this.createGauge(
105
- 'app_event_loop_lag_ms',
106
- 'Estimated event loop lag in milliseconds',
107
- this.measureLag
108
- )
109
- this.createGauge(
110
- 'app_container_memory_limit_bytes',
111
- 'Max RAM available to container from cgroup (memory.max)',
112
- this.getContainerMemoryLimit
113
- )
114
- this.createGauge(
115
- 'app_uptime_seconds',
116
- 'How long the process has been running',
117
- process.uptime
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
+ })
119
124
 
120
- this.createCounter(
121
- 'app_http_requests_total',
122
- 'Total number of HTTP requests handled by this process',
123
- [
124
- ...Object.keys(this.defaultLabels),
125
+ this.createGauge({
126
+ name: 'app_uptime_seconds',
127
+ help: 'How long the process has been running',
128
+ updateFn: process.uptime,
129
+ })
130
+
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,27 @@ class MetricsClient {
157
167
  })
158
168
  this.gauges[name] = g
159
169
 
160
- if (typeof updateFn === 'function') this.gaugeUpdaters[name] = updateFn
170
+ if (updateFn && typeof updateFn === 'function') {
171
+ this.gaugeUpdaters[name] = updateFn
172
+ }
161
173
 
162
174
  return g
163
175
  }
164
176
 
165
177
  /**
166
- * Create a counter metric.
167
- * Returns a trigger function to increment the counter manually.
168
- * @param {string} name
169
- * @param {string} help
170
- * @param {string[]} [labelNames]
171
- * @returns {function(data?: object, value?: number): void} triggerFn
178
+ * Create a Prometheus Counter metric.
179
+ *
180
+ * @param {object} params
181
+ * @param {string} params.name Metric name
182
+ * @param {string} params.help Metric description
183
+ * @param {string[]} [params.labelNames]
184
+ * Optional list of label names. Defaults to this.defaultLabels keys.
185
+ *
186
+ * @returns {function(data?: object, value?: number): void}
187
+ * A trigger function to increment the counter:
188
+ * triggerFn(labels?, incrementValue?)
172
189
  */
173
- createCounter(name, help, labelNames = Object.keys(this.defaultLabels)) {
190
+ createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {
174
191
  if (this.counters[name]) return this.counters[name].triggerFn
175
192
 
176
193
  const c = new client.Counter({
@@ -326,6 +343,9 @@ class MetricsClient {
326
343
  try {
327
344
  for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {
328
345
  try {
346
+ if (!updateFn) {
347
+ return
348
+ }
329
349
  const result = updateFn()
330
350
  const val = result instanceof Promise ? await result : result
331
351
  if (val !== undefined) this.gauges[name].set(this.defaultLabels, val)
@@ -337,7 +357,7 @@ class MetricsClient {
337
357
  }
338
358
  }
339
359
 
340
- await this.gateway.push({
360
+ await this.gatewayPush({
341
361
  jobName: this.appName,
342
362
  groupings: { process_type: this.processType, instance: this.dynoId },
343
363
  })
@@ -357,36 +377,54 @@ class MetricsClient {
357
377
  }
358
378
 
359
379
  /**
360
- * Start automatic periodic push of metrics.
380
+ * Start periodic metrics collection + push.
381
+ *
361
382
  * @param {number} [interval] Interval in seconds
383
+ * @param {function(): void|Promise<void>} [customPushMetics]
384
+ * Optional custom push function. If provided, Prometheus push is skipped.
362
385
  */
363
- startPush = (interval = this.intervalSec) => {
386
+ startPush = (interval = this.intervalSec, customPushMetics = () => {}) => {
364
387
  if (!this.enabled) {
365
388
  console.warn(`${this.prefixLogs} Metrics disabled`)
389
+ return
366
390
  }
367
391
 
368
- setInterval(() => {
369
- this.pushMetrics().catch(err => {
370
- console.error(`${this.prefixLogs} Failed to push metrics:`, err)
371
- })
372
- }, interval * 1000)
392
+ if (this.startupValidation && !this.startupValidation()) {
393
+ return
394
+ }
373
395
 
374
- console.warn(`${this.prefixLogs} Metrics collection started.`)
396
+ if (customPushMetics) {
397
+ setInterval(customPushMetics, interval * 1000)
398
+ } else {
399
+ setInterval(() => {
400
+ this.pushMetrics().catch(err => {
401
+ console.error(`${this.prefixLogs} Failed to push metrics:`, err)
402
+ })
403
+ }, interval * 1000)
404
+ }
405
+
406
+ console.warn(
407
+ `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s)`
408
+ )
375
409
  }
376
410
 
377
411
  cleanup = async () => {
378
412
  if (this.enabled) {
379
- await this.gateway.delete({
380
- jobName: this.appName,
381
- groupings: {
382
- process_type: this.processType,
383
- instance: this.dynoId,
384
- },
385
- })
413
+ await this.gatewayDelete()
386
414
  }
387
415
  process.exit(0)
388
416
  }
389
417
 
418
+ /**
419
+ * Remove old/stale dyno/instance metrics from PushGateway.
420
+ *
421
+ * Compares existing PushGateway metrics for this job and deletes any instances
422
+ * that do not match the current dynoId.
423
+ *
424
+ * @param {boolean} removeAllJobs If true, performs cleanup; otherwise does nothing
425
+ * @returns {Promise<void>}
426
+ * @private
427
+ */
390
428
  _clearOldWorkers = async removeAllJobs => {
391
429
  if (!removeAllJobs) return
392
430
 
@@ -426,10 +464,8 @@ class MetricsClient {
426
464
  }
427
465
 
428
466
  for (const instance of instances) {
429
- await this.gateway.delete({
430
- jobName: this.appName,
467
+ await this.gatewayDelete({
431
468
  groupings: {
432
- process_type: this.processType,
433
469
  instance,
434
470
  },
435
471
  })
@@ -446,10 +482,77 @@ class MetricsClient {
446
482
  }
447
483
  }
448
484
 
485
+ /**
486
+ * Delete metrics for this job/instance from PushGateway.
487
+ *
488
+ * @param {Object} [params]
489
+ * @param {string} [params.jobName] Job name (defaults to appName)
490
+ * @param {Object} [params.groupings] Grouping labels
491
+ * @param {string} [params.groupings.process_type] Process type label
492
+ * @param {string} [params.groupings.instance] Instance/dyno ID
493
+ * @returns {Promise<void>}
494
+ */
495
+ gatewayDelete = async (params = {}) => {
496
+ return this.gateway.delete({
497
+ jobName: params.jobName || this.appName,
498
+ groupings: {
499
+ process_type: params.groupings?.process_type || this.processType,
500
+ instance: params.groupings?.instance || this.dynoId,
501
+ },
502
+ })
503
+ }
504
+
505
+ /**
506
+ * Push metrics to PushGateway.
507
+ *
508
+ * @param {object} [params]
509
+ * @param {string} [params.jobName]
510
+ * @param {object} [params.groupings]
511
+ * @returns {Promise<void>}
512
+ */
513
+ gatewayPush = async (params = {}) => {
514
+ return this.gateway.push({
515
+ jobName: params.jobName || this.appName,
516
+ groupings: {
517
+ process_type: params.groupings?.process_type || this.processType,
518
+ instance: params.groupings?.instance || this.dynoId,
519
+ },
520
+ })
521
+ }
522
+
523
+ /**
524
+ * Merge the default metric labels (`app`, `dyno_id`, `process_type`)
525
+ * with custom label names.
526
+ *
527
+ * @param {string[]} labels Additional label names
528
+ * @returns {string[]} Combined label names
529
+ */
530
+ withDefaultLabels = (labels = []) => {
531
+ return [...Object.keys(this.defaultLabels), labels]
532
+ }
533
+
534
+ getDefaultLabels = (labels = []) => {
535
+ return this.defaultLabels
536
+ }
537
+
449
538
  _setCleanupHandlers = () => {
450
539
  process.on('SIGINT', this.cleanup)
451
540
  process.on('SIGTERM', this.cleanup)
452
541
  }
542
+
543
+ // GETTERS
544
+
545
+ get metricsEnabled() {
546
+ return this.enabled
547
+ }
548
+
549
+ get metricsLogValues() {
550
+ return this.logValues
551
+ }
552
+
553
+ get registry() {
554
+ return this.registry
555
+ }
453
556
  }
454
557
 
455
558
  module.exports = { MetricsClient }