@adalo/metrics 0.1.174 → 0.1.176

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 (30) hide show
  1. package/README.md +13 -14
  2. package/__tests__/httpMetricsRedisCollector.test.js +105 -1
  3. package/__tests__/httpMetricsRedisRecorder.test.js +22 -3
  4. package/lib/metrics/baseMetricsClient.d.ts +1 -1
  5. package/lib/metrics/baseMetricsClient.d.ts.map +1 -1
  6. package/lib/metrics/baseMetricsClient.js +1 -1
  7. package/lib/metrics/baseMetricsClient.js.map +1 -1
  8. package/lib/metrics/httpMetricsRedisCollector.d.ts +30 -24
  9. package/lib/metrics/httpMetricsRedisCollector.d.ts.map +1 -1
  10. package/lib/metrics/httpMetricsRedisCollector.js +65 -34
  11. package/lib/metrics/httpMetricsRedisCollector.js.map +1 -1
  12. package/lib/metrics/httpMetricsRedisRecorder.d.ts +9 -17
  13. package/lib/metrics/httpMetricsRedisRecorder.d.ts.map +1 -1
  14. package/lib/metrics/httpMetricsRedisRecorder.js +18 -16
  15. package/lib/metrics/httpMetricsRedisRecorder.js.map +1 -1
  16. package/lib/metrics/metricsClient.d.ts +1 -63
  17. package/lib/metrics/metricsClient.d.ts.map +1 -1
  18. package/lib/metrics/metricsClient.js +1 -77
  19. package/lib/metrics/metricsClient.js.map +1 -1
  20. package/lib/metrics/metricsProcessTypeUtils.d.ts +17 -9
  21. package/lib/metrics/metricsProcessTypeUtils.d.ts.map +1 -1
  22. package/lib/metrics/metricsProcessTypeUtils.js +18 -9
  23. package/lib/metrics/metricsProcessTypeUtils.js.map +1 -1
  24. package/package.json +3 -2
  25. package/src/metrics/baseMetricsClient.js +1 -1
  26. package/src/metrics/httpMetricsRedisCollector.js +78 -43
  27. package/src/metrics/httpMetricsRedisRecorder.js +23 -15
  28. package/src/metrics/metricsClient.js +1 -93
  29. package/src/metrics/metricsProcessTypeUtils.js +20 -9
  30. package/docs/http-metrics-redis.md +0 -19
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
 
3
+ const {
4
+ v4: uuidv4
5
+ } = require('uuid');
3
6
  const {
4
7
  BaseMetricsClient
5
8
  } = require('./baseMetricsClient');
@@ -9,31 +12,30 @@ const {
9
12
  } = require('./httpMetricsRedisStore');
10
13
 
11
14
  /**
12
- * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),
13
- * applies them to `app_requests_*` counters, then pushes the registry to the VM-agent (same as {@link BaseMetricsClient}).
14
- * **Minimal usage:** `{ redisClient }` only. Redis keys use segment **`web`** unless you pass **`redisProcessTypeForKeys`**.
15
- * `processType` / `appName` / `dynoId` follow {@link BaseMetricsClient} defaults (e.g. `BUILD_DYNO_PROCESS_TYPE`) and do **not** select Redis hash names.
16
- * Always passes `blockNodeDefaultMetrics: true` (HTTP-focused registry).
17
- *
18
- * @extends BaseMetricsClient
15
+ * Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.
16
+ * From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.
19
17
  */
18
+ const HTTP_PUSH_INSTANCE_ROTATE_MS = parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) || 7 * 24 * 60 * 60 * 1000;
19
+ const HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000;
20
20
  class HttpMetricsRedisCollector extends BaseMetricsClient {
21
21
  /**
22
22
  * @param {Object} [config]
23
- * @param {import('redis').RedisClient} config.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).
24
- * @param {string} [config.appName] Application name (defaults per {@link BaseMetricsClient})
25
- * @param {string} [config.dynoId] Dyno/instance ID
26
- * @param {string} [config.processType] Label `process_type` on push (default from env / base)
27
- * @param {boolean} [config.enabled] Enable collection and push
28
- * @param {boolean} [config.logValues] Log metric JSON to console
29
- * @param {string} [config.pushgatewayUrl] VM-agent import URL
30
- * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64)
31
- * @param {number} [config.intervalSec] Push interval (seconds)
32
- * @param {boolean} [config.removeOldMetrics] Clear old series on shutdown where supported
33
- * @param {function} [config.startupValidation] Run before first push
34
- * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent
35
- * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default **`web`**). Optional; only if writers use a non-`web` segment.
36
- * @param {number} [config.ttlSec] Passed to {@link HttpMetricsRedisStore} (should match writers)
23
+ * @param {import('redis').RedisClient} config.redisClient
24
+ * @param {string} [config.appName] Name of the application (defaults per {@link BaseMetricsClient})
25
+ * @param {string} [config.dynoId] Dyno/instance ID (defaults per {@link BaseMetricsClient})
26
+ * @param {string} [config.processType] Process type for labels (defaults per {@link BaseMetricsClient})
27
+ * @param {boolean} [config.enabled] Enable metrics collection (defaults per {@link BaseMetricsClient})
28
+ * @param {boolean} [config.logValues] Log metrics values to console (defaults per {@link BaseMetricsClient})
29
+ * @param {string} [config.pushgatewayUrl] VM-agent import URL (defaults per {@link BaseMetricsClient})
30
+ * @param {string} [config.pushgatewaySecret] Basic auth secret, Base64 (defaults per {@link BaseMetricsClient})
31
+ * @param {number} [config.intervalSec] Push interval in seconds (defaults per {@link BaseMetricsClient})
32
+ * @param {boolean} [config.removeOldMetrics] Enable clearing old metrics (defaults per {@link BaseMetricsClient})
33
+ * @param {function} [config.startupValidation] Run before first push (defaults per {@link BaseMetricsClient})
34
+ * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent (defaults per {@link BaseMetricsClient})
35
+ * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
36
+ * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
37
+ *
38
+ * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
37
39
  */
38
40
  constructor(config = {}) {
39
41
  const {
@@ -47,38 +49,64 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
47
49
  blockNodeDefaultMetrics: true
48
50
  });
49
51
  const keyProcessType = config.redisProcessTypeForKeys || 'web';
50
- this.defaultLabelsWithoutDynoId = {
51
- app: this.appName,
52
- process_type: keyProcessType
53
- };
54
52
  this._store = new HttpMetricsRedisStore({
55
53
  redisClient,
56
54
  appName: this.appName,
57
55
  processType: keyProcessType,
58
56
  ttlSec: config.ttlSec
59
57
  });
58
+ const now = Date.now();
59
+ this._httpPushInstanceId = uuidv4();
60
+ this._httpPushInstanceStartedAt = now;
61
+ this._httpRotationLastCheck = 0;
60
62
 
61
- /** @type {Set<string>} Redis field keys already primed with inc(0) + push in this process */
63
+ /** @type {Set<string>} */
62
64
  this._httpCounterPrimedKeys = new Set();
65
+ const httpLabelNames = this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code', 'http_instance_id']);
63
66
  this.createCounter({
64
67
  name: 'app_requests_total',
65
68
  help: 'Total number of HTTP requests',
66
- labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code']),
69
+ labelNames: httpLabelNames,
67
70
  useLabelsWithoutDynoId: true
68
71
  });
69
72
  this.createCounter({
70
73
  name: 'app_requests_total_duration',
71
74
  help: 'Total duration of HTTP requests in milliseconds',
72
- labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code']),
75
+ labelNames: httpLabelNames,
73
76
  useLabelsWithoutDynoId: true
74
77
  });
75
78
  }
