@adalo/metrics 0.0.0-staging.19 → 0.0.0-staging.20

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.
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAeA;;;;;;;GAOG;AACH;IACE;;;;;;;;;;;;;;;;OAgBG;IACH;qBAfW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBAoErC;IAhCC,8BAKE;CA6DL"}
1
+ {"version":3,"file":"httpMetricsRedisCollector.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisCollector.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH;IACE;;;;;;;;;;;;;;;;OAgBG;IACH;qBAfW,OAAO,OAAO,EAAE,WAAW;;;;;;;;;;;;;;mBAoErC;IAhCC,8BAKE;CAkEL"}
@@ -1,22 +1,11 @@
1
1
  "use strict";
2
2
 
3
- const cluster = require('cluster');
4
3
  const {
5
4
  BaseMetricsClient
6
5
  } = require('./baseMetricsClient');
7
6
  const {
8
7
  HttpMetricsRedisStore
9
8
  } = require('./httpMetricsRedisStore');
10
- function clusterWorkerTag() {
11
- try {
12
- if (cluster.isWorker && cluster.worker) {
13
- return ` cluster_worker_id=${cluster.worker.id}`;
14
- }
15
- } catch (_) {
16
- /* ignore */
17
- }
18
- return '';
19
- }
20
9
 
21
10
  /**
22
11
  * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),
@@ -85,21 +74,24 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
85
74
  * @returns {Promise<void>}
86
75
  */
87
76
  pushMetrics = async () => {
88
- const {
89
- pid
90
- } = process;
91
- const clusterTag = clusterWorkerTag();
92
- // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
93
- console.warn(`[TEMP_HTTP_METRICS_DIAG] http_metrics_publish_start pid=${pid}${clusterTag} dyno_id=${this.dynoId} process_type=${this.processType} app=${this.appName}`);
94
- // --- end TEMP_HTTP_METRICS_DIAG ---
95
77
  if (this._store && this.countersFunctions?.app_requests_total && this.countersFunctions?.app_requests_total_duration) {
96
- await this._store.flushToCounters((labels, value) => this.countersFunctions.app_requests_total(labels, value), (labels, value) => this.countersFunctions.app_requests_total_duration(labels, value));
78
+ const {
79
+ pid
80
+ } = process;
81
+ const t0 = Date.now();
82
+ // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
83
+ console.warn(`[TEMP_HTTP_METRICS_DIAG] http_collector_flush_begin pid=${pid} dyno_id=${this.dynoId} process_type=${this.processType} app=${this.appName}`);
84
+ // --- end TEMP_HTTP_METRICS_DIAG ---
85
+ const drainedOk = await this._store.flushToCounters((labels, value) => this.countersFunctions.app_requests_total(labels, value), (labels, value) => this.countersFunctions.app_requests_total_duration(labels, value));
86
+ // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
87
+ console.warn(`[TEMP_HTTP_METRICS_DIAG] http_collector_flush_end pid=${pid} lock_acquired_and_ran_drain=${drainedOk} elapsed_ms=${Date.now() - t0}`);
88
+ // --- end TEMP_HTTP_METRICS_DIAG ---
89
+ } else {
90
+ // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
91
+ console.warn(`[TEMP_HTTP_METRICS_DIAG] http_collector_flush_skipped pid=${process.pid} reason=missing_store_or_http_counters`);
92
+ // --- end TEMP_HTTP_METRICS_DIAG ---
97
93
  }
98
- const out = await this._pushMetrics();
99
- // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
100
- console.warn(`[TEMP_HTTP_METRICS_DIAG] http_metrics_publish_finished pid=${pid}${clusterTag} dyno_id=${this.dynoId} (registry POST to VM-agent / import completed for this tick)`);
101
- // --- end TEMP_HTTP_METRICS_DIAG ---
102
- return out;
94
+ return this._pushMetrics();
103
95
  };
104
96
  }
