@adalo/metrics 0.0.0-staging.32 → 0.0.0-staging.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/httpMetricsRedisCollector.test.js +47 -27
- package/lib/metrics/httpMetricsRedisCollector.d.ts +5 -2
- package/lib/metrics/httpMetricsRedisCollector.d.ts.map +1 -1
- package/lib/metrics/httpMetricsRedisCollector.js +7 -8
- package/lib/metrics/httpMetricsRedisCollector.js.map +1 -1
- package/package.json +2 -2
- package/src/metrics/httpMetricsRedisCollector.js +9 -17
|
@@ -129,34 +129,54 @@ describe('HttpMetricsRedisCollector', () => {
|
|
|
129
129
|
})
|
|
130
130
|
|
|
131
131
|
it('uses METRICS_HTTP_INSTANCE_ROTATE_MS when set (ms)', async () => {
|
|
132
|
-
process.env.METRICS_HTTP_INSTANCE_ROTATE_MS
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
const savedRotate = process.env.METRICS_HTTP_INSTANCE_ROTATE_MS
|
|
133
|
+
// Rotation ms is read at module load; isolateModules loads collector after env is set.
|
|
134
|
+
let IsolatedCollector
|
|
135
|
+
let isolatedRotateMs
|
|
136
|
+
jest.isolateModules(() => {
|
|
137
|
+
process.env.METRICS_HTTP_INSTANCE_ROTATE_MS = '500'
|
|
138
|
+
process.env.METRICS_DISABLE_PUSHGATEWAY = 'true'
|
|
139
|
+
const m = require('../src/metrics/httpMetricsRedisCollector')
|
|
140
|
+
IsolatedCollector = m.HttpMetricsRedisCollector
|
|
141
|
+
isolatedRotateMs = m.HTTP_PUSH_INSTANCE_ROTATE_MS
|
|
140
142
|
})
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
143
|
+
try {
|
|
144
|
+
expect(isolatedRotateMs).toBe(500)
|
|
145
|
+
|
|
146
|
+
const redis = createRedisV3Mock()
|
|
147
|
+
const field = buildFieldKey('GET', '/x', 200)
|
|
148
|
+
redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
|
|
149
|
+
cb(null, [
|
|
150
|
+
[field, '1'],
|
|
151
|
+
[field, '0'],
|
|
152
|
+
])
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const collector = new IsolatedCollector({
|
|
156
|
+
redisClient: redis,
|
|
157
|
+
appName: 'test-app',
|
|
158
|
+
processType: 'http-metrics',
|
|
159
|
+
redisProcessTypeForKeys: 'web',
|
|
160
|
+
disablePushgateway: true,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const clearSpy = jest.spyOn(collector, 'clearAllCounters')
|
|
164
|
+
await collector.pushMetrics()
|
|
165
|
+
collector._httpPushInstanceStartedAt = Date.now() - 501
|
|
166
|
+
collector._httpRotationLastCheck = 0
|
|
167
|
+
redis.eval.mockImplementation((lua, numKeys, k1, k2, cb) => {
|
|
168
|
+
cb(null, [[], []])
|
|
169
|
+
})
|
|
170
|
+
await collector.pushMetrics()
|
|
171
|
+
expect(clearSpy).toHaveBeenCalled()
|
|
172
|
+
clearSpy.mockRestore()
|
|
173
|
+
} finally {
|
|
174
|
+
if (savedRotate === undefined) {
|
|
175
|
+
delete process.env.METRICS_HTTP_INSTANCE_ROTATE_MS
|
|
176
|
+
} else {
|
|
177
|
+
process.env.METRICS_HTTP_INSTANCE_ROTATE_MS = savedRotate
|
|
178
|
+
}
|
|
179
|
+
}
|
|
160
180
|
})
|
|
161
181
|
|
|
162
182
|
it('primes new label sets with 0 then applies count (extra push only when labels are new)', async () => {
|
|
@@ -16,7 +16,7 @@ export class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
16
16
|
* @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
|
|
17
17
|
* @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
19
|
+
* `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
|
|
20
20
|
*/
|
|
21
21
|
constructor(config?: {
|
|
22
22
|
redisClient: import('redis').RedisClient;
|
|
@@ -46,7 +46,10 @@ export class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
46
46
|
/** @param {number} now */
|
|
47
47
|
_maybeRotateHttpPushInstance(now?: number): void;
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.
|
|
51
|
+
* From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.
|
|
52
|
+
*/
|
|
50
53
|
export const HTTP_PUSH_INSTANCE_ROTATE_MS: number;
|
|
51
54
|
import { BaseMetricsClient } from "./baseMetricsClient";
|
|
52
55
|
import { HttpMetricsRedisStore } from "./httpMetricsRedisStore";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAgBA;IACE;;;;;;;;;;;;;;;;;;OAkBG;IACH;qBAjBW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBAiErC;IAnCC,8BAKE;IAGF,yBAAmC;IACnC,mCAAqC;IACrC,+BAA+B;IAE/B,0BAA0B;IAC1B,wBADW,IAAI,MAAM,CAAC,CACiB;IAwBzC,wCAKC;IAED,0BAA0B;IAC1B,6BADY,MAAM,QAMjB;IAED,0BAA0B;IAC1B,mCADY,MAAM,QAUjB;CAiDF;AAzJD;;;GAGG;AACH,kDAEyB"}
|
|
@@ -11,13 +11,12 @@ const {
|
|
|
11
11
|
buildFieldKey
|
|
12
12
|
} = require('./httpMetricsRedisStore');
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
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.
|
|
17
|
+
*/
|
|
18
|
+
const HTTP_PUSH_INSTANCE_ROTATE_MS = parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) || 7 * 24 * 60 * 60 * 1000;
|
|
16
19
|
const HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000;
|
|
17
|
-
function effectiveHttpPushInstanceRotateMs() {
|
|
18
|
-
const envMs = parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10);
|
|
19
|
-
return Number.isFinite(envMs) && envMs > 0 ? envMs : HTTP_PUSH_INSTANCE_ROTATE_MS;
|
|
20
|
-
}
|
|
21
20
|
class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
22
21
|
/**
|
|
23
22
|
* @param {Object} [config]
|
|
@@ -36,7 +35,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
36
35
|
* @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
|
|
37
36
|
* @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
|
|
38
37
|
*
|
|
39
|
-
*
|
|
38
|
+
* `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
|
|
40
39
|
*/
|
|
41
40
|
constructor(config = {}) {
|
|
42
41
|
const {
|
|
@@ -98,7 +97,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
98
97
|
return;
|
|
99
98
|
}
|
|
100
99
|
this._httpRotationLastCheck = now;
|
|
101
|
-
if (now - this._httpPushInstanceStartedAt >=
|
|
100
|
+
if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {
|
|
102
101
|
this._rotateHttpPushInstance(now);
|
|
103
102
|
}
|
|
104
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpMetricsRedisCollector.js","names":["v4","uuidv4","require","BaseMetricsClient","HttpMetricsRedisStore","buildFieldKey","HTTP_PUSH_INSTANCE_ROTATE_MS","HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS","effectiveHttpPushInstanceRotateMs","envMs","parseInt","process","env","METRICS_HTTP_INSTANCE_ROTATE_MS","Number","isFinite","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/** Default interval before a new `http_instance_id` and in-memory counter reset (overridable by env). */\nconst HTTP_PUSH_INSTANCE_ROTATE_MS = 7 * 24 * 60 * 60 * 1000\nconst HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000\n\nfunction effectiveHttpPushInstanceRotateMs() {\n const envMs = parseInt(\n process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '',\n 10\n )\n return Number.isFinite(envMs) && envMs > 0\n ? envMs\n : HTTP_PUSH_INSTANCE_ROTATE_MS\n}\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 * Env `METRICS_HTTP_INSTANCE_ROTATE_MS`: optional positive integer (ms) for `http_instance_id` rotation; default is {@link HTTP_PUSH_INSTANCE_ROTATE_MS}.\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 (\n now - this._httpPushInstanceStartedAt >=\n effectiveHttpPushInstanceRotateMs()\n ) {\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
|
+
{"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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adalo/metrics",
|
|
3
|
-
"version": "0.0.0-staging.
|
|
3
|
+
"version": "0.0.0-staging.33",
|
|
4
4
|
"description": "Reusable metrics utilities for Node.js and Laravel apps",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"bee-queue": "^1.2.2",
|
|
28
28
|
"dotenv": "^8.2.0",
|
|
29
29
|
"mysql2": "^3.11.0",
|
|
30
|
-
"pg": "
|
|
30
|
+
"pg": "8.11.3",
|
|
31
31
|
"prom-client": "^15.1.3",
|
|
32
32
|
"uuid": "9.0.1"
|
|
33
33
|
},
|
|
@@ -5,20 +5,15 @@ const {
|
|
|
5
5
|
buildFieldKey,
|
|
6
6
|
} = require('./httpMetricsRedisStore')
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Ms between `http_instance_id` rotation + in-memory counter reset in {@link HttpMetricsRedisCollector}.
|
|
10
|
+
* From `process.env.METRICS_HTTP_INSTANCE_ROTATE_MS` if positive integer, else 7 days. Resolved at module load.
|
|
11
|
+
*/
|
|
12
|
+
const HTTP_PUSH_INSTANCE_ROTATE_MS =
|
|
13
|
+
parseInt(process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '', 10) ||
|
|
14
|
+
7 * 24 * 60 * 60 * 1000
|
|
10
15
|
const HTTP_PUSH_ROTATION_CHECK_THROTTLE_MS = 60 * 1000
|
|
11
16
|
|
|
12
|
-
function effectiveHttpPushInstanceRotateMs() {
|
|
13
|
-
const envMs = parseInt(
|
|
14
|
-
process.env.METRICS_HTTP_INSTANCE_ROTATE_MS || '',
|
|
15
|
-
10
|
|
16
|
-
)
|
|
17
|
-
return Number.isFinite(envMs) && envMs > 0
|
|
18
|
-
? envMs
|
|
19
|
-
: HTTP_PUSH_INSTANCE_ROTATE_MS
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
23
18
|
/**
|
|
24
19
|
* @param {Object} [config]
|
|
@@ -37,7 +32,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
37
32
|
* @param {string} [config.redisProcessTypeForKeys] Segment in Redis keys for HTTP hashes (default `web`).
|
|
38
33
|
* @param {number} [config.ttlSec] Redis hash TTL in seconds; should match writers’ {@link HttpMetricsRedisStore}.
|
|
39
34
|
*
|
|
40
|
-
*
|
|
35
|
+
* `http_instance_id` rotation interval: {@link HTTP_PUSH_INSTANCE_ROTATE_MS} (set at process start from env, see constant JSDoc).
|
|
41
36
|
*/
|
|
42
37
|
constructor(config = {}) {
|
|
43
38
|
const { redisClient } = config
|
|
@@ -111,10 +106,7 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
|
|
|
111
106
|
}
|
|
112
107
|
this._httpRotationLastCheck = now
|
|
113
108
|
|
|
114
|
-
if (
|
|
115
|
-
now - this._httpPushInstanceStartedAt >=
|
|
116
|
-
effectiveHttpPushInstanceRotateMs()
|
|
117
|
-
) {
|
|
109
|
+
if (now - this._httpPushInstanceStartedAt >= HTTP_PUSH_INSTANCE_ROTATE_MS) {
|
|
118
110
|
this._rotateHttpPushInstance(now)
|
|
119
111
|
}
|
|
120
112
|
}
|