79
+ _labelsForHttpPush(rowLabels) {
80
+ return {
81
+ ...rowLabels,
82
+ http_instance_id: this._httpPushInstanceId
83
+ };
84
+ }
85
+
86
+ /** @param {number} now */
87
+ _rotateHttpPushInstance(now) {
88
+ this._httpPushInstanceId = uuidv4();
89
+ this._httpPushInstanceStartedAt = now;
90
+ this.clearAllCounters();
91
+ this._httpCounterPrimedKeys.clear();
92
+ }
93
+
94
+ /** @param {number} now */
95
+ _maybeRotateHttpPushInstance(now = Date.now()) {
96
+ if (now - this._httpRotationLastCheck < HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS) {
97
+ return;
98
+ }
99
+ this._httpRotationLastCheck = now;
100
+ if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {
101
+ this._rotateHttpPushInstance(now);
102
+ }
103
+ }
76
104
 
77
105
  /**
78
- * Drains Redis into counters, then runs gauge updates and VM-agent push ({@link BaseMetricsClient#_pushMetrics}).
79
106
  * @returns {Promise<void>}
80
107
  */
81
108
  pushMetrics = async () => {
109
+ this._maybeRotateHttpPushInstance();
82
110
  if (this._store && this.countersFunctions?.app_requests_total && this.countersFunctions?.app_requests_total_duration) {
83
111
  const {
84
112
  ok,
@@ -90,10 +118,11 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
90
118
  let primedAny = false;
91
119
  for (const row of rows) {
92
120
  const key = buildFieldKey(row.labels.method, row.labels.route, row.labels.status_code);
121
+ const L = this._labelsForHttpPush(row.labels);
93
122
  if (!this._httpCounterPrimedKeys.has(key)) {
94
123
  this._httpCounterPrimedKeys.add(key);
95
- applyCount(row.labels, 0);
96
- applyDur(row.labels, 0);
124
+ applyCount(L, 0);
125
+ applyDur(L, 0);
97
126
  primedAny = true;
98
127
  }
99
128
  }
@@ -101,9 +130,10 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
101
130
  await this.gatewayPush();
102
131
  }
103
132
  for (const row of rows) {
104
- applyCount(row.labels, row.count);
133
+ const L = this._labelsForHttpPush(row.labels);
134
+ applyCount(L, row.count);
105
135
  if (row.dur > 0) {
106
- applyDur(row.labels, row.dur);
136
+ applyDur(L, row.dur);
107
137
  }
108
138
  }
109
139
  }
@@ -112,6 +142,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
112
142
  };
113
143
  }
114
144
  module.exports = {
115
- HttpMetricsRedisCollector
145
+ HttpMetricsRedisCollector,
146
+ HTTP_PUSH_INSTANCE_ROTATE_MS
116
147
  };
