@adonis-agora/telescope 0.1.0 → 0.3.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/CHANGELOG.md +37 -0
- package/dist/providers/telescope_provider.d.ts +16 -0
- package/dist/providers/telescope_provider.d.ts.map +1 -1
- package/dist/providers/telescope_provider.js +33 -2
- package/dist/providers/telescope_provider.js.map +1 -1
- package/dist/providers/telescope_ui_provider.d.ts.map +1 -1
- package/dist/providers/telescope_ui_provider.js +87 -1
- package/dist/providers/telescope_ui_provider.js.map +1 -1
- package/dist/providers/telescope_watchers_provider.d.ts +11 -0
- package/dist/providers/telescope_watchers_provider.d.ts.map +1 -1
- package/dist/providers/telescope_watchers_provider.js +58 -0
- package/dist/providers/telescope_watchers_provider.js.map +1 -1
- package/dist/src/define_config.d.ts +50 -0
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +9 -0
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/index.d.ts +23 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +17 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/metrics/metrics_service.d.ts +63 -0
- package/dist/src/metrics/metrics_service.d.ts.map +1 -0
- package/dist/src/metrics/metrics_service.js +116 -0
- package/dist/src/metrics/metrics_service.js.map +1 -0
- package/dist/src/metrics/rollup.d.ts +61 -0
- package/dist/src/metrics/rollup.d.ts.map +1 -0
- package/dist/src/metrics/rollup.js +114 -0
- package/dist/src/metrics/rollup.js.map +1 -0
- package/dist/src/metrics/stats.d.ts +112 -0
- package/dist/src/metrics/stats.d.ts.map +1 -0
- package/dist/src/metrics/stats.js +255 -0
- package/dist/src/metrics/stats.js.map +1 -0
- package/dist/src/metrics/timeseries.d.ts +22 -0
- package/dist/src/metrics/timeseries.d.ts.map +1 -0
- package/dist/src/metrics/timeseries.js +34 -0
- package/dist/src/metrics/timeseries.js.map +1 -0
- package/dist/src/metrics/traces.d.ts +27 -0
- package/dist/src/metrics/traces.d.ts.map +1 -0
- package/dist/src/metrics/traces.js +71 -0
- package/dist/src/metrics/traces.js.map +1 -0
- package/dist/src/metrics/waterfall.d.ts +44 -0
- package/dist/src/metrics/waterfall.d.ts.map +1 -0
- package/dist/src/metrics/waterfall.js +99 -0
- package/dist/src/metrics/waterfall.js.map +1 -0
- package/dist/src/query/n_plus_one.d.ts +60 -0
- package/dist/src/query/n_plus_one.d.ts.map +1 -0
- package/dist/src/query/n_plus_one.js +102 -0
- package/dist/src/query/n_plus_one.js.map +1 -0
- package/dist/src/registry.d.ts +9 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +6 -0
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sampling/sampling.d.ts +52 -0
- package/dist/src/sampling/sampling.d.ts.map +1 -0
- package/dist/src/sampling/sampling.js +111 -0
- package/dist/src/sampling/sampling.js.map +1 -0
- package/dist/src/sampling/sampling_store.d.ts +33 -0
- package/dist/src/sampling/sampling_store.d.ts.map +1 -0
- package/dist/src/sampling/sampling_store.js +65 -0
- package/dist/src/sampling/sampling_store.js.map +1 -0
- package/dist/src/stream/entry_events.d.ts +45 -0
- package/dist/src/stream/entry_events.d.ts.map +1 -0
- package/dist/src/stream/entry_events.js +56 -0
- package/dist/src/stream/entry_events.js.map +1 -0
- package/dist/src/stream/index.d.ts +6 -0
- package/dist/src/stream/index.d.ts.map +1 -0
- package/dist/src/stream/index.js +5 -0
- package/dist/src/stream/index.js.map +1 -0
- package/dist/src/stream/stream_handler.d.ts +42 -0
- package/dist/src/stream/stream_handler.d.ts.map +1 -0
- package/dist/src/stream/stream_handler.js +48 -0
- package/dist/src/stream/stream_handler.js.map +1 -0
- package/dist/src/stream/streaming_store.d.ts +31 -0
- package/dist/src/stream/streaming_store.d.ts.map +1 -0
- package/dist/src/stream/streaming_store.js +49 -0
- package/dist/src/stream/streaming_store.js.map +1 -0
- package/dist/src/ui/api.d.ts +39 -1
- package/dist/src/ui/api.d.ts.map +1 -1
- package/dist/src/ui/api.js +116 -1
- package/dist/src/ui/api.js.map +1 -1
- package/dist/src/ui/define_config.d.ts +27 -0
- package/dist/src/ui/define_config.d.ts.map +1 -1
- package/dist/src/ui/define_config.js +6 -0
- package/dist/src/ui/define_config.js.map +1 -1
- package/dist/src/ui/http.d.ts +40 -0
- package/dist/src/ui/http.d.ts.map +1 -1
- package/dist/src/ui/http.js +43 -0
- package/dist/src/ui/http.js.map +1 -1
- package/dist/src/ui/index.d.ts +5 -3
- package/dist/src/ui/index.d.ts.map +1 -1
- package/dist/src/ui/index.js +3 -1
- package/dist/src/ui/index.js.map +1 -1
- package/dist/src/ui/request_replay.d.ts +56 -0
- package/dist/src/ui/request_replay.d.ts.map +1 -0
- package/dist/src/ui/request_replay.js +120 -0
- package/dist/src/ui/request_replay.js.map +1 -0
- package/dist/src/watchers/define_config.d.ts +17 -1
- package/dist/src/watchers/define_config.d.ts.map +1 -1
- package/dist/src/watchers/define_config.js +13 -0
- package/dist/src/watchers/define_config.js.map +1 -1
- package/dist/src/watchers/events_watcher.d.ts +63 -0
- package/dist/src/watchers/events_watcher.d.ts.map +1 -0
- package/dist/src/watchers/events_watcher.js +81 -0
- package/dist/src/watchers/events_watcher.js.map +1 -0
- package/dist/src/watchers/index.d.ts +6 -0
- package/dist/src/watchers/index.d.ts.map +1 -1
- package/dist/src/watchers/index.js +6 -0
- package/dist/src/watchers/index.js.map +1 -1
- package/dist/src/watchers/queue_watcher.d.ts +97 -0
- package/dist/src/watchers/queue_watcher.d.ts.map +1 -0
- package/dist/src/watchers/queue_watcher.js +114 -0
- package/dist/src/watchers/queue_watcher.js.map +1 -0
- package/dist/src/watchers/redis_watcher.d.ts +103 -0
- package/dist/src/watchers/redis_watcher.d.ts.map +1 -0
- package/dist/src/watchers/redis_watcher.js +161 -0
- package/dist/src/watchers/redis_watcher.js.map +1 -0
- package/dist/stubs/config/telescope.stub +9 -0
- package/dist/stubs/config/telescope_ui.stub +13 -0
- package/dist/stubs/config/telescope_watchers.stub +14 -3
- package/package.json +8 -8
package/dist/src/index.js
CHANGED
|
@@ -17,11 +17,27 @@ export { defineTelescopeExtension } from './extension/types.js';
|
|
|
17
17
|
export { ExtensionRegistry } from './extension/registry.js';
|
|
18
18
|
// — config —
|
|
19
19
|
export { defineConfig, resolveConfig } from './define_config.js';
|
|
20
|
+
// — live-stream (SSE entry-events pub/sub) —
|
|
21
|
+
export { EntryEvents } from './stream/entry_events.js';
|
|
22
|
+
export { StreamingTelescopeStore } from './stream/streaming_store.js';
|
|
23
|
+
export { DEFAULT_HEARTBEAT_MS, streamEntries } from './stream/stream_handler.js';
|
|
24
|
+
// — sampling (tail-sampling on the write path) —
|
|
25
|
+
export { isErrorEntry, passesSampling, resolveSampling, samplingRates, } from './sampling/sampling.js';
|
|
26
|
+
export { SamplingTelescopeStore } from './sampling/sampling_store.js';
|
|
27
|
+
// — query analysis (N+1 detection) —
|
|
28
|
+
export { detectNPlusOne, detectNPlusOnePatterns } from './query/n_plus_one.js';
|
|
29
|
+
// — metrics (stats / timeseries / percentiles / traces / waterfall) —
|
|
30
|
+
export { MetricsService } from './metrics/metrics_service.js';
|
|
31
|
+
export { estimateLatencyPercentiles, percentile, summarizeStats, } from './metrics/stats.js';
|
|
32
|
+
export { bucketTimeseries } from './metrics/timeseries.js';
|
|
33
|
+
export { summarizeTraces } from './metrics/traces.js';
|
|
34
|
+
export { buildWaterfall } from './metrics/waterfall.js';
|
|
35
|
+
export { buildHistogram, emptyHistogram, estimatePercentileFromHistogram, HISTOGRAM_BUCKET_COUNT, histogramBucketIndex, incrementHistogram, LATENCY_BOUNDARIES_MS, mergeHistograms, normalizeHistogram, ROLLUP_BUCKET_MS, } from './metrics/rollup.js';
|
|
20
36
|
// — redaction —
|
|
21
37
|
export { compileRedactSpec, DEFAULT_MASK, DEFAULT_REDACT_KEYS, redact, redactBounded, redactBoundedWith, } from './redaction/redact.js';
|
|
22
38
|
export { RedactingTelescopeStore } from './redaction/redacting_store.js';
|
|
23
39
|
// — runtime (advanced) —
|
|
24
|
-
export { getTelescopeRuntime, resetTelescopeRuntime, setTelescopeExtensionRegistry, setTelescopeRuntime, } from './registry.js';
|
|
40
|
+
export { getTelescopeRuntime, resetTelescopeRuntime, setTelescopeEntryEvents, setTelescopeExtensionRegistry, setTelescopeRuntime, } from './registry.js';
|
|
25
41
|
// — structural ecosystem readers —
|
|
26
42
|
export { currentTraceId, getContextAccessor } from './context_accessor.js';
|
|
27
43
|
export { getDiagnosticsRegistry, isDiagnosticEvent, } from './diagnostics_registry.js';
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,kBAAkB;AAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAQ9C,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,eAAe;AACf,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAMrD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,oBAAoB;AACpB,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAehE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,kBAAkB;AAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAS3B,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAQ9C,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,eAAe;AACf,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAMrD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,oBAAoB;AACpB,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAehE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAUjE,6CAA6C;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGjF,iDAAiD;AACjD,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAEtE,qCAAqC;AACrC,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAO/E,sEAAsE;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAM9D,OAAO,EACL,0BAA0B,EAC1B,UAAU,EACV,cAAc,GACf,MAAM,oBAAoB,CAAC;AAW5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EACL,cAAc,EACd,cAAc,EACd,+BAA+B,EAC/B,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,gBAAgB;AAChB,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,yBAAyB;AACzB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAGvB,mCAAmC;AACnC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3E,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type NPlusOneInsight, type NPlusOnePattern } from '../query/n_plus_one.js';
|
|
2
|
+
import type { TelescopeStore } from '../store.js';
|
|
3
|
+
import { type StatsResult } from './stats.js';
|
|
4
|
+
import { type TimeseriesReport } from './timeseries.js';
|
|
5
|
+
import { type TraceSummary } from './traces.js';
|
|
6
|
+
import { type Waterfall } from './waterfall.js';
|
|
7
|
+
export interface StatsQuery {
|
|
8
|
+
/** Required — stats are computed per entry type. */
|
|
9
|
+
type: string;
|
|
10
|
+
windowMs: number;
|
|
11
|
+
buckets?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface TimeseriesQuery {
|
|
14
|
+
windowMs: number;
|
|
15
|
+
buckets?: number;
|
|
16
|
+
/** Optionally scope the series to a single entry type. */
|
|
17
|
+
type?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MetricsServiceOptions {
|
|
20
|
+
/** Threshold (ms) at/above which an entry counts as slow. Default 100. */
|
|
21
|
+
slowMs?: number;
|
|
22
|
+
defaultBuckets?: number;
|
|
23
|
+
scanCap?: number;
|
|
24
|
+
/** Minimum repetitions to flag an N+1 loop. Default 3. */
|
|
25
|
+
nPlusOneThreshold?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Read-only analytics over the {@link TelescopeStore}: per-type stats (latency
|
|
29
|
+
* percentiles, family/cache/status/exception breakdowns), throughput timeseries,
|
|
30
|
+
* a trace list, a per-trace span waterfall, and N+1 query detection.
|
|
31
|
+
*
|
|
32
|
+
* Storage-agnostic — it works through the {@link TelescopeStore.list} interface
|
|
33
|
+
* only (newest-first), never a specific provider. Analysis reads ALREADY-redacted
|
|
34
|
+
* stored entries, so redaction is never bypassed.
|
|
35
|
+
*/
|
|
36
|
+
export declare class MetricsService {
|
|
37
|
+
private readonly store;
|
|
38
|
+
private readonly slowMs;
|
|
39
|
+
private readonly defaultBuckets;
|
|
40
|
+
private readonly scanCap;
|
|
41
|
+
private readonly nPlusOneThreshold;
|
|
42
|
+
constructor(store: TelescopeStore, options?: MetricsServiceOptions);
|
|
43
|
+
/** Per-type analytics over a trailing window. */
|
|
44
|
+
getStats(query: StatsQuery): Promise<StatsResult>;
|
|
45
|
+
/** Throughput time-series over a trailing window (total + per-type per bucket). */
|
|
46
|
+
getTimeseries(query: TimeseriesQuery): Promise<TimeseriesReport>;
|
|
47
|
+
/** Recent traces, newest-last-seen first. */
|
|
48
|
+
getTraces(limit?: number): Promise<TraceSummary[]>;
|
|
49
|
+
/** The span waterfall for a single trace, or `null` when the trace is empty. */
|
|
50
|
+
getWaterfall(traceId: string): Promise<Waterfall | null>;
|
|
51
|
+
/**
|
|
52
|
+
* N+1 query patterns within a single trace (loop attribution + wasted-time
|
|
53
|
+
* ranking). The store returns newest-first; the detector needs oldest-first
|
|
54
|
+
* record order to attribute the driving parent, so we reverse.
|
|
55
|
+
*/
|
|
56
|
+
getNPlusOne(traceId: string, threshold?: number): Promise<NPlusOnePattern[]>;
|
|
57
|
+
/** Flat family-count N+1 insights within a single trace. */
|
|
58
|
+
getNPlusOneFlat(traceId: string, threshold?: number): Promise<NPlusOneInsight[]>;
|
|
59
|
+
private clampBuckets;
|
|
60
|
+
/** Fetch entries matching `query` (newest-first), capped at `scanCap`. */
|
|
61
|
+
private collect;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=metrics_service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics_service.d.ts","sourceRoot":"","sources":["../../../src/metrics/metrics_service.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EAGrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,KAAK,WAAW,EAA8C,MAAM,YAAY,CAAC;AAC1F,OAAO,EAAE,KAAK,gBAAgB,EAAoB,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,KAAK,YAAY,EAAmB,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,KAAK,SAAS,EAAkB,MAAM,gBAAgB,CAAC;AAShE,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAc;IAOvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IANxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAGxB,KAAK,EAAE,cAAc,EACtC,OAAO,GAAE,qBAA0B;IAQrC,iDAAiD;IAC3C,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAkCvD,mFAAmF;IAC7E,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IActE,6CAA6C;IACvC,SAAS,CAAC,KAAK,SAAK,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAKpD,gFAAgF;IAC1E,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAK9D;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAQlF,4DAA4D;IACtD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAMtF,OAAO,CAAC,YAAY;IAIpB,0EAA0E;YAC5D,OAAO;CAItB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { detectNPlusOne, detectNPlusOnePatterns, } from '../query/n_plus_one.js';
|
|
2
|
+
import { estimateLatencyPercentiles, summarizeStats } from './stats.js';
|
|
3
|
+
import { bucketTimeseries } from './timeseries.js';
|
|
4
|
+
import { summarizeTraces } from './traces.js';
|
|
5
|
+
import { buildWaterfall } from './waterfall.js';
|
|
6
|
+
const DEFAULT_BUCKETS = 60;
|
|
7
|
+
const MAX_BUCKETS = 500;
|
|
8
|
+
const DEFAULT_SLOW_MS = 100;
|
|
9
|
+
/** Safety cap on entries scanned per analytics request; surfaced via `truncated`. */
|
|
10
|
+
const DEFAULT_SCAN_CAP = 50_000;
|
|
11
|
+
const DEFAULT_NPLUSONE_THRESHOLD = 3;
|
|
12
|
+
/**
|
|
13
|
+
* Read-only analytics over the {@link TelescopeStore}: per-type stats (latency
|
|
14
|
+
* percentiles, family/cache/status/exception breakdowns), throughput timeseries,
|
|
15
|
+
* a trace list, a per-trace span waterfall, and N+1 query detection.
|
|
16
|
+
*
|
|
17
|
+
* Storage-agnostic — it works through the {@link TelescopeStore.list} interface
|
|
18
|
+
* only (newest-first), never a specific provider. Analysis reads ALREADY-redacted
|
|
19
|
+
* stored entries, so redaction is never bypassed.
|
|
20
|
+
*/
|
|
21
|
+
export class MetricsService {
|
|
22
|
+
store;
|
|
23
|
+
slowMs;
|
|
24
|
+
defaultBuckets;
|
|
25
|
+
scanCap;
|
|
26
|
+
nPlusOneThreshold;
|
|
27
|
+
constructor(store, options = {}) {
|
|
28
|
+
this.store = store;
|
|
29
|
+
this.slowMs = options.slowMs ?? DEFAULT_SLOW_MS;
|
|
30
|
+
this.defaultBuckets = options.defaultBuckets ?? DEFAULT_BUCKETS;
|
|
31
|
+
this.scanCap = options.scanCap ?? DEFAULT_SCAN_CAP;
|
|
32
|
+
this.nPlusOneThreshold = options.nPlusOneThreshold ?? DEFAULT_NPLUSONE_THRESHOLD;
|
|
33
|
+
}
|
|
34
|
+
/** Per-type analytics over a trailing window. */
|
|
35
|
+
async getStats(query) {
|
|
36
|
+
if (!Number.isFinite(query.windowMs) || query.windowMs <= 0) {
|
|
37
|
+
throw new RangeError(`windowMs must be a positive, finite number (got ${query.windowMs}).`);
|
|
38
|
+
}
|
|
39
|
+
const buckets = this.clampBuckets(query.buckets);
|
|
40
|
+
const windowEnd = new Date();
|
|
41
|
+
const windowStart = new Date(windowEnd.getTime() - query.windowMs);
|
|
42
|
+
const { entries, truncated } = await this.collect({
|
|
43
|
+
type: query.type,
|
|
44
|
+
after: windowStart,
|
|
45
|
+
});
|
|
46
|
+
// Histogram-based percentile estimate over the windowed durations — the
|
|
47
|
+
// storage-agnostic stand-in for the NestJS rollup fast path. count/max/slow
|
|
48
|
+
// stay raw-derived inside summarizeStats.
|
|
49
|
+
const durations = entries
|
|
50
|
+
.map((entry) => entry.durationMs)
|
|
51
|
+
.filter((d) => typeof d === 'number');
|
|
52
|
+
const latencyPercentiles = estimateLatencyPercentiles(durations);
|
|
53
|
+
return summarizeStats({
|
|
54
|
+
entries,
|
|
55
|
+
type: query.type,
|
|
56
|
+
windowStart,
|
|
57
|
+
windowEnd,
|
|
58
|
+
windowMs: query.windowMs,
|
|
59
|
+
buckets,
|
|
60
|
+
slowMs: this.slowMs,
|
|
61
|
+
truncated,
|
|
62
|
+
...(latencyPercentiles !== undefined ? { latencyPercentiles } : {}),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Throughput time-series over a trailing window (total + per-type per bucket). */
|
|
66
|
+
async getTimeseries(query) {
|
|
67
|
+
if (!Number.isFinite(query.windowMs) || query.windowMs <= 0) {
|
|
68
|
+
throw new RangeError(`windowMs must be a positive, finite number (got ${query.windowMs}).`);
|
|
69
|
+
}
|
|
70
|
+
const buckets = this.clampBuckets(query.buckets);
|
|
71
|
+
const windowEnd = new Date();
|
|
72
|
+
const windowStart = new Date(windowEnd.getTime() - query.windowMs);
|
|
73
|
+
const { entries } = await this.collect({
|
|
74
|
+
after: windowStart,
|
|
75
|
+
...(query.type !== undefined ? { type: query.type } : {}),
|
|
76
|
+
});
|
|
77
|
+
return bucketTimeseries(entries, windowStart, windowEnd, buckets);
|
|
78
|
+
}
|
|
79
|
+
/** Recent traces, newest-last-seen first. */
|
|
80
|
+
async getTraces(limit = 50) {
|
|
81
|
+
const { entries } = await this.collect({});
|
|
82
|
+
return summarizeTraces(entries, { limit });
|
|
83
|
+
}
|
|
84
|
+
/** The span waterfall for a single trace, or `null` when the trace is empty. */
|
|
85
|
+
async getWaterfall(traceId) {
|
|
86
|
+
const entries = await this.store.list({ traceId });
|
|
87
|
+
return buildWaterfall(entries);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* N+1 query patterns within a single trace (loop attribution + wasted-time
|
|
91
|
+
* ranking). The store returns newest-first; the detector needs oldest-first
|
|
92
|
+
* record order to attribute the driving parent, so we reverse.
|
|
93
|
+
*/
|
|
94
|
+
async getNPlusOne(traceId, threshold) {
|
|
95
|
+
const entries = await this.store.list({ traceId });
|
|
96
|
+
const ordered = [...entries].reverse();
|
|
97
|
+
return detectNPlusOnePatterns(ordered, {
|
|
98
|
+
threshold: threshold ?? this.nPlusOneThreshold,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Flat family-count N+1 insights within a single trace. */
|
|
102
|
+
async getNPlusOneFlat(traceId, threshold) {
|
|
103
|
+
const entries = await this.store.list({ traceId });
|
|
104
|
+
const ordered = [...entries].reverse();
|
|
105
|
+
return detectNPlusOne(ordered, threshold ?? this.nPlusOneThreshold);
|
|
106
|
+
}
|
|
107
|
+
clampBuckets(buckets) {
|
|
108
|
+
return Math.min(MAX_BUCKETS, Math.max(1, Math.floor(buckets ?? this.defaultBuckets)));
|
|
109
|
+
}
|
|
110
|
+
/** Fetch entries matching `query` (newest-first), capped at `scanCap`. */
|
|
111
|
+
async collect(query) {
|
|
112
|
+
const entries = await this.store.list({ ...query, limit: this.scanCap });
|
|
113
|
+
return { entries, truncated: entries.length >= this.scanCap };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=metrics_service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics_service.js","sourceRoot":"","sources":["../../../src/metrics/metrics_service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-aggregated latency histogram + percentile estimation. Ported faithfully
|
|
3
|
+
* from `nestjs-telescope`'s `rollup/rollup-store.ts` (the boundaries + histogram
|
|
4
|
+
* helpers) and `rollup/estimate-percentile.ts` (the bucket-resolution percentile).
|
|
5
|
+
*
|
|
6
|
+
* This is the storage-agnostic maths only — there is no rollup STORE in the
|
|
7
|
+
* Adonis port (the headless store has no rollup SPI). It exists so the metrics
|
|
8
|
+
* module can offer a histogram-based percentile path whose estimate agrees with
|
|
9
|
+
* the raw-scan nearest-rank percentile within one bucket-width (see the
|
|
10
|
+
* equivalence spec), exactly mirroring the NestJS invariants.
|
|
11
|
+
*/
|
|
12
|
+
/** One minute is the base rollup granularity. */
|
|
13
|
+
export declare const ROLLUP_BUCKET_MS = 60000;
|
|
14
|
+
/**
|
|
15
|
+
* Upper boundaries (inclusive, in ms) for the pre-aggregated latency histogram.
|
|
16
|
+
* Bucket `i` counts durations `d` where `d <= LATENCY_BOUNDARIES_MS[i]` and
|
|
17
|
+
* `d > LATENCY_BOUNDARIES_MS[i-1]`. A final OVERFLOW bucket counts everything
|
|
18
|
+
* greater than the last boundary, so a histogram always has
|
|
19
|
+
* `LATENCY_BOUNDARIES_MS.length + 1` cells.
|
|
20
|
+
*/
|
|
21
|
+
export declare const LATENCY_BOUNDARIES_MS: readonly number[];
|
|
22
|
+
/** The fixed cell count of every latency histogram (boundaries + 1 overflow). */
|
|
23
|
+
export declare const HISTOGRAM_BUCKET_COUNT: number;
|
|
24
|
+
/** A fresh, all-zeros histogram of the canonical fixed length. */
|
|
25
|
+
export declare function emptyHistogram(): number[];
|
|
26
|
+
/**
|
|
27
|
+
* The histogram cell a duration falls in: the first index `i` where
|
|
28
|
+
* `durationMs <= LATENCY_BOUNDARIES_MS[i]`, else the overflow index. Negative or
|
|
29
|
+
* zero durations land in cell 0.
|
|
30
|
+
*/
|
|
31
|
+
export declare function histogramBucketIndex(durationMs: number): number;
|
|
32
|
+
/** Increments the histogram cell for `durationMs` by one, in place. */
|
|
33
|
+
export declare function incrementHistogram(histogram: number[], durationMs: number): void;
|
|
34
|
+
/**
|
|
35
|
+
* Normalizes a (possibly legacy/undefined) histogram into a fixed-length
|
|
36
|
+
* zero-padded array — never NaN, never a wrong width.
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeHistogram(histogram: number[] | null | undefined): number[];
|
|
39
|
+
/**
|
|
40
|
+
* Element-wise additive merge of `addend` into a fresh histogram. Both are
|
|
41
|
+
* normalized first, so legacy/short arrays are safe.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mergeHistograms(target: number[], addend: number[] | null | undefined): number[];
|
|
44
|
+
/** Build a latency histogram from raw durations (ms). */
|
|
45
|
+
export declare function buildHistogram(durations: Iterable<number>): number[];
|
|
46
|
+
/**
|
|
47
|
+
* Estimates a percentile from a pre-aggregated latency histogram (O(buckets), no
|
|
48
|
+
* raw scan). Nearest-rank style at BUCKET resolution: the result is the UPPER
|
|
49
|
+
* boundary of the bucket that contains the target rank, so the estimate is an
|
|
50
|
+
* APPROXIMATION whose error is bounded by that bucket's width.
|
|
51
|
+
*
|
|
52
|
+
* - `total = Σ histogram`; `target rank = ceil(fraction * total)` (1-based,
|
|
53
|
+
* matching the raw nearest-rank `Math.ceil(fraction * n)`).
|
|
54
|
+
* - Walk the cumulative count until it reaches/crosses the target rank; return
|
|
55
|
+
* the containing bucket's upper boundary.
|
|
56
|
+
* - The OVERFLOW bucket has no finite upper boundary; we return the LAST boundary
|
|
57
|
+
* as a safe finite representative (documented under-estimate, never `Infinity`).
|
|
58
|
+
* - Returns 0 when the histogram is empty / total is 0.
|
|
59
|
+
*/
|
|
60
|
+
export declare function estimatePercentileFromHistogram(histogram: number[], fraction: number): number;
|
|
61
|
+
//# sourceMappingURL=rollup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollup.d.ts","sourceRoot":"","sources":["../../../src/metrics/rollup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAElD,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,QAAmC,CAAC;AAEvE,kEAAkE;AAClE,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGhF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAQnF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAO/F;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAIpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,+BAA+B,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmB7F"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-aggregated latency histogram + percentile estimation. Ported faithfully
|
|
3
|
+
* from `nestjs-telescope`'s `rollup/rollup-store.ts` (the boundaries + histogram
|
|
4
|
+
* helpers) and `rollup/estimate-percentile.ts` (the bucket-resolution percentile).
|
|
5
|
+
*
|
|
6
|
+
* This is the storage-agnostic maths only — there is no rollup STORE in the
|
|
7
|
+
* Adonis port (the headless store has no rollup SPI). It exists so the metrics
|
|
8
|
+
* module can offer a histogram-based percentile path whose estimate agrees with
|
|
9
|
+
* the raw-scan nearest-rank percentile within one bucket-width (see the
|
|
10
|
+
* equivalence spec), exactly mirroring the NestJS invariants.
|
|
11
|
+
*/
|
|
12
|
+
/** One minute is the base rollup granularity. */
|
|
13
|
+
export const ROLLUP_BUCKET_MS = 60_000;
|
|
14
|
+
/**
|
|
15
|
+
* Upper boundaries (inclusive, in ms) for the pre-aggregated latency histogram.
|
|
16
|
+
* Bucket `i` counts durations `d` where `d <= LATENCY_BOUNDARIES_MS[i]` and
|
|
17
|
+
* `d > LATENCY_BOUNDARIES_MS[i-1]`. A final OVERFLOW bucket counts everything
|
|
18
|
+
* greater than the last boundary, so a histogram always has
|
|
19
|
+
* `LATENCY_BOUNDARIES_MS.length + 1` cells.
|
|
20
|
+
*/
|
|
21
|
+
export const LATENCY_BOUNDARIES_MS = [
|
|
22
|
+
1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,
|
|
23
|
+
];
|
|
24
|
+
/** The fixed cell count of every latency histogram (boundaries + 1 overflow). */
|
|
25
|
+
export const HISTOGRAM_BUCKET_COUNT = LATENCY_BOUNDARIES_MS.length + 1;
|
|
26
|
+
/** A fresh, all-zeros histogram of the canonical fixed length. */
|
|
27
|
+
export function emptyHistogram() {
|
|
28
|
+
return new Array(HISTOGRAM_BUCKET_COUNT).fill(0);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The histogram cell a duration falls in: the first index `i` where
|
|
32
|
+
* `durationMs <= LATENCY_BOUNDARIES_MS[i]`, else the overflow index. Negative or
|
|
33
|
+
* zero durations land in cell 0.
|
|
34
|
+
*/
|
|
35
|
+
export function histogramBucketIndex(durationMs) {
|
|
36
|
+
for (let index = 0; index < LATENCY_BOUNDARIES_MS.length; index += 1) {
|
|
37
|
+
const boundary = LATENCY_BOUNDARIES_MS[index];
|
|
38
|
+
if (boundary !== undefined && durationMs <= boundary)
|
|
39
|
+
return index;
|
|
40
|
+
}
|
|
41
|
+
return LATENCY_BOUNDARIES_MS.length;
|
|
42
|
+
}
|
|
43
|
+
/** Increments the histogram cell for `durationMs` by one, in place. */
|
|
44
|
+
export function incrementHistogram(histogram, durationMs) {
|
|
45
|
+
const index = histogramBucketIndex(durationMs);
|
|
46
|
+
histogram[index] = (histogram[index] ?? 0) + 1;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Normalizes a (possibly legacy/undefined) histogram into a fixed-length
|
|
50
|
+
* zero-padded array — never NaN, never a wrong width.
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeHistogram(histogram) {
|
|
53
|
+
const normalized = emptyHistogram();
|
|
54
|
+
if (!Array.isArray(histogram))
|
|
55
|
+
return normalized;
|
|
56
|
+
for (let index = 0; index < HISTOGRAM_BUCKET_COUNT; index += 1) {
|
|
57
|
+
const value = histogram[index];
|
|
58
|
+
normalized[index] = typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
59
|
+
}
|
|
60
|
+
return normalized;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Element-wise additive merge of `addend` into a fresh histogram. Both are
|
|
64
|
+
* normalized first, so legacy/short arrays are safe.
|
|
65
|
+
*/
|
|
66
|
+
export function mergeHistograms(target, addend) {
|
|
67
|
+
const left = normalizeHistogram(target);
|
|
68
|
+
const right = normalizeHistogram(addend);
|
|
69
|
+
for (let index = 0; index < HISTOGRAM_BUCKET_COUNT; index += 1) {
|
|
70
|
+
left[index] = (left[index] ?? 0) + (right[index] ?? 0);
|
|
71
|
+
}
|
|
72
|
+
return left;
|
|
73
|
+
}
|
|
74
|
+
/** Build a latency histogram from raw durations (ms). */
|
|
75
|
+
export function buildHistogram(durations) {
|
|
76
|
+
const histogram = emptyHistogram();
|
|
77
|
+
for (const duration of durations)
|
|
78
|
+
incrementHistogram(histogram, duration);
|
|
79
|
+
return histogram;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Estimates a percentile from a pre-aggregated latency histogram (O(buckets), no
|
|
83
|
+
* raw scan). Nearest-rank style at BUCKET resolution: the result is the UPPER
|
|
84
|
+
* boundary of the bucket that contains the target rank, so the estimate is an
|
|
85
|
+
* APPROXIMATION whose error is bounded by that bucket's width.
|
|
86
|
+
*
|
|
87
|
+
* - `total = Σ histogram`; `target rank = ceil(fraction * total)` (1-based,
|
|
88
|
+
* matching the raw nearest-rank `Math.ceil(fraction * n)`).
|
|
89
|
+
* - Walk the cumulative count until it reaches/crosses the target rank; return
|
|
90
|
+
* the containing bucket's upper boundary.
|
|
91
|
+
* - The OVERFLOW bucket has no finite upper boundary; we return the LAST boundary
|
|
92
|
+
* as a safe finite representative (documented under-estimate, never `Infinity`).
|
|
93
|
+
* - Returns 0 when the histogram is empty / total is 0.
|
|
94
|
+
*/
|
|
95
|
+
export function estimatePercentileFromHistogram(histogram, fraction) {
|
|
96
|
+
const cells = normalizeHistogram(histogram);
|
|
97
|
+
let total = 0;
|
|
98
|
+
for (const count of cells)
|
|
99
|
+
total += count;
|
|
100
|
+
if (total <= 0)
|
|
101
|
+
return 0;
|
|
102
|
+
const boundedFraction = Math.min(1, Math.max(0, fraction));
|
|
103
|
+
const targetRank = Math.max(1, Math.ceil(boundedFraction * total));
|
|
104
|
+
const lastBoundary = LATENCY_BOUNDARIES_MS[LATENCY_BOUNDARIES_MS.length - 1] ?? 0;
|
|
105
|
+
let cumulative = 0;
|
|
106
|
+
for (let index = 0; index < cells.length; index += 1) {
|
|
107
|
+
cumulative += cells[index] ?? 0;
|
|
108
|
+
if (cumulative >= targetRank) {
|
|
109
|
+
return LATENCY_BOUNDARIES_MS[index] ?? lastBoundary;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return lastBoundary;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=rollup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollup.js","sourceRoot":"","sources":["../../../src/metrics/rollup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { type Entry } from '../entry.js';
|
|
2
|
+
import { type TimeseriesReport } from './timeseries.js';
|
|
3
|
+
/**
|
|
4
|
+
* Per-type analytics over a window of stored entries. Ported from
|
|
5
|
+
* `nestjs-telescope`'s `metrics/stats.ts`, adapted to the Adonis entry content
|
|
6
|
+
* shapes (request `status`, cache `operation` enum + `key`, exception `name`).
|
|
7
|
+
* Pure: callers fetch the windowed entries and supply the window bounds.
|
|
8
|
+
*/
|
|
9
|
+
export interface LatencyStats {
|
|
10
|
+
count: number;
|
|
11
|
+
p50: number;
|
|
12
|
+
p95: number;
|
|
13
|
+
p99: number;
|
|
14
|
+
max: number;
|
|
15
|
+
slow: number;
|
|
16
|
+
}
|
|
17
|
+
export interface FamilyLatency {
|
|
18
|
+
familyHash: string;
|
|
19
|
+
label: string;
|
|
20
|
+
count: number;
|
|
21
|
+
p50: number;
|
|
22
|
+
p99: number;
|
|
23
|
+
}
|
|
24
|
+
export interface CacheStats {
|
|
25
|
+
hits: number;
|
|
26
|
+
misses: number;
|
|
27
|
+
sets: number;
|
|
28
|
+
hitRatio: number;
|
|
29
|
+
topKeys: {
|
|
30
|
+
key: string;
|
|
31
|
+
count: number;
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
export interface StatusBreakdown {
|
|
35
|
+
'2xx': number;
|
|
36
|
+
'3xx': number;
|
|
37
|
+
'4xx': number;
|
|
38
|
+
'5xx': number;
|
|
39
|
+
other: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ExceptionGroupStats {
|
|
42
|
+
/** The family key — the entry's `familyHash` when present, else `${class}: ${message}`. */
|
|
43
|
+
key: string;
|
|
44
|
+
class: string;
|
|
45
|
+
message: string;
|
|
46
|
+
count: number;
|
|
47
|
+
/** Most recent occurrence in the window. */
|
|
48
|
+
lastAt: Date;
|
|
49
|
+
/** Per-bucket occurrence counts, aligned to the report's `overTime` buckets. */
|
|
50
|
+
overTime: number[];
|
|
51
|
+
}
|
|
52
|
+
export interface StatsResult {
|
|
53
|
+
type: string;
|
|
54
|
+
windowMs: number;
|
|
55
|
+
total: number;
|
|
56
|
+
/** Throughput over the window — reuses {@link bucketTimeseries}. */
|
|
57
|
+
overTime: TimeseriesReport;
|
|
58
|
+
/** Present for types whose entries carry a `durationMs`. */
|
|
59
|
+
latency?: LatencyStats;
|
|
60
|
+
/** Query only: top families by p99. */
|
|
61
|
+
families?: FamilyLatency[];
|
|
62
|
+
/** Cache only. */
|
|
63
|
+
cache?: CacheStats;
|
|
64
|
+
/** Request only. */
|
|
65
|
+
status?: StatusBreakdown;
|
|
66
|
+
/** Exception only: top groups by class+message, with count, last-seen, over-time. */
|
|
67
|
+
exceptions?: ExceptionGroupStats[];
|
|
68
|
+
/** Caller-supplied: whether the scan hit its cap. */
|
|
69
|
+
truncated: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Pre-estimated p50/p95/p99 (ms) supplied by a histogram-backed caller to
|
|
72
|
+
* replace the raw-scan percentiles. count/max/slow stay raw. */
|
|
73
|
+
export interface LatencyPercentilesOverride {
|
|
74
|
+
p50: number;
|
|
75
|
+
p95: number;
|
|
76
|
+
p99: number;
|
|
77
|
+
}
|
|
78
|
+
export interface SummarizeStatsInput {
|
|
79
|
+
entries: Entry[];
|
|
80
|
+
type: string;
|
|
81
|
+
windowStart: Date;
|
|
82
|
+
windowEnd: Date;
|
|
83
|
+
windowMs: number;
|
|
84
|
+
buckets: number;
|
|
85
|
+
slowMs: number;
|
|
86
|
+
truncated: boolean;
|
|
87
|
+
topFamilies?: number;
|
|
88
|
+
topKeys?: number;
|
|
89
|
+
topExceptions?: number;
|
|
90
|
+
/** When provided, p50/p95/p99 in the latency block are taken from these
|
|
91
|
+
* histogram estimates instead of the raw-scan computation. */
|
|
92
|
+
latencyPercentiles?: LatencyPercentilesOverride;
|
|
93
|
+
}
|
|
94
|
+
/** Nearest-rank percentile over a NON-EMPTY ascending array; 0 for empty.
|
|
95
|
+
* `q` in [0,1]; `idx = clamp(ceil(q*n)-1, 0, n-1)`. */
|
|
96
|
+
export declare function percentile(sortedAscending: number[], q: number): number;
|
|
97
|
+
/**
|
|
98
|
+
* Estimate p50/p95/p99 over a set of durations using the latency histogram,
|
|
99
|
+
* clamped to the exact max so a percentile is never reported above the observed
|
|
100
|
+
* maximum. Returns `undefined` when there are no samples (shape parity with the
|
|
101
|
+
* raw path's "no durations ⇒ no latency"). This is the storage-agnostic stand-in
|
|
102
|
+
* for the NestJS RollupStore fast path; the equivalence spec proves it agrees
|
|
103
|
+
* with the raw-scan percentile within one bucket-width.
|
|
104
|
+
*/
|
|
105
|
+
export declare function estimateLatencyPercentiles(durations: number[]): LatencyPercentilesOverride | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Aggregate a window of entries into per-type analytics: latency percentiles,
|
|
108
|
+
* query-family breakdown, cache hit/miss, request status breakdown, exception
|
|
109
|
+
* groups, and a throughput time-series. Pure.
|
|
110
|
+
*/
|
|
111
|
+
export declare function summarizeStats(input: SummarizeStatsInput): StatsResult;
|
|
112
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../../src/metrics/stats.ts"],"names":[],"mappings":"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"}
|