@bull-board/metrics 8.3.2 → 8.5.0
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/README.md +20 -0
- package/dist/HistoryAdmin.d.ts +11 -0
- package/dist/HistoryAdmin.js +23 -2
- package/dist/HistoryAdmin.js.map +1 -1
- package/dist/LatencySampler.d.ts +84 -0
- package/dist/LatencySampler.js +271 -0
- package/dist/LatencySampler.js.map +1 -0
- package/dist/LatencyStore.d.ts +18 -0
- package/dist/LatencyStore.js +217 -0
- package/dist/LatencyStore.js.map +1 -0
- package/dist/MetricsRecorder.d.ts +26 -0
- package/dist/MetricsRecorder.js +16 -0
- package/dist/MetricsRecorder.js.map +1 -1
- package/dist/RedisMetricsHistoryProvider.d.ts +3 -1
- package/dist/RedisMetricsHistoryProvider.js +68 -0
- package/dist/RedisMetricsHistoryProvider.js.map +1 -1
- package/dist/histogram.d.ts +27 -0
- package/dist/histogram.js +101 -0
- package/dist/histogram.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -41,6 +41,16 @@ On shutdown, call `recorder.stop()` and `provider.disconnect()`. Both only close
|
|
|
41
41
|
|
|
42
42
|
Timestamps and buckets are UTC.
|
|
43
43
|
|
|
44
|
+
## Job latency
|
|
45
|
+
|
|
46
|
+
Alongside the completed/failed counters, the recorder tracks two histograms per queue: wait time (`processedOn - timestamp`, how long a job sat before a worker picked it up) and run time (`finishedOn - processedOn`, how long the handler took). They diagnose different problems, so they're kept separate rather than combined into one number.
|
|
47
|
+
|
|
48
|
+
Both are collected by scanning the completed and failed sorted sets (BullMQ scores them by finish time via `moveToFinished`'s `ZADD`) past a watermark on the recorder's existing tick, so no worker changes are needed and there's no precondition on `queue.getMetrics()`.
|
|
49
|
+
|
|
50
|
+
The wait histogram only sees jobs that finished, so it goes quiet exactly when a queue is backed up and jobs stop finishing. A queue-age gauge (oldest job still waiting) is recorded alongside it for that reason, and the UI overlays it on the wait chart. Retries are excluded from wait time only, since `timestamp` is a job's creation but `processedOn` is its latest attempt. Percentiles are estimates bounded by bucket width; the bucket layout is fixed, not configurable, because two ranges with different layouts can't be merged into one percentile.
|
|
51
|
+
|
|
52
|
+
`removeOnComplete: true` deletes jobs the instant they finish, so there's nothing left to scan; that queue will never show latency data. Latency sampling is on by default; set `latency: false` on `MetricsRecorder` to turn it off.
|
|
53
|
+
|
|
44
54
|
## Storage
|
|
45
55
|
|
|
46
56
|
Every key lives under `bull-board:metrics:`. Each snapshot is written at three resolutions at once, each with its own retention, because they cost very different amounts:
|
|
@@ -61,6 +71,16 @@ At the defaults that's roughly 1.1 MB for a queue busy every minute of every day
|
|
|
61
71
|
|
|
62
72
|
The minute window is the one worth tuning: it holds essentially all the bytes, and it doubles as the recorder's catch-up window after downtime. `retentionDays: N` still works and sets the hourly and daily windows, leaving the minute window at its default.
|
|
63
73
|
|
|
74
|
+
Latency histograms and the queue-age gauge use a separate packed format and are measured separately, at 90 day retention:
|
|
75
|
+
|
|
76
|
+
| Scenario | Measured |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| One queue, both histograms plus the queue-age gauge, realistic distribution | 254.5 KB |
|
|
79
|
+
| Same, pathological: all 18 buckets populated heavily every hour | 574.6 KB |
|
|
80
|
+
| Shared `__global__` cross-queue rollup | ~224 KB once, for the whole board |
|
|
81
|
+
|
|
82
|
+
That rollup is a single shared cost, not multiplied per queue: 200 queues at typical traffic is roughly 200 × 254.5 KB, about 50 MB, plus the one shared 224 KB rollup. `sample()` takes about 9.11 ms per tick at 1000 finished jobs and about 12 Redis round trips per queue per tick, flat in job count; a subsampling cap above `maxLatencySamplesPerTick` keeps that bounded even at 10,000 jobs a tick.
|
|
83
|
+
|
|
64
84
|
Retention is enforced by Redis. Day-scoped keys expire on their own TTL; the daily totals hashes are trimmed to the window as each new day rolls in.
|
|
65
85
|
|
|
66
86
|
## Inspecting and clearing history
|
package/dist/HistoryAdmin.d.ts
CHANGED
|
@@ -91,6 +91,12 @@ export declare class MetricsHistoryAdmin {
|
|
|
91
91
|
* Deletes recorded history. Purging a single queue also subtracts that queue's minutes
|
|
92
92
|
* from the global rollup, so the cross-queue chart stays correct instead of keeping the
|
|
93
93
|
* removed queue's throughput folded into it forever.
|
|
94
|
+
*
|
|
95
|
+
* That correction covers the counter metrics only. The global runtime, waittime and
|
|
96
|
+
* queueage rollups keep the purged queue's contribution until their own retention drops
|
|
97
|
+
* it: a packed bucket vector cannot be decremented field by field, and a max gauge has no
|
|
98
|
+
* record of which queue produced the maximum, so there is nothing to subtract. The
|
|
99
|
+
* per-queue keys are still deleted either way. See SUMMABLE_METRICS.
|
|
94
100
|
*/
|
|
95
101
|
purge(opts?: PurgeOptions): Promise<PurgeResult>;
|
|
96
102
|
/**
|
|
@@ -101,12 +107,17 @@ export declare class MetricsHistoryAdmin {
|
|
|
101
107
|
* Fields that drain to zero are dropped: the recorder never writes a zero bucket, so a
|
|
102
108
|
* leftover zero would read as recorded-but-idle instead of not recorded.
|
|
103
109
|
* Returns the number of global keys it deleted.
|
|
110
|
+
*
|
|
111
|
+
* Only the summable metrics are touched; the latency ones are skipped outright rather than
|
|
112
|
+
* silently producing a no-op subtraction of their packed values. See SUMMABLE_METRICS.
|
|
104
113
|
*/
|
|
105
114
|
private subtractDayFromGlobal;
|
|
106
115
|
/**
|
|
107
116
|
* Same idea for the daily rollup: the global totals hash is the sum of the per-queue
|
|
108
117
|
* totals hashes, so it is corrected from those rather than re-derived from day hashes,
|
|
109
118
|
* which may already have expired. Returns the number of global fields it removed.
|
|
119
|
+
*
|
|
120
|
+
* Skips the latency metrics for the same reason as subtractDayFromGlobal.
|
|
110
121
|
*/
|
|
111
122
|
private subtractTotalsFromGlobal;
|
|
112
123
|
/**
|
package/dist/HistoryAdmin.js
CHANGED
|
@@ -9,7 +9,14 @@ const BATCH = 256;
|
|
|
9
9
|
/** Keys per pipelined `stats()` batch. Each key costs two commands. */
|
|
10
10
|
const MEASURE_BATCH = 250;
|
|
11
11
|
const DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
12
|
-
const METRICS = ['completed', 'failed'];
|
|
12
|
+
const METRICS = ['completed', 'failed', 'runtime', 'waittime', 'queueage'];
|
|
13
|
+
/**
|
|
14
|
+
* Metrics whose global rollup is a plain summable counter, so one queue's share can be taken
|
|
15
|
+
* back out of it with HINCRBY. The latency metrics are deliberately absent: runtime and
|
|
16
|
+
* waittime pack a whole bucket vector into a single field and queueage holds a max gauge,
|
|
17
|
+
* and neither can be corrected by a scalar decrement. See `purge`.
|
|
18
|
+
*/
|
|
19
|
+
const SUMMABLE_METRICS = ['completed', 'failed'];
|
|
13
20
|
/**
|
|
14
21
|
* Parses the three key shapes from the right, because queue names may themselves contain
|
|
15
22
|
* colons:
|
|
@@ -189,6 +196,12 @@ class MetricsHistoryAdmin {
|
|
|
189
196
|
* Deletes recorded history. Purging a single queue also subtracts that queue's minutes
|
|
190
197
|
* from the global rollup, so the cross-queue chart stays correct instead of keeping the
|
|
191
198
|
* removed queue's throughput folded into it forever.
|
|
199
|
+
*
|
|
200
|
+
* That correction covers the counter metrics only. The global runtime, waittime and
|
|
201
|
+
* queueage rollups keep the purged queue's contribution until their own retention drops
|
|
202
|
+
* it: a packed bucket vector cannot be decremented field by field, and a max gauge has no
|
|
203
|
+
* record of which queue produced the maximum, so there is nothing to subtract. The
|
|
204
|
+
* per-queue keys are still deleted either way. See SUMMABLE_METRICS.
|
|
192
205
|
*/
|
|
193
206
|
async purge(opts = {}) {
|
|
194
207
|
const before = opts.before === undefined ? null : toDay(opts.before);
|
|
@@ -246,9 +259,12 @@ class MetricsHistoryAdmin {
|
|
|
246
259
|
* Fields that drain to zero are dropped: the recorder never writes a zero bucket, so a
|
|
247
260
|
* leftover zero would read as recorded-but-idle instead of not recorded.
|
|
248
261
|
* Returns the number of global keys it deleted.
|
|
262
|
+
*
|
|
263
|
+
* Only the summable metrics are touched; the latency ones are skipped outright rather than
|
|
264
|
+
* silently producing a no-op subtraction of their packed values. See SUMMABLE_METRICS.
|
|
249
265
|
*/
|
|
250
266
|
async subtractDayFromGlobal(key, parsed) {
|
|
251
|
-
if (parsed.day === null) {
|
|
267
|
+
if (parsed.day === null || !SUMMABLE_METRICS.includes(parsed.metric)) {
|
|
252
268
|
return 0;
|
|
253
269
|
}
|
|
254
270
|
const minutes = await this.redis.hgetall(key);
|
|
@@ -277,8 +293,13 @@ class MetricsHistoryAdmin {
|
|
|
277
293
|
* Same idea for the daily rollup: the global totals hash is the sum of the per-queue
|
|
278
294
|
* totals hashes, so it is corrected from those rather than re-derived from day hashes,
|
|
279
295
|
* which may already have expired. Returns the number of global fields it removed.
|
|
296
|
+
*
|
|
297
|
+
* Skips the latency metrics for the same reason as subtractDayFromGlobal.
|
|
280
298
|
*/
|
|
281
299
|
async subtractTotalsFromGlobal(metric, days, values) {
|
|
300
|
+
if (!SUMMABLE_METRICS.includes(metric)) {
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
282
303
|
const globalTotals = (0, keys_1.totalsHashKey)(keys_1.GLOBAL_QUEUE, metric);
|
|
283
304
|
const pipeline = this.redis.multi();
|
|
284
305
|
const touched = [];
|
package/dist/HistoryAdmin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HistoryAdmin.js","sourceRoot":"","sources":["../src/HistoryAdmin.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HistoryAdmin.js","sourceRoot":"","sources":["../src/HistoryAdmin.ts"],"names":[],"mappings":";;;AAqFA,0CA0BC;AA/GD,qCAAmD;AACnD,iCAAoG;AAEpG,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,KAAK,GAAG,GAAG,CAAC;AAClB,uEAAuE;AACvE,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAC1C,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAC3E;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AA0DjD;;;;;;;;;;;GAWG;AACH,SAAgB,eAAe,CAAC,GAAW;IACzC,MAAM,MAAM,GAAG,GAAG,gBAAS,GAAG,CAAC;IAC/B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAErC,IAAI,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3F,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,gBAAS,EAAE,CAAC;QAC/D,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/F,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3C,OAAO,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACjG,CAAC;AAED,SAAS,UAAU;IACjB,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;QAC7B,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;QAC3B,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;KAC3B,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,SAAS,YAAY,CAAC,MAAiB;IACrC,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO,IAAA,oBAAa,EAAC,mBAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM;QAC3B,CAAC,CAAC,IAAA,kBAAW,EAAC,mBAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC;QACtD,CAAC,CAAC,IAAA,iBAAU,EAAC,mBAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,KAAK,CAAC,KAAoB;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,6CAA6C,KAAK,GAAG,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAa,mBAAmB;IAI9B,YAAY,IAAgC;QAC1C,IAAI,IAAI,CAAC,UAAU,YAAY,eAAK,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;YAC7B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,KAAK,GAAG,IAAI,eAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,KAAK;;QACT,MAAM,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;QACrD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,SAAS,GAAkB,IAAI,CAAC;QACpC,IAAI,SAAS,GAAkB,IAAI,CAAC;QAEpC,MAAM,KAAK,GAAyC,EAAE,CAAC;QACvD,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,MAAM,EAAE,CAAC;gBACX,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAEvE,KAAK,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YAClD,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAE1C,MAAM,KAAK,GAAG,MAAA,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAI;gBACzC,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,IAAI,EAAE,CAAC;gBACP,KAAK,EAAE,CAAC;gBACR,OAAO,EAAE,CAAC;gBACV,IAAI,EAAE,EAAE;gBACR,KAAK,EAAE,UAAU,EAAE;aACpB,CAAC;YACF,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;YAChB,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC;YACpB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YACnC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC;YACvC,IAAI,IAAI,CAAC,CAAC;YACV,KAAK,IAAI,IAAI,CAAC;YACd,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;YAC7B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC;YAEjC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,KAAK,CAAC,OAAO,IAAI,GAAG,CAAC;gBACrB,OAAO,IAAI,GAAG,CAAC;YACjB,CAAC;YACD,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;gBACf,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,IAAI,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;oBACjD,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC;gBACzB,CAAC;gBACD,IAAI,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;oBACjD,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACvE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/C,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACvE,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,OAAO,CAAC,IAAc;;QAClC,MAAM,GAAG,GAAoC,EAAE,CAAC;QAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,aAAa,EAAE,CAAC;YACpD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC;YAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACvC,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;gBACxB,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBAC9B,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,GAAG,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,MAAM,CAAC,MAAA,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAG,CAAC,GAAG,CAAC,CAAC,0CAAG,CAAC,CAAC,mCAAI,CAAC,CAAC,IAAI,CAAC;oBACzC,GAAG,EAAE,MAAM,CAAC,MAAA,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,0CAAG,CAAC,CAAC,mCAAI,CAAC,CAAC,IAAI,CAAC;iBAC7C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,KAAK,CAAC,OAAqB,EAAE;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrE,MAAM,MAAM,GAAgB,EAAE,WAAW,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;QACjE,MAAM,OAAO,GAAyC,EAAE,CAAC;QACzD,MAAM,UAAU,GAAyC,EAAE,CAAC;QAE5D,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC5D,SAAS;YACX,CAAC;YACD,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;gBACxB,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;YACnC,CAAC;iBAAM,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC;gBAClD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QAED,qFAAqF;QACrF,2EAA2E;QAC3E,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,mBAAY,CAAC;QAE7E,KAAK,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;YACtC,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,CAAC,WAAW,IAAI,MAAM,IAAI,CAAC,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACtE,CAAC;YACD,MAAM,CAAC,WAAW,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;QAED,KAAK,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,GAAG,MAAM,CAAC,CAAC;YAC7F,IAAI,YAAY,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;gBACrD,MAAM,CAAC,aAAa,IAAI,MAAM,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;YAC5F,CAAC;YACD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,MAAM,CAAC,WAAW,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACnD,SAAS;YACX,CAAC;YACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;gBAC7C,MAAM,CAAC,aAAa,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvC,MAAM,CAAC,WAAW,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;;;;OAWG;IACK,KAAK,CAAC,qBAAqB,CAAC,GAAW,EAAE,MAAiB;QAChE,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACrE,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,eAAC,OAAA,MAAM,CAAC,MAAA,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAG,CAAC,CAAC,0CAAG,CAAC,CAAC,mCAAI,CAAC,CAAC,IAAI,CAAC,CAAA,EAAA,CAAC,CAAC;QAC1E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;YAC/C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,wBAAwB,CACpC,MAAc,EACd,IAAc,EACd,MAAyB;QAEzB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,YAAY,GAAG,IAAA,oBAAa,EAAC,mBAAY,EAAE,MAAM,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;YACtB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QACH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,eAAC,OAAA,MAAM,CAAC,MAAA,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAG,CAAC,CAAC,0CAAG,CAAC,CAAC,mCAAI,CAAC,CAAC,IAAI,CAAC,CAAA,EAAA,CAAC,CAAC;QAC1E,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;YAC/C,OAAO,IAAI,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,CAAC,IAAI;QACjB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,IAAI,MAAM,GAAG,GAAG,CAAC;QACjB,GAAG,CAAC;YACF,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CACzC,MAAM,EACN,OAAO,EACP,GAAG,gBAAS,IAAI,EAChB,OAAO,EACP,UAAU,CACX,CAAC;YACF,MAAM,GAAG,IAAI,CAAC;YACd,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;gBACxB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACd,MAAM,GAAG,CAAC;gBACZ,CAAC;YACH,CAAC;QACH,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;IAC3B,CAAC;CACF;AAzRD,kDAyRC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { BaseAdapter } from '@bull-board/api/baseAdapter';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
import type { LatencyStore } from './LatencyStore';
|
|
4
|
+
export interface LatencySamplerOptions {
|
|
5
|
+
redis: Redis;
|
|
6
|
+
store: LatencyStore;
|
|
7
|
+
/** Recorder tick, used to size the lease and to bound a cold start. */
|
|
8
|
+
tickMs: number;
|
|
9
|
+
/** Above this, the tick subsamples uniformly rather than fetching every job. */
|
|
10
|
+
maxSamplesPerTick?: number;
|
|
11
|
+
/**
|
|
12
|
+
* How far back from now a scan stops. Defaults to SAFETY_MARGIN_MS. Injectable so tests
|
|
13
|
+
* can set it to 0 and sample jobs that just finished, rather than sleeping past the
|
|
14
|
+
* margin in every case.
|
|
15
|
+
*/
|
|
16
|
+
safetyMarginMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Called with anything `sample()` swallows. The default is silent, which keeps a broken
|
|
19
|
+
* latency scan from taking the counter snapshot down with it, but also makes a collector
|
|
20
|
+
* that is failing every tick look exactly like an idle board. Supply this to tell the two
|
|
21
|
+
* apart. Errors thrown by the hook itself are ignored, since a throwing reporter would
|
|
22
|
+
* undo the containment it was added to observe.
|
|
23
|
+
*/
|
|
24
|
+
onError?: (error: unknown, queueName: string) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Copies job durations out of BullMQ's finished sets on the recorder's tick.
|
|
28
|
+
*
|
|
29
|
+
* BullMQ's moveToFinished does `ZADD targetSet, timestamp, jobId` and writes the same value
|
|
30
|
+
* to finishedOn, so the completed and failed sets are sorted sets scored by finish time.
|
|
31
|
+
* Scanning past a stored watermark therefore returns exactly the jobs finished since the
|
|
32
|
+
* last tick, with no gaps and no bias. The only loss is a queue whose removeOnComplete
|
|
33
|
+
* trims faster than the tick runs.
|
|
34
|
+
*/
|
|
35
|
+
export declare class LatencySampler {
|
|
36
|
+
private readonly redis;
|
|
37
|
+
private readonly store;
|
|
38
|
+
private readonly tickMs;
|
|
39
|
+
private readonly maxSamples;
|
|
40
|
+
private readonly safetyMarginMs;
|
|
41
|
+
private readonly onError?;
|
|
42
|
+
private readonly id;
|
|
43
|
+
constructor(opts: LatencySamplerOptions);
|
|
44
|
+
static supports(adapter: BaseAdapter): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* One queue, one tick. Swallows its own errors: the counter snapshot is the more
|
|
47
|
+
* important metric and must not fail as collateral damage from a latency scan. Pass
|
|
48
|
+
* `onError` to see what was swallowed; without it a collector failing every tick is
|
|
49
|
+
* indistinguishable from a queue with nothing to sample.
|
|
50
|
+
*/
|
|
51
|
+
sample(adapter: BaseAdapter): Promise<void>;
|
|
52
|
+
private leaseKey;
|
|
53
|
+
/**
|
|
54
|
+
* Increments are not idempotent the way the counter upsert is, so two recorders scanning
|
|
55
|
+
* the same range would double every histogram. Only the lease holder scans.
|
|
56
|
+
*
|
|
57
|
+
* The TTL is a crash ceiling, not the normal lifetime: a process that dies mid-scan must
|
|
58
|
+
* not lock the queue out forever. The normal path releases in a finally, because a lease
|
|
59
|
+
* outliving its scan would make the next tick no-op and halve the sampling rate.
|
|
60
|
+
*/
|
|
61
|
+
private acquireLease;
|
|
62
|
+
/** Compare and delete, so a lease that already expired and was retaken is left alone. */
|
|
63
|
+
private releaseLease;
|
|
64
|
+
private watermarkKey;
|
|
65
|
+
/**
|
|
66
|
+
* Bounded rather than eternal: an unexpiring watermark would leave one key behind per
|
|
67
|
+
* queue forever once that queue is purged or decommissioned, which is exactly the
|
|
68
|
+
* unbounded-storage failure this package exists to avoid. The day retention is already the
|
|
69
|
+
* horizon everything else here is bounded by. Losing the watermark just means the next
|
|
70
|
+
* tick cold starts, which is already a supported path.
|
|
71
|
+
*/
|
|
72
|
+
private watermarkTtlSeconds;
|
|
73
|
+
private sampleDurations;
|
|
74
|
+
private flush;
|
|
75
|
+
/**
|
|
76
|
+
* The backlog is not all in one place. `wait` holds it while the queue is running, but
|
|
77
|
+
* pausing RENAMEs that list to `paused` and routes new jobs there, and anything added with
|
|
78
|
+
* a priority goes to the `prioritized` sorted set instead, which can leave `wait`
|
|
79
|
+
* permanently empty. Reading only `wait` reports a healthy zero for a queue that is badly
|
|
80
|
+
* backed up, which is the opposite of what this gauge is for, so all three are consulted
|
|
81
|
+
* and the worst age wins.
|
|
82
|
+
*/
|
|
83
|
+
private sampleQueueAge;
|
|
84
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LatencySampler = void 0;
|
|
4
|
+
const histogram_1 = require("./histogram");
|
|
5
|
+
const keys_1 = require("./keys");
|
|
6
|
+
const MS_PER_HOUR = 3600000;
|
|
7
|
+
const SECONDS_PER_DAY = 86400;
|
|
8
|
+
const DEFAULT_MAX_SAMPLES = 5000;
|
|
9
|
+
/**
|
|
10
|
+
* ZADD into the finished set and this scan are not atomic with respect to each other, so a
|
|
11
|
+
* job finishing microseconds before the scan can be absent from it. Advancing the watermark
|
|
12
|
+
* to the highest score seen would skip that job forever. Scanning only up to a slightly
|
|
13
|
+
* stale bound, and advancing the watermark to that bound rather than to what was observed,
|
|
14
|
+
* means anything finishing inside the margin is picked up by the next tick instead.
|
|
15
|
+
* Costs a few seconds of freshness in data that is bucketed hourly.
|
|
16
|
+
*
|
|
17
|
+
* The score is `Date.now()` read in the worker process and passed into BullMQ's Lua, not a
|
|
18
|
+
* Redis-side clock, so the margin only holds while the worker and this recorder agree on
|
|
19
|
+
* the time: the effective margin is `margin - skew`. A worker running far enough ahead of
|
|
20
|
+
* the recorder can still land jobs below an already-advanced watermark and lose them.
|
|
21
|
+
*/
|
|
22
|
+
const SAFETY_MARGIN_MS = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* Copies job durations out of BullMQ's finished sets on the recorder's tick.
|
|
25
|
+
*
|
|
26
|
+
* BullMQ's moveToFinished does `ZADD targetSet, timestamp, jobId` and writes the same value
|
|
27
|
+
* to finishedOn, so the completed and failed sets are sorted sets scored by finish time.
|
|
28
|
+
* Scanning past a stored watermark therefore returns exactly the jobs finished since the
|
|
29
|
+
* last tick, with no gaps and no bias. The only loss is a queue whose removeOnComplete
|
|
30
|
+
* trims faster than the tick runs.
|
|
31
|
+
*/
|
|
32
|
+
class LatencySampler {
|
|
33
|
+
constructor(opts) {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
this.id = `${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
36
|
+
this.redis = opts.redis;
|
|
37
|
+
this.store = opts.store;
|
|
38
|
+
this.tickMs = opts.tickMs;
|
|
39
|
+
this.maxSamples = (_a = opts.maxSamplesPerTick) !== null && _a !== void 0 ? _a : DEFAULT_MAX_SAMPLES;
|
|
40
|
+
this.safetyMarginMs = (_b = opts.safetyMarginMs) !== null && _b !== void 0 ? _b : SAFETY_MARGIN_MS;
|
|
41
|
+
this.onError = opts.onError;
|
|
42
|
+
}
|
|
43
|
+
static supports(adapter) {
|
|
44
|
+
return typeof adapter.getQueueKey === 'function';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One queue, one tick. Swallows its own errors: the counter snapshot is the more
|
|
48
|
+
* important metric and must not fail as collateral damage from a latency scan. Pass
|
|
49
|
+
* `onError` to see what was swallowed; without it a collector failing every tick is
|
|
50
|
+
* indistinguishable from a queue with nothing to sample.
|
|
51
|
+
*/
|
|
52
|
+
async sample(adapter) {
|
|
53
|
+
var _a;
|
|
54
|
+
if (!LatencySampler.supports(adapter)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const name = adapter.getName();
|
|
58
|
+
try {
|
|
59
|
+
if (!(await this.acquireLease(name))) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
await this.sampleDurations(adapter, name);
|
|
64
|
+
await this.sampleQueueAge(adapter, name);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await this.releaseLease(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
// Intentionally swallowed, see the method comment.
|
|
72
|
+
try {
|
|
73
|
+
(_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, error, name);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// A reporter that throws must not resurrect the failure this catch contains.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
leaseKey(name) {
|
|
81
|
+
return `${keys_1.NAMESPACE}:${name}:latency:lease`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Increments are not idempotent the way the counter upsert is, so two recorders scanning
|
|
85
|
+
* the same range would double every histogram. Only the lease holder scans.
|
|
86
|
+
*
|
|
87
|
+
* The TTL is a crash ceiling, not the normal lifetime: a process that dies mid-scan must
|
|
88
|
+
* not lock the queue out forever. The normal path releases in a finally, because a lease
|
|
89
|
+
* outliving its scan would make the next tick no-op and halve the sampling rate.
|
|
90
|
+
*/
|
|
91
|
+
async acquireLease(name) {
|
|
92
|
+
const held = await this.redis.set(this.leaseKey(name), this.id, 'PX', this.tickMs * 2, 'NX');
|
|
93
|
+
return held === 'OK';
|
|
94
|
+
}
|
|
95
|
+
/** Compare and delete, so a lease that already expired and was retaken is left alone. */
|
|
96
|
+
async releaseLease(name) {
|
|
97
|
+
await this.redis.eval(`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end
|
|
98
|
+
return 0`, 1, this.leaseKey(name), this.id);
|
|
99
|
+
}
|
|
100
|
+
watermarkKey(name) {
|
|
101
|
+
return `${keys_1.NAMESPACE}:${name}:latency:watermark`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Bounded rather than eternal: an unexpiring watermark would leave one key behind per
|
|
105
|
+
* queue forever once that queue is purged or decommissioned, which is exactly the
|
|
106
|
+
* unbounded-storage failure this package exists to avoid. The day retention is already the
|
|
107
|
+
* horizon everything else here is bounded by. Losing the watermark just means the next
|
|
108
|
+
* tick cold starts, which is already a supported path.
|
|
109
|
+
*/
|
|
110
|
+
watermarkTtlSeconds() {
|
|
111
|
+
return Math.max(1, Math.floor(this.store.retention.days * SECONDS_PER_DAY));
|
|
112
|
+
}
|
|
113
|
+
async sampleDurations(adapter, name) {
|
|
114
|
+
var _a;
|
|
115
|
+
const watermarkRaw = await this.redis.get(this.watermarkKey(name));
|
|
116
|
+
// Cold start begins at the previous tick rather than backfilling, since a first run
|
|
117
|
+
// against a large completed set would be a surprise fetch storm.
|
|
118
|
+
const watermark = watermarkRaw ? Number(watermarkRaw) : Date.now() - this.tickMs;
|
|
119
|
+
const upperBound = Date.now() - this.safetyMarginMs;
|
|
120
|
+
if (upperBound <= watermark) {
|
|
121
|
+
return; // ticks closer together than the margin; next tick covers this range
|
|
122
|
+
}
|
|
123
|
+
const ids = [];
|
|
124
|
+
for (const set of ['completed', 'failed']) {
|
|
125
|
+
const found = await this.redis.zrangebyscore(adapter.getQueueKey(set), `(${watermark}`, upperBound);
|
|
126
|
+
ids.push(...found);
|
|
127
|
+
}
|
|
128
|
+
if (ids.length === 0) {
|
|
129
|
+
await this.redis.set(this.watermarkKey(name), String(upperBound), 'EX', this.watermarkTtlSeconds());
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// The id list is one cheap round trip; the HMGETs are the real cost. Above the cap take
|
|
133
|
+
// a uniform subset rather than the first N, which would bias towards the tick's start.
|
|
134
|
+
const selected = ids.length > this.maxSamples ? sampleUniformly(ids, this.maxSamples) : ids;
|
|
135
|
+
// Counts are scaled back up by this ratio, so a subsampled hour reads as an estimate
|
|
136
|
+
// with the same shape rather than a dip. The fact that it was subsampled is currently
|
|
137
|
+
// invisible to clients: marking it would mean persisting a flag alongside the packed
|
|
138
|
+
// vector, which is a storage-format change. Known follow-up.
|
|
139
|
+
const ratio = ids.length / selected.length;
|
|
140
|
+
const pipeline = this.redis.pipeline();
|
|
141
|
+
for (const id of selected) {
|
|
142
|
+
pipeline.hmget(adapter.getQueueKey(String(id)), 'timestamp', 'processedOn', 'finishedOn',
|
|
143
|
+
// BullMQ 5 counts attempts in `atm`; `attemptsMade` is the pre-v5 name and is read
|
|
144
|
+
// as a fallback exactly the way Job.fromJSON does.
|
|
145
|
+
'atm', 'attemptsMade');
|
|
146
|
+
}
|
|
147
|
+
const rows = await pipeline.exec();
|
|
148
|
+
const runByHour = new Map();
|
|
149
|
+
const waitByHour = new Map();
|
|
150
|
+
for (const row of rows !== null && rows !== void 0 ? rows : []) {
|
|
151
|
+
const values = row === null || row === void 0 ? void 0 : row[1];
|
|
152
|
+
if (!values) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const [timestamp, processedOn, finishedOn, atm, attemptsMade] = values;
|
|
156
|
+
if (!processedOn || !finishedOn) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const finished = Number(finishedOn);
|
|
160
|
+
const processed = Number(processedOn);
|
|
161
|
+
const hour = Math.floor(finished / MS_PER_HOUR);
|
|
162
|
+
observe(runByHour, hour, finished - processed, ratio);
|
|
163
|
+
// A retried job's timestamp is its creation, but processedOn is the latest attempt,
|
|
164
|
+
// so wait would absorb every prior attempt and backoff. Run time is unaffected.
|
|
165
|
+
const attempts = Number((_a = atm !== null && atm !== void 0 ? atm : attemptsMade) !== null && _a !== void 0 ? _a : 0);
|
|
166
|
+
if (timestamp && attempts <= 1) {
|
|
167
|
+
// Every one of these is a Date.now() taken in some client process, not a Redis-side
|
|
168
|
+
// clock: timestamp in the producer, processedOn in the worker. Skew between the two
|
|
169
|
+
// can make the difference negative, hence the clamp.
|
|
170
|
+
observe(waitByHour, hour, Math.max(0, processed - Number(timestamp)), ratio);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
await this.flush(name, 'runtime', runByHour);
|
|
174
|
+
await this.flush(name, 'waittime', waitByHour);
|
|
175
|
+
// The bound, not the highest score observed. See SAFETY_MARGIN_MS.
|
|
176
|
+
await this.redis.set(this.watermarkKey(name), String(upperBound), 'EX', this.watermarkTtlSeconds());
|
|
177
|
+
}
|
|
178
|
+
async flush(name, metric, byHour) {
|
|
179
|
+
for (const [hour, vector] of byHour) {
|
|
180
|
+
// Scaled counts are fractional. Left unrounded, join(',') would write seventeen
|
|
181
|
+
// significant digits per bucket and blow up the packed value the storage design
|
|
182
|
+
// depends on staying small.
|
|
183
|
+
await this.store.addSamples(name, metric, hour, vector.map((v) => Math.round(v)));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The backlog is not all in one place. `wait` holds it while the queue is running, but
|
|
188
|
+
* pausing RENAMEs that list to `paused` and routes new jobs there, and anything added with
|
|
189
|
+
* a priority goes to the `prioritized` sorted set instead, which can leave `wait`
|
|
190
|
+
* permanently empty. Reading only `wait` reports a healthy zero for a queue that is badly
|
|
191
|
+
* backed up, which is the opposite of what this gauge is for, so all three are consulted
|
|
192
|
+
* and the worst age wins.
|
|
193
|
+
*/
|
|
194
|
+
async sampleQueueAge(adapter, name) {
|
|
195
|
+
var _a;
|
|
196
|
+
const hour = Math.floor(Date.now() / MS_PER_HOUR);
|
|
197
|
+
const candidates = await this.redis
|
|
198
|
+
.pipeline()
|
|
199
|
+
// BullMQ LPUSHes to the wait list and workers RPOPLPUSH from it, so the tail is oldest.
|
|
200
|
+
.lrange(adapter.getQueueKey('wait'), -1, -1)
|
|
201
|
+
.lrange(adapter.getQueueKey('paused'), -1, -1)
|
|
202
|
+
// The prioritized set is scored by priority, not by time, so neither end is guaranteed
|
|
203
|
+
// to hold the oldest job. Both ends are an approximation, and a cheap one: the true
|
|
204
|
+
// oldest would mean fetching the whole set every tick.
|
|
205
|
+
.zrange(adapter.getQueueKey('prioritized'), 0, 0)
|
|
206
|
+
.zrange(adapter.getQueueKey('prioritized'), -1, -1)
|
|
207
|
+
.exec();
|
|
208
|
+
// A pipeline reports failures per command: a Redis error arrives in the entry's error
|
|
209
|
+
// slot next to a null result, rather than as a throw. Reading only the result slot turns
|
|
210
|
+
// a failed read into an empty backlog and records an age of 0, which is the most
|
|
211
|
+
// reassuring number this gauge can produce, at the moment it is least entitled to. A gap
|
|
212
|
+
// in the series reads as "not measured" and is recoverable; a zero is a wrong answer that
|
|
213
|
+
// looks like a healthy one, so a tick that could not read cleanly records nothing.
|
|
214
|
+
if (!candidates || candidates.some(([error]) => error)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const ids = new Set();
|
|
218
|
+
for (const entry of candidates) {
|
|
219
|
+
for (const id of (_a = entry[1]) !== null && _a !== void 0 ? _a : []) {
|
|
220
|
+
ids.add(String(id));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (ids.size === 0) {
|
|
224
|
+
await this.store.recordQueueAge(name, hour, 0);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const stamps = this.redis.pipeline();
|
|
228
|
+
for (const id of ids) {
|
|
229
|
+
stamps.hget(adapter.getQueueKey(id), 'timestamp');
|
|
230
|
+
}
|
|
231
|
+
const rows = await stamps.exec();
|
|
232
|
+
// Same reasoning: a failed timestamp read does not zero the gauge, it understates it,
|
|
233
|
+
// which is the same wrong-but-reassuring answer in a quieter form.
|
|
234
|
+
if (!rows || rows.some(([error]) => error)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
let oldest = 0;
|
|
239
|
+
for (const row of rows) {
|
|
240
|
+
const raw = row[1];
|
|
241
|
+
if (!raw) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const enqueuedAt = Number(raw);
|
|
245
|
+
if (!Number.isFinite(enqueuedAt)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
oldest = Math.max(oldest, now - enqueuedAt);
|
|
249
|
+
}
|
|
250
|
+
await this.store.recordQueueAge(name, hour, Math.max(0, oldest));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
exports.LatencySampler = LatencySampler;
|
|
254
|
+
function observe(byHour, hour, durationMs, ratio) {
|
|
255
|
+
let vector = byHour.get(hour);
|
|
256
|
+
if (!vector) {
|
|
257
|
+
vector = (0, histogram_1.emptyVector)();
|
|
258
|
+
byHour.set(hour, vector);
|
|
259
|
+
}
|
|
260
|
+
vector[(0, histogram_1.bucketIndex)(durationMs)] += ratio;
|
|
261
|
+
}
|
|
262
|
+
/** Evenly spaced pick across the list, which preserves the distribution's shape. */
|
|
263
|
+
function sampleUniformly(ids, target) {
|
|
264
|
+
const stride = ids.length / target;
|
|
265
|
+
const out = [];
|
|
266
|
+
for (let i = 0; i < target; i++) {
|
|
267
|
+
out.push(ids[Math.floor(i * stride)]);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=LatencySampler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LatencySampler.js","sourceRoot":"","sources":["../src/LatencySampler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import type { Retention } from './HistoryStore';
|
|
3
|
+
export type LatencyMetric = 'runtime' | 'waittime';
|
|
4
|
+
export declare const QUEUE_AGE_METRIC = "queueage";
|
|
5
|
+
export declare class LatencyStore {
|
|
6
|
+
private readonly redis;
|
|
7
|
+
readonly retention: Retention;
|
|
8
|
+
constructor(opts: {
|
|
9
|
+
redis: Redis;
|
|
10
|
+
retention: Retention;
|
|
11
|
+
});
|
|
12
|
+
addSamples(queue: string, metric: LatencyMetric, hour: number, vector: number[]): Promise<void>;
|
|
13
|
+
recordQueueAge(queue: string, hour: number, ms: number): Promise<void>;
|
|
14
|
+
readRange(queue: string, metric: LatencyMetric, granularity: 'hour' | 'day', days: string[]): Promise<Record<string, number[]>>;
|
|
15
|
+
readQueueAge(queue: string, granularity: 'hour' | 'day', days: string[]): Promise<Record<string, number>>;
|
|
16
|
+
/** Epoch ms for an absolute hour index, for building response timestamps. */
|
|
17
|
+
static hourToMs(hour: number): number;
|
|
18
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LatencyStore = exports.QUEUE_AGE_METRIC = void 0;
|
|
4
|
+
const histogram_1 = require("./histogram");
|
|
5
|
+
const keys_1 = require("./keys");
|
|
6
|
+
exports.QUEUE_AGE_METRIC = 'queueage';
|
|
7
|
+
const SECONDS_PER_DAY = 86400;
|
|
8
|
+
const MS_PER_HOUR = 3600000;
|
|
9
|
+
function ttl(days) {
|
|
10
|
+
return String(Math.max(1, Math.floor(days * SECONDS_PER_DAY)));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Merges one tick's bucket vector into the queue's hour hash, its day totals, and the
|
|
14
|
+
* global rollup of both, in a single round trip.
|
|
15
|
+
*
|
|
16
|
+
* Packing all 18 counts into one hash field rather than one field per bucket is what keeps
|
|
17
|
+
* this affordable: Redis hash entry overhead dwarfs a two-byte count, so the packed form
|
|
18
|
+
* costs roughly an eighth of the field-per-bucket form. The trade is that HINCRBY no longer
|
|
19
|
+
* applies, hence the read-modify-write here.
|
|
20
|
+
*
|
|
21
|
+
* Unlike the counter path there is no delta trick, because the caller supplies increments
|
|
22
|
+
* rather than absolute values. Two recorders writing the same tick would double-count, which
|
|
23
|
+
* is what LatencySampler's lease prevents.
|
|
24
|
+
*
|
|
25
|
+
* KEYS[1] queue hour hash ARGV[1] hour field ARGV[4] hour-tier ttl
|
|
26
|
+
* KEYS[2] queue totals hash ARGV[2] day field ARGV[5] day-tier ttl
|
|
27
|
+
* KEYS[3] global hour hash ARGV[3] packed vector ARGV[6] oldest day to keep
|
|
28
|
+
* KEYS[4] global totals hash ARGV[7] bucket count
|
|
29
|
+
*/
|
|
30
|
+
const MERGE_VECTOR = `
|
|
31
|
+
local function merge(key, field, incoming, width)
|
|
32
|
+
local current = redis.call('HGET', key, field)
|
|
33
|
+
local out = {}
|
|
34
|
+
local i = 1
|
|
35
|
+
if current then
|
|
36
|
+
for value in string.gmatch(current, '([^,]+)') do
|
|
37
|
+
out[i] = tonumber(value) or 0
|
|
38
|
+
i = i + 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
while i <= width do
|
|
42
|
+
out[i] = 0
|
|
43
|
+
i = i + 1
|
|
44
|
+
end
|
|
45
|
+
local j = 1
|
|
46
|
+
for value in string.gmatch(incoming, '([^,]+)') do
|
|
47
|
+
out[j] = (out[j] or 0) + (tonumber(value) or 0)
|
|
48
|
+
j = j + 1
|
|
49
|
+
end
|
|
50
|
+
redis.call('HSET', key, field, table.concat(out, ','))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
local function trim(key, cutoff)
|
|
54
|
+
local fields = redis.call('HKEYS', key)
|
|
55
|
+
local stale = {}
|
|
56
|
+
for i = 1, #fields do
|
|
57
|
+
if fields[i] < cutoff then
|
|
58
|
+
stale[#stale + 1] = fields[i]
|
|
59
|
+
if #stale == 256 then
|
|
60
|
+
redis.call('HDEL', key, unpack(stale))
|
|
61
|
+
stale = {}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
if #stale > 0 then
|
|
66
|
+
redis.call('HDEL', key, unpack(stale))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
local width = tonumber(ARGV[7])
|
|
71
|
+
local newDay = redis.call('HEXISTS', KEYS[2], ARGV[2]) == 0
|
|
72
|
+
|
|
73
|
+
merge(KEYS[1], ARGV[1], ARGV[3], width)
|
|
74
|
+
merge(KEYS[2], ARGV[2], ARGV[3], width)
|
|
75
|
+
merge(KEYS[3], ARGV[1], ARGV[3], width)
|
|
76
|
+
merge(KEYS[4], ARGV[2], ARGV[3], width)
|
|
77
|
+
|
|
78
|
+
redis.call('EXPIRE', KEYS[1], ARGV[4])
|
|
79
|
+
redis.call('EXPIRE', KEYS[2], ARGV[5])
|
|
80
|
+
redis.call('EXPIRE', KEYS[3], ARGV[4])
|
|
81
|
+
redis.call('EXPIRE', KEYS[4], ARGV[5])
|
|
82
|
+
|
|
83
|
+
if newDay then
|
|
84
|
+
trim(KEYS[2], ARGV[6])
|
|
85
|
+
trim(KEYS[4], ARGV[6])
|
|
86
|
+
end
|
|
87
|
+
return 1
|
|
88
|
+
`;
|
|
89
|
+
/**
|
|
90
|
+
* Queue age is a gauge, so an hour holds the worst backlog seen in it and a day holds the
|
|
91
|
+
* worst of its hours. Summing would be meaningless -- which is also why the global rollup
|
|
92
|
+
* here is a MAX across queues rather than the four-key SUM MERGE_VECTOR does: "the oldest
|
|
93
|
+
* job waiting anywhere on this board" is the meaningful cross-queue number, adding queues'
|
|
94
|
+
* ages together would not be.
|
|
95
|
+
*
|
|
96
|
+
* Because it's "set if greater", a bucket's value never goes down within its own hour or
|
|
97
|
+
* day once a high value lands -- that's the correct meaning of "the worst backlog seen in
|
|
98
|
+
* this hour/day", both for a single queue and for the global rollup. A later, healthier
|
|
99
|
+
* queue doesn't erase an earlier spike from the same bucket; it only wins the buckets it
|
|
100
|
+
* writes to itself.
|
|
101
|
+
*
|
|
102
|
+
* The totals hashes need the same day cutoff MERGE_VECTOR applies: their TTL is refreshed on
|
|
103
|
+
* every tick, so without a trim they would accumulate one field per day for as long as the
|
|
104
|
+
* queue exists rather than for the retention window.
|
|
105
|
+
*
|
|
106
|
+
* KEYS[1] queue hour hash ARGV[1] hour field ARGV[4] hour-tier ttl
|
|
107
|
+
* KEYS[2] queue totals hash ARGV[2] day field ARGV[5] day-tier ttl
|
|
108
|
+
* KEYS[3] global hour hash ARGV[3] value ARGV[6] oldest day to keep
|
|
109
|
+
* KEYS[4] global totals hash
|
|
110
|
+
*/
|
|
111
|
+
const MAX_GAUGE = `
|
|
112
|
+
local function setMax(key, field, value)
|
|
113
|
+
local current = tonumber(redis.call('HGET', key, field) or '-1')
|
|
114
|
+
if value > current then
|
|
115
|
+
redis.call('HSET', key, field, value)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
local function trim(key, cutoff)
|
|
120
|
+
local fields = redis.call('HKEYS', key)
|
|
121
|
+
local stale = {}
|
|
122
|
+
for i = 1, #fields do
|
|
123
|
+
if fields[i] < cutoff then
|
|
124
|
+
stale[#stale + 1] = fields[i]
|
|
125
|
+
if #stale == 256 then
|
|
126
|
+
redis.call('HDEL', key, unpack(stale))
|
|
127
|
+
stale = {}
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
if #stale > 0 then
|
|
132
|
+
redis.call('HDEL', key, unpack(stale))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
local value = tonumber(ARGV[3])
|
|
137
|
+
local newDay = redis.call('HEXISTS', KEYS[2], ARGV[2]) == 0
|
|
138
|
+
|
|
139
|
+
setMax(KEYS[1], ARGV[1], value)
|
|
140
|
+
setMax(KEYS[2], ARGV[2], value)
|
|
141
|
+
setMax(KEYS[3], ARGV[1], value)
|
|
142
|
+
setMax(KEYS[4], ARGV[2], value)
|
|
143
|
+
|
|
144
|
+
redis.call('EXPIRE', KEYS[1], ARGV[4])
|
|
145
|
+
redis.call('EXPIRE', KEYS[2], ARGV[5])
|
|
146
|
+
redis.call('EXPIRE', KEYS[3], ARGV[4])
|
|
147
|
+
redis.call('EXPIRE', KEYS[4], ARGV[5])
|
|
148
|
+
|
|
149
|
+
if newDay then
|
|
150
|
+
trim(KEYS[2], ARGV[6])
|
|
151
|
+
trim(KEYS[4], ARGV[6])
|
|
152
|
+
end
|
|
153
|
+
return 1
|
|
154
|
+
`;
|
|
155
|
+
class LatencyStore {
|
|
156
|
+
constructor(opts) {
|
|
157
|
+
this.redis = opts.redis;
|
|
158
|
+
this.retention = opts.retention;
|
|
159
|
+
}
|
|
160
|
+
async addSamples(queue, metric, hour, vector) {
|
|
161
|
+
const day = (0, keys_1.minuteToDay)(hour * 60);
|
|
162
|
+
await this.redis.eval(MERGE_VECTOR, 4, (0, keys_1.hourHashKey)(queue, metric, day), (0, keys_1.totalsHashKey)(queue, metric), (0, keys_1.hourHashKey)(keys_1.GLOBAL_QUEUE, metric, day), (0, keys_1.totalsHashKey)(keys_1.GLOBAL_QUEUE, metric), String(hour), day, (0, histogram_1.packVector)(vector), ttl(this.retention.hours), ttl(this.retention.days), (0, keys_1.shiftDay)(day, -this.retention.days), String(histogram_1.BUCKET_COUNT));
|
|
163
|
+
}
|
|
164
|
+
async recordQueueAge(queue, hour, ms) {
|
|
165
|
+
const day = (0, keys_1.minuteToDay)(hour * 60);
|
|
166
|
+
await this.redis.eval(MAX_GAUGE, 4, (0, keys_1.hourHashKey)(queue, exports.QUEUE_AGE_METRIC, day), (0, keys_1.totalsHashKey)(queue, exports.QUEUE_AGE_METRIC), (0, keys_1.hourHashKey)(keys_1.GLOBAL_QUEUE, exports.QUEUE_AGE_METRIC, day), (0, keys_1.totalsHashKey)(keys_1.GLOBAL_QUEUE, exports.QUEUE_AGE_METRIC), String(hour), day, String(Math.max(0, Math.round(ms))), ttl(this.retention.hours), ttl(this.retention.days), (0, keys_1.shiftDay)(day, -this.retention.days));
|
|
167
|
+
}
|
|
168
|
+
async readRange(queue, metric, granularity, days) {
|
|
169
|
+
const out = {};
|
|
170
|
+
if (granularity === 'day') {
|
|
171
|
+
const raw = await this.redis.hgetall((0, keys_1.totalsHashKey)(queue, metric));
|
|
172
|
+
for (const day of days) {
|
|
173
|
+
if (raw[day] !== undefined) {
|
|
174
|
+
out[day] = (0, histogram_1.unpackVector)(raw[day]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
// Up to one key per retention day, so these go out together rather than as a serial
|
|
180
|
+
// chain of round trips, matching how the counter path reads its day hashes.
|
|
181
|
+
const perDay = await Promise.all(days.map((day) => this.redis.hgetall((0, keys_1.hourHashKey)(queue, metric, day))));
|
|
182
|
+
for (const raw of perDay) {
|
|
183
|
+
for (const field of Object.keys(raw)) {
|
|
184
|
+
out[field] = out[field]
|
|
185
|
+
? (0, histogram_1.mergeVectors)(out[field], (0, histogram_1.unpackVector)(raw[field]))
|
|
186
|
+
: (0, histogram_1.unpackVector)(raw[field]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
async readQueueAge(queue, granularity, days) {
|
|
192
|
+
var _a;
|
|
193
|
+
const out = {};
|
|
194
|
+
if (granularity === 'day') {
|
|
195
|
+
const raw = await this.redis.hgetall((0, keys_1.totalsHashKey)(queue, exports.QUEUE_AGE_METRIC));
|
|
196
|
+
for (const day of days) {
|
|
197
|
+
if (raw[day] !== undefined) {
|
|
198
|
+
out[day] = Number(raw[day]) || 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
const perDay = await Promise.all(days.map((day) => this.redis.hgetall((0, keys_1.hourHashKey)(queue, exports.QUEUE_AGE_METRIC, day))));
|
|
204
|
+
for (const raw of perDay) {
|
|
205
|
+
for (const field of Object.keys(raw)) {
|
|
206
|
+
out[field] = Math.max((_a = out[field]) !== null && _a !== void 0 ? _a : 0, Number(raw[field]) || 0);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
/** Epoch ms for an absolute hour index, for building response timestamps. */
|
|
212
|
+
static hourToMs(hour) {
|
|
213
|
+
return hour * MS_PER_HOUR;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.LatencyStore = LatencyStore;
|
|
217
|
+
//# sourceMappingURL=LatencyStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LatencyStore.js","sourceRoot":"","sources":["../src/LatencyStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,30 @@ export interface MetricsRecorderOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
retentionDays?: number;
|
|
21
21
|
snapshotIntervalMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Latency histograms and the queue-age gauge. On by default: the package exists to give
|
|
24
|
+
* boards without a metrics stack something useful, and an opt-in feature is one nobody
|
|
25
|
+
* finds. At default retention this costs roughly 250 to 300KB per queue under typical
|
|
26
|
+
* traffic, up to about 575KB in a pathological worst case, plus a one-off shared cost of
|
|
27
|
+
* roughly 224KB for the cross-queue rollup regardless of queue count.
|
|
28
|
+
*/
|
|
29
|
+
latency?: boolean;
|
|
30
|
+
/** Above this many finished jobs in one tick, the sampler subsamples. */
|
|
31
|
+
maxLatencySamplesPerTick?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Test-oriented escape hatch: overrides the sampler's default 5s safety margin (see
|
|
34
|
+
* `LatencySampler`'s `SAFETY_MARGIN_MS`), which otherwise excludes jobs that finished
|
|
35
|
+
* just before a scan. Lets a test read back a sample immediately instead of sleeping
|
|
36
|
+
* past the margin.
|
|
37
|
+
*/
|
|
38
|
+
latencySafetyMarginMs?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Notified whenever a latency tick fails. Latency errors are swallowed on purpose so a
|
|
41
|
+
* failing scan cannot take the counter snapshot with it, which also means a collector
|
|
42
|
+
* broken since startup looks the same as a board with no traffic. Default stays silent;
|
|
43
|
+
* wire this to your logger to tell an empty chart from a broken one.
|
|
44
|
+
*/
|
|
45
|
+
onLatencyError?: (error: unknown, queueName: string) => void;
|
|
22
46
|
}
|
|
23
47
|
export declare function resolveRetention(opts: {
|
|
24
48
|
retention?: Partial<Retention>;
|
|
@@ -33,6 +57,8 @@ export declare class MetricsRecorder {
|
|
|
33
57
|
private readonly lastMinute;
|
|
34
58
|
private timer;
|
|
35
59
|
private running;
|
|
60
|
+
readonly latencyEnabled: boolean;
|
|
61
|
+
private readonly latencySampler;
|
|
36
62
|
constructor(opts: MetricsRecorderOptions);
|
|
37
63
|
get retention(): Retention;
|
|
38
64
|
start(): void;
|
package/dist/MetricsRecorder.js
CHANGED
|
@@ -5,6 +5,8 @@ exports.resolveRetention = resolveRetention;
|
|
|
5
5
|
const ioredis_1 = require("ioredis");
|
|
6
6
|
const dataMapping_1 = require("./dataMapping");
|
|
7
7
|
const HistoryStore_1 = require("./HistoryStore");
|
|
8
|
+
const LatencySampler_1 = require("./LatencySampler");
|
|
9
|
+
const LatencyStore_1 = require("./LatencyStore");
|
|
8
10
|
const METRICS = ['completed', 'failed'];
|
|
9
11
|
const MS_PER_MINUTE = 60000;
|
|
10
12
|
const MINUTES_PER_DAY = 1440;
|
|
@@ -42,6 +44,17 @@ class MetricsRecorder {
|
|
|
42
44
|
this.ownsRedis = true;
|
|
43
45
|
}
|
|
44
46
|
this.store = new HistoryStore_1.HistoryStore({ redis: this.redis, retention: resolveRetention(opts) });
|
|
47
|
+
this.latencyEnabled = opts.latency !== false;
|
|
48
|
+
this.latencySampler = this.latencyEnabled
|
|
49
|
+
? new LatencySampler_1.LatencySampler({
|
|
50
|
+
redis: this.redis,
|
|
51
|
+
store: new LatencyStore_1.LatencyStore({ redis: this.redis, retention: resolveRetention(opts) }),
|
|
52
|
+
tickMs: this.intervalMs,
|
|
53
|
+
maxSamplesPerTick: opts.maxLatencySamplesPerTick,
|
|
54
|
+
safetyMarginMs: opts.latencySafetyMarginMs,
|
|
55
|
+
onError: opts.onLatencyError,
|
|
56
|
+
})
|
|
57
|
+
: null;
|
|
45
58
|
}
|
|
46
59
|
get retention() {
|
|
47
60
|
return this.store.retention;
|
|
@@ -79,6 +92,9 @@ class MetricsRecorder {
|
|
|
79
92
|
for (const metric of METRICS) {
|
|
80
93
|
await this.snapshotOne(adapter, name, metric);
|
|
81
94
|
}
|
|
95
|
+
if (this.latencySampler) {
|
|
96
|
+
await this.latencySampler.sample(adapter);
|
|
97
|
+
}
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
100
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricsRecorder.js","sourceRoot":"","sources":["../src/MetricsRecorder.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"MetricsRecorder.js","sourceRoot":"","sources":["../src/MetricsRecorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MetricsHistoryPoint, MetricsHistoryProvider, MetricsHistoryQuery } from '@bull-board/api/typings/app';
|
|
1
|
+
import type { MetricsHistoryPoint, MetricsHistoryProvider, MetricsHistoryQuery, MetricsLatencyPoint, MetricsLatencyQuery } from '@bull-board/api/typings/app';
|
|
2
2
|
import { Redis, type RedisOptions } from 'ioredis';
|
|
3
3
|
import { type HistoryStats, type PurgeOptions, type PurgeResult } from './HistoryAdmin';
|
|
4
4
|
import { type Retention } from './HistoryStore';
|
|
@@ -10,6 +10,7 @@ export interface RedisMetricsHistoryProviderOptions {
|
|
|
10
10
|
}
|
|
11
11
|
export declare class RedisMetricsHistoryProvider implements MetricsHistoryProvider {
|
|
12
12
|
private readonly store;
|
|
13
|
+
private readonly latencyStore;
|
|
13
14
|
private readonly admin;
|
|
14
15
|
private readonly redis;
|
|
15
16
|
private readonly ownsRedis;
|
|
@@ -21,4 +22,5 @@ export declare class RedisMetricsHistoryProvider implements MetricsHistoryProvid
|
|
|
21
22
|
/** Backs the board's "clear history" action. See MetricsHistoryAdmin.purge. */
|
|
22
23
|
purge(options?: PurgeOptions): Promise<PurgeResult>;
|
|
23
24
|
getHistory(query: MetricsHistoryQuery): Promise<MetricsHistoryPoint[]>;
|
|
25
|
+
getLatency(query: MetricsLatencyQuery): Promise<MetricsLatencyPoint[]>;
|
|
24
26
|
}
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RedisMetricsHistoryProvider = void 0;
|
|
4
4
|
const ioredis_1 = require("ioredis");
|
|
5
|
+
const histogram_1 = require("./histogram");
|
|
5
6
|
const HistoryAdmin_1 = require("./HistoryAdmin");
|
|
6
7
|
const HistoryStore_1 = require("./HistoryStore");
|
|
7
8
|
const keys_1 = require("./keys");
|
|
9
|
+
const LatencyStore_1 = require("./LatencyStore");
|
|
8
10
|
const MetricsRecorder_1 = require("./MetricsRecorder");
|
|
9
11
|
const MS_PER_HOUR = 3600000;
|
|
10
12
|
class RedisMetricsHistoryProvider {
|
|
@@ -20,6 +22,7 @@ class RedisMetricsHistoryProvider {
|
|
|
20
22
|
const retention = (0, MetricsRecorder_1.resolveRetention)(opts);
|
|
21
23
|
this.retentionDays = retention.days;
|
|
22
24
|
this.store = new HistoryStore_1.HistoryStore({ redis: this.redis, retention });
|
|
25
|
+
this.latencyStore = new LatencyStore_1.LatencyStore({ redis: this.redis, retention });
|
|
23
26
|
this.admin = new HistoryAdmin_1.MetricsHistoryAdmin({ connection: this.redis });
|
|
24
27
|
}
|
|
25
28
|
disconnect() {
|
|
@@ -43,6 +46,19 @@ class RedisMetricsHistoryProvider {
|
|
|
43
46
|
const maxSpanMs = (this.retentionDays + 1) * 86400000;
|
|
44
47
|
const from = Math.max(query.from, query.to - maxSpanMs);
|
|
45
48
|
const days = (0, keys_1.dayRange)(from, query.to);
|
|
49
|
+
if (query.metric === 'queueage') {
|
|
50
|
+
const ages = await this.latencyStore.readQueueAge(queue, query.granularity, days);
|
|
51
|
+
// Day points are stamped at the day's start, so an intraday `from` would drop the day
|
|
52
|
+
// it falls in. Floored, exactly as the counter path below does it.
|
|
53
|
+
const lowerBound = query.granularity === 'day' ? dayFloor(query.from) : query.from;
|
|
54
|
+
return Object.keys(ages)
|
|
55
|
+
.map((key) => ({
|
|
56
|
+
ts: query.granularity === 'day' ? (0, keys_1.dayToStartMs)(key) : Number(key) * MS_PER_HOUR,
|
|
57
|
+
value: ages[key],
|
|
58
|
+
}))
|
|
59
|
+
.filter((p) => p.ts >= lowerBound && p.ts <= query.to)
|
|
60
|
+
.sort((a, b) => a.ts - b.ts);
|
|
61
|
+
}
|
|
46
62
|
if (query.granularity === 'day') {
|
|
47
63
|
const rawTotals = await this.store.readDailyTotalsRaw(queue, query.metric, days);
|
|
48
64
|
// Empty history only when no day in range was ever recorded (all fields missing).
|
|
@@ -74,6 +90,58 @@ class RedisMetricsHistoryProvider {
|
|
|
74
90
|
.map(([ts, value]) => ({ ts, value }))
|
|
75
91
|
.sort((a, b) => a.ts - b.ts);
|
|
76
92
|
}
|
|
93
|
+
async getLatency(query) {
|
|
94
|
+
var _a;
|
|
95
|
+
const queue = (_a = query.queue) !== null && _a !== void 0 ? _a : keys_1.GLOBAL_QUEUE;
|
|
96
|
+
const maxSpanMs = (this.retentionDays + 1) * 86400000;
|
|
97
|
+
const from = Math.max(query.from, query.to - maxSpanMs);
|
|
98
|
+
const days = (0, keys_1.dayRange)(from, query.to);
|
|
99
|
+
if (query.granularity === 'range') {
|
|
100
|
+
// Percentiles don't merge: averaging per-day p95s isn't the same number as the p95 of
|
|
101
|
+
// the whole range. Read the day tier's bucket vectors and merge them, then compute each
|
|
102
|
+
// requested percentile once from the summed vector.
|
|
103
|
+
const raw = await this.latencyStore.readRange(queue, query.metric, 'day', days);
|
|
104
|
+
let merged = (0, histogram_1.emptyVector)();
|
|
105
|
+
for (const day of days) {
|
|
106
|
+
const vector = raw[day];
|
|
107
|
+
if (vector) {
|
|
108
|
+
merged = (0, histogram_1.mergeVectors)(merged, vector);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const count = (0, histogram_1.vectorTotal)(merged);
|
|
112
|
+
if (count === 0) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const values = {};
|
|
116
|
+
for (const p of query.percentiles) {
|
|
117
|
+
values[String(p)] = (0, histogram_1.quantile)(merged, p);
|
|
118
|
+
}
|
|
119
|
+
return [{ ts: query.from, count: Math.round(count), values }];
|
|
120
|
+
}
|
|
121
|
+
const raw = await this.latencyStore.readRange(queue, query.metric, query.granularity, days);
|
|
122
|
+
// Same day-start alignment as getHistory: comparing a day bucket against a raw `from`
|
|
123
|
+
// would drop the oldest day and leave this chart one bucket shorter than the throughput
|
|
124
|
+
// chart drawn for the same range.
|
|
125
|
+
const lowerBound = query.granularity === 'day' ? dayFloor(query.from) : query.from;
|
|
126
|
+
const points = [];
|
|
127
|
+
for (const key of Object.keys(raw)) {
|
|
128
|
+
const ts = query.granularity === 'day' ? (0, keys_1.dayToStartMs)(key) : Number(key) * MS_PER_HOUR;
|
|
129
|
+
if (ts < lowerBound || ts > query.to) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const vector = raw[key];
|
|
133
|
+
const count = (0, histogram_1.vectorTotal)(vector);
|
|
134
|
+
if (count === 0) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const values = {};
|
|
138
|
+
for (const p of query.percentiles) {
|
|
139
|
+
values[String(p)] = (0, histogram_1.quantile)(vector, p);
|
|
140
|
+
}
|
|
141
|
+
points.push({ ts, count: Math.round(count), values });
|
|
142
|
+
}
|
|
143
|
+
return points.sort((a, b) => a.ts - b.ts);
|
|
144
|
+
}
|
|
77
145
|
}
|
|
78
146
|
exports.RedisMetricsHistoryProvider = RedisMetricsHistoryProvider;
|
|
79
147
|
function dayFloor(ms) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedisMetricsHistoryProvider.js","sourceRoot":"","sources":["../src/RedisMetricsHistoryProvider.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"RedisMetricsHistoryProvider.js","sourceRoot":"","sources":["../src/RedisMetricsHistoryProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed bucket boundaries in milliseconds, roughly 2-3x steps. Fixed rather than
|
|
3
|
+
* configurable because two ranges holding different layouts cannot be merged into one
|
|
4
|
+
* percentile without layout versioning, and the merge is what makes multi-day queries work.
|
|
5
|
+
*
|
|
6
|
+
* The tail past 10 minutes exists for wait time, where a stalled queue genuinely sits for
|
|
7
|
+
* hours. Without it p99 wait collapses to "over 10 minutes" exactly when it needs to be
|
|
8
|
+
* actionable.
|
|
9
|
+
*/
|
|
10
|
+
export declare const BUCKET_BOUNDS: number[];
|
|
11
|
+
/** One bucket per bound, plus a final overflow bucket for anything past the last bound. */
|
|
12
|
+
export declare const BUCKET_COUNT: number;
|
|
13
|
+
export declare function bucketIndex(ms: number): number;
|
|
14
|
+
export declare function emptyVector(): number[];
|
|
15
|
+
export declare function packVector(vector: number[]): string;
|
|
16
|
+
export declare function unpackVector(raw: string | null): number[];
|
|
17
|
+
export declare function mergeVectors(a: number[], b: number[]): number[];
|
|
18
|
+
export declare function vectorTotal(vector: number[]): number;
|
|
19
|
+
/**
|
|
20
|
+
* Prometheus-style histogram_quantile: find the bucket holding the rank, then interpolate
|
|
21
|
+
* linearly across that bucket's span. The result is an estimate bounded by bucket width,
|
|
22
|
+
* never more precise than the layout allows.
|
|
23
|
+
*
|
|
24
|
+
* Samples in the overflow bucket have no upper bound to interpolate towards, so they report
|
|
25
|
+
* the last finite bound rather than inventing a ceiling.
|
|
26
|
+
*/
|
|
27
|
+
export declare function quantile(vector: number[], p: number): number;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BUCKET_COUNT = exports.BUCKET_BOUNDS = void 0;
|
|
4
|
+
exports.bucketIndex = bucketIndex;
|
|
5
|
+
exports.emptyVector = emptyVector;
|
|
6
|
+
exports.packVector = packVector;
|
|
7
|
+
exports.unpackVector = unpackVector;
|
|
8
|
+
exports.mergeVectors = mergeVectors;
|
|
9
|
+
exports.vectorTotal = vectorTotal;
|
|
10
|
+
exports.quantile = quantile;
|
|
11
|
+
/**
|
|
12
|
+
* Fixed bucket boundaries in milliseconds, roughly 2-3x steps. Fixed rather than
|
|
13
|
+
* configurable because two ranges holding different layouts cannot be merged into one
|
|
14
|
+
* percentile without layout versioning, and the merge is what makes multi-day queries work.
|
|
15
|
+
*
|
|
16
|
+
* The tail past 10 minutes exists for wait time, where a stalled queue genuinely sits for
|
|
17
|
+
* hours. Without it p99 wait collapses to "over 10 minutes" exactly when it needs to be
|
|
18
|
+
* actionable.
|
|
19
|
+
*/
|
|
20
|
+
exports.BUCKET_BOUNDS = [
|
|
21
|
+
10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000, 300000, 600000,
|
|
22
|
+
1800000, 3600000, 21600000,
|
|
23
|
+
];
|
|
24
|
+
/** One bucket per bound, plus a final overflow bucket for anything past the last bound. */
|
|
25
|
+
exports.BUCKET_COUNT = exports.BUCKET_BOUNDS.length + 1;
|
|
26
|
+
function bucketIndex(ms) {
|
|
27
|
+
for (let i = 0; i < exports.BUCKET_BOUNDS.length; i++) {
|
|
28
|
+
if (ms <= exports.BUCKET_BOUNDS[i]) {
|
|
29
|
+
return i;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return exports.BUCKET_COUNT - 1;
|
|
33
|
+
}
|
|
34
|
+
function emptyVector() {
|
|
35
|
+
return new Array(exports.BUCKET_COUNT).fill(0);
|
|
36
|
+
}
|
|
37
|
+
function packVector(vector) {
|
|
38
|
+
return vector.join(',');
|
|
39
|
+
}
|
|
40
|
+
function unpackVector(raw) {
|
|
41
|
+
const out = emptyVector();
|
|
42
|
+
if (!raw) {
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
const parts = raw.split(',');
|
|
46
|
+
for (let i = 0; i < exports.BUCKET_COUNT && i < parts.length; i++) {
|
|
47
|
+
out[i] = Number(parts[i]) || 0;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function mergeVectors(a, b) {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
const out = emptyVector();
|
|
54
|
+
for (let i = 0; i < exports.BUCKET_COUNT; i++) {
|
|
55
|
+
out[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) + ((_b = b[i]) !== null && _b !== void 0 ? _b : 0);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function vectorTotal(vector) {
|
|
60
|
+
var _a;
|
|
61
|
+
let total = 0;
|
|
62
|
+
for (let i = 0; i < exports.BUCKET_COUNT; i++) {
|
|
63
|
+
total += (_a = vector[i]) !== null && _a !== void 0 ? _a : 0;
|
|
64
|
+
}
|
|
65
|
+
return total;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Prometheus-style histogram_quantile: find the bucket holding the rank, then interpolate
|
|
69
|
+
* linearly across that bucket's span. The result is an estimate bounded by bucket width,
|
|
70
|
+
* never more precise than the layout allows.
|
|
71
|
+
*
|
|
72
|
+
* Samples in the overflow bucket have no upper bound to interpolate towards, so they report
|
|
73
|
+
* the last finite bound rather than inventing a ceiling.
|
|
74
|
+
*/
|
|
75
|
+
function quantile(vector, p) {
|
|
76
|
+
var _a;
|
|
77
|
+
const total = vectorTotal(vector);
|
|
78
|
+
if (total === 0) {
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
const target = (p / 100) * total;
|
|
82
|
+
let cumulative = 0;
|
|
83
|
+
for (let i = 0; i < exports.BUCKET_COUNT; i++) {
|
|
84
|
+
const count = (_a = vector[i]) !== null && _a !== void 0 ? _a : 0;
|
|
85
|
+
if (count === 0) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (cumulative + count >= target) {
|
|
89
|
+
if (i === exports.BUCKET_COUNT - 1) {
|
|
90
|
+
return exports.BUCKET_BOUNDS[exports.BUCKET_BOUNDS.length - 1];
|
|
91
|
+
}
|
|
92
|
+
const lower = i === 0 ? 0 : exports.BUCKET_BOUNDS[i - 1];
|
|
93
|
+
const upper = exports.BUCKET_BOUNDS[i];
|
|
94
|
+
const withinBucket = (target - cumulative) / count;
|
|
95
|
+
return Math.round(lower + (upper - lower) * withinBucket);
|
|
96
|
+
}
|
|
97
|
+
cumulative += count;
|
|
98
|
+
}
|
|
99
|
+
return exports.BUCKET_BOUNDS[exports.BUCKET_BOUNDS.length - 1];
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=histogram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"histogram.js","sourceRoot":"","sources":["../src/histogram.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,8 @@ export { MetricsRecorder } from './MetricsRecorder';
|
|
|
4
4
|
export type { MetricsRecorderOptions } from './MetricsRecorder';
|
|
5
5
|
export { RedisMetricsHistoryProvider } from './RedisMetricsHistoryProvider';
|
|
6
6
|
export type { RedisMetricsHistoryProviderOptions } from './RedisMetricsHistoryProvider';
|
|
7
|
+
export { LatencySampler } from './LatencySampler';
|
|
8
|
+
export type { LatencySamplerOptions } from './LatencySampler';
|
|
9
|
+
export { LatencyStore, QUEUE_AGE_METRIC } from './LatencyStore';
|
|
10
|
+
export type { LatencyMetric } from './LatencyStore';
|
|
11
|
+
export { BUCKET_BOUNDS, BUCKET_COUNT, quantile } from './histogram';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RedisMetricsHistoryProvider = exports.MetricsRecorder = exports.MetricsHistoryAdmin = void 0;
|
|
3
|
+
exports.quantile = exports.BUCKET_COUNT = exports.BUCKET_BOUNDS = exports.QUEUE_AGE_METRIC = exports.LatencyStore = exports.LatencySampler = exports.RedisMetricsHistoryProvider = exports.MetricsRecorder = exports.MetricsHistoryAdmin = void 0;
|
|
4
4
|
var HistoryAdmin_1 = require("./HistoryAdmin");
|
|
5
5
|
Object.defineProperty(exports, "MetricsHistoryAdmin", { enumerable: true, get: function () { return HistoryAdmin_1.MetricsHistoryAdmin; } });
|
|
6
6
|
var MetricsRecorder_1 = require("./MetricsRecorder");
|
|
7
7
|
Object.defineProperty(exports, "MetricsRecorder", { enumerable: true, get: function () { return MetricsRecorder_1.MetricsRecorder; } });
|
|
8
8
|
var RedisMetricsHistoryProvider_1 = require("./RedisMetricsHistoryProvider");
|
|
9
9
|
Object.defineProperty(exports, "RedisMetricsHistoryProvider", { enumerable: true, get: function () { return RedisMetricsHistoryProvider_1.RedisMetricsHistoryProvider; } });
|
|
10
|
+
var LatencySampler_1 = require("./LatencySampler");
|
|
11
|
+
Object.defineProperty(exports, "LatencySampler", { enumerable: true, get: function () { return LatencySampler_1.LatencySampler; } });
|
|
12
|
+
var LatencyStore_1 = require("./LatencyStore");
|
|
13
|
+
Object.defineProperty(exports, "LatencyStore", { enumerable: true, get: function () { return LatencyStore_1.LatencyStore; } });
|
|
14
|
+
Object.defineProperty(exports, "QUEUE_AGE_METRIC", { enumerable: true, get: function () { return LatencyStore_1.QUEUE_AGE_METRIC; } });
|
|
15
|
+
var histogram_1 = require("./histogram");
|
|
16
|
+
Object.defineProperty(exports, "BUCKET_BOUNDS", { enumerable: true, get: function () { return histogram_1.BUCKET_BOUNDS; } });
|
|
17
|
+
Object.defineProperty(exports, "BUCKET_COUNT", { enumerable: true, get: function () { return histogram_1.BUCKET_COUNT; } });
|
|
18
|
+
Object.defineProperty(exports, "quantile", { enumerable: true, get: function () { return histogram_1.quantile; } });
|
|
10
19
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAAqD;AAA5C,mHAAA,mBAAmB,OAAA;AAQ5B,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AAExB,6EAA4E;AAAnE,0IAAA,2BAA2B,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAAqD;AAA5C,mHAAA,mBAAmB,OAAA;AAQ5B,qDAAoD;AAA3C,kHAAA,eAAe,OAAA;AAExB,6EAA4E;AAAnE,0IAAA,2BAA2B,OAAA;AAEpC,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AAEvB,+CAAgE;AAAvD,4GAAA,YAAY,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAEvC,yCAAoE;AAA3D,0GAAA,aAAa,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,qGAAA,QAAQ,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bull-board/metrics",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
4
4
|
"description": "Opt-in long-retention historical job metrics recorder and provider for bull-board.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bull",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test": "jest"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@bull-board/api": "8.
|
|
35
|
+
"@bull-board/api": "8.5.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"ioredis": "^5.0.0"
|