117
148
  //# sourceMappingURL=httpMetricsRedisCollector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.js","names":["BaseMetricsClient","require","HttpMetricsRedisStore","buildFieldKey","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","defaultLabelsWithoutDynoId","app","appName","process_type","_store","processType","ttlSec","_httpCounterPrimedKeys","Set","createCounter","name","help","labelNames","withDefaultLabelsWithoutDynoId","useLabelsWithoutDynoId","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","ok","rows","drainRows","length","applyCount","labels","value","applyDur","primedAny","row","key","method","route","status_code","has","add","gatewayPush","count","dur","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { BaseMetricsClient } = require('./baseMetricsClient')\nconst {\n HttpMetricsRedisStore,\n buildFieldKey,\n} = require('./httpMetricsRedisStore')\n\n/**\n * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),\n * applies them to `app_requests_*` counters, then pushes the registry to the VM-agent (same as {@link BaseMetricsClient}).\n * **Minimal usage:** `{ redisClient }` only. Redis keys use segment **`web`** unless you pass **`redisProcessTypeForKeys`**.\n * `processType` / `appName` / `dynoId` follow {@link BaseMetricsClient} defaults (e.g. `BUILD_DYNO_PROCESS_TYPE`) and do **not** select Redis hash names.\n * Always passes `blockNodeDefaultMetrics: true` (HTTP-focused registry).\n *\n * @extends BaseMetricsClient\n */\nclass HttpMetricsRedisCollector extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {import('redis').RedisClient} config.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} [config.appName] Application name (defaults per {@link BaseMetricsClient})\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Label `process_type` on push (default from env / base)\n * @param {boolean} [config.enabled] Enable collection and push\n * @param {boolean} [config.logValues] Log metric JSON to console\n * @param {string} [config.pushgatewayUrl] VM-agent import URL\n * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64)\n * @param {number} [config.intervalSec] Push interval (seconds)\n * @param {boolean} [config.removeOldMetrics] Clear old series on shutdown where supported\n * @param {function} [config.startupValidation] Run before first push\n * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent\n * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default **`web`**). Optional; only if writers use a non-`web` segment.\n * @param {number} [config.ttlSec] Passed to {@link HttpMetricsRedisStore} (should match writers)\n */\n constructor(config = {}) {\n const { redisClient } = config\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisCollector: redisClient is required')\n }\n\n super({\n ...config,\n blockNodeDefaultMetrics: true,\n })\n\n const keyProcessType = config.redisProcessTypeForKeys || 'web'\n\n this.defaultLabelsWithoutDynoId = {\n app: this.appName,\n process_type: keyProcessType,\n }\n\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: this.appName,\n processType: keyProcessType,\n ttlSec: config.ttlSec,\n })\n\n /** @type {Set<string>} Redis field keys already primed with inc(0) + push in this process */\n this._httpCounterPrimedKeys = new Set()\n\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n ]),\n useLabelsWithoutDynoId: true,\n })\n\n this.createCounter({\n name: 'app_requests_total_duration',\n help: 'Total duration of HTTP requests in milliseconds',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n ]),\n useLabelsWithoutDynoId: true,\n })\n }\n\n /**\n * Drains Redis into counters, then runs gauge updates and VM-agent push ({@link BaseMetricsClient#_pushMetrics}).\n * @returns {Promise<void>}\n */\n pushMetrics = async () => {\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n const { ok, rows } = await this._store.drainRows()\n if (ok && rows.length > 0) {\n const applyCount = (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value)\n const applyDur = (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n\n let primedAny = false\n for (const row of rows) {\n const key = buildFieldKey(\n row.labels.method,\n row.labels.route,\n row.labels.status_code\n )\n if (!this._httpCounterPrimedKeys.has(key)) {\n this._httpCounterPrimedKeys.add(key)\n applyCount(row.labels, 0)\n applyDur(row.labels, 0)\n primedAny = true\n }\n }\n if (primedAny) {\n await this.gatewayPush()\n }\n for (const row of rows) {\n applyCount(row.labels, row.count)\n if (row.dur > 0) {\n applyDur(row.labels, row.dur)\n }\n }\n }\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = { HttpMetricsRedisCollector }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisCollector.js","names":["v4","uuidv4","require","BaseMetricsClient","HttpMetricsRedisStore","buildFieldKey","HTTP_PUSH_INSTANCE_ROTATE_MS","parseInt","process","env","METRICS_HTTP_INSTANCE_ROTATE_MS","HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","_store","appName","processType","ttlSec","now","Date","_httpPushInstanceId","_httpPushInstanceStartedAt","_httpRotationLastCheck","_httpCounterPrimedKeys","Set","httpLabelNames","withDefaultLabelsWithoutDynoId","createCounter","name","help","labelNames","useLabelsWithoutDynoId","_labelsForHttpPush","rowLabels","http_instance_id","_rotateHttpPushInstance","clearAllCounters","clear","_maybeRotateHttpPushInstance","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","ok","rows","drainRows","length","applyCount","labels","value","applyDur","primedAny","row","key","method","route","status_code","L","has","add","gatewayPush","count","dur","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { v4: uuidv4 } = require('uuid')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\nconst {\n HttpMetricsRedisStore,\n buildFieldKey,\n} = require('./httpMetricsRedisStore')\n\n/**\n * Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.\n * From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.\n */\nconst HTTP_PUSH_INSTANCE_ROTATE_MS =\n parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) ||\n 7 * 24 * 60 * 60 * 1000\nconst HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000\n\nclass HttpMetricsRedisCollector extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {import('redis').RedisClient} config.redisClient\n * @param {string} [config.appName] Name of the application (defaults per {@link BaseMetricsClient})\n * @param {string} [config.dynoId] Dyno/instance ID (defaults per {@link BaseMetricsClient})\n * @param {string} [config.processType] Process type for labels (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.enabled] Enable metrics collection (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.logValues] Log metrics values to console (defaults per {@link BaseMetricsClient})\n * @param {string} [config.pushgatewayUrl] VM-agent import URL (defaults per {@link BaseMetricsClient})\n * @param {string} [config.pushgatewaySecret] Basic auth secret, Base64 (defaults per {@link BaseMetricsClient})\n * @param {number} [config.intervalSec] Push interval in seconds (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.removeOldMetrics] Enable clearing old metrics (defaults per {@link BaseMetricsClient})\n * @param {function} [config.startupValidation] Run before first push (defaults per {@link BaseMetricsClient})\n * @param {boolean} [config.disablePushgateway] Skip POST to VM-agent (defaults per {@link BaseMetricsClient})\n * @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).\n * @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.\n *\n * `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).\n */\n constructor(config = {}) {\n const { redisClient } = config\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisCollector: redisClient is required')\n }\n\n super({\n ...config,\n blockNodeDefaultMetrics: true,\n })\n\n const keyProcessType = config.redisProcessTypeForKeys || 'web'\n\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: this.appName,\n processType: keyProcessType,\n ttlSec: config.ttlSec,\n })\n\n const now = Date.now()\n this._httpPushInstanceId = uuidv4()\n this._httpPushInstanceStartedAt = now\n this._httpRotationLastCheck = 0\n\n /** @type {Set<string>} */\n this._httpCounterPrimedKeys = new Set()\n\n const httpLabelNames = this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n 'http_instance_id',\n ])\n\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: httpLabelNames,\n useLabelsWithoutDynoId: true,\n })\n\n this.createCounter({\n name: 'app_requests_total_duration',\n help: 'Total duration of HTTP requests in milliseconds',\n labelNames: httpLabelNames,\n useLabelsWithoutDynoId: true,\n })\n }\n\n _labelsForHttpPush(rowLabels) {\n return {\n ...rowLabels,\n http_instance_id: this._httpPushInstanceId,\n }\n }\n\n /** @param {number} now */\n _rotateHttpPushInstance(now) {\n this._httpPushInstanceId = uuidv4()\n this._httpPushInstanceStartedAt = now\n this.clearAllCounters()\n this._httpCounterPrimedKeys.clear()\n }\n\n /** @param {number} now */\n _maybeRotateHttpPushInstance(now = Date.now()) {\n if (now - this._httpRotationLastCheck < HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS) {\n return\n }\n this._httpRotationLastCheck = now\n\n if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {\n this._rotateHttpPushInstance(now)\n }\n }\n\n /**\n * @returns {Promise<void>}\n */\n pushMetrics = async () => {\n this._maybeRotateHttpPushInstance()\n\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n const { ok, rows } = await this._store.drainRows()\n if (ok && rows.length > 0) {\n const applyCount = (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value)\n const applyDur = (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n\n let primedAny = false\n for (const row of rows) {\n const key = buildFieldKey(\n row.labels.method,\n row.labels.route,\n row.labels.status_code\n )\n const L = this._labelsForHttpPush(row.labels)\n if (!this._httpCounterPrimedKeys.has(key)) {\n this._httpCounterPrimedKeys.add(key)\n applyCount(L, 0)\n applyDur(L, 0)\n primedAny = true\n }\n }\n if (primedAny) {\n await this.gatewayPush()\n }\n for (const row of rows) {\n const L = this._labelsForHttpPush(row.labels)\n applyCount(L, row.count)\n if (row.dur > 0) {\n applyDur(L, row.dur)\n }\n }\n }\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisCollector,\n HTTP_PUSH_INSTANCE_ROTATE_MS,\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,26 +1,21 @@
1
- /**
2
- * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).
3
- * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.
4
- *
5
- * @see HttpMetricsRedisStore
6
- */
7
1
  export class HttpMetricsRedisRecorder {
8
2
  /**
9
- * @param {Object} opts
10
- * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).
11
- * @param {string} [opts.appName] Optional override; default same as {@link BaseMetricsClient}: `BUILD_APP_NAME` or `unknown-app`.
12
- * @param {string} [opts.processType] Redis key segment (default **`web`**, same as {@link HttpMetricsRedisCollector}).
13
- * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).
3
+ * @param {Object} [opts]
4
+ * @param {import('redis').RedisClient} [opts.redisClient] Required if `METRICS_HTTP_ENABLED` env is `true`.
5
+ * @param {string} [opts.appName]
6
+ * @param {string} [opts.processType]
7
+ * @param {number} [opts.ttlSec]
14
8
  */
15
9
  constructor({ redisClient, appName, processType, ttlSec }?: {
16
- redisClient: import('redis').RedisClient;
10
+ redisClient?: import("redis").RedisClient | undefined;
17
11
  appName?: string | undefined;
18
12
  processType?: string | undefined;
19
13
  ttlSec?: number | undefined;
20
- });
14
+ } | undefined);
15
+ _enabled: boolean;
21
16
  processType: string;
22
17
  appName: string;
