@adalo/metrics 0.0.0-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.env.example +14 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc +61 -0
  4. package/.github/pull_request_template.md +14 -0
  5. package/.github/workflows/code-style.yml +29 -0
  6. package/.github/workflows/deploy-staging.yml +34 -0
  7. package/.github/workflows/deploy.yml +29 -0
  8. package/.github/workflows/tests.yml +17 -0
  9. package/.idea/codeStyles/Project.xml +101 -0
  10. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  11. package/.idea/git_toolbox_prj.xml +15 -0
  12. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  13. package/.idea/jsLibraryMappings.xml +6 -0
  14. package/.idea/prettier.xml +6 -0
  15. package/.idea/vcs.xml +6 -0
  16. package/.prettierrc +10 -0
  17. package/README-health.md +234 -0
  18. package/README.md +120 -0
  19. package/__tests__/metricsRedisClient.test.js +138 -0
  20. package/babel.config.js +20 -0
  21. package/lib/health/databaseChecker.d.ts +43 -0
  22. package/lib/health/databaseChecker.d.ts.map +1 -0
  23. package/lib/health/databaseChecker.js +189 -0
  24. package/lib/health/databaseChecker.js.map +1 -0
  25. package/lib/health/healthCheckCache.d.ts +59 -0
  26. package/lib/health/healthCheckCache.d.ts.map +1 -0
  27. package/lib/health/healthCheckCache.js +187 -0
  28. package/lib/health/healthCheckCache.js.map +1 -0
  29. package/lib/health/healthCheckClient.d.ts +124 -0
  30. package/lib/health/healthCheckClient.d.ts.map +1 -0
  31. package/lib/health/healthCheckClient.js +324 -0
  32. package/lib/health/healthCheckClient.js.map +1 -0
  33. package/lib/health/healthCheckUtils.d.ts +52 -0
  34. package/lib/health/healthCheckUtils.d.ts.map +1 -0
  35. package/lib/health/healthCheckUtils.js +129 -0
  36. package/lib/health/healthCheckUtils.js.map +1 -0
  37. package/lib/health/healthCheckWorker.d.ts +2 -0
  38. package/lib/health/healthCheckWorker.d.ts.map +1 -0
  39. package/lib/health/healthCheckWorker.js +70 -0
  40. package/lib/health/healthCheckWorker.js.map +1 -0
  41. package/lib/index.d.ts +10 -0
  42. package/lib/index.d.ts.map +1 -0
  43. package/lib/index.js +105 -0
  44. package/lib/index.js.map +1 -0
  45. package/lib/metrics/baseMetricsClient.d.ts +174 -0
  46. package/lib/metrics/baseMetricsClient.d.ts.map +1 -0
  47. package/lib/metrics/baseMetricsClient.js +428 -0
  48. package/lib/metrics/baseMetricsClient.js.map +1 -0
  49. package/lib/metrics/metricsClient.d.ts +95 -0
  50. package/lib/metrics/metricsClient.d.ts.map +1 -0
  51. package/lib/metrics/metricsClient.js +239 -0
  52. package/lib/metrics/metricsClient.js.map +1 -0
  53. package/lib/metrics/metricsDatabaseClient.d.ts +74 -0
  54. package/lib/metrics/metricsDatabaseClient.d.ts.map +1 -0
  55. package/lib/metrics/metricsDatabaseClient.js +218 -0
  56. package/lib/metrics/metricsDatabaseClient.js.map +1 -0
  57. package/lib/metrics/metricsQueueRedisClient.d.ts +57 -0
  58. package/lib/metrics/metricsQueueRedisClient.d.ts.map +1 -0
  59. package/lib/metrics/metricsQueueRedisClient.js +277 -0
  60. package/lib/metrics/metricsQueueRedisClient.js.map +1 -0
  61. package/lib/metrics/metricsRedisClient.d.ts +71 -0
  62. package/lib/metrics/metricsRedisClient.d.ts.map +1 -0
  63. package/lib/metrics/metricsRedisClient.js +370 -0
  64. package/lib/metrics/metricsRedisClient.js.map +1 -0
  65. package/lib/redisUtils.d.ts +53 -0
  66. package/lib/redisUtils.d.ts.map +1 -0
  67. package/lib/redisUtils.js +140 -0
  68. package/lib/redisUtils.js.map +1 -0
  69. package/package.json +66 -0
  70. package/scripts/README.md +43 -0
  71. package/scripts/clearMetrics.js +6 -0
  72. package/src/health/databaseChecker.js +183 -0
  73. package/src/health/healthCheckCache.js +216 -0
  74. package/src/health/healthCheckClient.js +347 -0
  75. package/src/health/healthCheckUtils.js +125 -0
  76. package/src/health/healthCheckWorker.js +71 -0
  77. package/src/index.ts +9 -0
  78. package/src/metrics/baseMetricsClient.js +494 -0
  79. package/src/metrics/metricsClient.js +284 -0
  80. package/src/metrics/metricsDatabaseClient.js +236 -0
  81. package/src/metrics/metricsQueueRedisClient.js +352 -0
  82. package/src/metrics/metricsRedisClient.js +417 -0
  83. package/src/redisUtils.js +155 -0
  84. package/tsconfig.json +19 -0
  85. package/tsconfig.types.json +11 -0
