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
|
@@ -1,72 +1,264 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const local_repository_1 = require("../storage/local.repository");
|
|
6
|
+
exports.CorrelationService = void 0;
|
|
5
7
|
const statistics_1 = require("../utility/statistics");
|
|
8
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
9
|
+
const correlation_replay_1 = require("./utility/correlation-replay");
|
|
10
|
+
const correlation_finding_1 = require("./utility/correlation-finding");
|
|
11
|
+
const correlation_input_1 = require("./utility/correlation-input");
|
|
12
|
+
const TRANSFORM = 'raw';
|
|
13
|
+
const SCOPES = [{}];
|
|
6
14
|
class CorrelationService {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
requiredSampleSize;
|
|
16
|
+
parameters;
|
|
17
|
+
deps;
|
|
18
|
+
transform;
|
|
19
|
+
scopes;
|
|
20
|
+
analysisIntervalMs;
|
|
21
|
+
evaluateEvery;
|
|
22
|
+
busy = false;
|
|
23
|
+
lastRunAt = Number.NEGATIVE_INFINITY;
|
|
24
|
+
lastEvaluated = new Map();
|
|
25
|
+
results = [];
|
|
26
|
+
lowCoverage = new Set();
|
|
27
|
+
assessment = {
|
|
28
|
+
status: 'INSUFFICIENT_DATA',
|
|
29
|
+
evidence: [],
|
|
30
|
+
recommendation: 'Analysis not yet started.'
|
|
31
|
+
};
|
|
32
|
+
constructor(deps, options) {
|
|
33
|
+
this.deps = deps;
|
|
34
|
+
this.requiredSampleSize = (0, statistics_1.calculateSampleSizeCorrelation)(options.minCorrelation, options.alpha, options.power);
|
|
35
|
+
this.parameters = {
|
|
36
|
+
requiredSampleSize: this.requiredSampleSize,
|
|
37
|
+
minCorrelation: options.minCorrelation,
|
|
38
|
+
alpha: options.alpha,
|
|
39
|
+
power: options.power,
|
|
40
|
+
strongThreshold: options.strongThreshold,
|
|
41
|
+
maxLag: options.maxLag,
|
|
42
|
+
maxWindow: Math.max(options.maxWindow, this.requiredSampleSize),
|
|
43
|
+
minCoverage: options.minCoverage,
|
|
44
|
+
gapPolicy: 'compact',
|
|
45
|
+
};
|
|
46
|
+
this.transform = TRANSFORM;
|
|
47
|
+
this.scopes = SCOPES;
|
|
48
|
+
this.analysisIntervalMs = options.analysisIntervalMs;
|
|
49
|
+
this.evaluateEvery = options.evaluateEveryIntervals;
|
|
50
|
+
}
|
|
51
|
+
getResults() { return this.results; }
|
|
52
|
+
getAssessment() { return this.assessment; }
|
|
53
|
+
pack() { return { results: this.results, assessment: this.assessment }; }
|
|
54
|
+
tick(now = Date.now()) {
|
|
55
|
+
if (this.busy)
|
|
56
|
+
return;
|
|
57
|
+
if (now - this.lastRunAt < this.analysisIntervalMs)
|
|
58
|
+
return;
|
|
59
|
+
this.lastRunAt = now;
|
|
60
|
+
this.busy = true;
|
|
61
|
+
this.evaluate(now)
|
|
62
|
+
.catch(err => logger_1.default.error('Correlation evaluation failed:', err))
|
|
63
|
+
.finally(() => { this.busy = false; });
|
|
11
64
|
}
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
65
|
+
async evaluate(now) {
|
|
66
|
+
const sessionId = this.deps.identity.sessionId();
|
|
67
|
+
let dashboardFindings = [];
|
|
68
|
+
const durable = this.deps.finding.durable;
|
|
69
|
+
const consistency = durable ? 'committed' : 'live';
|
|
70
|
+
const bounds = this.deps.queries.latestWindow(this.parameters.maxWindow, consistency);
|
|
71
|
+
if (!bounds)
|
|
16
72
|
return;
|
|
73
|
+
const endSequence = bounds.sequenceTo;
|
|
74
|
+
for (let s = 0; s < this.scopes.length; s++) {
|
|
75
|
+
const scope = this.scopes[s];
|
|
76
|
+
const due = correlation_input_1.PAIRS.filter(pair => {
|
|
77
|
+
const last = this.lastEvaluated.get(this.seriesFor(sessionId, scope, pair));
|
|
78
|
+
return last === undefined || endSequence - last >= this.evaluateEvery;
|
|
79
|
+
});
|
|
80
|
+
if (due.length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
const result = await this.deps.queries.query({
|
|
83
|
+
sessionId,
|
|
84
|
+
from: bounds.from,
|
|
85
|
+
to: bounds.to,
|
|
86
|
+
method: scope.method,
|
|
87
|
+
route: scope.route,
|
|
88
|
+
consistency,
|
|
89
|
+
});
|
|
90
|
+
const points = result.points.filter(p => p.sequenceFrom >= bounds.sequenceFrom && p.sequenceTo <= endSequence);
|
|
91
|
+
if (points.length === 0)
|
|
92
|
+
continue;
|
|
93
|
+
const findings = [];
|
|
94
|
+
for (const pair of due) {
|
|
95
|
+
const series = this.seriesFor(sessionId, scope, pair);
|
|
96
|
+
this.lastEvaluated.set(series, endSequence);
|
|
97
|
+
try {
|
|
98
|
+
findings.push(this.buildFinding(sessionId, scope, pair, points, now));
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logger_1.default.error(`Correlation analysis failed for ${(0, correlation_input_1.pairId)(pair.x, pair.y)}:`, err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let committed;
|
|
105
|
+
if (durable) {
|
|
106
|
+
const saved = await Promise.all(findings.map(f => this.deps.finding.save(f).catch(() => false)));
|
|
107
|
+
committed = findings.filter((f, i) => {
|
|
108
|
+
if (!saved[i])
|
|
109
|
+
logger_1.default.error(`Correlation finding not persisted, not published: ${f.findingId}`);
|
|
110
|
+
return saved[i];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
committed = findings.map(f => ({ ...f, qualityFlags: [...f.qualityFlags, 'NOT_PERSISTED'].sort() }));
|
|
115
|
+
}
|
|
116
|
+
if (s === 0)
|
|
117
|
+
dashboardFindings = committed;
|
|
17
118
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
119
|
+
if (dashboardFindings.length > 0)
|
|
120
|
+
this.publish(dashboardFindings);
|
|
121
|
+
}
|
|
122
|
+
seriesFor(sessionId, scope, pair) {
|
|
123
|
+
return (0, correlation_finding_1.seriesKey)({
|
|
124
|
+
sessionId, scope, pairId: (0, correlation_input_1.pairId)(pair.x, pair.y), resolutionMs: null,
|
|
125
|
+
transform: this.transform, parameters: this.parameters,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
buildFinding(sessionId, scope, pair, points, now) {
|
|
129
|
+
const pid = (0, correlation_input_1.pairId)(pair.x, pair.y);
|
|
130
|
+
const prepared = (0, correlation_input_1.prepareSeries)(points, pair, this.transform, this.parameters.minCoverage);
|
|
131
|
+
const result = (0, correlation_input_1.runAnalysis)(prepared, pair, this.parameters);
|
|
132
|
+
const endSequence = prepared.windowEndSequence;
|
|
133
|
+
const key = (0, correlation_finding_1.evaluationKey)(this.seriesFor(sessionId, scope, pair), endSequence, prepared.digest);
|
|
134
|
+
return {
|
|
135
|
+
findingId: (0, correlation_finding_1.findingIdFor)({ sessionId, scope, pairId: pid, windowEndSequence: endSequence, evaluationKey: key }),
|
|
136
|
+
evaluationKey: key,
|
|
137
|
+
sessionId,
|
|
138
|
+
instanceId: this.deps.identity.instanceId,
|
|
139
|
+
scope,
|
|
140
|
+
metricX: pair.x,
|
|
141
|
+
metricY: pair.y,
|
|
142
|
+
windowStart: prepared.windowStart,
|
|
143
|
+
windowEnd: prepared.windowEnd,
|
|
144
|
+
resolutionMs: null,
|
|
145
|
+
transform: this.transform,
|
|
146
|
+
methodVersion: correlation_finding_1.CORRELATION_METHOD_VERSION,
|
|
147
|
+
parameters: this.parameters,
|
|
148
|
+
input: {
|
|
149
|
+
sessionId,
|
|
150
|
+
bucketCount: prepared.bucketCount,
|
|
151
|
+
ranges: prepared.ranges,
|
|
152
|
+
digest: prepared.digest,
|
|
153
|
+
},
|
|
154
|
+
inputSchemaVersion: correlation_finding_1.INPUT_SCHEMA_VERSION,
|
|
155
|
+
createdAt: new Date(now).toISOString(),
|
|
156
|
+
source: 'live',
|
|
157
|
+
validPairCount: prepared.validPairCount,
|
|
158
|
+
droppedPairCount: prepared.droppedPairCount,
|
|
159
|
+
coverage: prepared.coverage,
|
|
160
|
+
qualityFlags: prepared.qualityFlags,
|
|
161
|
+
result,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async replay(findingId) {
|
|
165
|
+
const finding = await this.deps.finding.find(findingId);
|
|
166
|
+
if (!finding)
|
|
167
|
+
return { status: 'NOT_FOUND', findingId };
|
|
168
|
+
return (0, correlation_replay_1.verifyFinding)(this.deps.queries, finding);
|
|
36
169
|
}
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
170
|
+
publish(findings) {
|
|
171
|
+
const byId = new Map(findings.map(f => [`${f.metricX}->${f.metricY}`, f]));
|
|
172
|
+
const results = [];
|
|
173
|
+
const lowCoverage = new Set();
|
|
174
|
+
for (const pair of correlation_input_1.PAIRS) {
|
|
175
|
+
const id = (0, correlation_input_1.pairId)(pair.x, pair.y);
|
|
176
|
+
const f = byId.get(id);
|
|
177
|
+
if (!f)
|
|
178
|
+
continue;
|
|
179
|
+
if (f.qualityFlags.includes('LOW_COVERAGE'))
|
|
180
|
+
lowCoverage.add(id);
|
|
181
|
+
results.push(this.toUiResult(pair, f));
|
|
182
|
+
}
|
|
183
|
+
this.results = results;
|
|
184
|
+
this.lowCoverage = lowCoverage;
|
|
185
|
+
this.assessment = this.assess(results);
|
|
186
|
+
}
|
|
187
|
+
toUiResult(pair, f) {
|
|
188
|
+
const x = correlation_input_1.METRICS[pair.x];
|
|
189
|
+
const y = correlation_input_1.METRICS[pair.y];
|
|
190
|
+
const unexpectedDirection = f.result.direction !== 'NONE' && f.result.direction !== pair.expectedDirection;
|
|
191
|
+
return {
|
|
192
|
+
id: (0, correlation_input_1.pairId)(pair.x, pair.y),
|
|
193
|
+
xLabel: x.label,
|
|
194
|
+
yLabel: y.label,
|
|
195
|
+
expectedDirection: pair.expectedDirection,
|
|
196
|
+
unexpectedDirection,
|
|
197
|
+
analysis: unexpectedDirection
|
|
198
|
+
? { ...f.result, recommendation: `${f.result.recommendation} The direction of the correlation is opposite to the expected one.` }
|
|
199
|
+
: f.result,
|
|
200
|
+
findingId: f.findingId,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
isStrongPositive(id, results) {
|
|
204
|
+
if (this.lowCoverage.has(id))
|
|
205
|
+
return false;
|
|
206
|
+
const result = results.find(r => r.id === id);
|
|
207
|
+
if (!result)
|
|
208
|
+
return false;
|
|
209
|
+
const { status, direction } = result.analysis;
|
|
210
|
+
return (status === 'STRONG_LINEAR_ASSOCIATION' || status === 'STRONG_MONOTONIC_NONLINEAR_ASSOCIATION')
|
|
211
|
+
&& direction === 'POSITIVE';
|
|
212
|
+
}
|
|
213
|
+
assess(results) {
|
|
214
|
+
const coreId = (0, correlation_input_1.pairId)('rps', 'p95Latency');
|
|
215
|
+
const core = results.find(r => r.id === coreId);
|
|
216
|
+
if (!core || core.analysis.status === 'INSUFFICIENT_DATA' || this.lowCoverage.has(coreId)) {
|
|
42
217
|
return {
|
|
43
|
-
status: '
|
|
44
|
-
|
|
218
|
+
status: 'INSUFFICIENT_DATA',
|
|
219
|
+
evidence: [],
|
|
220
|
+
recommendation: 'No sufficient data to assess the saturation pattern.'
|
|
45
221
|
};
|
|
46
222
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
recommendation: `Linearna degradacija. Varijable zajednički rastu.`
|
|
58
|
-
};
|
|
223
|
+
const supporting = [
|
|
224
|
+
{ id: (0, correlation_input_1.pairId)('eventLoopLag', 'p95Latency'), text: 'Event Loop Lag ↔ p95 Latency' },
|
|
225
|
+
{ id: (0, correlation_input_1.pairId)('rps', 'errorRate'), text: 'RPS ↔ error rate' }
|
|
226
|
+
];
|
|
227
|
+
const evidence = [];
|
|
228
|
+
if (this.isStrongPositive(coreId, results)) {
|
|
229
|
+
evidence.push('RPS ↔ p95 Latency');
|
|
230
|
+
for (const item of supporting) {
|
|
231
|
+
if (this.isStrongPositive(item.id, results))
|
|
232
|
+
evidence.push(item.text);
|
|
59
233
|
}
|
|
60
234
|
}
|
|
235
|
+
if (evidence.length >= 2) {
|
|
236
|
+
return {
|
|
237
|
+
status: 'POSSIBLE_SATURATION',
|
|
238
|
+
evidence,
|
|
239
|
+
recommendation: `Possible saturation pattern: in the observed period there is a strong positive correlation (${evidence.join('; ')}). Correlation does not prove causality; check CPU, GC and external dependencies.`
|
|
240
|
+
};
|
|
241
|
+
}
|
|
61
242
|
return {
|
|
62
|
-
status: '
|
|
63
|
-
|
|
243
|
+
status: 'NO_SATURATION_PATTERN',
|
|
244
|
+
evidence,
|
|
245
|
+
recommendation: 'In the observed period, no pattern indicating saturation was detected.'
|
|
64
246
|
};
|
|
65
247
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
248
|
+
relatedFindings(metric) {
|
|
249
|
+
return this.results
|
|
250
|
+
.filter(r => {
|
|
251
|
+
const [x, y] = r.id.split('->');
|
|
252
|
+
return (x === metric || y === metric) && !this.lowCoverage.has(r.id)
|
|
253
|
+
&& (r.analysis.status === 'STRONG_LINEAR_ASSOCIATION' || r.analysis.status === 'STRONG_MONOTONIC_NONLINEAR_ASSOCIATION');
|
|
254
|
+
})
|
|
255
|
+
.map(r => ({
|
|
256
|
+
findingId: r.findingId,
|
|
257
|
+
pairId: r.id,
|
|
258
|
+
status: String(r.analysis.status),
|
|
259
|
+
direction: String(r.analysis.direction),
|
|
260
|
+
pearsonR: r.analysis.pearsonR,
|
|
261
|
+
}));
|
|
70
262
|
}
|
|
71
263
|
}
|
|
72
|
-
exports.
|
|
264
|
+
exports.CorrelationService = CorrelationService;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TelemetryQueryService, SeriesPoint } from './telemetry-query.service';
|
|
2
|
+
import type { BucketsMessage, BucketsRequest, LiveBucketDto } from '../domain';
|
|
3
|
+
export declare function toLiveBucketDto(sessionId: string, p: SeriesPoint): LiveBucketDto;
|
|
4
|
+
export declare class DashboardLiveService {
|
|
5
|
+
private readonly queries;
|
|
6
|
+
private readonly currentSessionId;
|
|
7
|
+
constructor(queries: TelemetryQueryService, currentSessionId: () => string);
|
|
8
|
+
getLive(): BucketsMessage | null;
|
|
9
|
+
getBackFill(req: BucketsRequest): Promise<BucketsMessage>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DashboardLiveService = void 0;
|
|
4
|
+
exports.toLiveBucketDto = toLiveBucketDto;
|
|
5
|
+
const bucket_metric_1 = require("../storage/stores/bucket-metric");
|
|
6
|
+
const LIVE_WINDOW = 5;
|
|
7
|
+
const DEFAULT_BACKFILL_LIMIT = 120;
|
|
8
|
+
const MAX_BACKFILL_LIMIT = 600;
|
|
9
|
+
const FALLBACK_BACKFILL_MS = 10 * 60_000;
|
|
10
|
+
const MAX_BACKFILL_AGE_MS = 60 * 60_000;
|
|
11
|
+
function toLiveBucketDto(sessionId, p) {
|
|
12
|
+
const m = (0, bucket_metric_1.deriveMetrics)(p.requests, p.durationMs);
|
|
13
|
+
return {
|
|
14
|
+
bucketId: `${sessionId}:${p.sequenceTo}`,
|
|
15
|
+
sequence: p.sequenceTo,
|
|
16
|
+
startTime: p.startTime,
|
|
17
|
+
endTime: p.endTime,
|
|
18
|
+
durationMs: p.durationMs,
|
|
19
|
+
committed: p.committed === true,
|
|
20
|
+
qualityFlags: p.qualityFlags,
|
|
21
|
+
requestCount: m.requestCount,
|
|
22
|
+
clientErrorCount: m.clientErrorCount,
|
|
23
|
+
serverErrorCount: m.serverErrorCount,
|
|
24
|
+
rps: m.rps,
|
|
25
|
+
averageLatencyMs: m.averageLatency,
|
|
26
|
+
p50Ms: m.p50,
|
|
27
|
+
p95Ms: m.p95,
|
|
28
|
+
p99Ms: m.p99,
|
|
29
|
+
errorRate: m.errorRate,
|
|
30
|
+
runtime: {
|
|
31
|
+
eventLoopLagMs: p.runtime.loopDelay.meanMs,
|
|
32
|
+
heapUsageBytes: p.runtime.memoryUsage.heapUsage,
|
|
33
|
+
heapSizeBytes: p.runtime.memoryUsage.heapSize,
|
|
34
|
+
rssMemoryBytes: p.runtime.memoryUsage.rssMemory,
|
|
35
|
+
heapLimitBytes: p.runtime.memoryUsage.heapLimit,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
class DashboardLiveService {
|
|
40
|
+
queries;
|
|
41
|
+
currentSessionId;
|
|
42
|
+
constructor(queries, currentSessionId) {
|
|
43
|
+
this.queries = queries;
|
|
44
|
+
this.currentSessionId = currentSessionId;
|
|
45
|
+
}
|
|
46
|
+
// for publishing, sinhronous, returns the latest live buckets message from RAM
|
|
47
|
+
getLive() {
|
|
48
|
+
const sessionId = this.currentSessionId();
|
|
49
|
+
const result = this.queries.recent(LIVE_WINDOW, { consistency: 'live' });
|
|
50
|
+
if (result.points.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
const buckets = result.points.map(p => toLiveBucketDto(sessionId, p));
|
|
53
|
+
return {
|
|
54
|
+
subscriptionId: null,
|
|
55
|
+
sessionId,
|
|
56
|
+
kind: 'live',
|
|
57
|
+
buckets,
|
|
58
|
+
lastSequence: buckets[buckets.length - 1].sequence,
|
|
59
|
+
requestedAfterSequence: null,
|
|
60
|
+
gaps: [],
|
|
61
|
+
truncated: false,
|
|
62
|
+
serverTime: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// for backfill, asynchronous, returns the requested live buckets message from storage
|
|
66
|
+
// called on loading, reconnecting, filling gaps, etc.
|
|
67
|
+
async getBackFill(req) {
|
|
68
|
+
const sessionId = this.currentSessionId();
|
|
69
|
+
const limit = Math.min(Math.max(1, req.limit ?? DEFAULT_BACKFILL_LIMIT), MAX_BACKFILL_LIMIT);
|
|
70
|
+
const after = req.sessionId === sessionId ? req.afterSequence : null;
|
|
71
|
+
let points = [];
|
|
72
|
+
let gaps = [];
|
|
73
|
+
let truncated = false;
|
|
74
|
+
if (after === null) {
|
|
75
|
+
points = this.queries.recent(limit, { consistency: 'live' }).points;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const requestedFrom = req.afterEndTime ? Date.parse(req.afterEndTime) : now - FALLBACK_BACKFILL_MS;
|
|
80
|
+
const from = new Date(Math.max(requestedFrom, now - MAX_BACKFILL_AGE_MS));
|
|
81
|
+
const result = await this.queries.query({
|
|
82
|
+
sessionId,
|
|
83
|
+
from: from.toISOString(),
|
|
84
|
+
to: new Date(now).toISOString(),
|
|
85
|
+
consistency: 'live'
|
|
86
|
+
});
|
|
87
|
+
points = result.points.filter(p => p.sequenceTo > after);
|
|
88
|
+
if (points.length > limit) {
|
|
89
|
+
truncated = true;
|
|
90
|
+
points = points.slice(-limit);
|
|
91
|
+
}
|
|
92
|
+
gaps = result.issues
|
|
93
|
+
.filter((i) => i.type === 'GAP')
|
|
94
|
+
.map(i => [i.fromSequence, i.toSequence]);
|
|
95
|
+
}
|
|
96
|
+
const buckets = points.map(p => toLiveBucketDto(sessionId, p));
|
|
97
|
+
return {
|
|
98
|
+
subscriptionId: req.subscriptionId,
|
|
99
|
+
sessionId,
|
|
100
|
+
kind: 'backfill',
|
|
101
|
+
buckets,
|
|
102
|
+
lastSequence: buckets.length ? buckets[buckets.length - 1].sequence : null,
|
|
103
|
+
requestedAfterSequence: after,
|
|
104
|
+
gaps,
|
|
105
|
+
truncated,
|
|
106
|
+
serverTime: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.DashboardLiveService = DashboardLiveService;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TelemetryQueryService, SeriesPoint } from './telemetry-query.service';
|
|
2
|
+
import type { BucketsMessage, BucketsRequest, LiveBucketDto } from '../domain';
|
|
3
|
+
export interface DashboardServiceOptions {
|
|
4
|
+
liveWindow: number;
|
|
5
|
+
backfillLimit: number;
|
|
6
|
+
maxBackfillAgeMs: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function toLiveBucketDto(sessionId: string, p: SeriesPoint): LiveBucketDto;
|
|
9
|
+
export declare class DashboardService {
|
|
10
|
+
private readonly queries;
|
|
11
|
+
private readonly currentSessionId;
|
|
12
|
+
private readonly options;
|
|
13
|
+
constructor(queries: TelemetryQueryService, currentSessionId: () => string, options: DashboardServiceOptions);
|
|
14
|
+
/**
|
|
15
|
+
* Get the latest live buckets message from RAM
|
|
16
|
+
* @returns BucketsMessage | null
|
|
17
|
+
*/
|
|
18
|
+
getLive(): BucketsMessage | null;
|
|
19
|
+
getBackFill(req: BucketsRequest): Promise<BucketsMessage>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DashboardService = void 0;
|
|
4
|
+
exports.toLiveBucketDto = toLiveBucketDto;
|
|
5
|
+
const bucket_metric_1 = require("../storage/stores/bucket-metric");
|
|
6
|
+
const MAX_BACKFILL_LIMIT = 600;
|
|
7
|
+
const FALLBACK_BACKFILL_MS = 10 * 60_000;
|
|
8
|
+
function toLiveBucketDto(sessionId, p) {
|
|
9
|
+
const m = (0, bucket_metric_1.deriveMetrics)(p.requests, p.durationMs);
|
|
10
|
+
const rt = (0, bucket_metric_1.deriveRuntime)(p.runtime, p.durationMs);
|
|
11
|
+
return {
|
|
12
|
+
bucketId: `${sessionId}:${p.sequenceTo}`,
|
|
13
|
+
sequence: p.sequenceTo,
|
|
14
|
+
startTime: p.startTime,
|
|
15
|
+
endTime: p.endTime,
|
|
16
|
+
durationMs: p.durationMs,
|
|
17
|
+
committed: p.committed === true,
|
|
18
|
+
qualityFlags: p.qualityFlags,
|
|
19
|
+
requestCount: m.requestCount,
|
|
20
|
+
clientErrorCount: m.clientErrorCount,
|
|
21
|
+
serverErrorCount: m.serverErrorCount,
|
|
22
|
+
rps: m.rps,
|
|
23
|
+
averageLatencyMs: m.averageLatency,
|
|
24
|
+
p50Ms: m.p50,
|
|
25
|
+
p95Ms: m.p95,
|
|
26
|
+
p99Ms: m.p99,
|
|
27
|
+
errorRate: m.errorRate,
|
|
28
|
+
runtime: {
|
|
29
|
+
eventLoopLagMs: rt.eventLoopDelayMeanMs ?? 0,
|
|
30
|
+
heapUsageBytes: rt.heapUsedBytes ?? 0,
|
|
31
|
+
heapSizeBytes: rt.heapTotalBytes ?? 0,
|
|
32
|
+
rssMemoryBytes: rt.rssBytes ?? 0,
|
|
33
|
+
heapLimitBytes: rt.heapLimitBytes ?? 0,
|
|
34
|
+
cpuPercent: rt.cpuPercent ?? 0,
|
|
35
|
+
cpuUserPercent: rt.cpuUserPercent ?? 0,
|
|
36
|
+
cpuSystemPercent: rt.cpuSystemPercent ?? 0,
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
class DashboardService {
|
|
41
|
+
queries;
|
|
42
|
+
currentSessionId;
|
|
43
|
+
options;
|
|
44
|
+
constructor(queries, currentSessionId, options) {
|
|
45
|
+
this.queries = queries;
|
|
46
|
+
this.currentSessionId = currentSessionId;
|
|
47
|
+
this.options = options;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the latest live buckets message from RAM
|
|
51
|
+
* @returns BucketsMessage | null
|
|
52
|
+
*/
|
|
53
|
+
getLive() {
|
|
54
|
+
const sessionId = this.currentSessionId();
|
|
55
|
+
const result = this.queries.recent(this.options.liveWindow, { consistency: 'live' });
|
|
56
|
+
if (result.points.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
const buckets = result.points.map(p => toLiveBucketDto(sessionId, p));
|
|
59
|
+
return {
|
|
60
|
+
subscriptionId: null,
|
|
61
|
+
sessionId,
|
|
62
|
+
kind: 'live',
|
|
63
|
+
buckets,
|
|
64
|
+
lastSequence: buckets[buckets.length - 1].sequence,
|
|
65
|
+
requestedAfterSequence: null,
|
|
66
|
+
gaps: [],
|
|
67
|
+
truncated: false,
|
|
68
|
+
serverTime: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async getBackFill(req) {
|
|
72
|
+
const sessionId = this.currentSessionId();
|
|
73
|
+
const limit = Math.min(Math.max(1, Math.floor(req.limit ?? this.options.backfillLimit)), MAX_BACKFILL_LIMIT);
|
|
74
|
+
const after = req.sessionId === sessionId && typeof req.afterSequence === 'number' && req.afterSequence >= 0
|
|
75
|
+
? Math.floor(req.afterSequence) : null;
|
|
76
|
+
const message = (points, gaps, truncated) => {
|
|
77
|
+
const buckets = points.map(p => toLiveBucketDto(sessionId, p));
|
|
78
|
+
return {
|
|
79
|
+
subscriptionId: req.subscriptionId,
|
|
80
|
+
sessionId,
|
|
81
|
+
kind: 'backfill',
|
|
82
|
+
buckets,
|
|
83
|
+
lastSequence: buckets.length ? buckets[buckets.length - 1].sequence : null,
|
|
84
|
+
requestedAfterSequence: after,
|
|
85
|
+
gaps,
|
|
86
|
+
truncated,
|
|
87
|
+
serverTime: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const newest = this.queries.latestWindow(1, 'live');
|
|
91
|
+
if (!newest)
|
|
92
|
+
return message([], [], false);
|
|
93
|
+
if (after === null) {
|
|
94
|
+
const result = this.queries.recent(limit, { consistency: 'live' });
|
|
95
|
+
return message(result.points, gapsOf(result.issues), false);
|
|
96
|
+
}
|
|
97
|
+
if (after >= newest.sequenceTo)
|
|
98
|
+
return message([], [], false);
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const known = this.queries.liveBucket(after)?.endTime
|
|
101
|
+
?? this.queries.liveBucket(after + 1)?.startTime
|
|
102
|
+
?? req.afterEndTime;
|
|
103
|
+
const requestedFrom = known ? Date.parse(known) : now - FALLBACK_BACKFILL_MS;
|
|
104
|
+
const from = Math.max(Number.isNaN(requestedFrom) ? now - FALLBACK_BACKFILL_MS : requestedFrom, now - this.options.maxBackfillAgeMs);
|
|
105
|
+
const result = await this.queries.query({
|
|
106
|
+
sessionId,
|
|
107
|
+
from: new Date(from).toISOString(),
|
|
108
|
+
to: newest.to,
|
|
109
|
+
consistency: 'live',
|
|
110
|
+
});
|
|
111
|
+
let points = result.points.filter(p => p.sequenceFrom > after && p.sequenceTo <= newest.sequenceTo);
|
|
112
|
+
let truncated = false;
|
|
113
|
+
if (points.length > limit) {
|
|
114
|
+
truncated = true;
|
|
115
|
+
points = points.slice(0, limit);
|
|
116
|
+
}
|
|
117
|
+
const sourceFailed = result.issues.some(i => i.type === 'SOURCE_ERROR');
|
|
118
|
+
const upper = truncated ? points[points.length - 1].sequenceTo : newest.sequenceTo;
|
|
119
|
+
const gaps = sourceFailed ? [] : missingRanges(after + 1, upper, points);
|
|
120
|
+
return message(points, gaps, truncated);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.DashboardService = DashboardService;
|
|
124
|
+
function gapsOf(issues) {
|
|
125
|
+
return issues
|
|
126
|
+
.filter((i) => i.type === 'GAP')
|
|
127
|
+
.map(i => [i.fromSequence, i.toSequence]);
|
|
128
|
+
}
|
|
129
|
+
function missingRanges(from, to, points) {
|
|
130
|
+
const ranges = [];
|
|
131
|
+
let next = from;
|
|
132
|
+
for (const p of [...points].sort((a, b) => a.sequenceFrom - b.sequenceFrom)) {
|
|
133
|
+
if (p.sequenceFrom > next)
|
|
134
|
+
ranges.push([next, Math.min(p.sequenceFrom - 1, to)]);
|
|
135
|
+
next = Math.max(next, p.sequenceTo + 1);
|
|
136
|
+
}
|
|
137
|
+
if (next <= to)
|
|
138
|
+
ranges.push([next, to]);
|
|
139
|
+
return ranges;
|
|
140
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { MetricsQueryResult, TelemetryQueryService } from './telemetry-query.service';
|
|
2
|
+
import type { ApplicationEventType, IncidentSeverity, IncidentState, IncidentsSnapshot, RelatedFinding } from '../domain';
|
|
3
|
+
export interface Observation {
|
|
4
|
+
valid: boolean;
|
|
5
|
+
value: number | null;
|
|
6
|
+
reason?: string;
|
|
7
|
+
endMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IncidentTiming {
|
|
10
|
+
pendingForMs: number;
|
|
11
|
+
recoveryForMs: number;
|
|
12
|
+
resolvedHoldMs: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RuleTracker {
|
|
15
|
+
state: IncidentState;
|
|
16
|
+
sinceMs: number | null;
|
|
17
|
+
breachStartMs: number | null;
|
|
18
|
+
recoveryStartMs: number | null;
|
|
19
|
+
}
|
|
20
|
+
export interface IncidentRule {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
metric: string;
|
|
24
|
+
unit: string;
|
|
25
|
+
severity: IncidentSeverity;
|
|
26
|
+
threshold: number;
|
|
27
|
+
recoverBelow: number;
|
|
28
|
+
measure(window: MetricsQueryResult): Omit<Observation, 'endMs'>;
|
|
29
|
+
}
|
|
30
|
+
export declare const initialTracker: () => RuleTracker;
|
|
31
|
+
export declare function stepRule(t: RuleTracker, obs: Observation, rule: Pick<IncidentRule, 'threshold' | 'recoverBelow'>, timing: IncidentTiming): RuleTracker;
|
|
32
|
+
export interface IncidentRuleOptions {
|
|
33
|
+
windowIntervals?: number;
|
|
34
|
+
minRequests?: number;
|
|
35
|
+
serverErrorRate?: number;
|
|
36
|
+
p95LatencyMs?: number;
|
|
37
|
+
eventLoopLagMs?: number;
|
|
38
|
+
recoveryRatio?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function defaultIncidentRules(o: Required<IncidentRuleOptions>): IncidentRule[];
|
|
41
|
+
export declare const CAUSE_NOTE = "Related findings show which metrics changed together during the incident. Correlation does not establish the cause.";
|
|
42
|
+
export interface IncidentServiceDeps {
|
|
43
|
+
queries: TelemetryQueryService;
|
|
44
|
+
sessionId: () => string;
|
|
45
|
+
durable: () => boolean;
|
|
46
|
+
related?: (metric: string) => RelatedFinding[];
|
|
47
|
+
emit?: (event: {
|
|
48
|
+
type: ApplicationEventType;
|
|
49
|
+
reason: string;
|
|
50
|
+
details: Record<string, unknown>;
|
|
51
|
+
}) => unknown;
|
|
52
|
+
}
|
|
53
|
+
export interface IncidentServiceOptions extends Required<IncidentRuleOptions>, IncidentTiming {
|
|
54
|
+
historySize: number;
|
|
55
|
+
rules?: IncidentRule[];
|
|
56
|
+
}
|
|
57
|
+
export declare class IncidentService {
|
|
58
|
+
private readonly deps;
|
|
59
|
+
private readonly timing;
|
|
60
|
+
private readonly windowIntervals;
|
|
61
|
+
private readonly historySize;
|
|
62
|
+
private readonly entries;
|
|
63
|
+
private history;
|
|
64
|
+
private lastSequence;
|
|
65
|
+
private lastSessionId;
|
|
66
|
+
constructor(deps: IncidentServiceDeps, options: IncidentServiceOptions);
|
|
67
|
+
tick(): void;
|
|
68
|
+
evaluate(): boolean;
|
|
69
|
+
snapshot(): IncidentsSnapshot;
|
|
70
|
+
private apply;
|
|
71
|
+
}
|