23
- _store: HttpMetricsRedisStore;
18
+ _store: HttpMetricsRedisStore | null;
24
19
  /**
25
20
  * @param {Object} params
26
21
  * @param {string} params.method
@@ -35,9 +30,6 @@ export class HttpMetricsRedisRecorder {
35
30
  duration: number;
36
31
  }): void;
37
32
  /**
38
- * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.
39
- * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.
40
- *
41
33
  * @param {import('http').IncomingMessage} req
42
34
  * @param {import('http').ServerResponse} res
43
35
  * @param {function} next
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisRecorder.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACb,OAAO;QACP,WAAW;QACX,MAAM;OAgB9B;IARC,oBAA8B;IAC9B,gBAA8B;IAC9B,8BAKE;IAGJ;;;;;;OAMG;IACH;QAL0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACS,QAAQ,EAAvB,MAAM;aAIhB;IAED;;;;;;;OAOG;IACH,kCAJW,OAAO,MAAM,EAAE,eAAe,OAC9B,OAAO,MAAM,EAAE,cAAc,0BAsBvC;CACF"}
1
+ {"version":3,"file":"httpMetricsRedisRecorder.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"names":[],"mappings":"AAEA;IACE;;;;;;OAMG;IACH;;;;;mBA0BC;IAzBC,kBAA2D;IAGzD,oBAA8B;IAC9B,gBAAqE;IACrE,qCAAkB;IAsBtB;;;;;;OAMG;IACH;QAL0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACS,QAAQ,EAAvB,MAAM;aAKhB;IAED;;;;OAIG;IACH,kCAJW,OAAO,MAAM,EAAE,eAAe,OAC9B,OAAO,MAAM,EAAE,cAAc,0BA0BvC;CACF"}
@@ -3,20 +3,13 @@
3
3
  const {
4
4
  HttpMetricsRedisStore
5
5
  } = require('./httpMetricsRedisStore');
6
-
7
- /**
8
- * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).
9
- * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.
10
- *
11
- * @see HttpMetricsRedisStore
12
- */
13
6
  class HttpMetricsRedisRecorder {
14
7
  /**
15
- * @param {Object} opts
16
- * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).
17
- * @param {string} [opts.appName] Optional override; default same as {@link BaseMetricsClient}: `BUILD_APP_NAME` or `unknown-app`.
18
- * @param {string} [opts.processType] Redis key segment (default **`web`**, same as {@link HttpMetricsRedisCollector}).
19
- * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).
8
+ * @param {Object} [opts]
9
+ * @param {import('redis').RedisClient} [opts.redisClient] Required if `METRICS_HTTP_ENABLED` env is `true`.
10
+ * @param {string} [opts.appName]
11
+ * @param {string} [opts.processType]
12
+ * @param {number} [opts.ttlSec]
20
13
  */
21
14
  constructor({
22
15
  redisClient,
@@ -24,8 +17,15 @@ class HttpMetricsRedisRecorder {
24
17
  processType = 'web',
25
18
  ttlSec
26
19
  } = {}) {
20
+ this._enabled = process.env.METRICS_HTTP_ENABLED === 'true';
21
+ if (!this._enabled) {
22
+ this.processType = processType;
23
+ this.appName = appName || process.env.BUILD_APP_NAME || 'unknown-app';
24
+ this._store = null;
25
+ return;
26
+ }
27
27
  if (redisClient == null) {
28
- throw new Error('HttpMetricsRedisRecorder: redisClient is required');
28
+ throw new Error('HttpMetricsRedisRecorder: redisClient is required when METRICS_HTTP_ENABLED=true');
29
29
  }
30
30
  const resolvedAppName = appName || process.env.BUILD_APP_NAME || 'unknown-app';
31
31
  this.processType = processType;
@@ -51,18 +51,20 @@ class HttpMetricsRedisRecorder {
51
51
  status_code,
52
52
  duration
53
53
  }) {
54
+ if (!this._enabled || !this._store) return;
54
55
  this._store.record(method, route, status_code, duration);
55
56
  }
56
57
 
57
58
  /**
58
- * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.
59
- * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.
60
- *
61
59
  * @param {import('http').IncomingMessage} req
62
60
  * @param {import('http').ServerResponse} res
63
61
  * @param {function} next
64
62
  */
65
63
  trackHttpRequestMiddleware = (req, res, next) => {
64
+ if (!this._enabled) {
65
+ next();
66
+ return;
67
+ }
66
68
  if (req.method === 'OPTIONS') {
67
69
  next();
68
70
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisRecorder.js","names":["HttpMetricsRedisStore","require","HttpMetricsRedisRecorder","constructor","redisClient","appName","processType","ttlSec","Error","resolvedAppName","process","env","BUILD_APP_NAME","_store","trackHttpRequest","method","route","status_code","duration","record","trackHttpRequestMiddleware","req","res","next","start","Date","now","on","path","statusCode","module","exports"],"sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"sourcesContent":["const { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')\n\n/**\n * Records HTTP request aggregates only to Redis (no in-process Prometheus counters on this path).\n * Pair with {@link HttpMetricsRedisCollector} on a drain process to flush into counters and push to the VM-agent.\n *\n * @see HttpMetricsRedisStore\n */\nclass HttpMetricsRedisRecorder {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient **Required.** Injected client (same pattern as {@link RedisMetricsClient}).\n * @param {string} [opts.appName] Optional override; default same as {@link BaseMetricsClient}: `BUILD_APP_NAME` or `unknown-app`.\n * @param {string} [opts.processType] Redis key segment (default **`web`**, same as {@link HttpMetricsRedisCollector}).\n * @param {number} [opts.ttlSec] Redis hash key TTL in seconds (sliding; default 120).\n */\n constructor({ redisClient, appName, processType = 'web', ttlSec } = {}) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisRecorder: redisClient is required')\n }\n const resolvedAppName =\n appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.processType = processType\n this.appName = resolvedAppName\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: resolvedAppName,\n processType,\n ttlSec,\n })\n }\n\n /**\n * @param {Object} params\n * @param {string} params.method\n * @param {string} params.route\n * @param {number} params.status_code\n * @param {number} params.duration\n */\n trackHttpRequest({ method, route, status_code, duration }) {\n this._store.record(method, route, status_code, duration)\n }\n\n /**\n * Express middleware: appends a `finish` listener and writes one aggregate row per request to Redis.\n * Does not check `METRICS_ENABLED`; the app should only mount this when HTTP Redis metrics should run.\n *\n * @param {import('http').IncomingMessage} req\n * @param {import('http').ServerResponse} res\n * @param {function} next\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { HttpMetricsRedisRecorder }\n"],"mappings":";;AAAA,MAAM;EAAEA;AAAsB,CAAC,GAAGC,OAAO,CAAC,yBAAyB,CAAC;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA,MAAMC,wBAAwB,CAAC;EAC7B;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAAC;IAAEC,WAAW;IAAEC,OAAO;IAAEC,WAAW,GAAG,KAAK;IAAEC;EAAO,CAAC,GAAG,CAAC,CAAC,EAAE;IACtE,IAAIH,WAAW,IAAI,IAAI,EAAE;MACvB,MAAM,IAAII,KAAK,CAAC,mDAAmD,CAAC;IACtE;IACA,MAAMC,eAAe,GACnBJ,OAAO,IAAIK,OAAO,CAACC,GAAG,CAACC,cAAc,IAAI,aAAa;IACxD,IAAI,CAACN,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACD,OAAO,GAAGI,eAAe;IAC9B,IAAI,CAACI,MAAM,GAAG,IAAIb,qBAAqB,CAAC;MACtCI,WAAW;MACXC,OAAO,EAAEI,eAAe;MACxBH,WAAW;MACXC;IACF,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACEO,gBAAgBA,CAAC;IAAEC,MAAM;IAAEC,KAAK;IAAEC,WAAW;IAAEC;EAAS,CAAC,EAAE;IACzD,IAAI,CAACL,MAAM,CAACM,MAAM,CAACJ,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,CAAC;EAC1D;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;EACEE,0BAA0B,GAAGA,CAACC,GAAG,EAAEC,GAAG,EAAEC,IAAI,KAAK;IAC/C,IAAIF,GAAG,CAACN,MAAM,KAAK,SAAS,EAAE;MAC5BQ,IAAI,CAAC,CAAC;MACN;IACF;IAEA,MAAMC,KAAK,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;IACxBJ,GAAG,CAACK,EAAE,CAAC,QAAQ,EAAE,MAAM;MACrB,MAAMX,KAAK,GAAGK,GAAG,CAACL,KAAK,EAAEY,IAAI,IAAIP,GAAG,CAACO,IAAI,IAAI,SAAS;MAEtD,IAAI,CAACd,gBAAgB,CAAC;QACpBC,MAAM,EAAEM,GAAG,CAACN,MAAM;QAClBC,KAAK;QACLC,WAAW,EAAEK,GAAG,CAACO,UAAU;QAC3BX,QAAQ,EAAEO,IAAI,CAACC,GAAG,CAAC,CAAC,GAAGF;MACzB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFD,IAAI,CAAC,CAAC;EACR,CAAC;AACH;AAEAO,MAAM,CAACC,OAAO,GAAG;EAAE7B;AAAyB,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisRecorder.js","names":["HttpMetricsRedisStore","require","HttpMetricsRedisRecorder","constructor","redisClient","appName","processType","ttlSec","_enabled","process","env","METRICS_HTTP_ENABLED","BUILD_APP_NAME","_store","Error","resolvedAppName","trackHttpRequest","method","route","status_code","duration","record","trackHttpRequestMiddleware","req","res","next","start","Date","now","on","path","statusCode","module","exports"],"sources":["../../src/metrics/httpMetricsRedisRecorder.js"],"sourcesContent":["const { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')\n\nclass HttpMetricsRedisRecorder {\n /**\n * @param {Object} [opts]\n * @param {import('redis').RedisClient} [opts.redisClient] Required if `METRICS_HTTP_ENABLED` env is `true`.\n * @param {string} [opts.appName]\n * @param {string} [opts.processType]\n * @param {number} [opts.ttlSec]\n */\n constructor({ redisClient, appName, processType = 'web', ttlSec } = {}) {\n this._enabled = process.env.METRICS_HTTP_ENABLED === 'true'\n\n if (!this._enabled) {\n this.processType = processType\n this.appName = appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this._store = null\n return\n }\n\n if (redisClient == null) {\n throw new Error(\n 'HttpMetricsRedisRecorder: redisClient is required when METRICS_HTTP_ENABLED=true'\n )\n }\n\n const resolvedAppName =\n appName || process.env.BUILD_APP_NAME || 'unknown-app'\n this.processType = processType\n this.appName = resolvedAppName\n this._store = new HttpMetricsRedisStore({\n redisClient,\n appName: resolvedAppName,\n processType,\n ttlSec,\n })\n }\n\n /**\n * @param {Object} params\n * @param {string} params.method\n * @param {string} params.route\n * @param {number} params.status_code\n * @param {number} params.duration\n */\n trackHttpRequest({ method, route, status_code, duration }) {\n if (!this._enabled || !this._store) return\n this._store.record(method, route, status_code, duration)\n }\n\n /**\n * @param {import('http').IncomingMessage} req\n * @param {import('http').ServerResponse} res\n * @param {function} next\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (!this._enabled) {\n next()\n return\n }\n if (req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { HttpMetricsRedisRecorder }\n"],"mappings":";;AAAA,MAAM;EAAEA;AAAsB,CAAC,GAAGC,OAAO,CAAC,yBAAyB,CAAC;AAEpE,MAAMC,wBAAwB,CAAC;EAC7B;AACF;AACA;AACA;AACA;AACA;AACA;EACEC,WAAWA,CAAC;IAAEC,WAAW;IAAEC,OAAO;IAAEC,WAAW,GAAG,KAAK;IAAEC;EAAO,CAAC,GAAG,CAAC,CAAC,EAAE;IACtE,IAAI,CAACC,QAAQ,GAAGC,OAAO,CAACC,GAAG,CAACC,oBAAoB,KAAK,MAAM;IAE3D,IAAI,CAAC,IAAI,CAACH,QAAQ,EAAE;MAClB,IAAI,CAACF,WAAW,GAAGA,WAAW;MAC9B,IAAI,CAACD,OAAO,GAAGA,OAAO,IAAII,OAAO,CAACC,GAAG,CAACE,cAAc,IAAI,aAAa;MACrE,IAAI,CAACC,MAAM,GAAG,IAAI;MAClB;IACF;IAEA,IAAIT,WAAW,IAAI,IAAI,EAAE;MACvB,MAAM,IAAIU,KAAK,CACb,kFACF,CAAC;IACH;IAEA,MAAMC,eAAe,GACnBV,OAAO,IAAII,OAAO,CAACC,GAAG,CAACE,cAAc,IAAI,aAAa;IACxD,IAAI,CAACN,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACD,OAAO,GAAGU,eAAe;IAC9B,IAAI,CAACF,MAAM,GAAG,IAAIb,qBAAqB,CAAC;MACtCI,WAAW;MACXC,OAAO,EAAEU,eAAe;MACxBT,WAAW;MACXC;IACF,CAAC,CAAC;EACJ;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACES,gBAAgBA,CAAC;IAAEC,MAAM;IAAEC,KAAK;IAAEC,WAAW;IAAEC;EAAS,CAAC,EAAE;IACzD,IAAI,CAAC,IAAI,CAACZ,QAAQ,IAAI,CAAC,IAAI,CAACK,MAAM,EAAE;IACpC,IAAI,CAACA,MAAM,CAACQ,MAAM,CAACJ,MAAM,EAAEC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,CAAC;EAC1D;;EAEA;AACF;AACA;AACA;AACA;EACEE,0BAA0B,GAAGA,CAACC,GAAG,EAAEC,GAAG,EAAEC,IAAI,KAAK;IAC/C,IAAI,CAAC,IAAI,CAACjB,QAAQ,EAAE;MAClBiB,IAAI,CAAC,CAAC;MACN;IACF;IACA,IAAIF,GAAG,CAACN,MAAM,KAAK,SAAS,EAAE;MAC5BQ,IAAI,CAAC,CAAC;MACN;IACF;IAEA,MAAMC,KAAK,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;IACxBJ,GAAG,CAACK,EAAE,CAAC,QAAQ,EAAE,MAAM;MACrB,MAAMX,KAAK,GAAGK,GAAG,CAACL,KAAK,EAAEY,IAAI,IAAIP,GAAG,CAACO,IAAI,IAAI,SAAS;MAEtD,IAAI,CAACd,gBAAgB,CAAC;QACpBC,MAAM,EAAEM,GAAG,CAACN,MAAM;QAClBC,KAAK;QACLC,WAAW,EAAEK,GAAG,CAACO,UAAU;QAC3BX,QAAQ,EAAEO,IAAI,CAACC,GAAG,CAAC,CAAC,GAAGF;MACzB,CAAC,CAAC;IACJ,CAAC,CAAC;IAEFD,IAAI,CAAC,CAAC;EACR,CAAC;AACH;AAEAO,MAAM,CAACC,OAAO,GAAG;EAAE/B;AAAyB,CAAC","ignoreList":[]}
@@ -1,46 +1,8 @@
1
1
  /**
2
- * MetricsClient handles Prometheus metrics collection and push.
3
- * Supports gauges, default process metrics, optional HTTP counters, and custom metrics.
4
- *
5
- * **HTTP metrics:** In-process counters only (`app_requests_*`), gated by `httpMetricsEnabled` /
6
- * `METRICS_HTTP_ENABLED`. For Redis-backed HTTP aggregation (multi-web / cluster), use
7
- * {@link HttpMetricsRedisRecorder} and {@link HttpMetricsRedisCollector} — not this class.
8
- *
2
+ * Process gauges (CPU, memory, event loop lag, uptime, etc.) on top of {@link BaseMetricsClient}.
9
3
  * @extends BaseMetricsClient
10
4
  */
11
5
  export class MetricsClient extends BaseMetricsClient {
12
- /**
13
- * @param {Object} [config]
14
- * @param {string} [config.appName] Name of the application
15
- * @param {string} [config.dynoId] Dyno/instance ID
16
- * @param {string} [config.processType] Process type (web, worker, etc.)
17
- * @param {boolean} [config.enabled] Enable metrics collection
18
- * @param {boolean} [config.httpMetricsEnabled] Enable HTTP request metrics (`app_requests_total`, `app_requests_total_duration`); defaults from `METRICS_HTTP_ENABLED === 'true'`
19
- * @param {boolean} [config.logValues] Log metrics values to console
20
- * @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
21
- * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of `user:password`)
22
- * @param {number} [config.intervalSec] Interval in seconds for pushing metrics
23
- * @param {boolean} [config.removeOldMetrics] Enable to clear metrics by service name
24
- * @param {function} [config.startupValidation] Add to validate on start push
25
- * @param {boolean} [config.disablePushgateway] Disable pushing to VM-agent (use HTTP scraping instead)
26
- * @param {boolean} [config.blockNodeDefaultMetrics] When true, skip prom-client default process metrics (rare; see {@link BaseMetricsClient})
27
- */
28
- constructor(config?: {
29
- appName?: string | undefined;
30
- dynoId?: string | undefined;
31
- processType?: string | undefined;
32
- enabled?: boolean | undefined;
33
- httpMetricsEnabled?: boolean | undefined;
34
- logValues?: boolean | undefined;
35
- pushgatewayUrl?: string | undefined;
36
- pushgatewaySecret?: string | undefined;
37
- intervalSec?: number | undefined;
38
- removeOldMetrics?: boolean | undefined;
39
- startupValidation?: Function | undefined;
40
- disablePushgateway?: boolean | undefined;
41
- blockNodeDefaultMetrics?: boolean | undefined;
42
- } | undefined);
43
- httpMetricsEnabled: boolean;
44
6
  _lastUsageMicros: number;
45
7
  _lastCheckTime: number;
46
8
  /**
@@ -73,30 +35,6 @@ export class MetricsClient extends BaseMetricsClient {
73
35
  * @returns {Promise<number>}
74
36
  */
75
37
  measureLag(): Promise<number>;
76
- /**
77
- * Increment HTTP request counters (in-process). No-op if `httpMetricsEnabled` is false.
78
- *
79
- * @param {Object} params
80
- * @param {string} params.method HTTP method
81
- * @param {string} params.route Route or path pattern
82
- * @param {number} params.status_code HTTP status code
83
- * @param {number} params.duration Duration in milliseconds
84
- */
85
- trackHttpRequest({ method, route, status_code, duration }: {
86
- method: string;
87
- route: string;
88
- status_code: number;
89
- duration: number;
90
- }): void;
91
- /**
92
- * Express middleware: records `app_requests_*` on response finish.
93
- * Skips when disabled, HTTP metrics off, or `OPTIONS`.
94
- *
95
- * @param {import('http').IncomingMessage} req
96
- * @param {import('http').ServerResponse} res
97
- * @param {function} next
98
- */
99
- trackHttpRequestMiddleware: (req: import('http').IncomingMessage, res: import('http').ServerResponse, next: Function) => void;
100
38
  }
101
39
  import { BaseMetricsClient } from "./baseMetricsClient";
102
40
  //# sourceMappingURL=metricsClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metricsClient.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsClient.js"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH;IACE;;;;;;;;;;;;;;;OAeG;IACH;;;;;;;;;;;;;;mBAYC;IATC,4BAGiD;IAEjD,yBAAyB;IACzB,uBAAgC;IAKlC;;;OAGG;IACH,4BA4DC;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;;;;;;;;OAQG;IACH;QAL0B,MAAM,EAArB,MAAM;QACS,KAAK,EAApB,MAAM;QACS,WAAW,EAA1B,MAAM;QACS,QAAQ,EAAvB,MAAM;aAkBhB;IAED;;;;;;;OAOG;IACH,kCAJW,OAAO,MAAM,EAAE,eAAe,OAC9B,OAAO,MAAM,EAAE,cAAc,0BAsBvC;CACF"}
1
+ {"version":3,"file":"metricsClient.d.ts","sourceRoot":"","sources":["../../src/metrics/metricsClient.js"],"names":[],"mappings":"AAIA;;;GAGG;AACH;IAmBI,yBAAyB;IACzB,uBAAgC;IAKlC;;;OAGG;IACH,4BAoCC;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;CACF"}
@@ -7,13 +7,7 @@ const {
7
7
  } = require('./baseMetricsClient');
8
8
 
9
9
  /**
10
- * MetricsClient handles Prometheus metrics collection and push.
11
- * Supports gauges, default process metrics, optional HTTP counters, and custom metrics.
12
- *
13
- * **HTTP metrics:** In-process counters only (`app_requests_*`), gated by `httpMetricsEnabled` /
14
- * `METRICS_HTTP_ENABLED`. For Redis-backed HTTP aggregation (multi-web / cluster), use
15
- * {@link HttpMetricsRedisRecorder} and {@link HttpMetricsRedisCollector} — not this class.
16
- *
10
+ * Process gauges (CPU, memory, event loop lag, uptime, etc.) on top of {@link BaseMetricsClient}.
17
11
  * @extends BaseMetricsClient
18
12
  */
19
13
  class MetricsClient extends BaseMetricsClient {
@@ -23,7 +17,6 @@ class MetricsClient extends BaseMetricsClient {
23
17
  * @param {string} [config.dynoId] Dyno/instance ID
24
18
  * @param {string} [config.processType] Process type (web, worker, etc.)
25
19
  * @param {boolean} [config.enabled] Enable metrics collection
26
- * @param {boolean} [config.httpMetricsEnabled] Enable HTTP request metrics (`app_requests_total`, `app_requests_total_duration`); defaults from `METRICS_HTTP_ENABLED === 'true'`
27
20
  * @param {boolean} [config.logValues] Log metrics values to console
28
21
  * @param {string} [config.pushgatewayUrl] Push URL (VM-agent import endpoint, e.g. .../api/v1/import/prometheus). /metrics is for GET (scrape), not POST (push).
29
22
  * @param {string} [config.pushgatewaySecret] Basic auth secret (Base64 of `user:password`)
@@ -35,7 +28,6 @@ class MetricsClient extends BaseMetricsClient {
35
28
  */
36
29
  constructor(config = {}) {
37
30
  super(config);
38
- this.httpMetricsEnabled = config.httpMetricsEnabled !== undefined ? config.httpMetricsEnabled : process.env.METRICS_HTTP_ENABLED === 'true';
39
31
  this._lastUsageMicros = 0;
40
32
  this._lastCheckTime = Date.now();
41
33
  this._initDefaultMetrics();
@@ -76,20 +68,6 @@ class MetricsClient extends BaseMetricsClient {
76
68
  help: 'How long the process has been running',
77
69
  updateFn: process.uptime
78
70
  });
79
- if (this.httpMetricsEnabled) {
80
- this.createCounter({
81
- name: 'app_requests_total',
82
- help: 'Total number of HTTP requests',
83
- labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code']),
84
- useLabelsWithoutDynoId: true
85
- });
86
- this.createCounter({
87
- name: 'app_requests_total_duration',
88
- help: 'Total duration of HTTP requests in milliseconds',
89
- labelNames: this.withDefaultLabelsWithoutDynoId(['method', 'route', 'status_code']),
90
- useLabelsWithoutDynoId: true
91
- });
92
- }
93
71
  };
94
72
 
95
73
  /**
@@ -178,60 +156,6 @@ class MetricsClient extends BaseMetricsClient {
178
156
  setImmediate(() => resolve(Date.now() - start));
179
157
  });
180
158
  }
181
-
182
- /**
183
- * Increment HTTP request counters (in-process). No-op if `httpMetricsEnabled` is false.
184
- *
185
- * @param {Object} params
186
- * @param {string} params.method HTTP method
187
- * @param {string} params.route Route or path pattern
188
- * @param {number} params.status_code HTTP status code
189
- * @param {number} params.duration Duration in milliseconds
190
- */
191
- trackHttpRequest({
192
- method,
193
- route,
194
- status_code,
195
- duration
196
- }) {
197
- if (!this.httpMetricsEnabled) return;
198
- this.countersFunctions?.app_requests_total({
199
- method,
200
- route,
201
- status_code
202
- });
203
- this.countersFunctions?.app_requests_total_duration({
204
- method,
205
- route,
206
- status_code
207
- }, duration);
208
- }
209
-
210
- /**
211
- * Express middleware: records `app_requests_*` on response finish.
212
- * Skips when disabled, HTTP metrics off, or `OPTIONS`.
213
- *
214
- * @param {import('http').IncomingMessage} req
215
- * @param {import('http').ServerResponse} res
216
- * @param {function} next
217
- */
218
- trackHttpRequestMiddleware = (req, res, next) => {
219
- if (!this.enabled || !this.httpMetricsEnabled || req.method === 'OPTIONS') {
220
- next();
221
- return;
222
- }
223
- const start = Date.now();
224
- res.on('finish', () => {
225
- const route = req.route?.path || req.path || 'unknown';
226
- this.trackHttpRequest({
227
- method: req.method,
228
- route,
229
- status_code: res.statusCode,
230
- duration: Date.now() - start
231
- });
232
- });
233
- next();
234
- };
235
159
  }
236
160
  module.exports = {
237
161
  MetricsClient
@@ -1 +1 @@
1
- {"version":3,"file":"metricsClient.js","names":["fs","require","os","BaseMetricsClient","MetricsClient","constructor","config","httpMetricsEnabled","undefined","process","env","METRICS_HTTP_ENABLED","_lastUsageMicros","_lastCheckTime","Date","now","_initDefaultMetrics","createGauge","name","help","updateFn","getCpuUsagePercent","getAvailableCPUs","getContainerMemoryUsage","measureLag","getContainerMemoryLimit","uptime","createCounter","labelNames","withDefaultLabelsWithoutDynoId","useLabelsWithoutDynoId","stat","readFileSync","match","currentUsage","parseInt","deltaUsage","deltaTime","cpuMaxPath","existsSync","quotaStr","periodStr","trim","split","cpus","length","memoryUsage","rss","path","val","parsed","totalmem","Promise","resolve","start","setImmediate","trackHttpRequest","method","route","status_code","duration","countersFunctions","app_requests_total","app_requests_total_duration","trackHttpRequestMiddleware","req","res","next","enabled","on","statusCode","module","exports"],"sources":["../../src/metrics/metricsClient.js"],"sourcesContent":["const fs = require('fs')\nconst os = require('os')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\n\n/**\n * MetricsClient handles Prometheus metrics collection and push.\n * Supports gauges, default process metrics, optional HTTP counters, and custom metrics.\n *\n * **HTTP metrics:** In-process counters only (`app_requests_*`), gated by `httpMetricsEnabled` /\n * `METRICS_HTTP_ENABLED`. For Redis-backed HTTP aggregation (multi-web / cluster), use\n * {@link HttpMetricsRedisRecorder} and {@link HttpMetricsRedisCollector} — not this class.\n *\n * @extends BaseMetricsClient\n */\nclass MetricsClient extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {string} [config.appName] Name of the application\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Process type (web, worker, etc.)\n * @param {boolean} [config.enabled] Enable metrics collection\n * @param {boolean} [config.httpMetricsEnabled] Enable HTTP request metrics (`app_requests_total`, `app_requests_total_duration`); defaults from `METRICS_HTTP_ENABLED === 'true'`\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 VM-agent (use HTTP scraping instead)\n * @param {boolean} [config.blockNodeDefaultMetrics] When true, skip prom-client default process metrics (rare; see {@link BaseMetricsClient})\n */\n constructor(config = {}) {\n super(config)\n\n this.httpMetricsEnabled =\n config.httpMetricsEnabled !== undefined\n ? config.httpMetricsEnabled\n : process.env.METRICS_HTTP_ENABLED === 'true'\n\n this._lastUsageMicros = 0\n this._lastCheckTime = Date.now()\n\n this._initDefaultMetrics()\n }\n\n /**\n * Register all built-in default Gauges and Counters.\n * @private\n */\n _initDefaultMetrics = () => {\n this.createGauge({\n name: 'app_process_cpu_usage_percent',\n help: 'Current CPU usage of the Node.js process in percent',\n updateFn: this.getCpuUsagePercent,\n })\n\n this.createGauge({\n name: 'app_available_cpu_count',\n help: 'How many CPU cores are available to this process',\n updateFn: this.getAvailableCPUs,\n })\n\n this.createGauge({\n name: 'app_container_memory_usage_bytes',\n help: 'Current container RAM usage from cgroup',\n updateFn: this.getContainerMemoryUsage,\n })\n\n this.createGauge({\n name: 'app_event_loop_lag_ms',\n help: 'Estimated event loop lag in milliseconds',\n updateFn: this.measureLag,\n })\n\n this.createGauge({\n name: 'app_container_memory_limit_bytes',\n help: 'Max RAM available to container from cgroup (memory.max)',\n updateFn: this.getContainerMemoryLimit,\n })\n\n this.createGauge({\n name: 'app_uptime_seconds',\n help: 'How long the process has been running',\n updateFn: process.uptime,\n })\n\n if (this.httpMetricsEnabled) {\n this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n ]),\n useLabelsWithoutDynoId: true,\n })\n\n this.createCounter({\n name: 'app_requests_total_duration',\n help: 'Total duration of HTTP requests in milliseconds',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'status_code',\n ]),\n useLabelsWithoutDynoId: true,\n })\n }\n }\n\n /**\n * Get CPU usage percent (cgroup-aware)\n * @returns {number}\n */\n getCpuUsagePercent = () => {\n try {\n const stat = fs.readFileSync('/sys/fs/cgroup/cpu.stat', 'utf-8')\n const match = stat.match(/usage_usec (\\d+)/)\n if (!match) return 0\n\n const now = Date.now()\n const currentUsage = parseInt(match[1], 10)\n\n if (this._lastUsageMicros === 0) {\n this._lastUsageMicros = currentUsage\n this._lastCheckTime = now\n return 0\n }\n\n const deltaUsage = currentUsage - this._lastUsageMicros\n const deltaTime = now - this._lastCheckTime\n\n this._lastUsageMicros = currentUsage\n this._lastCheckTime = now\n\n return (deltaUsage / (deltaTime * 1000)) * 100\n } catch {\n return 0\n }\n }\n\n /**\n * Available CPU cores (cgroup quota or `os.cpus().length`).\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 * Container memory usage in bytes (`memory.current` or RSS fallback).\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 * Container memory limit in bytes (`memory.max` or host total).\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 * Event loop lag sample in milliseconds.\n * @returns {Promise<number>}\n */\n measureLag() {\n return new Promise(resolve => {\n const start = Date.now()\n setImmediate(() => resolve(Date.now() - start))\n })\n }\n\n /**\n * Increment HTTP request counters (in-process). No-op if `httpMetricsEnabled` is false.\n *\n * @param {Object} params\n * @param {string} params.method HTTP method\n * @param {string} params.route Route or path pattern\n * @param {number} params.status_code HTTP status code\n * @param {number} params.duration Duration in milliseconds\n */\n trackHttpRequest({ method, route, status_code, duration }) {\n if (!this.httpMetricsEnabled) return\n\n this.countersFunctions?.app_requests_total({\n method,\n route,\n status_code,\n })\n this.countersFunctions?.app_requests_total_duration(\n {\n method,\n route,\n status_code,\n },\n duration\n )\n }\n\n /**\n * Express middleware: records `app_requests_*` on response finish.\n * Skips when disabled, HTTP metrics off, or `OPTIONS`.\n *\n * @param {import('http').IncomingMessage} req\n * @param {import('http').ServerResponse} res\n * @param {function} next\n */\n trackHttpRequestMiddleware = (req, res, next) => {\n if (!this.enabled || !this.httpMetricsEnabled || req.method === 'OPTIONS') {\n next()\n return\n }\n\n const start = Date.now()\n res.on('finish', () => {\n const route = req.route?.path || req.path || 'unknown'\n\n this.trackHttpRequest({\n method: req.method,\n route,\n status_code: res.statusCode,\n duration: Date.now() - start,\n })\n })\n\n next()\n }\n}\n\nmodule.exports = { MetricsClient }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"metricsClient.js","names":["fs","require","os","BaseMetricsClient","MetricsClient","constructor","config","_lastUsageMicros","_lastCheckTime","Date","now","_initDefaultMetrics","createGauge","name","help","updateFn","getCpuUsagePercent","getAvailableCPUs","getContainerMemoryUsage","measureLag","getContainerMemoryLimit","process","uptime","stat","readFileSync","match","currentUsage","parseInt","deltaUsage","deltaTime","cpuMaxPath","existsSync","quotaStr","periodStr","trim","split","cpus","length","memoryUsage","rss","path","val","parsed","totalmem","Promise","resolve","start","setImmediate","module","exports"],"sources":["../../src/metrics/metricsClient.js"],"sourcesContent":["const fs = require('fs')\nconst os = require('os')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\n\n/**\n * Process gauges (CPU, memory, event loop lag, uptime, etc.) on top of {@link BaseMetricsClient}.\n * @extends BaseMetricsClient\n */\nclass MetricsClient extends BaseMetricsClient {\n /**\n * @param {Object} [config]\n * @param {string} [config.appName] Name of the application\n * @param {string} [config.dynoId] Dyno/instance ID\n * @param {string} [config.processType] Process type (web, worker, etc.)\n * @param {boolean} [config.enabled] Enable metrics collection\n * @param {boolean} [config.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 VM-agent (use HTTP scraping instead)\n * @param {boolean} [config.blockNodeDefaultMetrics] When true, skip prom-client default process metrics (rare; see {@link BaseMetricsClient})\n */\n constructor(config = {}) {\n super(config)\n\n this._lastUsageMicros = 0\n this._lastCheckTime = Date.now()\n\n this._initDefaultMetrics()\n }\n\n /**\n * Register all built-in default Gauges and Counters.\n * @private\n */\n _initDefaultMetrics = () => {\n this.createGauge({\n name: 'app_process_cpu_usage_percent',\n help: 'Current CPU usage of the Node.js process in percent',\n updateFn: this.getCpuUsagePercent,\n })\n\n this.createGauge({\n name: 'app_available_cpu_count',\n help: 'How many CPU cores are available to this process',\n updateFn: this.getAvailableCPUs,\n })\n\n this.createGauge({\n name: 'app_container_memory_usage_bytes',\n help: 'Current container RAM usage from cgroup',\n updateFn: this.getContainerMemoryUsage,\n })\n\n this.createGauge({\n name: 'app_event_loop_lag_ms',\n help: 'Estimated event loop lag in milliseconds',\n updateFn: this.measureLag,\n })\n\n this.createGauge({\n name: 'app_container_memory_limit_bytes',\n help: 'Max RAM available to container from cgroup (memory.max)',\n updateFn: this.getContainerMemoryLimit,\n })\n\n this.createGauge({\n name: 'app_uptime_seconds',\n help: 'How long the process has been running',\n updateFn: process.uptime,\n })\n }\n\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 * Available CPU cores (cgroup quota or `os.cpus().length`).\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 * Container memory usage in bytes (`memory.current` or RSS fallback).\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 * Container memory limit in bytes (`memory.max` or host total).\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 * Event loop lag sample in milliseconds.\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\nmodule.exports = { MetricsClient }\n"],"mappings":"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","ignoreList":[]}