import type { BaseAdapter } from '@bull-board/api/baseAdapter'; import { type MetricsConnection } from './connection'; import { type Retention } from './HistoryStore'; /** * Minute detail is the expensive tier by two orders of magnitude, so it defaults to a week * rather than the full window: long enough to match the recommended `MetricsTime.ONE_WEEK` * worker buffer, so the recorder can be down for a week and still catch up completely. * The hourly and daily rollups are cheap enough to keep for the whole window. */ export declare const DEFAULT_RETENTION: Retention; export interface MetricsRecorderOptions { /** * A function is resolved on every tick, for a board whose queue set changes while it * runs. An array is read once, at construction. */ queues: BaseAdapter[] | (() => BaseAdapter[]); connection: MetricsConnection; /** * Redis key namespace, defaulting to `bull-board:metrics`. Set it to separate two boards * sharing one Redis, and give the reading `RedisMetricsHistoryProvider` the same value. * * On a Redis Cluster the namespace has to sit in one hash slot, since the rollup scripts * write a queue's keys and the cross-queue keys in one EVAL. A prefix with no `{...}` hash * tag is wrapped in one, so `staging:metrics` becomes `{staging:metrics}`; supply your own * tag to choose the slot yourself. */ prefix?: string; /** Per-resolution retention in days. Unspecified tiers fall back to the defaults. */ retention?: Partial; /** * Shorthand that sets the daily and hourly windows. Minute retention stays at its * default unless raised explicitly, since that is the tier that drives storage size. */ retentionDays?: number; snapshotIntervalMs?: number; /** * Latency histograms and the queue-age gauge. On by default: the package exists to give * boards without a metrics stack something useful, and an opt-in feature is one nobody * finds. At default retention this costs roughly 250 to 300KB per queue under typical * traffic, up to about 575KB in a pathological worst case, plus a one-off shared cost of * roughly 224KB for the cross-queue rollup regardless of queue count. */ latency?: boolean; /** Above this many finished jobs in one tick, the sampler subsamples. */ maxLatencySamplesPerTick?: number; /** * Test-oriented escape hatch: overrides the sampler's default 5s safety margin (see * `LatencySampler`'s `SAFETY_MARGIN_MS`), which otherwise excludes jobs that finished * just before a scan. Lets a test read back a sample immediately instead of sleeping * past the margin. */ latencySafetyMarginMs?: number; /** * Notified whenever a latency tick fails. Latency errors are swallowed on purpose so a * failing scan cannot take the counter snapshot with it, which also means a collector * broken since startup looks the same as a board with no traffic. Default stays silent; * wire this to your logger to tell an empty chart from a broken one. */ onLatencyError?: (error: unknown, queueName: string) => void; } export declare function resolveRetention(opts: { retention?: Partial; retentionDays?: number; }): Retention; export declare class MetricsRecorder { private readonly resolveQueues; private readonly store; private readonly redis; private readonly ownsRedis; private readonly intervalMs; private readonly lastMinute; private timer; private running; private stopped; readonly latencyEnabled: boolean; private readonly latencySampler; constructor(opts: MetricsRecorderOptions); get retention(): Retention; start(): void; stop(): void; snapshot(): Promise; /** * Incrementally copies BullMQ's per-minute ring buffer into long-retention storage. * `seenUpTo` is a per-(queue, metric) watermark of the newest minute already written. * getMetrics() returns points newest-first, so we walk from the newest and stop at the * first minute we've already stored: everything past it is older and stored too. Fresh * minutes are upserted (safe against overlapping windows across ticks), then the * watermark advances. So the first tick backfills the buffer and every later tick only * writes the minutes that appeared since. */ private snapshotOne; }