apm-optima 1.0.4 → 2.0.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 +54 -23
- package/dashboard-out/404/index.html +1 -1
- package/dashboard-out/404.html +1 -1
- package/dashboard-out/__next.__PAGE__.txt +4 -4
- package/dashboard-out/__next._full.txt +17 -17
- package/dashboard-out/__next._head.txt +4 -4
- package/dashboard-out/__next._index.txt +10 -10
- package/dashboard-out/__next._tree.txt +2 -2
- package/dashboard-out/_next/static/chunks/{2xjcsu4pgp14_.js → 062oipeclh9bq.js} +1 -1
- package/dashboard-out/_next/static/chunks/0ncbimuip0-o7.css +3 -0
- package/dashboard-out/_next/static/chunks/0xtgenoch3q77.js +1 -0
- package/dashboard-out/_next/static/chunks/1xln1-s87x5yp.js +27 -0
- package/dashboard-out/_not-found/__next._full.txt +15 -15
- package/dashboard-out/_not-found/__next._head.txt +4 -4
- package/dashboard-out/_not-found/__next._index.txt +10 -10
- package/dashboard-out/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/dashboard-out/_not-found/__next._not-found.txt +3 -3
- package/dashboard-out/_not-found/__next._tree.txt +2 -2
- package/dashboard-out/_not-found/index.html +1 -1
- package/dashboard-out/_not-found/index.txt +15 -15
- package/dashboard-out/index.html +1 -1
- package/dashboard-out/index.txt +17 -17
- package/dist/adapters/session-export.handler.d.ts +4 -0
- package/dist/adapters/session-export.handler.js +29 -0
- package/dist/adapters/socket.options.d.ts +3 -0
- package/dist/adapters/socket.options.js +16 -0
- package/dist/config/config.manager.d.ts +80 -3
- package/dist/config/config.manager.js +269 -89
- package/dist/config/config.types.d.ts +89 -13
- package/dist/core/delivery/buckets-request.d.ts +2 -0
- package/dist/core/delivery/buckets-request.js +18 -0
- package/dist/core/delivery/index.d.ts +1 -0
- package/dist/core/delivery/index.js +1 -0
- package/dist/core/delivery/metrics.publisher.d.ts +23 -10
- package/dist/core/delivery/metrics.publisher.js +22 -22
- package/dist/core/delivery/websocket.events.d.ts +14 -3
- package/dist/core/delivery/websocket.events.js +13 -2
- package/dist/core/domain/correlation.types.d.ts +37 -0
- package/dist/core/domain/correlation.types.js +2 -0
- package/dist/core/domain/dashboard.types.d.ts +16 -69
- package/dist/core/domain/dashboard.types.js +29 -0
- package/dist/core/domain/endpoints.types.d.ts +3 -0
- package/dist/core/domain/event.types.d.ts +14 -0
- package/dist/core/domain/event.types.js +2 -0
- package/dist/core/domain/incident.types.d.ts +50 -0
- package/dist/core/domain/incident.types.js +2 -0
- package/dist/core/domain/index.d.ts +5 -0
- package/dist/core/domain/index.js +5 -0
- package/dist/core/domain/live-bucket.types.d.ts +86 -0
- package/dist/core/domain/live-bucket.types.js +2 -0
- package/dist/core/domain/metrics.types.d.ts +41 -11
- package/dist/core/domain/session.types.d.ts +93 -0
- package/dist/core/domain/session.types.js +2 -0
- package/dist/core/domain/telemetry.types.d.ts +1 -0
- package/dist/core/organizers/event.manager.d.ts +19 -0
- package/dist/core/organizers/event.manager.js +73 -0
- package/dist/core/organizers/index.d.ts +2 -0
- package/dist/core/organizers/index.js +18 -0
- package/dist/core/organizers/interval.manager.d.ts +18 -0
- package/dist/core/organizers/interval.manager.js +54 -0
- package/dist/core/storage/index.d.ts +1 -0
- package/dist/core/storage/index.js +1 -0
- package/dist/core/storage/local.repository.d.ts +12 -12
- package/dist/core/storage/local.repository.js +38 -38
- package/dist/core/storage/persistence/persistence-writer.d.ts +48 -0
- package/dist/core/storage/persistence/persistence-writer.js +191 -0
- package/dist/core/storage/persistence/segment-index.d.ts +27 -0
- package/dist/core/storage/persistence/segment-index.js +206 -0
- package/dist/core/storage/persistence/session-registry.d.ts +28 -0
- package/dist/core/storage/persistence/session-registry.js +127 -0
- package/dist/core/storage/persistence/session-summary.d.ts +3 -0
- package/dist/core/storage/persistence/session-summary.js +112 -0
- package/dist/core/storage/persistence.layer.d.ts +69 -0
- package/dist/core/storage/persistence.layer.js +404 -0
- package/dist/core/storage/runtime/anomaly-detector.d.ts +11 -0
- package/dist/core/storage/runtime/anomaly-detector.js +42 -0
- package/dist/core/storage/stores/analytics.store.d.ts +5 -5
- package/dist/core/storage/stores/analytics.store.js +39 -12
- package/dist/core/storage/stores/bucket-metric.d.ts +85 -0
- package/dist/core/storage/stores/bucket-metric.js +168 -0
- package/dist/core/storage/stores/bucket-view.d.ts +44 -0
- package/dist/core/storage/stores/bucket-view.js +63 -0
- package/dist/core/storage/stores/bucket.store.d.ts +39 -36
- package/dist/core/storage/stores/bucket.store.js +112 -60
- package/dist/core/storage/stores/dashboard.store.d.ts +11 -4
- package/dist/core/storage/stores/dashboard.store.js +179 -47
- package/dist/core/storage/stores/endpoint.store.d.ts +1 -0
- package/dist/core/storage/stores/endpoint.store.js +21 -5
- package/dist/core/storage/stores/index.d.ts +0 -7
- package/dist/core/storage/stores/index.js +0 -7
- package/dist/core/storage/stores/metrics.store.d.ts +1 -1
- package/dist/core/storage/stores/metrics.store.js +1 -0
- package/dist/core/storage/stores/runtime.store.d.ts +77 -0
- package/dist/core/storage/stores/runtime.store.js +222 -0
- package/dist/core/storage/utility/common.d.ts +1 -0
- package/dist/core/storage/utility/common.js +11 -0
- package/dist/core/storage/utility/file-buffer.d.ts +31 -0
- package/dist/core/storage/utility/file-buffer.js +238 -0
- package/dist/core/storage/utility/histogram.d.ts +1 -2
- package/dist/core/storage/utility/histogram.js +1 -2
- package/dist/core/storage/utility/index.d.ts +2 -0
- package/dist/core/storage/utility/index.js +2 -0
- package/dist/core/storage/utility/persistence-files.d.ts +3 -0
- package/dist/core/storage/utility/persistence-files.js +34 -0
- package/dist/core/storage/utility/persistence-writer.d.ts +39 -0
- package/dist/core/storage/utility/persistence-writer.js +183 -0
- package/dist/core/storage/utility/session-export.d.ts +3 -0
- package/dist/core/storage/utility/session-export.js +31 -0
- package/dist/core/storage/utility/session-registry.d.ts +21 -0
- package/dist/core/storage/utility/session-registry.js +91 -0
- package/dist/core/storage/utility/session-window.d.ts +8 -0
- package/dist/core/storage/utility/session-window.js +43 -0
- package/dist/core/telemetry/analytics.service.d.ts +18 -0
- package/dist/core/telemetry/analytics.service.js +100 -0
- package/dist/core/telemetry/collector.service.d.ts +17 -15
- package/dist/core/telemetry/collector.service.js +23 -80
- package/dist/core/telemetry/correlation.service.d.ts +57 -9
- package/dist/core/telemetry/correlation.service.js +246 -54
- package/dist/core/telemetry/dashboard-live.service.d.ts +10 -0
- package/dist/core/telemetry/dashboard-live.service.js +110 -0
- package/dist/core/telemetry/dashboard.service.d.ts +20 -0
- package/dist/core/telemetry/dashboard.service.js +140 -0
- package/dist/core/telemetry/incident.service.d.ts +71 -0
- package/dist/core/telemetry/incident.service.js +239 -0
- package/dist/core/telemetry/logger.d.ts +4 -0
- package/dist/core/telemetry/logger.js +5 -2
- package/dist/core/telemetry/runtime.service.d.ts +14 -0
- package/dist/core/telemetry/runtime.service.js +56 -0
- package/dist/core/telemetry/telemetry-query.service.d.ts +82 -0
- package/dist/core/telemetry/telemetry-query.service.js +231 -0
- package/dist/core/telemetry/telemetry.service.d.ts +10 -4
- package/dist/core/telemetry/telemetry.service.js +60 -10
- package/dist/core/telemetry/utility/correlation-analysis.d.ts +9 -0
- package/dist/core/telemetry/utility/correlation-analysis.js +143 -0
- package/dist/core/telemetry/utility/correlation-finding.d.ts +70 -0
- package/dist/core/telemetry/utility/correlation-finding.js +41 -0
- package/dist/core/telemetry/utility/correlation-input.d.ts +59 -0
- package/dist/core/telemetry/utility/correlation-input.js +139 -0
- package/dist/core/telemetry/utility/correlation-replay.d.ts +24 -0
- package/dist/core/telemetry/utility/correlation-replay.js +46 -0
- package/dist/core/utility/correlation.analysis.d.ts +9 -0
- package/dist/core/utility/correlation.analysis.js +144 -0
- package/dist/core/utility/statistics.d.ts +65 -0
- package/dist/core/utility/statistics.js +141 -0
- package/dist/express/index.d.ts +1 -1
- package/dist/express/index.js +7 -5
- package/dist/express/metrics-websocket.adapter.d.ts +8 -2
- package/dist/express/metrics-websocket.adapter.js +52 -17
- package/dist/express/metrics.adapter.d.ts +1 -0
- package/dist/express/metrics.adapter.js +8 -1
- package/dist/express/metrics.bootstrap.d.ts +6 -1
- package/dist/express/metrics.bootstrap.js +48 -22
- package/dist/express/metrics.dashboard.d.ts +2 -1
- package/dist/express/metrics.dashboard.js +4 -1
- package/dist/express/metrics.middleware.d.ts +2 -1
- package/dist/express/metrics.middleware.js +23 -20
- package/dist/nest/metrics-websocket.adapter.d.ts +19 -3
- package/dist/nest/metrics-websocket.adapter.js +101 -21
- package/dist/nest/metrics.adapter.d.ts +4 -1
- package/dist/nest/metrics.adapter.js +24 -1
- package/dist/nest/metrics.bootstrap.service.d.ts +7 -3
- package/dist/nest/metrics.bootstrap.service.js +56 -21
- package/dist/nest/metrics.interceptor.d.ts +3 -3
- package/dist/nest/metrics.interceptor.js +11 -8
- package/dist/nest/metrics.module.d.ts +3 -0
- package/dist/nest/metrics.module.js +27 -9
- package/dist/runtime/index.d.ts +32 -0
- package/dist/runtime/index.js +104 -0
- package/dist/simulation/simulation.d.ts +5 -16
- package/dist/simulation/simulation.js +5 -8
- package/package.json +2 -1
- package/dashboard-out/_next/static/chunks/20t9qbodfrw_z.js +0 -3
- package/dashboard-out/_next/static/chunks/23soo6qkun-yv.js +0 -1
- package/dashboard-out/_next/static/chunks/3xoopocxi2vi8.css +0 -3
- /package/dashboard-out/_next/static/{u8vU2V6fhASeGVCpWU3eW → 17egwqgk36_DSRF55LJZG}/_buildManifest.js +0 -0
- /package/dashboard-out/_next/static/{u8vU2V6fhASeGVCpWU3eW → 17egwqgk36_DSRF55LJZG}/_clientMiddlewareManifest.js +0 -0
- /package/dashboard-out/_next/static/{u8vU2V6fhASeGVCpWU3eW → 17egwqgk36_DSRF55LJZG}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IncidentService = exports.CAUSE_NOTE = exports.initialTracker = void 0;
|
|
4
|
+
exports.stepRule = stepRule;
|
|
5
|
+
exports.defaultIncidentRules = defaultIncidentRules;
|
|
6
|
+
const bucket_metric_1 = require("../storage/stores/bucket-metric");
|
|
7
|
+
const initialTracker = () => ({ state: 'NORMAL', sinceMs: null, breachStartMs: null, recoveryStartMs: null });
|
|
8
|
+
exports.initialTracker = initialTracker;
|
|
9
|
+
function stepRule(t, obs, rule, timing) {
|
|
10
|
+
const now = obs.endMs;
|
|
11
|
+
const valid = obs.valid && obs.value !== null;
|
|
12
|
+
const breach = valid && obs.value > rule.threshold;
|
|
13
|
+
switch (t.state) {
|
|
14
|
+
case 'NORMAL':
|
|
15
|
+
return breach ? { state: 'PENDING', sinceMs: now, breachStartMs: now, recoveryStartMs: null } : t;
|
|
16
|
+
case 'PENDING':
|
|
17
|
+
if (!breach)
|
|
18
|
+
return { state: 'NORMAL', sinceMs: now, breachStartMs: null, recoveryStartMs: null };
|
|
19
|
+
if (now - t.breachStartMs >= timing.pendingForMs) {
|
|
20
|
+
return { state: 'FIRING', sinceMs: now, breachStartMs: t.breachStartMs, recoveryStartMs: null };
|
|
21
|
+
}
|
|
22
|
+
return t;
|
|
23
|
+
case 'FIRING': {
|
|
24
|
+
if (!valid || obs.value > rule.recoverBelow)
|
|
25
|
+
return { ...t, recoveryStartMs: null };
|
|
26
|
+
const recoveryStartMs = t.recoveryStartMs ?? now;
|
|
27
|
+
if (now - recoveryStartMs >= timing.recoveryForMs) {
|
|
28
|
+
return { state: 'RESOLVED', sinceMs: now, breachStartMs: null, recoveryStartMs: null };
|
|
29
|
+
}
|
|
30
|
+
return { ...t, recoveryStartMs };
|
|
31
|
+
}
|
|
32
|
+
case 'RESOLVED':
|
|
33
|
+
if (breach)
|
|
34
|
+
return { state: 'PENDING', sinceMs: now, breachStartMs: now, recoveryStartMs: null };
|
|
35
|
+
if (now - (t.sinceMs ?? now) >= timing.resolvedHoldMs) {
|
|
36
|
+
return { state: 'NORMAL', sinceMs: now, breachStartMs: null, recoveryStartMs: null };
|
|
37
|
+
}
|
|
38
|
+
return t;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function completeWindow(w, windowIntervals) {
|
|
42
|
+
if (w.total.bucketCount < windowIntervals)
|
|
43
|
+
return 'INCOMPLETE_WINDOW';
|
|
44
|
+
if (w.issues.some(i => i.type === 'GAP'))
|
|
45
|
+
return 'GAPS';
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
function defaultIncidentRules(o) {
|
|
49
|
+
const requestWindow = (w) => {
|
|
50
|
+
const incomplete = completeWindow(w, o.windowIntervals);
|
|
51
|
+
if (incomplete)
|
|
52
|
+
return incomplete;
|
|
53
|
+
if (w.total.requests.requestCount < o.minRequests)
|
|
54
|
+
return 'TOO_FEW_REQUESTS';
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
id: 'server-error-rate',
|
|
60
|
+
title: 'High server error rate (5xx)',
|
|
61
|
+
metric: 'errorRate',
|
|
62
|
+
unit: '%',
|
|
63
|
+
severity: 'critical',
|
|
64
|
+
threshold: o.serverErrorRate * 100,
|
|
65
|
+
recoverBelow: o.serverErrorRate * 100 * o.recoveryRatio,
|
|
66
|
+
measure: w => {
|
|
67
|
+
const reason = requestWindow(w);
|
|
68
|
+
if (reason)
|
|
69
|
+
return { valid: false, value: null, reason };
|
|
70
|
+
const r = w.total.requests;
|
|
71
|
+
return { valid: true, value: (r.serverErrorCount / r.requestCount) * 100 };
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'p95-latency',
|
|
76
|
+
title: 'High p95 latency',
|
|
77
|
+
metric: 'p95Latency',
|
|
78
|
+
unit: 'ms',
|
|
79
|
+
severity: 'warning',
|
|
80
|
+
threshold: o.p95LatencyMs,
|
|
81
|
+
recoverBelow: o.p95LatencyMs * o.recoveryRatio,
|
|
82
|
+
measure: w => {
|
|
83
|
+
const reason = requestWindow(w);
|
|
84
|
+
if (reason)
|
|
85
|
+
return { valid: false, value: null, reason };
|
|
86
|
+
return { valid: true, value: (0, bucket_metric_1.deriveMetrics)(w.total.requests, w.total.durationMs).p95 };
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'event-loop-lag',
|
|
91
|
+
title: 'High event loop lag',
|
|
92
|
+
metric: 'eventLoopLag',
|
|
93
|
+
unit: 'ms',
|
|
94
|
+
severity: 'warning',
|
|
95
|
+
threshold: o.eventLoopLagMs,
|
|
96
|
+
recoverBelow: o.eventLoopLagMs * o.recoveryRatio,
|
|
97
|
+
measure: w => {
|
|
98
|
+
const incomplete = completeWindow(w, o.windowIntervals);
|
|
99
|
+
if (incomplete)
|
|
100
|
+
return { valid: false, value: null, reason: incomplete };
|
|
101
|
+
const runtime = (0, bucket_metric_1.emptyRuntime)();
|
|
102
|
+
for (const p of w.points)
|
|
103
|
+
(0, bucket_metric_1.mergeRuntimeInto)(runtime, p.runtime, true);
|
|
104
|
+
const mean = (0, bucket_metric_1.deriveRuntime)(runtime, w.total.durationMs).eventLoopDelayMeanMs;
|
|
105
|
+
return mean === null
|
|
106
|
+
? { valid: false, value: null, reason: 'NO_EVENT_LOOP_SAMPLES' }
|
|
107
|
+
: { valid: true, value: mean };
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
exports.CAUSE_NOTE = 'Related findings show which metrics changed together during the incident. Correlation does not establish the cause.';
|
|
113
|
+
class IncidentService {
|
|
114
|
+
deps;
|
|
115
|
+
timing;
|
|
116
|
+
windowIntervals;
|
|
117
|
+
historySize;
|
|
118
|
+
entries;
|
|
119
|
+
history = [];
|
|
120
|
+
lastSequence = null;
|
|
121
|
+
lastSessionId = null;
|
|
122
|
+
constructor(deps, options) {
|
|
123
|
+
this.deps = deps;
|
|
124
|
+
this.timing = {
|
|
125
|
+
pendingForMs: options.pendingForMs,
|
|
126
|
+
recoveryForMs: options.recoveryForMs,
|
|
127
|
+
resolvedHoldMs: options.resolvedHoldMs,
|
|
128
|
+
};
|
|
129
|
+
this.windowIntervals = options.windowIntervals;
|
|
130
|
+
this.historySize = options.historySize;
|
|
131
|
+
const rules = options.rules ?? defaultIncidentRules(options);
|
|
132
|
+
this.entries = rules.map(rule => ({ rule, tracker: (0, exports.initialTracker)(), last: null, incident: null }));
|
|
133
|
+
}
|
|
134
|
+
tick() {
|
|
135
|
+
this.evaluate();
|
|
136
|
+
}
|
|
137
|
+
evaluate() {
|
|
138
|
+
const sessionId = this.deps.sessionId();
|
|
139
|
+
if (sessionId !== this.lastSessionId) {
|
|
140
|
+
this.lastSessionId = sessionId;
|
|
141
|
+
this.lastSequence = null;
|
|
142
|
+
for (const e of this.entries) {
|
|
143
|
+
e.tracker = (0, exports.initialTracker)();
|
|
144
|
+
e.last = null;
|
|
145
|
+
e.incident = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const window = this.deps.queries.recent(this.windowIntervals, {
|
|
149
|
+
consistency: this.deps.durable() ? 'committed' : 'live',
|
|
150
|
+
});
|
|
151
|
+
const last = window.points[window.points.length - 1];
|
|
152
|
+
if (!last || last.sequenceTo === this.lastSequence)
|
|
153
|
+
return false;
|
|
154
|
+
this.lastSequence = last.sequenceTo;
|
|
155
|
+
const endMs = Date.parse(last.endTime);
|
|
156
|
+
for (const entry of this.entries) {
|
|
157
|
+
const obs = { ...entry.rule.measure(window), endMs };
|
|
158
|
+
const prev = entry.tracker;
|
|
159
|
+
entry.tracker = stepRule(prev, obs, entry.rule, this.timing);
|
|
160
|
+
entry.last = obs;
|
|
161
|
+
this.apply(entry, prev.state, window, sessionId);
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
snapshot() {
|
|
166
|
+
const iso = (ms) => ms === null ? null : new Date(ms).toISOString();
|
|
167
|
+
const rules = this.entries.map(({ rule, tracker, last, incident }) => ({
|
|
168
|
+
ruleId: rule.id,
|
|
169
|
+
title: rule.title,
|
|
170
|
+
metric: rule.metric,
|
|
171
|
+
unit: rule.unit,
|
|
172
|
+
severity: rule.severity,
|
|
173
|
+
state: tracker.state,
|
|
174
|
+
since: iso(tracker.sinceMs),
|
|
175
|
+
value: last?.value ?? null,
|
|
176
|
+
threshold: rule.threshold,
|
|
177
|
+
recoverBelow: rule.recoverBelow,
|
|
178
|
+
valid: last?.valid ?? false,
|
|
179
|
+
reason: last?.reason,
|
|
180
|
+
incidentId: incident?.incidentId ?? null,
|
|
181
|
+
}));
|
|
182
|
+
const incidents = [...this.history].sort((a, b) => (a.status === b.status ? 0 : a.status === 'FIRING' ? -1 : 1) || b.firedAt.localeCompare(a.firedAt));
|
|
183
|
+
return { rules, incidents, serverTime: new Date().toISOString() };
|
|
184
|
+
}
|
|
185
|
+
apply(entry, prevState, window, sessionId) {
|
|
186
|
+
const { rule, tracker, last } = entry;
|
|
187
|
+
const value = last?.valid ? last.value : null;
|
|
188
|
+
if (prevState !== 'FIRING' && tracker.state === 'FIRING') {
|
|
189
|
+
const first = window.points[0];
|
|
190
|
+
const lastPoint = window.points[window.points.length - 1];
|
|
191
|
+
const incident = {
|
|
192
|
+
incidentId: `${sessionId}:${rule.id}:${lastPoint.sequenceTo}`,
|
|
193
|
+
sessionId,
|
|
194
|
+
ruleId: rule.id,
|
|
195
|
+
title: rule.title,
|
|
196
|
+
metric: rule.metric,
|
|
197
|
+
unit: rule.unit,
|
|
198
|
+
severity: rule.severity,
|
|
199
|
+
status: 'FIRING',
|
|
200
|
+
threshold: rule.threshold,
|
|
201
|
+
recoverBelow: rule.recoverBelow,
|
|
202
|
+
breachStartedAt: new Date(tracker.breachStartMs).toISOString(),
|
|
203
|
+
firedAt: new Date(tracker.sinceMs).toISOString(),
|
|
204
|
+
resolvedAt: null,
|
|
205
|
+
peakValue: value ?? rule.threshold,
|
|
206
|
+
lastValue: value,
|
|
207
|
+
sequenceFrom: first.sequenceFrom,
|
|
208
|
+
sequenceTo: lastPoint.sequenceTo,
|
|
209
|
+
relatedFindings: this.deps.related?.(rule.metric) ?? [],
|
|
210
|
+
causeNote: exports.CAUSE_NOTE,
|
|
211
|
+
};
|
|
212
|
+
entry.incident = incident;
|
|
213
|
+
this.history.push(incident);
|
|
214
|
+
if (this.history.length > this.historySize)
|
|
215
|
+
this.history.splice(0, this.history.length - this.historySize);
|
|
216
|
+
void this.deps.emit?.({ type: 'INCIDENT_OPENED', reason: `${rule.title}`, details: { ...incident } });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const incident = entry.incident;
|
|
220
|
+
if (!incident)
|
|
221
|
+
return;
|
|
222
|
+
if (tracker.state === 'FIRING') {
|
|
223
|
+
if (value !== null) {
|
|
224
|
+
incident.lastValue = value;
|
|
225
|
+
incident.peakValue = Math.max(incident.peakValue, value);
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (prevState === 'FIRING' && tracker.state === 'RESOLVED') {
|
|
230
|
+
incident.status = 'RESOLVED';
|
|
231
|
+
incident.resolvedAt = new Date(tracker.sinceMs).toISOString();
|
|
232
|
+
if (value !== null)
|
|
233
|
+
incident.lastValue = value;
|
|
234
|
+
entry.incident = null;
|
|
235
|
+
void this.deps.emit?.({ type: 'INCIDENT_RESOLVED', reason: `${rule.title}`, details: { ...incident } });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.IncidentService = IncidentService;
|
|
@@ -3,6 +3,10 @@ declare class Logger {
|
|
|
3
3
|
private static readonly RESET;
|
|
4
4
|
private static readonly BOLD;
|
|
5
5
|
private static readonly DIM;
|
|
6
|
+
private static consoleLog;
|
|
7
|
+
static configure(options: {
|
|
8
|
+
consoleLog: boolean;
|
|
9
|
+
}): void;
|
|
6
10
|
private static readonly COLORS;
|
|
7
11
|
static log(req: Request, res: Response, duration: number): void;
|
|
8
12
|
static error(message: string, ...args: unknown[]): void;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const config_1 = require("../../config");
|
|
4
3
|
class Logger {
|
|
5
4
|
static RESET = '\x1b[0m';
|
|
6
5
|
static BOLD = '\x1b[1m';
|
|
7
6
|
static DIM = '\x1b[2m';
|
|
7
|
+
static consoleLog = true;
|
|
8
|
+
static configure(options) {
|
|
9
|
+
Logger.consoleLog = options.consoleLog;
|
|
10
|
+
}
|
|
8
11
|
static COLORS = {
|
|
9
12
|
GET: '\x1b[32m', // Green
|
|
10
13
|
POST: '\x1b[36m', // Cyan
|
|
@@ -16,7 +19,7 @@ class Logger {
|
|
|
16
19
|
ERROR: '\x1b[31m' // Red (5xx)
|
|
17
20
|
};
|
|
18
21
|
static log(req, res, duration) {
|
|
19
|
-
if (!
|
|
22
|
+
if (!Logger.consoleLog)
|
|
20
23
|
return;
|
|
21
24
|
const timestamp = new Date().toISOString().split('T')[1].slice(0, -1);
|
|
22
25
|
const timeFormatted = `${duration.toFixed(2)}ms`.padStart(8, ' ');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReadonlyConfig } from "../../config";
|
|
2
|
+
import type { HealthDetails, SystemStaticInfo } from "../domain";
|
|
3
|
+
import type { LocalRepository } from "../storage";
|
|
4
|
+
export declare class RuntimeService {
|
|
5
|
+
private readonly storage;
|
|
6
|
+
private readonly config;
|
|
7
|
+
readonly eventLoopLagThresholdMs: number;
|
|
8
|
+
readonly env: string;
|
|
9
|
+
readonly startedAt: number;
|
|
10
|
+
readonly nodeVersion: string;
|
|
11
|
+
constructor(storage: LocalRepository, config: ReadonlyConfig);
|
|
12
|
+
getSystemStaticInfo(): SystemStaticInfo;
|
|
13
|
+
get(): HealthDetails;
|
|
14
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeService = void 0;
|
|
4
|
+
class RuntimeService {
|
|
5
|
+
storage;
|
|
6
|
+
config;
|
|
7
|
+
eventLoopLagThresholdMs;
|
|
8
|
+
env;
|
|
9
|
+
startedAt;
|
|
10
|
+
nodeVersion;
|
|
11
|
+
constructor(storage, config) {
|
|
12
|
+
this.storage = storage;
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.eventLoopLagThresholdMs = config.thresholds.eventLoopLagMs;
|
|
15
|
+
this.env = process.env.NODE_ENV ?? 'unknown';
|
|
16
|
+
this.nodeVersion = process.version;
|
|
17
|
+
this.startedAt = Date.now();
|
|
18
|
+
}
|
|
19
|
+
getSystemStaticInfo() {
|
|
20
|
+
return {
|
|
21
|
+
uptime: Date.now() - this.startedAt,
|
|
22
|
+
nodeVersion: this.nodeVersion,
|
|
23
|
+
env: this.env
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
get() {
|
|
27
|
+
const details = this.storage.runtime.details();
|
|
28
|
+
const gc = details.gc;
|
|
29
|
+
return {
|
|
30
|
+
cpu: {
|
|
31
|
+
numberOfCores: details.numberOfCores,
|
|
32
|
+
perCoreUsage: details.perCoreUsage,
|
|
33
|
+
},
|
|
34
|
+
memory: {
|
|
35
|
+
rssMemoryTotal: details.rssMemoryTotal,
|
|
36
|
+
externalMemory: details.externalMemory,
|
|
37
|
+
},
|
|
38
|
+
eventLoop: {
|
|
39
|
+
threshold: this.eventLoopLagThresholdMs,
|
|
40
|
+
},
|
|
41
|
+
handles: details.handles,
|
|
42
|
+
garbageCollection: {
|
|
43
|
+
gcCount: gc.gcCount,
|
|
44
|
+
gcTime: gc.gcTime,
|
|
45
|
+
gcPauseAverage: gc.gcCount > 0 ? parseFloat((gc.gcTime / gc.gcCount).toFixed(2)) : 0,
|
|
46
|
+
minorGC: gc.minorGC,
|
|
47
|
+
majorGC: gc.majorGC,
|
|
48
|
+
incrementalGC: gc.incrementalGC,
|
|
49
|
+
heapSpaces: gc.heapSpaces,
|
|
50
|
+
gcTotals: gc.gcTotals,
|
|
51
|
+
},
|
|
52
|
+
runtime: details.runtime,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.RuntimeService = RuntimeService;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { deriveMetrics, type MetricBucket, type RequestAggregate, type EndpointAggregate, type RuntimeInterval } from "../storage/stores/bucket-metric";
|
|
2
|
+
export type MetricsQuery = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
method?: string;
|
|
7
|
+
route?: string;
|
|
8
|
+
resolutionMs?: number;
|
|
9
|
+
consistency: 'committed' | 'live';
|
|
10
|
+
includeEndpoints?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type QueryIssue = {
|
|
13
|
+
type: 'CONFLICT';
|
|
14
|
+
bucketId: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'GAP';
|
|
17
|
+
fromSequence: number;
|
|
18
|
+
toSequence: number;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'OUT_OF_ORDER';
|
|
21
|
+
bucketId: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'SOURCE_ERROR';
|
|
24
|
+
detail: string;
|
|
25
|
+
};
|
|
26
|
+
export interface SeriesPoint {
|
|
27
|
+
startTime: string;
|
|
28
|
+
endTime: string;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
bucketCount: number;
|
|
31
|
+
sequenceFrom: number;
|
|
32
|
+
sequenceTo: number;
|
|
33
|
+
requests: RequestAggregate;
|
|
34
|
+
maxRps: number;
|
|
35
|
+
endpoints?: EndpointAggregate[];
|
|
36
|
+
runtime: RuntimeInterval;
|
|
37
|
+
qualityFlags: string[];
|
|
38
|
+
committed: boolean | null;
|
|
39
|
+
}
|
|
40
|
+
export interface MetricsQueryResult {
|
|
41
|
+
query: MetricsQuery;
|
|
42
|
+
points: SeriesPoint[];
|
|
43
|
+
total: {
|
|
44
|
+
requests: RequestAggregate;
|
|
45
|
+
durationMs: number;
|
|
46
|
+
bucketCount: number;
|
|
47
|
+
maxRps: number;
|
|
48
|
+
derived: ReturnType<typeof deriveMetrics>;
|
|
49
|
+
};
|
|
50
|
+
issues: QueryIssue[];
|
|
51
|
+
complete: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface CommittedBucketSource {
|
|
54
|
+
read(sessionId: string, fromMs: number, toMs: number, onIssue: (issue: QueryIssue) => void): AsyncIterable<MetricBucket>;
|
|
55
|
+
}
|
|
56
|
+
export interface LiveBucketEntry {
|
|
57
|
+
bucket: MetricBucket;
|
|
58
|
+
committed: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface LiveBucketSource {
|
|
61
|
+
snapshot(): LiveBucketEntry[];
|
|
62
|
+
}
|
|
63
|
+
export interface TelemetryQueryDeps {
|
|
64
|
+
committed: CommittedBucketSource;
|
|
65
|
+
live: LiveBucketSource;
|
|
66
|
+
currentSessionId: () => string;
|
|
67
|
+
}
|
|
68
|
+
export declare class TelemetryQueryService {
|
|
69
|
+
private readonly deps;
|
|
70
|
+
constructor(deps: TelemetryQueryDeps);
|
|
71
|
+
query(q: MetricsQuery): Promise<MetricsQueryResult>;
|
|
72
|
+
recent(count: number, opts?: Partial<Pick<MetricsQuery, 'consistency' | 'method' | 'route' | 'resolutionMs' | 'includeEndpoints'>>): MetricsQueryResult;
|
|
73
|
+
latestWindow(count: number, consistency: MetricsQuery['consistency']): {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
from: string;
|
|
76
|
+
to: string;
|
|
77
|
+
sequenceFrom: number;
|
|
78
|
+
sequenceTo: number;
|
|
79
|
+
} | null;
|
|
80
|
+
liveBucket(sequence: number): MetricBucket | undefined;
|
|
81
|
+
buckets(q: MetricsQuery, onIssue: (i: QueryIssue) => void): AsyncGenerator<LiveBucketEntry>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TelemetryQueryService = void 0;
|
|
4
|
+
const bucket_metric_1 = require("../storage/stores/bucket-metric");
|
|
5
|
+
function stableStringify(v) {
|
|
6
|
+
if (v === null || typeof v !== 'object')
|
|
7
|
+
return JSON.stringify(v);
|
|
8
|
+
if (Array.isArray(v))
|
|
9
|
+
return `[${v.map(stableStringify).join(',')}]`;
|
|
10
|
+
const o = v;
|
|
11
|
+
return `{${Object.keys(o).sort().map(k => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(',')}}`;
|
|
12
|
+
}
|
|
13
|
+
const fingerprint = (b) => stableStringify(b);
|
|
14
|
+
function parseRange(q) {
|
|
15
|
+
const fromMs = Date.parse(q.from);
|
|
16
|
+
const toMs = Date.parse(q.to);
|
|
17
|
+
if (Number.isNaN(fromMs) || Number.isNaN(toMs))
|
|
18
|
+
throw new RangeError('Invalid time range');
|
|
19
|
+
if (fromMs > toMs)
|
|
20
|
+
throw new RangeError('`from` must not be after `to`');
|
|
21
|
+
return { fromMs, toMs };
|
|
22
|
+
}
|
|
23
|
+
const overlaps = (b, fromMs, toMs) => Date.parse(b.startTime) <= toMs && Date.parse(b.endTime) >= fromMs;
|
|
24
|
+
function project(b, q) {
|
|
25
|
+
if (!q.method && !q.route)
|
|
26
|
+
return { requests: b.requests, endpoints: b.endpoints };
|
|
27
|
+
const matching = b.endpoints.filter(e => (!q.method || e.method === q.method) && (!q.route || e.route === q.route));
|
|
28
|
+
const requests = (0, bucket_metric_1.createAggregate)();
|
|
29
|
+
for (const e of matching)
|
|
30
|
+
(0, bucket_metric_1.mergeInto)(requests, e);
|
|
31
|
+
return { requests, endpoints: matching };
|
|
32
|
+
}
|
|
33
|
+
class SeriesAccumulator {
|
|
34
|
+
q;
|
|
35
|
+
issues;
|
|
36
|
+
groups = new Map();
|
|
37
|
+
total = (0, bucket_metric_1.createAggregate)();
|
|
38
|
+
totalDurationMs = 0;
|
|
39
|
+
maxRps = 0;
|
|
40
|
+
bucketCount = 0;
|
|
41
|
+
prevSequence = null;
|
|
42
|
+
constructor(q, issues) {
|
|
43
|
+
this.q = q;
|
|
44
|
+
this.issues = issues;
|
|
45
|
+
}
|
|
46
|
+
add(b, committed) {
|
|
47
|
+
if (this.prevSequence !== null) {
|
|
48
|
+
if (b.sequence <= this.prevSequence) {
|
|
49
|
+
this.issues.push({ type: 'OUT_OF_ORDER', bucketId: b.bucketId });
|
|
50
|
+
}
|
|
51
|
+
else if (b.sequence > this.prevSequence + 1) {
|
|
52
|
+
this.issues.push({ type: 'GAP', fromSequence: this.prevSequence + 1, toSequence: b.sequence - 1 });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this.prevSequence = this.prevSequence === null ? b.sequence : Math.max(this.prevSequence, b.sequence);
|
|
56
|
+
const p = project(b, this.q);
|
|
57
|
+
this.bucketCount++;
|
|
58
|
+
this.totalDurationMs += b.durationMs;
|
|
59
|
+
(0, bucket_metric_1.mergeInto)(this.total, p.requests);
|
|
60
|
+
const bucketRps = (0, bucket_metric_1.deriveMetrics)(p.requests, b.durationMs).rps;
|
|
61
|
+
this.maxRps = Math.max(this.maxRps, bucketRps);
|
|
62
|
+
const size = this.q.resolutionMs && this.q.resolutionMs > 0 ? this.q.resolutionMs : 0;
|
|
63
|
+
const key = size ? Math.floor(Date.parse(b.startTime) / size) : b.sequence;
|
|
64
|
+
let g = this.groups.get(key);
|
|
65
|
+
if (!g) {
|
|
66
|
+
g = {
|
|
67
|
+
startTime: b.startTime, endTime: b.endTime,
|
|
68
|
+
durationMs: 0, bucketCount: 0,
|
|
69
|
+
sequenceFrom: b.sequence, sequenceTo: b.sequence,
|
|
70
|
+
requests: (0, bucket_metric_1.createAggregate)(),
|
|
71
|
+
maxRps: 0,
|
|
72
|
+
endpoints: this.q.includeEndpoints ? new Map() : undefined,
|
|
73
|
+
runtime: (0, bucket_metric_1.emptyRuntime)(),
|
|
74
|
+
flags: new Set(),
|
|
75
|
+
committedCount: 0,
|
|
76
|
+
uncommittedCount: 0
|
|
77
|
+
};
|
|
78
|
+
this.groups.set(key, g);
|
|
79
|
+
}
|
|
80
|
+
(0, bucket_metric_1.mergeInto)(g.requests, p.requests);
|
|
81
|
+
g.maxRps = Math.max(g.maxRps, bucketRps);
|
|
82
|
+
g.durationMs += b.durationMs;
|
|
83
|
+
g.bucketCount++;
|
|
84
|
+
if (committed === true)
|
|
85
|
+
g.committedCount++;
|
|
86
|
+
else if (committed === false)
|
|
87
|
+
g.uncommittedCount++;
|
|
88
|
+
if (b.startTime < g.startTime)
|
|
89
|
+
g.startTime = b.startTime;
|
|
90
|
+
if (b.endTime > g.endTime)
|
|
91
|
+
g.endTime = b.endTime;
|
|
92
|
+
g.sequenceFrom = Math.min(g.sequenceFrom, b.sequence);
|
|
93
|
+
const isLatest = b.sequence >= g.sequenceTo;
|
|
94
|
+
if (isLatest)
|
|
95
|
+
g.sequenceTo = b.sequence;
|
|
96
|
+
(0, bucket_metric_1.mergeRuntimeInto)(g.runtime, b.runtime, isLatest);
|
|
97
|
+
for (const f of b.qualityFlags)
|
|
98
|
+
g.flags.add(f);
|
|
99
|
+
if (g.endpoints) {
|
|
100
|
+
for (const e of p.endpoints) {
|
|
101
|
+
const ek = `${e.method}:${e.route}`;
|
|
102
|
+
let t = g.endpoints.get(ek);
|
|
103
|
+
if (!t) {
|
|
104
|
+
t = { method: e.method, route: e.route, ...(0, bucket_metric_1.createAggregate)() };
|
|
105
|
+
g.endpoints.set(ek, t);
|
|
106
|
+
}
|
|
107
|
+
(0, bucket_metric_1.mergeInto)(t, e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
finish() {
|
|
112
|
+
const points = [...this.groups.values()]
|
|
113
|
+
.sort((a, b) => (a.startTime < b.startTime ? -1 : a.startTime > b.startTime ? 1 : 0))
|
|
114
|
+
.map(g => ({
|
|
115
|
+
startTime: g.startTime,
|
|
116
|
+
endTime: g.endTime,
|
|
117
|
+
durationMs: g.durationMs,
|
|
118
|
+
bucketCount: g.bucketCount,
|
|
119
|
+
sequenceFrom: g.sequenceFrom,
|
|
120
|
+
sequenceTo: g.sequenceTo,
|
|
121
|
+
requests: g.requests,
|
|
122
|
+
maxRps: g.maxRps,
|
|
123
|
+
endpoints: g.endpoints ? [...g.endpoints.values()] : undefined,
|
|
124
|
+
runtime: g.runtime,
|
|
125
|
+
qualityFlags: [...g.flags].sort(),
|
|
126
|
+
committed: g.uncommittedCount > 0 ? false : g.committedCount === g.bucketCount ? true : null
|
|
127
|
+
}));
|
|
128
|
+
return {
|
|
129
|
+
query: this.q,
|
|
130
|
+
points,
|
|
131
|
+
total: {
|
|
132
|
+
requests: this.total,
|
|
133
|
+
durationMs: this.totalDurationMs,
|
|
134
|
+
bucketCount: this.bucketCount,
|
|
135
|
+
maxRps: this.maxRps,
|
|
136
|
+
derived: (0, bucket_metric_1.deriveMetrics)(this.total, this.totalDurationMs),
|
|
137
|
+
},
|
|
138
|
+
issues: this.issues,
|
|
139
|
+
complete: this.issues.length === 0,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
class TelemetryQueryService {
|
|
144
|
+
deps;
|
|
145
|
+
constructor(deps) {
|
|
146
|
+
this.deps = deps;
|
|
147
|
+
}
|
|
148
|
+
async query(q) {
|
|
149
|
+
const issues = [];
|
|
150
|
+
const acc = new SeriesAccumulator(q, issues);
|
|
151
|
+
for await (const e of this.buckets(q, i => issues.push(i)))
|
|
152
|
+
acc.add(e.bucket, e.committed);
|
|
153
|
+
return acc.finish();
|
|
154
|
+
}
|
|
155
|
+
recent(count, opts = {}) {
|
|
156
|
+
const consistency = opts.consistency ?? 'live';
|
|
157
|
+
const entries = this.deps.live.snapshot()
|
|
158
|
+
.filter(e => consistency === 'live' || e.committed)
|
|
159
|
+
.slice(-Math.max(0, count));
|
|
160
|
+
const first = entries[0]?.bucket;
|
|
161
|
+
const last = entries[entries.length - 1]?.bucket;
|
|
162
|
+
const now = new Date().toISOString();
|
|
163
|
+
const q = {
|
|
164
|
+
sessionId: this.deps.currentSessionId(),
|
|
165
|
+
from: first?.startTime ?? now,
|
|
166
|
+
to: last?.endTime ?? now,
|
|
167
|
+
consistency,
|
|
168
|
+
method: opts.method,
|
|
169
|
+
route: opts.route,
|
|
170
|
+
resolutionMs: opts.resolutionMs,
|
|
171
|
+
includeEndpoints: opts.includeEndpoints,
|
|
172
|
+
};
|
|
173
|
+
const acc = new SeriesAccumulator(q, []);
|
|
174
|
+
for (const e of entries)
|
|
175
|
+
acc.add(e.bucket, e.committed);
|
|
176
|
+
return acc.finish();
|
|
177
|
+
}
|
|
178
|
+
latestWindow(count, consistency) {
|
|
179
|
+
const entries = this.deps.live.snapshot()
|
|
180
|
+
.filter(e => consistency === 'live' || e.committed)
|
|
181
|
+
.slice(-Math.max(1, count));
|
|
182
|
+
if (entries.length === 0)
|
|
183
|
+
return null;
|
|
184
|
+
const first = entries[0].bucket;
|
|
185
|
+
const last = entries[entries.length - 1].bucket;
|
|
186
|
+
return {
|
|
187
|
+
sessionId: this.deps.currentSessionId(),
|
|
188
|
+
from: first.startTime,
|
|
189
|
+
to: last.endTime,
|
|
190
|
+
sequenceFrom: first.sequence,
|
|
191
|
+
sequenceTo: last.sequence,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
liveBucket(sequence) {
|
|
195
|
+
return this.deps.live.snapshot().find(e => e.bucket.sequence === sequence)?.bucket;
|
|
196
|
+
}
|
|
197
|
+
async *buckets(q, onIssue) {
|
|
198
|
+
const { fromMs, toMs } = parseRange(q);
|
|
199
|
+
const isCurrent = q.sessionId === this.deps.currentSessionId();
|
|
200
|
+
const ramAll = isCurrent ? this.deps.live.snapshot() : [];
|
|
201
|
+
const ram = ramAll.filter(e => overlaps(e.bucket, fromMs, toMs) && (q.consistency === 'live' || e.committed));
|
|
202
|
+
const ramById = new Map(ram.map(e => [e.bucket.bucketId, e.bucket]));
|
|
203
|
+
const oldest = ramAll[0]?.bucket;
|
|
204
|
+
const covered = oldest !== undefined &&
|
|
205
|
+
(oldest.sequence === 1 || Date.parse(oldest.startTime) <= fromMs);
|
|
206
|
+
const handled = new Set();
|
|
207
|
+
if (!covered) {
|
|
208
|
+
try {
|
|
209
|
+
for await (const b of this.deps.committed.read(q.sessionId, fromMs, toMs, onIssue)) {
|
|
210
|
+
const mem = ramById.get(b.bucketId);
|
|
211
|
+
if (mem && fingerprint(mem) !== fingerprint(b)) {
|
|
212
|
+
handled.add(b.bucketId);
|
|
213
|
+
onIssue({ type: 'CONFLICT', bucketId: b.bucketId });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
handled.add(b.bucketId);
|
|
217
|
+
yield { bucket: b, committed: true };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
onIssue({ type: 'SOURCE_ERROR', detail: err instanceof Error ? err.message : String(err) });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const e of ram) {
|
|
225
|
+
if (handled.has(e.bucket.bucketId))
|
|
226
|
+
continue;
|
|
227
|
+
yield e;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.TelemetryQueryService = TelemetryQueryService;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { TelemetryRequest } from '../domain';
|
|
2
|
-
|
|
2
|
+
import type { ReadonlyConfig } from '../../config';
|
|
3
|
+
export declare class TelemetryService {
|
|
3
4
|
private readonly storage;
|
|
4
|
-
|
|
5
|
+
private readonly excludePatterns;
|
|
6
|
+
private inFlight;
|
|
7
|
+
private drainWaiters;
|
|
8
|
+
constructor(storage: {
|
|
5
9
|
record(request: TelemetryRequest): void;
|
|
6
|
-
});
|
|
10
|
+
}, config: ReadonlyConfig);
|
|
7
11
|
private isPathExcluded;
|
|
12
|
+
track(): () => void;
|
|
13
|
+
get inFlightCount(): number;
|
|
14
|
+
whenDrained(timeoutMs: number): Promise<boolean>;
|
|
8
15
|
record(data: Omit<TelemetryRequest, 'timestamp' | 'responseTime'>, startHrTime: bigint): TelemetryRequest | null;
|
|
9
16
|
}
|
|
10
|
-
export default TelemetryService;
|