@@ -0,0 +1,428 @@
1
+ "use strict";
2
+
3
+ const client = require('prom-client');
4
+ const https = require('https');
5
+ const http = require('http');
6
+ const {
7
+ URL
8
+ } = require('url');
9
+
10
+ /**
11
+ * BaseMetricsClient provides common functionality for all metrics clients.
12
+ * Handles registry setup, push to remote (VM-agent), default labels, and common operations.
13
+ * Always pushes registry to the configured URL (POST Prometheus text format + Basic auth). No Pushgateway.
14
+ */
15
+ class BaseMetricsClient {
16
+ /**
17
+ * @param {Object} config
18
+ * @param {string} [config.appName] Name of the application
19
+ * @param {string} [config.dynoId] Dyno/instance ID
20
+ * @param {string} [config.processType] Process type (web, worker, etc.)
21
+ * @param {boolean} [config.enabled] Enable metrics collection
22
+ * @param {boolean} [config.logValues] Log metrics values to console
23
+ * @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
24
+ * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of user:password)
25
+ * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
26
+ * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
27
+ * @param {function} [config.startupValidation] Add to validate on start push.
28
+ * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)
29
+ */
30
+ constructor(config = {}) {
31
+ this.appName = config.appName || process.env.BUILD_APP_NAME || 'unknown-app';
32
+ this.dynoId = config.dynoId || process.env.HOSTNAME || 'unknown-dyno';
33
+ this.processType = config.processType || process.env.BUILD_DYNO_PROCESS_TYPE || 'undefined_build_dyno_type';
34
+ this.enabled = config.enabled ?? process.env.METRICS_ENABLED === 'true';
35
+ this.logValues = config.logValues ?? process.env.METRICS_LOG_VALUES === 'true';
36
+ this.pushgatewayUrl = config.pushgatewayUrl || process.env.METRICS_PUSHGATEWAY_URL || '';
37
+ this.authToken = config.pushgatewaySecret || process.env.METRICS_PUSHGATEWAY_SECRET || '';
38
+ this.intervalSec = config.intervalSec || parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) || 15;
39
+ this.startupValidation = config.startupValidation;
40
+ this.disablePushgateway = config.disablePushgateway ?? process.env.METRICS_DISABLE_PUSHGATEWAY === 'true';
41
+ this.removeOldMetrics = config.removeOldMetrics ?? process.env.METRICS_REMOVE_OLD_METRICS === 'true';
42
+ this.prefixLogs = `[${this.processType}] [${this.appName}] [${this.dynoId}] [Monitoring]`;
43
+ this._registry = new client.Registry();
44
+ client.collectDefaultMetrics({
45
+ register: this._registry
46
+ });
47
+ this.defaultLabels = {
48
+ app: this.appName,
49
+ dyno_id: this.dynoId,
50
+ process_type: this.processType
51
+ };
52
+
53
+ // Always push to configured URL (VM-agent). No Pushgateway.
54
+ this.gateway = null;
55
+ this.gauges = {};
56
+ this.counters = {};
57
+ this.countersFunctions = {};
58
+
59
+ /** @type {Object<string, function(): number | Promise<number>>} */
60
+ this.gaugeUpdaters = {};
61
+ this._clearOldWorkers(config.removeOldMetrics);
62
+ this._setCleanupHandlers();
63
+ this.keepProcessAliveWhenDisabled = true;
64
+ }
65
+
66
+ /**
67
+ * Create a gauge metric.
68
+ * @param {Object} options - Gauge configuration
69
+ * @param {string} options.name - Name of the gauge
70
+ * @param {string} options.help - Help text describing the gauge
71
+ * @param {function(): number|Promise<number>} [options.updateFn] - Optional function returning the gauge value
72
+ * @param {string[]} [options.labelNames] - Optional custom label names
73
+ * @returns {import('prom-client').Gauge} The created Prometheus gauge
74
+ */
75
+ createGauge = ({
76
+ name,
77
+ help,
78
+ updateFn,
79
+ labelNames = Object.keys(this.defaultLabels)
80
+ }) => {
81
+ if (this.gauges[name]) return this.gauges[name];
82
+ const g = new client.Gauge({
83
+ name,
84
+ help,
85
+ labelNames,
86
+ registers: [this._registry]
87
+ });
88
+ this.gauges[name] = g;
89
+ if (updateFn && typeof updateFn === 'function') {
90
+ this.gaugeUpdaters[name] = updateFn;
91
+ }
92
+ return g;
93
+ };
94
+
95
+ /**
96
+ * Create a Prometheus Counter metric.
97
+ *
98
+ * @param {Object} params - Counter configuration
99
+ * @param {string} params.name - Metric name
100
+ * @param {string} params.help - Metric description
101
+ * @param {string[]} [params.labelNames] - Optional list of label names. Defaults to this.defaultLabels keys.
102
+ *
103
+ * @returns {(labels?: Object, incrementValue?: number) => void}
104
+ * A function to increment the counter.
105
+ * Usage: (labels?, incrementValue?)
106
+ */
107
+ createCounter({
108
+ name,
109
+ help,
110
+ labelNames = Object.keys(this.defaultLabels)
111
+ }) {
112
+ if (this.counters[name]) return this.countersFunctions[name];
113
+ const c = new client.Counter({
114
+ name,
115
+ help,
116
+ labelNames,
117
+ registers: [this._registry]
118
+ });
119
+ this.counters[name] = c;
120
+ this.countersFunctions = {
121
+ ...this.countersFunctions,
122
+ [name]: (data = {}, value = 1) => {
123
+ c.inc({
124
+ ...this.defaultLabels,
125
+ ...data
126
+ }, value);
127
+ }
128
+ };
129
+ return this.countersFunctions[name];
130
+ }
131
+
132
+ /**
133
+ * Clear all collected counters
134
+ */
135
+ clearAllCounters = () => {
136
+ if (this.metricsLogValues) {
137
+ console.log('Counters to clear: ', Object.keys(this.counters));
138
+ }
139
+ Object.values(this.counters).forEach(counter => counter.reset());
140
+ };
141
+
142
+ /**
143
+ * Push all gauges and counters to PushGateway and optionally log.
144
+ */
145
+ _pushMetrics = async () => {
146
+ try {
147
+ for (const [name, updateFn] of Object.entries(this.gaugeUpdaters)) {
148
+ try {
149
+ if (!updateFn) {
150
+ continue;
151
+ }
152
+ const result = updateFn();
153
+ const val = result instanceof Promise ? await result : result;
154
+ if (val !== undefined) this.gauges[name].set(this.defaultLabels, val);
155
+ } catch (err) {
156
+ console.error(`${this.prefixLogs} Failed to update gauge ${name}:`, err);
157
+ }
158
+ }
159
+ if (!this.disablePushgateway) {
160
+ await this.gatewayPush();
161
+ }
162
+ // this.clearAllCounters() //TODO: or uncommit or delete (based on grafana expectation)
163
+
164
+ if (this.logValues) {
165
+ const metrics = await this._registry.getMetricsAsJSON();
166
+ console.log(`${this.prefixLogs} Metrics:\n`, JSON.stringify(metrics, null, 2));
167
+ }
168
+ } catch (err) {
169
+ console.error(`${this.prefixLogs} Failed to push metrics:`, err);
170
+ }
171
+ };
172
+ _startPush = (interval = this.intervalSec, customPushMetics = undefined) => {
173
+ if (!this.enabled) {
174
+ console.warn(`${this.prefixLogs} Metrics disabled`);
175
+ if (this.keepProcessAliveWhenDisabled && !this._idleInterval) {
176
+ this._idleInterval = setInterval(() => {}, 60 * 60 * 1000);
177
+ }
178
+ return;
179
+ }
180
+ if (this._idleInterval) {
181
+ clearInterval(this._idleInterval);
182
+ this._idleInterval = null;
183
+ }
184
+ if (this.startupValidation && !this.startupValidation()) {
185
+ return;
186
+ }
187
+ const runPush = () => {
188
+ if (customPushMetics && typeof customPushMetics === 'function') {
189
+ return Promise.resolve(customPushMetics());
190
+ }
191
+ return this.pushMetrics();
192
+ };
193
+ if (customPushMetics && typeof customPushMetics === 'function') {
194
+ setInterval(() => customPushMetics(), interval * 1000);
195
+ } else {
196
+ setInterval(() => {
197
+ runPush().catch(err => {
198
+ console.error(`${this.prefixLogs} Failed to push metrics:`, err);
199
+ });
200
+ }, interval * 1000);
201
+ }
202
+
203
+ // First push immediately so metrics appear without waiting for the first interval
204
+ runPush().catch(err => {
205
+ console.error(`${this.prefixLogs} Failed to push metrics (initial):`, err);
206
+ });
207
+ let pushOrigin = 'none';
208
+ try {
209
+ if (this.pushgatewayUrl && this.pushgatewayUrl.trim()) {
210
+ pushOrigin = new URL(this.pushgatewayUrl.trim()).origin;
211
+ }
212
+ } catch {
213
+ pushOrigin = 'invalid URL';
214
+ }
215
+ console.warn(`${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s, push: ${pushOrigin})`);
216
+ };
217
+ pushMetrics = async () => {
218
+ return this._pushMetrics();
219
+ };
220
+
221
+ /**
222
+ * Start periodic metrics collection and push.
223
+ *
224
+ * This method wraps the internal `_startPush` method.
225
+ * If a `customPushMetrics` function is provided, it will be executed
226
+ * at the given interval instead of the default `pushMetrics` behavior.
227
+ *
228
+ * @param {number} [interval=this.intervalSec] - Interval in seconds between pushes.
229
+ * @param {() => void | Promise<void>} [customPushMetrics] - Optional custom push function. If provided, Prometheus push is skipped.
230
+ */
231
+ startPush = (interval, customPushMetics = undefined) => {
232
+ this._startPush(interval, customPushMetics);
233
+ };
234
+
235
+ /**
236
+ * Cleanup metrics and exit process.
237
+ * @returns {Promise<void>}
238
+ */
239
+ cleanup = async () => {
240
+ if (this.enabled) {
241
+ await this.gatewayDelete();
242
+ }
243
+ process.exit(0);
244
+ };
245
+
246
+ /**
247
+ * Remove old/stale dyno/instance metrics from PushGateway.
248
+ *
249
+ * Compares existing PushGateway metrics for this job and deletes any instances
250
+ * that do not match the current dynoId.
251
+ *
252
+ * @param {boolean} removeOldMetrics If true, performs cleanup; otherwise does nothing
253
+ * @returns {Promise<void>}
254
+ * @private
255
+ */
256
+ _clearOldWorkers = async removeOldMetrics => {
257
+ // No Pushgateway; VM-agent does not support per-instance delete. Skip.
258
+ };
259
+
260
+ /**
261
+ * On shutdown: optionally delete this instance's metrics from VictoriaMetrics (by app, dyno_id, process_type).
262
+ * @returns {Promise<void>}
263
+ */
264
+ gatewayDelete = async () => {
265
+ if (this.removeOldMetrics && this.pushgatewayUrl && this.pushgatewayUrl.trim()) {
266
+ await this._deleteFromVMByLabels().catch(err => {
267
+ console.warn(`${this.prefixLogs} Deletion from VM on shutdown failed:`, err.message);
268
+ });
269
+ }
270
+ };
271
+
272
+ /**
273
+ * Call VictoriaMetrics delete_series API to remove all series matching this instance's labels (app, dyno_id, process_type).
274
+ * @private
275
+ */
276
+ _deleteFromVMByLabels = () => {
277
+ const esc = s => String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
278
+ const selector = `{app="${esc(this.appName)}",dyno_id="${esc(this.dynoId)}",process_type="${esc(this.processType)}"}`;
279
+ let origin;
280
+ try {
281
+ const u = new URL((this.pushgatewayUrl || '').trim());
282
+ origin = u.origin;
283
+ } catch {
284
+ return Promise.reject(new Error('Invalid push URL'));
285
+ }
286
+ const path = `/api/v1/admin/tsdb/delete_series?match[]=${encodeURIComponent(selector)}`;
287
+ return new Promise((resolve, reject) => {
288
+ const u = new URL(origin);
289
+ const req = (u.protocol === 'https:' ? https : http).request({
290
+ hostname: u.hostname,
291
+ port: u.port || (u.protocol === 'https:' ? 443 : 80),
292
+ path,
293
+ method: 'POST',
294
+ headers: {
295
+ 'Content-Length': '0',
296
+ Authorization: this.authToken ? `Basic ${this.authToken}` : undefined
297
+ },
298
+ agent: u.protocol === 'https:' ? new https.Agent({
299
+ keepAlive: false
300
+ }) : undefined
301
+ }, res => {
302
+ if (res.statusCode >= 200 && res.statusCode < 300) resolve();else {
303
+ let data = '';
304
+ res.on('data', chunk => {
305
+ data += chunk;
306
+ });
307
+ res.on('end', () => reject(new Error(`Delete failed: ${res.statusCode} ${data}`)));
308
+ }
309
+ });
310
+ req.on('error', reject);
311
+ req.end();
312
+ });
313
+ };
314
+
315
+ /**
316
+ * Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.
317
+ *
318
+ * @param {object} [params] Unused; kept for API compatibility.
319
+ * @returns {Promise<void>}
320
+ */
321
+ gatewayPush = async (params = {}) => {
322
+ if (this.disablePushgateway) {
323
+ console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`);
324
+ return Promise.resolve();
325
+ }
326
+ if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {
327
+ console.warn(`${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`);
328
+ return Promise.resolve();
329
+ }
330
+ return this._pushToVMAgent();
331
+ };
332
+
333
+ /**
334
+ * POST registry (Prometheus text format) to VM-agent. VM-agent accepts push at /api/v1/import/prometheus; /metrics is GET (scrape) only.
335
+ * @private
336
+ */
337
+ _pushToVMAgent = () => {
338
+ let pushUrl = (this.pushgatewayUrl || '').trim();
339
+ try {
340
+ const u = new URL(pushUrl);
341
+ if (!u.pathname || u.pathname === '/' || u.pathname === '/metrics') {
342
+ pushUrl = `${u.origin}/api/v1/import/prometheus${u.search}`;
343
+ }
344
+ } catch {
345
+ // leave pushUrl as-is
346
+ }
347
+ return new Promise((resolve, reject) => {
348
+ const u = new URL(pushUrl);
349
+ const req = (u.protocol === 'https:' ? https : http).request({
350
+ hostname: u.hostname,
351
+ port: u.port || (u.protocol === 'https:' ? 443 : 80),
352
+ path: u.pathname + u.search,
353
+ method: 'POST',
354
+ headers: {
355
+ 'Content-Type': client.register.contentType,
356
+ Authorization: this.authToken ? `Basic ${this.authToken}` : undefined
357
+ },
358
+ agent: u.protocol === 'https:' ? new https.Agent({
359
+ keepAlive: true
360
+ }) : undefined
361
+ }, res => {
362
+ if (res.statusCode >= 200 && res.statusCode < 300) {
363
+ resolve();
364
+ } else {
365
+ let data = '';
366
+ res.on('data', chunk => {
367
+ data += chunk;
368
+ });
369
+ res.on('end', () => reject(new Error(`Push failed: ${res.statusCode} ${data}`)));
370
+ }
371
+ });
372
+ req.on('error', reject);
373
+ this._registry.metrics().then(metrics => {
374
+ req.setHeader('Content-Length', Buffer.byteLength(metrics, 'utf8'));
375
+ req.end(metrics, 'utf8');
376
+ }).catch(reject);
377
+ });
378
+ };
379
+
380
+ /**
381
+ * Merge the default metric labels (`app`, `dyno_id`, `process_type`)
382
+ * with custom label names.
383
+ *
384
+ * @param {string[]} labels Additional label names
385
+ * @returns {string[]} Combined label names
386
+ */
387
+ withDefaultLabels = (labels = []) => {
388
+ return [...Object.keys(this.defaultLabels), ...labels];
389
+ };
390
+ getDefaultLabels = (labels = []) => {
391
+ return this.defaultLabels;
392
+ };
393
+ _setCleanupHandlers = () => {
394
+ process.on('SIGINT', this.cleanup);
395
+ process.on('SIGTERM', this.cleanup);
396
+ };
397
+
398
+ // GETTERS
399
+
400
+ get metricsEnabled() {
401
+ return this.enabled;
402
+ }
403
+ get metricsLogValues() {
404
+ return this.logValues;
405
+ }
406
+ get registry() {
407
+ return this._registry;
408
+ }
409
+ async getMetricsAsString() {
410
+ return this._registry.metrics();
411
+ }
412
+ metricsMiddleware() {
413
+ return async (req, res) => {
414
+ try {
415
+ const metrics = await this.getMetricsAsString();
416
+ res.set('Content-Type', client.register.contentType);
417
+ res.end(metrics);
418
+ } catch (err) {
419
+ console.error(`${this.prefixLogs} Failed to get metrics:`, err);
420
+ res.status(500).end('Failed to collect metrics');
421
+ }
422
+ };
423
+ }
424
+ }
425
+ module.exports = {
426
+ BaseMetricsClient
427
+ };
428
+ //# sourceMappingURL=baseMetricsClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseMetricsClient.js","names":["client","require","https","http","URL","BaseMetricsClient","constructor","config","appName","process","env","BUILD_APP_NAME","dynoId","HOSTNAME","processType","BUILD_DYNO_PROCESS_TYPE","enabled","METRICS_ENABLED","logValues","METRICS_LOG_VALUES","pushgatewayUrl","METRICS_PUSHGATEWAY_URL","authToken","pushgatewaySecret","METRICS_PUSHGATEWAY_SECRET","intervalSec","parseInt","METRICS_INTERVAL_SEC","startupValidation","disablePushgateway","METRICS_DISABLE_PUSHGATEWAY","removeOldMetrics","METRICS_REMOVE_OLD_METRICS","prefixLogs","_registry","Registry","collectDefaultMetrics","register","defaultLabels","app","dyno_id","process_type","gateway","gauges","counters","countersFunctions","gaugeUpdaters","_clearOldWorkers","_setCleanupHandlers","keepProcessAliveWhenDisabled","createGauge","name","help","updateFn","labelNames","Object","keys","g","Gauge","registers","createCounter","c","Counter","data","value","inc","clearAllCounters","metricsLogValues","console","log","values","forEach","counter","reset","_pushMetrics","entries","result","val","Promise","undefined","set","err","error","gatewayPush","metrics","getMetricsAsJSON","JSON","stringify","_startPush","interval","customPushMetics","warn","_idleInterval","setInterval","clearInterval","runPush","resolve","pushMetrics","catch","pushOrigin","trim","origin","startPush","cleanup","gatewayDelete","exit","_deleteFromVMByLabels","message","esc","s","String","replace","selector","u","reject","Error","path","encodeURIComponent","req","protocol","request","hostname","port","method","headers","Authorization","agent","Agent","keepAlive","res","statusCode","on","chunk","end","params","_pushToVMAgent","pushUrl","pathname","search","contentType","then","setHeader","Buffer","byteLength","withDefaultLabels","labels","getDefaultLabels","metricsEnabled","registry","getMetricsAsString","metricsMiddleware","status","module","exports"],"sources":["../../src/metrics/baseMetricsClient.js"],"sourcesContent":["const client = require('prom-client')\nconst https = require('https')\nconst http = require('http')\nconst { URL } = require('url')\n\n/**\n * BaseMetricsClient provides common functionality for all metrics clients.\n * Handles registry setup, push to remote (VM-agent), default labels, and common operations.\n * Always pushes registry to the configured URL (POST Prometheus text format + Basic auth). No Pushgateway.\n */\nclass BaseMetricsClient {\n /**\n * @param {Object} config\n * @param {string} [config.appName] Name of the application\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Process type (web, worker, etc.)\n * @param {boolean} [config.enabled] Enable metrics collection\n * @param {boolean} [config.logValues] Log metrics values to console\n * @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).\n * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of user:password)\n * @param {number} [config.intervalSec] Interval in seconds for pushing metrics\n * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name\n * @param {function} [config.startupValidation] Add to validate on start push.\n * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)\n */\n constructor(config = {}) {\n this.appName = config.appName || process.env.BUILD_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.authToken =\n config.pushgatewaySecret || process.env.METRICS_PUSHGATEWAY_SECRET || ''\n this.intervalSec =\n config.intervalSec ||\n parseInt(process.env.METRICS_INTERVAL_SEC || '', 10) ||\n 15\n this.startupValidation = config.startupValidation\n this.disablePushgateway =\n config.disablePushgateway ??\n process.env.METRICS_DISABLE_PUSHGATEWAY === 'true'\n this.removeOldMetrics =\n config.removeOldMetrics ??\n process.env.METRICS_REMOVE_OLD_METRICS === 'true'\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 // Always push to configured URL (VM-agent). No Pushgateway.\n this.gateway = null\n this.gauges = {}\n this.counters = {}\n this.countersFunctions = {}\n\n /** @type {Object<string, function(): number | Promise<number>>} */\n this.gaugeUpdaters = {}\n\n this._clearOldWorkers(config.removeOldMetrics)\n this._setCleanupHandlers()\n\n this.keepProcessAliveWhenDisabled = true\n }\n\n /**\n * Create a gauge metric.\n * @param {Object} options - Gauge configuration\n * @param {string} options.name - Name of the gauge\n * @param {string} options.help - Help text describing the gauge\n * @param {function(): number|Promise<number>} [options.updateFn] - Optional function returning the gauge value\n * @param {string[]} [options.labelNames] - Optional custom label names\n * @returns {import('prom-client').Gauge} The created Prometheus 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 - Counter configuration\n * @param {string} params.name - Metric name\n * @param {string} params.help - Metric description\n * @param {string[]} [params.labelNames] - Optional list of label names. Defaults to this.defaultLabels keys.\n *\n * @returns {(labels?: Object, incrementValue?: number) => void}\n * A function to increment the counter.\n * Usage: (labels?, incrementValue?)\n */\n createCounter({ name, help, labelNames = Object.keys(this.defaultLabels) }) {\n if (this.counters[name]) return this.countersFunctions[name]\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 this.countersFunctions = {\n ...this.countersFunctions,\n [name]: (data = {}, value = 1) => {\n c.inc({ ...this.defaultLabels, ...data }, value)\n },\n }\n\n return this.countersFunctions[name]\n }\n\n /**\n * Clear all collected counters\n */\n clearAllCounters = () => {\n if (this.metricsLogValues) {\n console.log('Counters to clear: ', Object.keys(this.counters))\n }\n Object.values(this.counters).forEach(counter => counter.reset())\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 continue\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 if (!this.disablePushgateway) {\n await this.gatewayPush()\n }\n // this.clearAllCounters() //TODO: or uncommit or delete (based on grafana expectation)\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 _startPush = (interval = this.intervalSec, customPushMetics = undefined) => {\n if (!this.enabled) {\n console.warn(`${this.prefixLogs} Metrics disabled`)\n if (this.keepProcessAliveWhenDisabled && !this._idleInterval) {\n this._idleInterval = setInterval(() => {}, 60 * 60 * 1000)\n }\n return\n }\n\n if (this._idleInterval) {\n clearInterval(this._idleInterval)\n this._idleInterval = null\n }\n\n if (this.startupValidation && !this.startupValidation()) {\n return\n }\n\n const runPush = () => {\n if (customPushMetics && typeof customPushMetics === 'function') {\n return Promise.resolve(customPushMetics())\n }\n return this.pushMetrics()\n }\n\n if (customPushMetics && typeof customPushMetics === 'function') {\n setInterval(() => customPushMetics(), interval * 1000)\n } else {\n setInterval(() => {\n runPush().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics:`, err)\n })\n }, interval * 1000)\n }\n\n // First push immediately so metrics appear without waiting for the first interval\n runPush().catch(err => {\n console.error(`${this.prefixLogs} Failed to push metrics (initial):`, err)\n })\n\n let pushOrigin = 'none'\n try {\n if (this.pushgatewayUrl && this.pushgatewayUrl.trim()) {\n pushOrigin = new URL(this.pushgatewayUrl.trim()).origin\n }\n } catch {\n pushOrigin = 'invalid URL'\n }\n console.warn(\n `${this.prefixLogs} Metrics collection started. (interval: ${this.intervalSec}s, push: ${pushOrigin})`\n )\n }\n\n pushMetrics = async () => {\n return this._pushMetrics()\n }\n\n /**\n * Start periodic metrics collection and push.\n *\n * This method wraps the internal `_startPush` method.\n * If a `customPushMetrics` function is provided, it will be executed\n * at the given interval instead of the default `pushMetrics` behavior.\n *\n * @param {number} [interval=this.intervalSec] - Interval in seconds between pushes.\n * @param {() => void | Promise<void>} [customPushMetrics] - Optional custom push function. If provided, Prometheus push is skipped.\n */\n startPush = (interval, customPushMetics = undefined) => {\n this._startPush(interval, customPushMetics)\n }\n\n /**\n * Cleanup metrics and exit process.\n * @returns {Promise<void>}\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} removeOldMetrics If true, performs cleanup; otherwise does nothing\n * @returns {Promise<void>}\n * @private\n */\n _clearOldWorkers = async removeOldMetrics => {\n // No Pushgateway; VM-agent does not support per-instance delete. Skip.\n }\n\n /**\n * On shutdown: optionally delete this instance's metrics from VictoriaMetrics (by app, dyno_id, process_type).\n * @returns {Promise<void>}\n */\n gatewayDelete = async () => {\n if (\n this.removeOldMetrics &&\n this.pushgatewayUrl &&\n this.pushgatewayUrl.trim()\n ) {\n await this._deleteFromVMByLabels().catch(err => {\n console.warn(\n `${this.prefixLogs} Deletion from VM on shutdown failed:`,\n err.message\n )\n })\n }\n }\n\n /**\n * Call VictoriaMetrics delete_series API to remove all series matching this instance's labels (app, dyno_id, process_type).\n * @private\n */\n _deleteFromVMByLabels = () => {\n const esc = s => String(s).replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')\n const selector = `{app=\"${esc(this.appName)}\",dyno_id=\"${esc(\n this.dynoId\n )}\",process_type=\"${esc(this.processType)}\"}`\n let origin\n try {\n const u = new URL((this.pushgatewayUrl || '').trim())\n origin = u.origin\n } catch {\n return Promise.reject(new Error('Invalid push URL'))\n }\n const path = `/api/v1/admin/tsdb/delete_series?match[]=${encodeURIComponent(\n selector\n )}`\n return new Promise((resolve, reject) => {\n const u = new URL(origin)\n const req = (u.protocol === 'https:' ? https : http).request(\n {\n hostname: u.hostname,\n port: u.port || (u.protocol === 'https:' ? 443 : 80),\n path,\n method: 'POST',\n headers: {\n 'Content-Length': '0',\n Authorization: this.authToken\n ? `Basic ${this.authToken}`\n : undefined,\n },\n agent:\n u.protocol === 'https:'\n ? new https.Agent({ keepAlive: false })\n : undefined,\n },\n res => {\n if (res.statusCode >= 200 && res.statusCode < 300) resolve()\n else {\n let data = ''\n res.on('data', chunk => {\n data += chunk\n })\n res.on('end', () =>\n reject(new Error(`Delete failed: ${res.statusCode} ${data}`))\n )\n }\n }\n )\n req.on('error', reject)\n req.end()\n })\n }\n\n /**\n * Push registry to configured URL (VM-agent). POST Prometheus text format + Basic auth.\n *\n * @param {object} [params] Unused; kept for API compatibility.\n * @returns {Promise<void>}\n */\n gatewayPush = async (params = {}) => {\n if (this.disablePushgateway) {\n console.warn(\n `${this.prefixLogs} Metrics push skipped: METRICS_DISABLE_PUSHGATEWAY is set`\n )\n return Promise.resolve()\n }\n if (!this.pushgatewayUrl || !this.pushgatewayUrl.trim()) {\n console.warn(\n `${this.prefixLogs} Metrics push skipped: METRICS_PUSHGATEWAY_URL is not set`\n )\n return Promise.resolve()\n }\n return this._pushToVMAgent()\n }\n\n /**\n * POST registry (Prometheus text format) to VM-agent. VM-agent accepts push at /api/v1/import/prometheus; /metrics is GET (scrape) only.\n * @private\n */\n _pushToVMAgent = () => {\n let pushUrl = (this.pushgatewayUrl || '').trim()\n try {\n const u = new URL(pushUrl)\n if (!u.pathname || u.pathname === '/' || u.pathname === '/metrics') {\n pushUrl = `${u.origin}/api/v1/import/prometheus${u.search}`\n }\n } catch {\n // leave pushUrl as-is\n }\n return new Promise((resolve, reject) => {\n const u = new URL(pushUrl)\n const req = (u.protocol === 'https:' ? https : http).request(\n {\n hostname: u.hostname,\n port: u.port || (u.protocol === 'https:' ? 443 : 80),\n path: u.pathname + u.search,\n method: 'POST',\n headers: {\n 'Content-Type': client.register.contentType,\n Authorization: this.authToken\n ? `Basic ${this.authToken}`\n : undefined,\n },\n agent:\n u.protocol === 'https:'\n ? new https.Agent({ keepAlive: true })\n : undefined,\n },\n res => {\n if (res.statusCode >= 200 && res.statusCode < 300) {\n resolve()\n } else {\n let data = ''\n res.on('data', chunk => {\n data += chunk\n })\n res.on('end', () =>\n reject(new Error(`Push failed: ${res.statusCode} ${data}`))\n )\n }\n }\n )\n req.on('error', reject)\n this._registry\n .metrics()\n .then(metrics => {\n req.setHeader('Content-Length', Buffer.byteLength(metrics, 'utf8'))\n req.end(metrics, 'utf8')\n })\n .catch(reject)\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 async getMetricsAsString() {\n return this._registry.metrics()\n }\n\n metricsMiddleware() {\n return async (req, res) => {\n try {\n const metrics = await this.getMetricsAsString()\n res.set('Content-Type', client.register.contentType)\n res.end(metrics)\n } catch (err) {\n console.error(`${this.prefixLogs} Failed to get metrics:`, err)\n res.status(500).end('Failed to collect metrics')\n }\n }\n }\n}\n\nmodule.exports = { BaseMetricsClient }\n"],"mappings":";;AAAA,MAAMA,MAAM,GAAGC,OAAO,CAAC,aAAa,CAAC;AACrC,MAAMC,KAAK,GAAGD,OAAO,CAAC,OAAO,CAAC;AAC9B,MAAME,IAAI,GAAGF,OAAO,CAAC,MAAM,CAAC;AAC5B,MAAM;EAAEG;AAAI,CAAC,GAAGH,OAAO,CAAC,KAAK,CAAC;;AAE9B;AACA;AACA;AACA;AACA;AACA,MAAMI,iBAAiB,CAAC;EACtB;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAACC,MAAM,GAAG,CAAC,CAAC,EAAE;IACvB,IAAI,CAACC,OAAO,GAAGD,MAAM,CAACC,OAAO,IAAIC,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;IAC5E,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,SAAS,GACZf,MAAM,CAACgB,iBAAiB,IAAId,OAAO,CAACC,GAAG,CAACc,0BAA0B,IAAI,EAAE;IAC1E,IAAI,CAACC,WAAW,GACdlB,MAAM,CAACkB,WAAW,IAClBC,QAAQ,CAACjB,OAAO,CAACC,GAAG,CAACiB,oBAAoB,IAAI,EAAE,EAAE,EAAE,CAAC,IACpD,EAAE;IACJ,IAAI,CAACC,iBAAiB,GAAGrB,MAAM,CAACqB,iBAAiB;IACjD,IAAI,CAACC,kBAAkB,GACrBtB,MAAM,CAACsB,kBAAkB,IACzBpB,OAAO,CAACC,GAAG,CAACoB,2BAA2B,KAAK,MAAM;IACpD,IAAI,CAACC,gBAAgB,GACnBxB,MAAM,CAACwB,gBAAgB,IACvBtB,OAAO,CAACC,GAAG,CAACsB,0BAA0B,KAAK,MAAM;IAEnD,IAAI,CAACC,UAAU,GAAG,IAAI,IAAI,CAACnB,WAAW,MAAM,IAAI,CAACN,OAAO,MAAM,IAAI,CAACI,MAAM,gBAAgB;IAEzF,IAAI,CAACsB,SAAS,GAAG,IAAIlC,MAAM,CAACmC,QAAQ,CAAC,CAAC;IACtCnC,MAAM,CAACoC,qBAAqB,CAAC;MAAEC,QAAQ,EAAE,IAAI,CAACH;IAAU,CAAC,CAAC;IAE1D,IAAI,CAACI,aAAa,GAAG;MACnBC,GAAG,EAAE,IAAI,CAAC/B,OAAO;MACjBgC,OAAO,EAAE,IAAI,CAAC5B,MAAM;MACpB6B,YAAY,EAAE,IAAI,CAAC3B;IACrB,CAAC;;IAED;IACA,IAAI,CAAC4B,OAAO,GAAG,IAAI;IACnB,IAAI,CAACC,MAAM,GAAG,CAAC,CAAC;IAChB,IAAI,CAACC,QAAQ,GAAG,CAAC,CAAC;IAClB,IAAI,CAACC,iBAAiB,GAAG,CAAC,CAAC;;IAE3B;IACA,IAAI,CAACC,aAAa,GAAG,CAAC,CAAC;IAEvB,IAAI,CAACC,gBAAgB,CAACxC,MAAM,CAACwB,gBAAgB,CAAC;IAC9C,IAAI,CAACiB,mBAAmB,CAAC,CAAC;IAE1B,IAAI,CAACC,4BAA4B,GAAG,IAAI;EAC1C;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,WAAW,GAAGA,CAAC;IACbC,IAAI;IACJC,IAAI;IACJC,QAAQ;IACRC,UAAU,GAAGC,MAAM,CAACC,IAAI,CAAC,IAAI,CAAClB,aAAa;EAC7C,CAAC,KAAK;IACJ,IAAI,IAAI,CAACK,MAAM,CAACQ,IAAI,CAAC,EAAE,OAAO,IAAI,CAACR,MAAM,CAACQ,IAAI,CAAC;IAE/C,MAAMM,CAAC,GAAG,IAAIzD,MAAM,CAAC0D,KAAK,CAAC;MACzBP,IAAI;MACJC,IAAI;MACJE,UAAU;MACVK,SAAS,EAAE,CAAC,IAAI,CAACzB,SAAS;IAC5B,CAAC,CAAC;IACF,IAAI,CAACS,MAAM,CAACQ,IAAI,CAAC,GAAGM,CAAC;IAErB,IAAIJ,QAAQ,IAAI,OAAOA,QAAQ,KAAK,UAAU,EAAE;MAC9C,IAAI,CAACP,aAAa,CAACK,IAAI,CAAC,GAAGE,QAAQ;IACrC;IAEA,OAAOI,CAAC;EACV,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEG,aAAaA,CAAC;IAAET,IAAI;IAAEC,IAAI;IAAEE,UAAU,GAAGC,MAAM,CAACC,IAAI,CAAC,IAAI,CAAClB,aAAa;EAAE,CAAC,EAAE;IAC1E,IAAI,IAAI,CAACM,QAAQ,CAACO,IAAI,CAAC,EAAE,OAAO,IAAI,CAACN,iBAAiB,CAACM,IAAI,CAAC;IAE5D,MAAMU,CAAC,GAAG,IAAI7D,MAAM,CAAC8D,OAAO,CAAC;MAC3BX,IAAI;MACJC,IAAI;MACJE,UAAU;MACVK,SAAS,EAAE,CAAC,IAAI,CAACzB,SAAS;IAC5B,CAAC,CAAC;IACF,IAAI,CAACU,QAAQ,CAACO,IAAI,CAAC,GAAGU,CAAC;IAEvB,IAAI,CAAChB,iBAAiB,GAAG;MACvB,GAAG,IAAI,CAACA,iBAAiB;MACzB,CAACM,IAAI,GAAG,CAACY,IAAI,GAAG,CAAC,CAAC,EAAEC,KAAK,GAAG,CAAC,KAAK;QAChCH,CAAC,CAACI,GAAG,CAAC;UAAE,GAAG,IAAI,CAAC3B,aAAa;UAAE,GAAGyB;QAAK,CAAC,EAAEC,KAAK,CAAC;MAClD;IACF,CAAC;IAED,OAAO,IAAI,CAACnB,iBAAiB,CAACM,IAAI,CAAC;EACrC;;EAEA;AACF;AACA;EACEe,gBAAgB,GAAGA,CAAA,KAAM;IACvB,IAAI,IAAI,CAACC,gBAAgB,EAAE;MACzBC,OAAO,CAACC,GAAG,CAAC,qBAAqB,EAAEd,MAAM,CAACC,IAAI,CAAC,IAAI,CAACZ,QAAQ,CAAC,CAAC;IAChE;IACAW,MAAM,CAACe,MAAM,CAAC,IAAI,CAAC1B,QAAQ,CAAC,CAAC2B,OAAO,CAACC,OAAO,IAAIA,OAAO,CAACC,KAAK,CAAC,CAAC,CAAC;EAClE,CAAC;;EAED;AACF;AACA;EACEC,YAAY,GAAG,MAAAA,CAAA,KAAY;IACzB,IAAI;MACF,KAAK,MAAM,CAACvB,IAAI,EAAEE,QAAQ,CAAC,IAAIE,MAAM,CAACoB,OAAO,CAAC,IAAI,CAAC7B,aAAa,CAAC,EAAE;QACjE,IAAI;UACF,IAAI,CAACO,QAAQ,EAAE;YACb;UACF;UACA,MAAMuB,MAAM,GAAGvB,QAAQ,CAAC,CAAC;UACzB,MAAMwB,GAAG,GAAGD,MAAM,YAAYE,OAAO,GAAG,MAAMF,MAAM,GAAGA,MAAM;UAC7D,IAAIC,GAAG,KAAKE,SAAS,EAAE,IAAI,CAACpC,MAAM,CAACQ,IAAI,CAAC,CAAC6B,GAAG,CAAC,IAAI,CAAC1C,aAAa,EAAEuC,GAAG,CAAC;QACvE,CAAC,CAAC,OAAOI,GAAG,EAAE;UACZb,OAAO,CAACc,KAAK,CACX,GAAG,IAAI,CAACjD,UAAU,2BAA2BkB,IAAI,GAAG,EACpD8B,GACF,CAAC;QACH;MACF;MAEA,IAAI,CAAC,IAAI,CAACpD,kBAAkB,EAAE;QAC5B,MAAM,IAAI,CAACsD,WAAW,CAAC,CAAC;MAC1B;MACA;;MAEA,IAAI,IAAI,CAACjE,SAAS,EAAE;QAClB,MAAMkE,OAAO,GAAG,MAAM,IAAI,CAAClD,SAAS,CAACmD,gBAAgB,CAAC,CAAC;QACvDjB,OAAO,CAACC,GAAG,CACT,GAAG,IAAI,CAACpC,UAAU,aAAa,EAC/BqD,IAAI,CAACC,SAAS,CAACH,OAAO,EAAE,IAAI,EAAE,CAAC,CACjC,CAAC;MACH;IACF,CAAC,CAAC,OAAOH,GAAG,EAAE;MACZb,OAAO,CAACc,KAAK,CAAC,GAAG,IAAI,CAACjD,UAAU,0BAA0B,EAAEgD,GAAG,CAAC;IAClE;EACF,CAAC;EAEDO,UAAU,GAAGA,CAACC,QAAQ,GAAG,IAAI,CAAChE,WAAW,EAAEiE,gBAAgB,GAAGX,SAAS,KAAK;IAC1E,IAAI,CAAC,IAAI,CAAC/D,OAAO,EAAE;MACjBoD,OAAO,CAACuB,IAAI,CAAC,GAAG,IAAI,CAAC1D,UAAU,mBAAmB,CAAC;MACnD,IAAI,IAAI,CAACgB,4BAA4B,IAAI,CAAC,IAAI,CAAC2C,aAAa,EAAE;QAC5D,IAAI,CAACA,aAAa,GAAGC,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;MAC5D;MACA;IACF;IAEA,IAAI,IAAI,CAACD,aAAa,EAAE;MACtBE,aAAa,CAAC,IAAI,CAACF,aAAa,CAAC;MACjC,IAAI,CAACA,aAAa,GAAG,IAAI;IAC3B;IAEA,IAAI,IAAI,CAAChE,iBAAiB,IAAI,CAAC,IAAI,CAACA,iBAAiB,CAAC,CAAC,EAAE;MACvD;IACF;IAEA,MAAMmE,OAAO,GAAGA,CAAA,KAAM;MACpB,IAAIL,gBAAgB,IAAI,OAAOA,gBAAgB,KAAK,UAAU,EAAE;QAC9D,OAAOZ,OAAO,CAACkB,OAAO,CAACN,gBAAgB,CAAC,CAAC,CAAC;MAC5C;MACA,OAAO,IAAI,CAACO,WAAW,CAAC,CAAC;IAC3B,CAAC;IAED,IAAIP,gBAAgB,IAAI,OAAOA,gBAAgB,KAAK,UAAU,EAAE;MAC9DG,WAAW,CAAC,MAAMH,gBAAgB,CAAC,CAAC,EAAED,QAAQ,GAAG,IAAI,CAAC;IACxD,CAAC,MAAM;MACLI,WAAW,CAAC,MAAM;QAChBE,OAAO,CAAC,CAAC,CAACG,KAAK,CAACjB,GAAG,IAAI;UACrBb,OAAO,CAACc,KAAK,CAAC,GAAG,IAAI,CAACjD,UAAU,0BAA0B,EAAEgD,GAAG,CAAC;QAClE,CAAC,CAAC;MACJ,CAAC,EAAEQ,QAAQ,GAAG,IAAI,CAAC;IACrB;;IAEA;IACAM,OAAO,CAAC,CAAC,CAACG,KAAK,CAACjB,GAAG,IAAI;MACrBb,OAAO,CAACc,KAAK,CAAC,GAAG,IAAI,CAACjD,UAAU,oCAAoC,EAAEgD,GAAG,CAAC;IAC5E,CAAC,CAAC;IAEF,IAAIkB,UAAU,GAAG,MAAM;IACvB,IAAI;MACF,IAAI,IAAI,CAAC/E,cAAc,IAAI,IAAI,CAACA,cAAc,CAACgF,IAAI,CAAC,CAAC,EAAE;QACrDD,UAAU,GAAG,IAAI/F,GAAG,CAAC,IAAI,CAACgB,cAAc,CAACgF,IAAI,CAAC,CAAC,CAAC,CAACC,MAAM;MACzD;IACF,CAAC,CAAC,MAAM;MACNF,UAAU,GAAG,aAAa;IAC5B;IACA/B,OAAO,CAACuB,IAAI,CACV,GAAG,IAAI,CAAC1D,UAAU,2CAA2C,IAAI,CAACR,WAAW,YAAY0E,UAAU,GACrG,CAAC;EACH,CAAC;EAEDF,WAAW,GAAG,MAAAA,CAAA,KAAY;IACxB,OAAO,IAAI,CAACvB,YAAY,CAAC,CAAC;EAC5B,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE4B,SAAS,GAAGA,CAACb,QAAQ,EAAEC,gBAAgB,GAAGX,SAAS,KAAK;IACtD,IAAI,CAACS,UAAU,CAACC,QAAQ,EAAEC,gBAAgB,CAAC;EAC7C,CAAC;;EAED;AACF;AACA;AACA;EACEa,OAAO,GAAG,MAAAA,CAAA,KAAY;IACpB,IAAI,IAAI,CAACvF,OAAO,EAAE;MAChB,MAAM,IAAI,CAACwF,aAAa,CAAC,CAAC;IAC5B;IACA/F,OAAO,CAACgG,IAAI,CAAC,CAAC,CAAC;EACjB,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE1D,gBAAgB,GAAG,MAAMhB,gBAAgB,IAAI;IAC3C;EAAA,CACD;;EAED;AACF;AACA;AACA;EACEyE,aAAa,GAAG,MAAAA,CAAA,KAAY;IAC1B,IACE,IAAI,CAACzE,gBAAgB,IACrB,IAAI,CAACX,cAAc,IACnB,IAAI,CAACA,cAAc,CAACgF,IAAI,CAAC,CAAC,EAC1B;MACA,MAAM,IAAI,CAACM,qBAAqB,CAAC,CAAC,CAACR,KAAK,CAACjB,GAAG,IAAI;QAC9Cb,OAAO,CAACuB,IAAI,CACV,GAAG,IAAI,CAAC1D,UAAU,uCAAuC,EACzDgD,GAAG,CAAC0B,OACN,CAAC;MACH,CAAC,CAAC;IACJ;EACF,CAAC;;EAED;AACF;AACA;AACA;EACED,qBAAqB,GAAGA,CAAA,KAAM;IAC5B,MAAME,GAAG,GAAGC,CAAC,IAAIC,MAAM,CAACD,CAAC,CAAC,CAACE,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAACA,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;IACtE,MAAMC,QAAQ,GAAG,SAASJ,GAAG,CAAC,IAAI,CAACpG,OAAO,CAAC,cAAcoG,GAAG,CAC1D,IAAI,CAAChG,MACP,CAAC,mBAAmBgG,GAAG,CAAC,IAAI,CAAC9F,WAAW,CAAC,IAAI;IAC7C,IAAIuF,MAAM;IACV,IAAI;MACF,MAAMY,CAAC,GAAG,IAAI7G,GAAG,CAAC,CAAC,IAAI,CAACgB,cAAc,IAAI,EAAE,EAAEgF,IAAI,CAAC,CAAC,CAAC;MACrDC,MAAM,GAAGY,CAAC,CAACZ,MAAM;IACnB,CAAC,CAAC,MAAM;MACN,OAAOvB,OAAO,CAACoC,MAAM,CAAC,IAAIC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtD;IACA,MAAMC,IAAI,GAAG,4CAA4CC,kBAAkB,CACzEL,QACF,CAAC,EAAE;IACH,OAAO,IAAIlC,OAAO,CAAC,CAACkB,OAAO,EAAEkB,MAAM,KAAK;MACtC,MAAMD,CAAC,GAAG,IAAI7G,GAAG,CAACiG,MAAM,CAAC;MACzB,MAAMiB,GAAG,GAAG,CAACL,CAAC,CAACM,QAAQ,KAAK,QAAQ,GAAGrH,KAAK,GAAGC,IAAI,EAAEqH,OAAO,CAC1D;QACEC,QAAQ,EAAER,CAAC,CAACQ,QAAQ;QACpBC,IAAI,EAAET,CAAC,CAACS,IAAI,KAAKT,CAAC,CAACM,QAAQ,KAAK,QAAQ,GAAG,GAAG,GAAG,EAAE,CAAC;QACpDH,IAAI;QACJO,MAAM,EAAE,MAAM;QACdC,OAAO,EAAE;UACP,gBAAgB,EAAE,GAAG;UACrBC,aAAa,EAAE,IAAI,CAACvG,SAAS,GACzB,SAAS,IAAI,CAACA,SAAS,EAAE,GACzByD;QACN,CAAC;QACD+C,KAAK,EACHb,CAAC,CAACM,QAAQ,KAAK,QAAQ,GACnB,IAAIrH,KAAK,CAAC6H,KAAK,CAAC;UAAEC,SAAS,EAAE;QAAM,CAAC,CAAC,GACrCjD;MACR,CAAC,EACDkD,GAAG,IAAI;QACL,IAAIA,GAAG,CAACC,UAAU,IAAI,GAAG,IAAID,GAAG,CAACC,UAAU,GAAG,GAAG,EAAElC,OAAO,CAAC,CAAC,MACvD;UACH,IAAIjC,IAAI,GAAG,EAAE;UACbkE,GAAG,CAACE,EAAE,CAAC,MAAM,EAAEC,KAAK,IAAI;YACtBrE,IAAI,IAAIqE,KAAK;UACf,CAAC,CAAC;UACFH,GAAG,CAACE,EAAE,CAAC,KAAK,EAAE,MACZjB,MAAM,CAAC,IAAIC,KAAK,CAAC,kBAAkBc,GAAG,CAACC,UAAU,IAAInE,IAAI,EAAE,CAAC,CAC9D,CAAC;QACH;MACF,CACF,CAAC;MACDuD,GAAG,CAACa,EAAE,CAAC,OAAO,EAAEjB,MAAM,CAAC;MACvBI,GAAG,CAACe,GAAG,CAAC,CAAC;IACX,CAAC,CAAC;EACJ,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;EACElD,WAAW,GAAG,MAAAA,CAAOmD,MAAM,GAAG,CAAC,CAAC,KAAK;IACnC,IAAI,IAAI,CAACzG,kBAAkB,EAAE;MAC3BuC,OAAO,CAACuB,IAAI,CACV,GAAG,IAAI,CAAC1D,UAAU,2DACpB,CAAC;MACD,OAAO6C,OAAO,CAACkB,OAAO,CAAC,CAAC;IAC1B;IACA,IAAI,CAAC,IAAI,CAAC5E,cAAc,IAAI,CAAC,IAAI,CAACA,cAAc,CAACgF,IAAI,CAAC,CAAC,EAAE;MACvDhC,OAAO,CAACuB,IAAI,CACV,GAAG,IAAI,CAAC1D,UAAU,2DACpB,CAAC;MACD,OAAO6C,OAAO,CAACkB,OAAO,CAAC,CAAC;IAC1B;IACA,OAAO,IAAI,CAACuC,cAAc,CAAC,CAAC;EAC9B,CAAC;;EAED;AACF;AACA;AACA;EACEA,cAAc,GAAGA,CAAA,KAAM;IACrB,IAAIC,OAAO,GAAG,CAAC,IAAI,CAACpH,cAAc,IAAI,EAAE,EAAEgF,IAAI,CAAC,CAAC;IAChD,IAAI;MACF,MAAMa,CAAC,GAAG,IAAI7G,GAAG,CAACoI,OAAO,CAAC;MAC1B,IAAI,CAACvB,CAAC,CAACwB,QAAQ,IAAIxB,CAAC,CAACwB,QAAQ,KAAK,GAAG,IAAIxB,CAAC,CAACwB,QAAQ,KAAK,UAAU,EAAE;QAClED,OAAO,GAAG,GAAGvB,CAAC,CAACZ,MAAM,4BAA4BY,CAAC,CAACyB,MAAM,EAAE;MAC7D;IACF,CAAC,CAAC,MAAM;MACN;IAAA;IAEF,OAAO,IAAI5D,OAAO,CAAC,CAACkB,OAAO,EAAEkB,MAAM,KAAK;MACtC,MAAMD,CAAC,GAAG,IAAI7G,GAAG,CAACoI,OAAO,CAAC;MAC1B,MAAMlB,GAAG,GAAG,CAACL,CAAC,CAACM,QAAQ,KAAK,QAAQ,GAAGrH,KAAK,GAAGC,IAAI,EAAEqH,OAAO,CAC1D;QACEC,QAAQ,EAAER,CAAC,CAACQ,QAAQ;QACpBC,IAAI,EAAET,CAAC,CAACS,IAAI,KAAKT,CAAC,CAACM,QAAQ,KAAK,QAAQ,GAAG,GAAG,GAAG,EAAE,CAAC;QACpDH,IAAI,EAAEH,CAAC,CAACwB,QAAQ,GAAGxB,CAAC,CAACyB,MAAM;QAC3Bf,MAAM,EAAE,MAAM;QACdC,OAAO,EAAE;UACP,cAAc,EAAE5H,MAAM,CAACqC,QAAQ,CAACsG,WAAW;UAC3Cd,aAAa,EAAE,IAAI,CAACvG,SAAS,GACzB,SAAS,IAAI,CAACA,SAAS,EAAE,GACzByD;QACN,CAAC;QACD+C,KAAK,EACHb,CAAC,CAACM,QAAQ,KAAK,QAAQ,GACnB,IAAIrH,KAAK,CAAC6H,KAAK,CAAC;UAAEC,SAAS,EAAE;QAAK,CAAC,CAAC,GACpCjD;MACR,CAAC,EACDkD,GAAG,IAAI;QACL,IAAIA,GAAG,CAACC,UAAU,IAAI,GAAG,IAAID,GAAG,CAACC,UAAU,GAAG,GAAG,EAAE;UACjDlC,OAAO,CAAC,CAAC;QACX,CAAC,MAAM;UACL,IAAIjC,IAAI,GAAG,EAAE;UACbkE,GAAG,CAACE,EAAE,CAAC,MAAM,EAAEC,KAAK,IAAI;YACtBrE,IAAI,IAAIqE,KAAK;UACf,CAAC,CAAC;UACFH,GAAG,CAACE,EAAE,CAAC,KAAK,EAAE,MACZjB,MAAM,CAAC,IAAIC,KAAK,CAAC,gBAAgBc,GAAG,CAACC,UAAU,IAAInE,IAAI,EAAE,CAAC,CAC5D,CAAC;QACH;MACF,CACF,CAAC;MACDuD,GAAG,CAACa,EAAE,CAAC,OAAO,EAAEjB,MAAM,CAAC;MACvB,IAAI,CAAChF,SAAS,CACXkD,OAAO,CAAC,CAAC,CACTwD,IAAI,CAACxD,OAAO,IAAI;QACfkC,GAAG,CAACuB,SAAS,CAAC,gBAAgB,EAAEC,MAAM,CAACC,UAAU,CAAC3D,OAAO,EAAE,MAAM,CAAC,CAAC;QACnEkC,GAAG,CAACe,GAAG,CAACjD,OAAO,EAAE,MAAM,CAAC;MAC1B,CAAC,CAAC,CACDc,KAAK,CAACgB,MAAM,CAAC;IAClB,CAAC,CAAC;EACJ,CAAC;;EAED;AACF;AACA;AACA;AACA;AACA;AACA;EACE8B,iBAAiB,GAAGA,CAACC,MAAM,GAAG,EAAE,KAAK;IACnC,OAAO,CAAC,GAAG1F,MAAM,CAACC,IAAI,CAAC,IAAI,CAAClB,aAAa,CAAC,EAAE,GAAG2G,MAAM,CAAC;EACxD,CAAC;EAEDC,gBAAgB,GAAGA,CAACD,MAAM,GAAG,EAAE,KAAK;IAClC,OAAO,IAAI,CAAC3G,aAAa;EAC3B,CAAC;EAEDU,mBAAmB,GAAGA,CAAA,KAAM;IAC1BvC,OAAO,CAAC0H,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC5B,OAAO,CAAC;IAClC9F,OAAO,CAAC0H,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC5B,OAAO,CAAC;EACrC,CAAC;;EAED;;EAEA,IAAI4C,cAAcA,CAAA,EAAG;IACnB,OAAO,IAAI,CAACnI,OAAO;EACrB;EAEA,IAAImD,gBAAgBA,CAAA,EAAG;IACrB,OAAO,IAAI,CAACjD,SAAS;EACvB;EAEA,IAAIkI,QAAQA,CAAA,EAAG;IACb,OAAO,IAAI,CAAClH,SAAS;EACvB;EAEA,MAAMmH,kBAAkBA,CAAA,EAAG;IACzB,OAAO,IAAI,CAACnH,SAAS,CAACkD,OAAO,CAAC,CAAC;EACjC;EAEAkE,iBAAiBA,CAAA,EAAG;IAClB,OAAO,OAAOhC,GAAG,EAAEW,GAAG,KAAK;MACzB,IAAI;QACF,MAAM7C,OAAO,GAAG,MAAM,IAAI,CAACiE,kBAAkB,CAAC,CAAC;QAC/CpB,GAAG,CAACjD,GAAG,CAAC,cAAc,EAAEhF,MAAM,CAACqC,QAAQ,CAACsG,WAAW,CAAC;QACpDV,GAAG,CAACI,GAAG,CAACjD,OAAO,CAAC;MAClB,CAAC,CAAC,OAAOH,GAAG,EAAE;QACZb,OAAO,CAACc,KAAK,CAAC,GAAG,IAAI,CAACjD,UAAU,yBAAyB,EAAEgD,GAAG,CAAC;QAC/DgD,GAAG,CAACsB,MAAM,CAAC,GAAG,CAAC,CAAClB,GAAG,CAAC,2BAA2B,CAAC;MAClD;IACF,CAAC;EACH;AACF;AAEAmB,MAAM,CAACC,OAAO,GAAG;EAAEpJ;AAAkB,CAAC","ignoreList":[]}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * MetricsClient handles Prometheus metrics collection and push.
3
+ * Supports gauges, counters, default metrics, and custom metrics.
4
+ * Extends BaseMetricsClient for common functionality.
5
+ */
6
+ export class MetricsClient extends BaseMetricsClient {
7
+ /**
8
+ * @param {Object} config
9
+ * @param {string} [config.appName] Name of the application
10
+ * @param {string} [config.dynoId] Dyno/instance ID
11
+ * @param {string} [config.processType] Process type (web, worker, etc.)
12
+ * @param {boolean} [config.enabled] Enable metrics collection
13
+ * @param {boolean} [config.httpMetricsEnabled=false] Enable HTTP request metrics (app_requests_total, app_requests_total_duration)
14
+ * @param {boolean} [config.logValues] Log metrics values to console
15
+ * @param {string} [config.pushgatewayUrl] PushGateway URL
16
+ * @param {string} [config.pushgatewaySecret] PushGateway secret token
17
+ * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
18
+ * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
19
+ * @param {function} [config.startupValidation] Add to validate on start push.
20
+ * @param {boolean} [config.disablePushgateway] Disable pushing to Pushgateway (use HTTP scraping instead)
21
+ */
22
+ constructor(config?: {
23
+ appName?: string | undefined;
24
+ dynoId?: string | undefined;
25
+ processType?: string | undefined;
26
+ enabled?: boolean | undefined;
27
+ httpMetricsEnabled?: boolean | undefined;
28
+ logValues?: boolean | undefined;
29
+ pushgatewayUrl?: string | undefined;
30
+ pushgatewaySecret?: string | undefined;
31
+ intervalSec?: number | undefined;
32
+ removeOldMetrics?: boolean | undefined;
33
+ startupValidation?: Function | undefined;
34
+ disablePushgateway?: boolean | undefined;
35
+ });
36
+ httpMetricsEnabled: boolean;
37
+ _lastUsageMicros: number;
38
+ _lastCheckTime: number;
39
+ /**
40
+ * Register all built-in default Gauges and Counters.
41
+ * @private
42
+ */
43
+ private _initDefaultMetrics;
44
+ /**
45
+ * Get CPU usage percent (cgroup-aware)
46
+ * @returns {number}
47
+ */
48
+ getCpuUsagePercent: () => number;
49
+ /**
50
+ * Get available CPU cores.
51
+ * @returns {number}
52
+ */
53
+ getAvailableCPUs(): number;
54
+ /**
55
+ * Get container memory usage in bytes.
56
+ * @returns {number}
57
+ */
58
+ getContainerMemoryUsage(): number;
59
+ /**
60
+ * Get container memory limit in bytes.
61
+ * @returns {number}
62
+ */
63
+ getContainerMemoryLimit(): number;
64
+ /**
65
+ * Measure event loop lag in ms.
66
+ * @returns {Promise<number>}
67
+ */
68
+ measureLag(): Promise<number>;
69
+ /**
70
+ * Increment the HTTP requests counter with detailed request information.
71
+ *
72
+ * @param {Object} params - The parameters for the request counter.
73
+ * @param {string} params.method - HTTP method (GET, POST, etc.).
74
+ * @param {string} params.route - The full requested URL or route.
75
+ * @param {number} params.status_code - HTTP response status code.
76
+ * @param {string} [params.appId=''] - Optional application identifier.
77
+ * @param {string} [params.databaseId=''] - Optional database identifier.
78
+ * @param {number} params.duration - Request duration in milliseconds.
79
+ */
80
+ trackHttpRequest({ method, route, status_code, appId, databaseId, duration, }: {
81
+ method: string;
82
+ route: string;
83
+ status_code: number;
84
+ appId?: string | undefined;
85
+ databaseId?: string | undefined;
86
+ duration: number;
87
+ }): void;
88
+ /**
89
+ * Express middleware to track HTTP requests.
90
+ * Track the `app_requests_total` and `app_requests_total_duration` metric.
91
+ */
92
+ trackHttpRequestMiddleware: (req: any, res: any, next: any) => void;
93
+ }
94
+ import { BaseMetricsClient } from "./baseMetricsClient";
95
+ //# sourceMappingURL=metricsClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsClient.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsClient.js"],"names":[],"mappings":"AAIA;;;;GAIG;AACH;IACE;;;;;;;;;;;;;;OAcG;IACH;QAb2B,OAAO;QACP,MAAM;QACN,WAAW;QACV,OAAO;QACP,kBAAkB;QAClB,SAAS;QACV,cAAc;QACd,iBAAiB;QACjB,WAAW;QACV,gBAAgB;QACf,iBAAiB;QAClB,kBAAkB;OAc7C;IATC,4BAGO;IAEP,yBAAyB;IACzB,uBAAgC;IAKlC;;;OAGG;IACH,4BA8DC;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;;;;;;;;;;OAUG;IACH;QAP0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACU,KAAK;QACL,UAAU;QACX,QAAQ,EAAvB,MAAM;aA6BhB;IAED;;;OAGG;IACH,oEA+BC;CACF"}