105
97
  module.exports = {
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisCollector.js","names":["cluster","require","BaseMetricsClient","HttpMetricsRedisStore","clusterWorkerTag","isWorker","worker","id","_","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","process","env","METRICS_HTTP_REDIS_KEY_PROCESS_TYPE","defaultLabelsWithoutDynoId","app","appName","process_type","_store","processType","ttlSec","createCounter","name","help","labelNames","withDefaultLabelsWithoutDynoId","useLabelsWithoutDynoId","pushMetrics","pid","clusterTag","console","warn","dynoId","countersFunctions","app_requests_total","app_requests_total_duration","flushToCounters","labels","value","out","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const cluster = require('cluster')\nconst { BaseMetricsClient } = require('./baseMetricsClient')\nconst { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')\n\nfunction clusterWorkerTag() {\n try {\n if (cluster.isWorker && cluster.worker) {\n return ` cluster_worker_id=${cluster.worker.id}`\n }\n } catch (_) {\n /* ignore */\n }\n return ''\n}\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 * Always passes `blockNodeDefaultMetrics: true` to the base client (HTTP-only registry; no default Node heap/event-loop metrics).\n * Redis key segment must match writers (`appName` + `processType` / {@link HttpMetricsRedisStore}).\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`; env `METRICS_HTTP_REDIS_KEY_PROCESS_TYPE` overrides)\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 =\n config.redisProcessTypeForKeys ||\n process.env.METRICS_HTTP_REDIS_KEY_PROCESS_TYPE ||\n '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 this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\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 'appId',\n 'databaseId',\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 const { pid } = process\n const clusterTag = clusterWorkerTag()\n // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---\n console.warn(\n `[TEMP_HTTP_METRICS_DIAG] http_metrics_publish_start pid=${pid}${clusterTag} dyno_id=${this.dynoId} process_type=${this.processType} app=${this.appName}`\n )\n // --- end TEMP_HTTP_METRICS_DIAG ---\n if (\n this._store &&\n this.countersFunctions?.app_requests_total &&\n this.countersFunctions?.app_requests_total_duration\n ) {\n await this._store.flushToCounters(\n (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value),\n (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n )\n }\n const out = await this._pushMetrics()\n // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---\n console.warn(\n `[TEMP_HTTP_METRICS_DIAG] http_metrics_publish_finished pid=${pid}${clusterTag} dyno_id=${this.dynoId} (registry POST to VM-agent / import completed for this tick)`\n )\n // --- end TEMP_HTTP_METRICS_DIAG ---\n return out\n }\n}\n\nmodule.exports = { HttpMetricsRedisCollector }\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisCollector.js","names":["BaseMetricsClient","require","HttpMetricsRedisStore","HttpMetricsRedisCollector","constructor","config","redisClient","Error","blockNodeDefaultMetrics","keyProcessType","redisProcessTypeForKeys","process","env","METRICS_HTTP_REDIS_KEY_PROCESS_TYPE","defaultLabelsWithoutDynoId","app","appName","process_type","_store","processType","ttlSec","createCounter","name","help","labelNames","withDefaultLabelsWithoutDynoId","useLabelsWithoutDynoId","pushMetrics","countersFunctions","app_requests_total","app_requests_total_duration","pid","t0","Date","now","console","warn","dynoId","drainedOk","flushToCounters","labels","value","_pushMetrics","module","exports"],"sources":["../../src/metrics/httpMetricsRedisCollector.js"],"sourcesContent":["const { BaseMetricsClient } = require('./baseMetricsClient')\nconst { HttpMetricsRedisStore } = 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 * Always passes `blockNodeDefaultMetrics: true` to the base client (HTTP-only registry; no default Node heap/event-loop metrics).\n * Redis key segment must match writers (`appName` + `processType` / {@link HttpMetricsRedisStore}).\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`; env `METRICS_HTTP_REDIS_KEY_PROCESS_TYPE` overrides)\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 =\n config.redisProcessTypeForKeys ||\n process.env.METRICS_HTTP_REDIS_KEY_PROCESS_TYPE ||\n '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 this.createCounter({\n name: 'app_requests_total',\n help: 'Total number of HTTP requests',\n labelNames: this.withDefaultLabelsWithoutDynoId([\n 'method',\n 'route',\n 'appId',\n 'databaseId',\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 'appId',\n 'databaseId',\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 { pid } = process\n const t0 = Date.now()\n // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---\n console.warn(\n `[TEMP_HTTP_METRICS_DIAG] http_collector_flush_begin pid=${pid} dyno_id=${this.dynoId} process_type=${this.processType} app=${this.appName}`\n )\n // --- end TEMP_HTTP_METRICS_DIAG ---\n const drainedOk = await this._store.flushToCounters(\n (labels, value) =>\n this.countersFunctions.app_requests_total(labels, value),\n (labels, value) =>\n this.countersFunctions.app_requests_total_duration(labels, value)\n )\n // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---\n console.warn(\n `[TEMP_HTTP_METRICS_DIAG] http_collector_flush_end pid=${pid} lock_acquired_and_ran_drain=${drainedOk} elapsed_ms=${Date.now() - t0}`\n )\n // --- end TEMP_HTTP_METRICS_DIAG ---\n } else {\n // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---\n console.warn(\n `[TEMP_HTTP_METRICS_DIAG] http_collector_flush_skipped pid=${process.pid} reason=missing_store_or_http_counters`\n )\n // --- end TEMP_HTTP_METRICS_DIAG ---\n }\n return this._pushMetrics()\n }\n}\n\nmodule.exports = { HttpMetricsRedisCollector }\n"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisStore.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisStore.js"],"names":[],"mappings":"AAkEA;;;GAGG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACd,OAAO,EAApB,MAAM;QACO,WAAW,EAAxB,MAAM;QACQ,MAAM;OAiB9B;IAXC,qCAA0B;IAC1B,eAGwC;IAIxC,iBAAiD;IACjD,eAA6C;IAC7C,gBAAqD;IAGvD;;;OAGG;IACH,sBAEC;IAED;;;;;;;OAOG;IACH,eAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,cACN,MAAM,QA4BhB;IAED;;;;OAIG;IACH,qCAJoB,MAAM,SAAS,MAAM,KAAK,IAAI,0BAC9B,MAAM,SAAS,MAAM,KAAK,IAAI,GACrC,QAAQ,OAAO,CAAC,CAwH5B;CACF;AAtOD;;;;;;;GAOG;AACH,sCAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,GACJ,MAAM,CAIlB;AA3CD;;;GAGG;AACH,wBAFU,MAAM,CAEQ;AAiBxB;;GAEG;AACH,wCAFa,OAAO,CASnB;AAzBD;;;GAGG;AACH,iDAFU,MAAM,CAE8B"}
1
+ {"version":3,"file":"httpMetricsRedisStore.d.ts","sourceRoot":"","sources":["../../src/metrics/httpMetricsRedisStore.js"],"names":[],"mappings":"AAkEA;;;GAGG;AACH;IACE;;;;;;OAMG;IACH;QAL6C,WAAW,EAA7C,OAAO,OAAO,EAAE,WAAW;QACd,OAAO,EAApB,MAAM;QACO,WAAW,EAAxB,MAAM;QACQ,MAAM;OAiB9B;IAXC,qCAA0B;IAC1B,eAGwC;IAIxC,iBAAiD;IACjD,eAA6C;IAC7C,gBAAqD;IAGvD;;;OAGG;IACH,sBAEC;IAED;;;;;;;OAOG;IACH,eAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,cACN,MAAM,QA4BhB;IAED;;;;OAIG;IACH,qCAJoB,MAAM,SAAS,MAAM,KAAK,IAAI,0BAC9B,MAAM,SAAS,MAAM,KAAK,IAAI,GACrC,QAAQ,OAAO,CAAC,CA2H5B;CACF;AAzOD;;;;;;;GAOG;AACH,sCAPW,MAAM,SACN,MAAM,cACN,MAAM,SACN,MAAM,cACN,MAAM,GACJ,MAAM,CAIlB;AA3CD;;;GAGG;AACH,wBAFU,MAAM,CAEQ;AAiBxB;;GAEG;AACH,wCAFa,OAAO,CASnB;AAzBD;;;GAGG;AACH,iDAFU,MAAM,CAE8B"}
@@ -194,9 +194,14 @@ class HttpMetricsRedisStore {
194
194
  applyDuration(labels, dur);
195
195
  }
196
196
  }
197
- const emptyHint = fieldKeys.length === 0 ? 'no_hash_entries' : totalUnits === 0 ? 'entries_present_but_zero_counts' : 'ok';
198
- const noTrafficHint = fieldKeys.length === 0 ? ' | if web had traffic: check web uses HttpMetricsRedisRecorder (METRICS_HTTP_ENABLED, not METRICS_HTTP_USE_BUILTIN_COUNTERS), same REDIS, BUILD_APP_NAME, key segment `web`' : '';
199
- tempHttpDiagLog(`http_redis_drain_collected pid=${process.pid} countKey=${this.countKey} durKey=${this.durKey} hash_fields_read=${fieldKeys.length} label_groups_applied=${appliedGroups} sum_request_counts=${totalUnits} state=${emptyHint} sample=${samples.join(' | ') || '—'}${noTrafficHint}`);
197
+ let emptyHint = 'ok';
198
+ if (fieldKeys.length === 0) {
199
+ emptyHint = 'no_hash_entries';
200
+ } else if (totalUnits === 0) {
201
+ emptyHint = 'entries_present_but_zero_counts';
202
+ }
203
+ const noTrafficHint = fieldKeys.length === 0 ? ' hint=web_must_use_HttpMetricsRedisRecorder_same_redis_APP_web_segment' : '';
204
+ tempHttpDiagLog(truncateForDiag(`http_redis_drain_collected pid=${process.pid} countKey=${this.countKey} durKey=${this.durKey} hash_fields_read=${fieldKeys.length} label_groups_applied=${appliedGroups} sum_request_counts=${totalUnits} state=${emptyHint} sample=${samples.join(' | ') || '—'}${noTrafficHint}`, 900));
200
205
  } catch (e) {
201
206
  console.error('[HttpMetricsRedisStore] flush apply failed:', e.message);
202
207
  tempHttpDiagLog(`http_redis_drain_apply_failed pid=${process.pid} ${e.message}`);
@@ -1 +1 @@
1
- {"version":3,"file":"httpMetricsRedisStore.js","names":["FIELD_SEP","DEFAULT_HTTP_METRICS_REDIS_TTL_SEC","DRAIN_LUA","isRedisPeerInstalled","require","resolve","buildFieldKey","method","route","statusCode","appId","databaseId","String","join","hgetallPairsToObject","pairs","o","length","i","tempHttpDiagLog","message","console","warn","truncateForDiag","s","maxLen","t","slice","HttpMetricsRedisStore","constructor","redisClient","appName","processType","ttlSec","Error","_client","keySeg","encodeURIComponent","countKey","durKey","lockKey","_ensureClient","record","durationMs","client","field","dur","Math","max","round","Number","process","pid","multi","hincrby","expire","exec","err","error","e","flushToCounters","applyCount","applyDuration","Promise","set","setErr","ok","eval","evalErr","raw","finish","del","Array","isArray","counts","durs","fieldKeys","Object","keys","totalUnits","appliedGroups","samples","count","parseInt","parts","split","m","statusStr","aid","did","push","labels","status_code","emptyHint","noTrafficHint","module","exports"],"sources":["../../src/metrics/httpMetricsRedisStore.js"],"sourcesContent":["/**\n * Record separator for hash fields (avoids collisions when route contains \"|\").\n * @type {string}\n */\nconst FIELD_SEP = '\\x1e'\n\n/**\n * Default Redis key TTL in seconds (sliding: refreshed on each `record` write).\n * @type {number}\n */\nconst DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120\n\nconst DRAIN_LUA = `\nlocal function drain(key)\n local v = redis.call('HGETALL', key)\n redis.call('DEL', key)\n return v\nend\nreturn {drain(KEYS[1]), drain(KEYS[2])}\n`\n\n/**\n * @returns {boolean} Whether the `redis` npm package is resolvable (optional peer).\n */\nfunction isRedisPeerInstalled() {\n try {\n require.resolve('redis')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @returns {string}\n */\nfunction buildFieldKey(method, route, statusCode, appId, databaseId) {\n return [method, route, String(statusCode), appId, databaseId].join(FIELD_SEP)\n}\n\nfunction hgetallPairsToObject(pairs) {\n const o = {}\n if (!pairs || !pairs.length) {\n return o\n }\n for (let i = 0; i < pairs.length; i += 2) {\n o[pairs[i]] = pairs[i + 1]\n }\n return o\n}\n\n/** --- TEMP_HTTP_METRICS_DIAG: delete this helper and all calls when done debugging --- */\nfunction tempHttpDiagLog(message) {\n console.warn(`[TEMP_HTTP_METRICS_DIAG] ${message}`)\n}\n\nfunction truncateForDiag(s, maxLen = 220) {\n const t = String(s)\n return t.length > maxLen ? `${t.slice(0, maxLen - 3)}...` : t\n}\n\n/**\n * Redis HTTP aggregate store. Uses an **injected** client (same pattern as {@link RedisMetricsClient}).\n * Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.\n */\nclass HttpMetricsRedisStore {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient\n * @param {string} opts.appName BUILD_APP_NAME (key segment)\n * @param {string} opts.processType logical process for key (e.g. web)\n * @param {number} [opts.ttlSec] Expire keys after this many seconds (default 120)\n */\n constructor({ redisClient, appName, processType, ttlSec }) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisStore: redisClient is required')\n }\n this._client = redisClient\n this.ttlSec =\n typeof ttlSec === 'number' && ttlSec > 0\n ? ttlSec\n : DEFAULT_HTTP_METRICS_REDIS_TTL_SEC\n const keySeg = `${encodeURIComponent(appName)}:${encodeURIComponent(\n processType\n )}`\n this.countKey = `metrics:http:v2:${keySeg}:count`\n this.durKey = `metrics:http:v2:${keySeg}:dur`\n this.lockKey = `metrics:http:v2:${keySeg}:drain_lock`\n }\n\n /**\n * @returns {import('redis').RedisClient}\n * @private\n */\n _ensureClient() {\n return this._client\n }\n\n /**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @param {number} durationMs\n */\n record(method, route, statusCode, appId, databaseId, durationMs) {\n try {\n const client = this._ensureClient()\n const field = buildFieldKey(method, route, statusCode, appId, databaseId)\n const dur = Math.max(0, Math.round(Number(durationMs) || 0))\n tempHttpDiagLog(\n `http_redis_write_prepare pid=${process.pid} countKey=${\n this.countKey\n } durKey=${this.durKey} hincr_field=${truncateForDiag(\n field\n )} durationMs=${dur}`\n )\n client\n .multi()\n .hincrby(this.countKey, field, 1)\n .hincrby(this.durKey, field, dur)\n .expire(this.countKey, this.ttlSec)\n .expire(this.durKey, this.ttlSec)\n .exec(err => {\n if (err) {\n console.error('[HttpMetricsRedisStore] record failed:', err.message)\n }\n })\n } catch (e) {\n console.error('[HttpMetricsRedisStore] record:', e.message)\n }\n }\n\n /**\n * @param {(labels: Object, value: number) => void} applyCount\n * @param {(labels: Object, value: number) => void} applyDuration\n * @returns {Promise<boolean>}\n */\n flushToCounters(applyCount, applyDuration) {\n let client\n try {\n client = this._ensureClient()\n } catch (e) {\n console.error('[HttpMetricsRedisStore] flush:', e.message)\n return Promise.resolve(false)\n }\n return new Promise(resolve => {\n client.set(this.lockKey, '1', 'EX', 25, 'NX', (setErr, ok) => {\n if (setErr || ok !== 'OK') {\n tempHttpDiagLog(\n `http_redis_drain_skip pid=${process.pid} countKey=${\n this.countKey\n } reason=${setErr ? setErr.message : 'lock_not_held'}`\n )\n resolve(false)\n return\n }\n client.eval(\n DRAIN_LUA,\n 2,\n this.countKey,\n this.durKey,\n (evalErr, raw) => {\n const finish = () => {\n client.del(this.lockKey, () => resolve(true))\n }\n\n if (evalErr) {\n console.error(\n '[HttpMetricsRedisStore] drain failed:',\n evalErr.message\n )\n tempHttpDiagLog(\n `http_redis_drain_got_error pid=${process.pid} message=${evalErr.message}`\n )\n finish()\n return\n }\n\n try {\n if (!raw || !Array.isArray(raw) || raw.length < 2) {\n tempHttpDiagLog(\n `http_redis_drain_collected pid=${process.pid} countKey=${\n this.countKey\n } hash_fields=0 sum_request_counts=0 (empty — keys deleted or no data)`\n )\n finish()\n return\n }\n const counts = hgetallPairsToObject(raw[0])\n const durs = hgetallPairsToObject(raw[1])\n const fieldKeys = Object.keys(counts)\n let totalUnits = 0\n let appliedGroups = 0\n const samples = []\n for (const field of fieldKeys) {\n const count = parseInt(counts[field], 10)\n if (!count || count < 1) {\n continue\n }\n const dur = parseInt(durs[field] || '0', 10) || 0\n const parts = field.split(FIELD_SEP)\n if (parts.length !== 5) {\n continue\n }\n totalUnits += count\n appliedGroups += 1\n const [m, route, statusStr, aid, did] = parts\n if (samples.length < 4) {\n samples.push(`${m} ${route} ${statusStr} x${count}`)\n }\n const labels = {\n method: m,\n route,\n status_code: statusStr,\n appId: aid,\n databaseId: did,\n }\n applyCount(labels, count)\n if (dur > 0) {\n applyDuration(labels, dur)\n }\n }\n const emptyHint =\n fieldKeys.length === 0\n ? 'no_hash_entries'\n : totalUnits === 0\n ? 'entries_present_but_zero_counts'\n : 'ok'\n const noTrafficHint =\n fieldKeys.length === 0\n ? ' | if web had traffic: check web uses HttpMetricsRedisRecorder (METRICS_HTTP_ENABLED, not METRICS_HTTP_USE_BUILTIN_COUNTERS), same REDIS, BUILD_APP_NAME, key segment `web`'\n : ''\n tempHttpDiagLog(\n `http_redis_drain_collected pid=${process.pid} countKey=${\n this.countKey\n } durKey=${this.durKey} hash_fields_read=${\n fieldKeys.length\n } label_groups_applied=${appliedGroups} sum_request_counts=${totalUnits} state=${emptyHint} sample=${samples.join(\n ' | '\n ) || '—'}${noTrafficHint}`\n )\n } catch (e) {\n console.error(\n '[HttpMetricsRedisStore] flush apply failed:',\n e.message\n )\n tempHttpDiagLog(\n `http_redis_drain_apply_failed pid=${process.pid} ${e.message}`\n )\n }\n finish()\n }\n )\n })\n })\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisStore,\n buildFieldKey,\n FIELD_SEP,\n isRedisPeerInstalled,\n DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"httpMetricsRedisStore.js","names":["FIELD_SEP","DEFAULT_HTTP_METRICS_REDIS_TTL_SEC","DRAIN_LUA","isRedisPeerInstalled","require","resolve","buildFieldKey","method","route","statusCode","appId","databaseId","String","join","hgetallPairsToObject","pairs","o","length","i","tempHttpDiagLog","message","console","warn","truncateForDiag","s","maxLen","t","slice","HttpMetricsRedisStore","constructor","redisClient","appName","processType","ttlSec","Error","_client","keySeg","encodeURIComponent","countKey","durKey","lockKey","_ensureClient","record","durationMs","client","field","dur","Math","max","round","Number","process","pid","multi","hincrby","expire","exec","err","error","e","flushToCounters","applyCount","applyDuration","Promise","set","setErr","ok","eval","evalErr","raw","finish","del","Array","isArray","counts","durs","fieldKeys","Object","keys","totalUnits","appliedGroups","samples","count","parseInt","parts","split","m","statusStr","aid","did","push","labels","status_code","emptyHint","noTrafficHint","module","exports"],"sources":["../../src/metrics/httpMetricsRedisStore.js"],"sourcesContent":["/**\n * Record separator for hash fields (avoids collisions when route contains \"|\").\n * @type {string}\n */\nconst FIELD_SEP = '\\x1e'\n\n/**\n * Default Redis key TTL in seconds (sliding: refreshed on each `record` write).\n * @type {number}\n */\nconst DEFAULT_HTTP_METRICS_REDIS_TTL_SEC = 120\n\nconst DRAIN_LUA = `\nlocal function drain(key)\n local v = redis.call('HGETALL', key)\n redis.call('DEL', key)\n return v\nend\nreturn {drain(KEYS[1]), drain(KEYS[2])}\n`\n\n/**\n * @returns {boolean} Whether the `redis` npm package is resolvable (optional peer).\n */\nfunction isRedisPeerInstalled() {\n try {\n require.resolve('redis')\n return true\n } catch {\n return false\n }\n}\n\n/**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @returns {string}\n */\nfunction buildFieldKey(method, route, statusCode, appId, databaseId) {\n return [method, route, String(statusCode), appId, databaseId].join(FIELD_SEP)\n}\n\nfunction hgetallPairsToObject(pairs) {\n const o = {}\n if (!pairs || !pairs.length) {\n return o\n }\n for (let i = 0; i < pairs.length; i += 2) {\n o[pairs[i]] = pairs[i + 1]\n }\n return o\n}\n\n/** --- TEMP_HTTP_METRICS_DIAG: delete this helper and all calls when done debugging --- */\nfunction tempHttpDiagLog(message) {\n console.warn(`[TEMP_HTTP_METRICS_DIAG] ${message}`)\n}\n\nfunction truncateForDiag(s, maxLen = 220) {\n const t = String(s)\n return t.length > maxLen ? `${t.slice(0, maxLen - 3)}...` : t\n}\n\n/**\n * Redis HTTP aggregate store. Uses an **injected** client (same pattern as {@link RedisMetricsClient}).\n * Expects `redis` v3-style API: `multi().hincrby().expire().exec(cb)`, `eval`, `set`, `del`.\n */\nclass HttpMetricsRedisStore {\n /**\n * @param {Object} opts\n * @param {import('redis').RedisClient} opts.redisClient\n * @param {string} opts.appName BUILD_APP_NAME (key segment)\n * @param {string} opts.processType logical process for key (e.g. web)\n * @param {number} [opts.ttlSec] Expire keys after this many seconds (default 120)\n */\n constructor({ redisClient, appName, processType, ttlSec }) {\n if (redisClient == null) {\n throw new Error('HttpMetricsRedisStore: redisClient is required')\n }\n this._client = redisClient\n this.ttlSec =\n typeof ttlSec === 'number' && ttlSec > 0\n ? ttlSec\n : DEFAULT_HTTP_METRICS_REDIS_TTL_SEC\n const keySeg = `${encodeURIComponent(appName)}:${encodeURIComponent(\n processType\n )}`\n this.countKey = `metrics:http:v2:${keySeg}:count`\n this.durKey = `metrics:http:v2:${keySeg}:dur`\n this.lockKey = `metrics:http:v2:${keySeg}:drain_lock`\n }\n\n /**\n * @returns {import('redis').RedisClient}\n * @private\n */\n _ensureClient() {\n return this._client\n }\n\n /**\n * @param {string} method\n * @param {string} route\n * @param {number} statusCode\n * @param {string} appId\n * @param {string} databaseId\n * @param {number} durationMs\n */\n record(method, route, statusCode, appId, databaseId, durationMs) {\n try {\n const client = this._ensureClient()\n const field = buildFieldKey(method, route, statusCode, appId, databaseId)\n const dur = Math.max(0, Math.round(Number(durationMs) || 0))\n tempHttpDiagLog(\n `http_redis_write_prepare pid=${process.pid} countKey=${\n this.countKey\n } durKey=${this.durKey} hincr_field=${truncateForDiag(\n field\n )} durationMs=${dur}`\n )\n client\n .multi()\n .hincrby(this.countKey, field, 1)\n .hincrby(this.durKey, field, dur)\n .expire(this.countKey, this.ttlSec)\n .expire(this.durKey, this.ttlSec)\n .exec(err => {\n if (err) {\n console.error('[HttpMetricsRedisStore] record failed:', err.message)\n }\n })\n } catch (e) {\n console.error('[HttpMetricsRedisStore] record:', e.message)\n }\n }\n\n /**\n * @param {(labels: Object, value: number) => void} applyCount\n * @param {(labels: Object, value: number) => void} applyDuration\n * @returns {Promise<boolean>}\n */\n flushToCounters(applyCount, applyDuration) {\n let client\n try {\n client = this._ensureClient()\n } catch (e) {\n console.error('[HttpMetricsRedisStore] flush:', e.message)\n return Promise.resolve(false)\n }\n return new Promise(resolve => {\n client.set(this.lockKey, '1', 'EX', 25, 'NX', (setErr, ok) => {\n if (setErr || ok !== 'OK') {\n tempHttpDiagLog(\n `http_redis_drain_skip pid=${process.pid} countKey=${\n this.countKey\n } reason=${setErr ? setErr.message : 'lock_not_held'}`\n )\n resolve(false)\n return\n }\n client.eval(\n DRAIN_LUA,\n 2,\n this.countKey,\n this.durKey,\n (evalErr, raw) => {\n const finish = () => {\n client.del(this.lockKey, () => resolve(true))\n }\n\n if (evalErr) {\n console.error(\n '[HttpMetricsRedisStore] drain failed:',\n evalErr.message\n )\n tempHttpDiagLog(\n `http_redis_drain_got_error pid=${process.pid} message=${evalErr.message}`\n )\n finish()\n return\n }\n\n try {\n if (!raw || !Array.isArray(raw) || raw.length < 2) {\n tempHttpDiagLog(\n `http_redis_drain_collected pid=${process.pid} countKey=${\n this.countKey\n } hash_fields=0 sum_request_counts=0 (empty — keys deleted or no data)`\n )\n finish()\n return\n }\n const counts = hgetallPairsToObject(raw[0])\n const durs = hgetallPairsToObject(raw[1])\n const fieldKeys = Object.keys(counts)\n let totalUnits = 0\n let appliedGroups = 0\n const samples = []\n for (const field of fieldKeys) {\n const count = parseInt(counts[field], 10)\n if (!count || count < 1) {\n continue\n }\n const dur = parseInt(durs[field] || '0', 10) || 0\n const parts = field.split(FIELD_SEP)\n if (parts.length !== 5) {\n continue\n }\n totalUnits += count\n appliedGroups += 1\n const [m, route, statusStr, aid, did] = parts\n if (samples.length < 4) {\n samples.push(`${m} ${route} ${statusStr} x${count}`)\n }\n const labels = {\n method: m,\n route,\n status_code: statusStr,\n appId: aid,\n databaseId: did,\n }\n applyCount(labels, count)\n if (dur > 0) {\n applyDuration(labels, dur)\n }\n }\n let emptyHint = 'ok'\n if (fieldKeys.length === 0) {\n emptyHint = 'no_hash_entries'\n } else if (totalUnits === 0) {\n emptyHint = 'entries_present_but_zero_counts'\n }\n const noTrafficHint =\n fieldKeys.length === 0\n ? ' hint=web_must_use_HttpMetricsRedisRecorder_same_redis_APP_web_segment'\n : ''\n tempHttpDiagLog(\n truncateForDiag(\n `http_redis_drain_collected pid=${process.pid} countKey=${\n this.countKey\n } durKey=${this.durKey} hash_fields_read=${\n fieldKeys.length\n } label_groups_applied=${appliedGroups} sum_request_counts=${totalUnits} state=${emptyHint} sample=${\n samples.join(' | ') || '—'\n }${noTrafficHint}`,\n 900\n )\n )\n } catch (e) {\n console.error(\n '[HttpMetricsRedisStore] flush apply failed:',\n e.message\n )\n tempHttpDiagLog(\n `http_redis_drain_apply_failed pid=${process.pid} ${e.message}`\n )\n }\n finish()\n }\n )\n })\n })\n }\n}\n\nmodule.exports = {\n HttpMetricsRedisStore,\n buildFieldKey,\n FIELD_SEP,\n isRedisPeerInstalled,\n DEFAULT_HTTP_METRICS_REDIS_TTL_SEC,\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.19",
3
+ "version": "0.0.0-staging.20",
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",
@@ -1,18 +1,6 @@
1
- const cluster = require('cluster')
2
1
  const { BaseMetricsClient } = require('./baseMetricsClient')
3
2
  const { HttpMetricsRedisStore } = require('./httpMetricsRedisStore')
4
3
 
5
- function clusterWorkerTag() {
6
- try {
7
- if (cluster.isWorker && cluster.worker) {
8
- return ` cluster_worker_id=${cluster.worker.id}`
9
- }
10
- } catch (_) {
11
- /* ignore */
12
- }
13
- return ''
14
- }
15
-
16
4
  /**
17
5
  * Drain worker: reads HTTP aggregates from Redis (written by {@link HttpMetricsRedisRecorder}),
18
6
  * applies them to `app_requests_*` counters, then pushes the registry to the VM-agent (same as {@link BaseMetricsClient}).
@@ -99,32 +87,37 @@ class HttpMetricsRedisCollector extends BaseMetricsClient {
99
87
  * @returns {Promise<void>}
100
88
  */
101
89
  pushMetrics = async () => {
102
- const { pid } = process
103
- const clusterTag = clusterWorkerTag()
104
- // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
105
- console.warn(
106
- `[TEMP_HTTP_METRICS_DIAG] http_metrics_publish_start pid=${pid}${clusterTag} dyno_id=${this.dynoId} process_type=${this.processType} app=${this.appName}`
107
- )
108
- // --- end TEMP_HTTP_METRICS_DIAG ---
109
90
  if (
110
91
  this._store &&
111
92
  this.countersFunctions?.app_requests_total &&
112
93
  this.countersFunctions?.app_requests_total_duration
113
94
  ) {
114
- await this._store.flushToCounters(
95
+ const { pid } = process
96
+ const t0 = Date.now()
97
+ // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
98
+ console.warn(
99
+ `[TEMP_HTTP_METRICS_DIAG] http_collector_flush_begin pid=${pid} dyno_id=${this.dynoId} process_type=${this.processType} app=${this.appName}`
100
+ )
101
+ // --- end TEMP_HTTP_METRICS_DIAG ---
102
+ const drainedOk = await this._store.flushToCounters(
115
103
  (labels, value) =>
116
104
  this.countersFunctions.app_requests_total(labels, value),
117
105
  (labels, value) =>
118
106
  this.countersFunctions.app_requests_total_duration(labels, value)
119
107
  )
108
+ // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
109
+ console.warn(
110
+ `[TEMP_HTTP_METRICS_DIAG] http_collector_flush_end pid=${pid} lock_acquired_and_ran_drain=${drainedOk} elapsed_ms=${Date.now() - t0}`
111
+ )
112
+ // --- end TEMP_HTTP_METRICS_DIAG ---
113
+ } else {
114
+ // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
115
+ console.warn(
116
+ `[TEMP_HTTP_METRICS_DIAG] http_collector_flush_skipped pid=${process.pid} reason=missing_store_or_http_counters`
117
+ )
118
+ // --- end TEMP_HTTP_METRICS_DIAG ---
120
119
  }
121
- const out = await this._pushMetrics()
122
- // --- TEMP_HTTP_METRICS_DIAG: remove when done debugging ---
123
- console.warn(
124
- `[TEMP_HTTP_METRICS_DIAG] http_metrics_publish_finished pid=${pid}${clusterTag} dyno_id=${this.dynoId} (registry POST to VM-agent / import completed for this tick)`
125
- )
126
- // --- end TEMP_HTTP_METRICS_DIAG ---
127
- return out
120
+ return this._pushMetrics()
128
121
  }
129
122
  }
130
123
 
@@ -227,24 +227,27 @@ class HttpMetricsRedisStore {
227
227
  applyDuration(labels, dur)
228
228
  }
229
229
  }
230
- const emptyHint =
231
- fieldKeys.length === 0
232
- ? 'no_hash_entries'
233
- : totalUnits === 0
234
- ? 'entries_present_but_zero_counts'
235
- : 'ok'
230
+ let emptyHint = 'ok'
231
+ if (fieldKeys.length === 0) {
232
+ emptyHint = 'no_hash_entries'
233
+ } else if (totalUnits === 0) {
234
+ emptyHint = 'entries_present_but_zero_counts'
235
+ }
236
236
  const noTrafficHint =
237
237
  fieldKeys.length === 0
238
- ? ' | if web had traffic: check web uses HttpMetricsRedisRecorder (METRICS_HTTP_ENABLED, not METRICS_HTTP_USE_BUILTIN_COUNTERS), same REDIS, BUILD_APP_NAME, key segment `web`'
238
+ ? ' hint=web_must_use_HttpMetricsRedisRecorder_same_redis_APP_web_segment'
239
239
  : ''
240
240
  tempHttpDiagLog(
241
- `http_redis_drain_collected pid=${process.pid} countKey=${
242
- this.countKey
243
- } durKey=${this.durKey} hash_fields_read=${
244
- fieldKeys.length
245
- } label_groups_applied=${appliedGroups} sum_request_counts=${totalUnits} state=${emptyHint} sample=${samples.join(
246
- ' | '
247
- ) || '—'}${noTrafficHint}`
241
+ truncateForDiag(
242
+ `http_redis_drain_collected pid=${process.pid} countKey=${
243
+ this.countKey
244
+ } durKey=${this.durKey} hash_fields_read=${
245
+ fieldKeys.length
246
+ } label_groups_applied=${appliedGroups} sum_request_counts=${totalUnits} state=${emptyHint} sample=${
247
+ samples.join(' | ') || '—'
248
+ }${noTrafficHint}`,
249
+ 900
250
+ )
248
251
  )
249
252
  } catch (e) {
250
253
  console.error(