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,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HISTOGRAM_BUCKET_COUNT = exports.HISTOGRAM_UPPER_BOUNDS_MS = exports.HISTOGRAM_SCHEMA_ID = exports.BUCKET_SCHEMA_VERSION = void 0;
|
|
4
|
+
exports.createAggregate = createAggregate;
|
|
5
|
+
exports.histogramIndex = histogramIndex;
|
|
6
|
+
exports.recordInto = recordInto;
|
|
7
|
+
exports.mergeInto = mergeInto;
|
|
8
|
+
exports.mergeAggregates = mergeAggregates;
|
|
9
|
+
exports.percentileFromAggregate = percentileFromAggregate;
|
|
10
|
+
exports.countSlowerThan = countSlowerThan;
|
|
11
|
+
exports.deriveMetrics = deriveMetrics;
|
|
12
|
+
exports.emptyRuntime = emptyRuntime;
|
|
13
|
+
exports.mergeRuntimeInto = mergeRuntimeInto;
|
|
14
|
+
exports.deriveRuntime = deriveRuntime;
|
|
15
|
+
exports.deepFreeze = deepFreeze;
|
|
16
|
+
exports.BUCKET_SCHEMA_VERSION = 3;
|
|
17
|
+
exports.HISTOGRAM_SCHEMA_ID = 'lat-v1';
|
|
18
|
+
exports.HISTOGRAM_UPPER_BOUNDS_MS = [
|
|
19
|
+
5, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200,
|
|
20
|
+
250, 300, 350, 400, 450, 500, 550, 600, 700, 800, 900, 1000, 1500, 2000, 3000, 5000,
|
|
21
|
+
10_000, 30_000,
|
|
22
|
+
];
|
|
23
|
+
exports.HISTOGRAM_BUCKET_COUNT = exports.HISTOGRAM_UPPER_BOUNDS_MS.length + 1;
|
|
24
|
+
function createAggregate() {
|
|
25
|
+
return {
|
|
26
|
+
requestCount: 0, clientErrorCount: 0, serverErrorCount: 0,
|
|
27
|
+
durationSumMs: 0, durationMaxMs: null, durationMinMs: null,
|
|
28
|
+
histogramSchemaId: exports.HISTOGRAM_SCHEMA_ID,
|
|
29
|
+
histogramCounts: new Array(exports.HISTOGRAM_BUCKET_COUNT).fill(0),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function histogramIndex(duration) {
|
|
33
|
+
let lo = 0, hi = exports.HISTOGRAM_UPPER_BOUNDS_MS.length;
|
|
34
|
+
while (lo < hi) {
|
|
35
|
+
const mid = (lo + hi) >> 1;
|
|
36
|
+
if (duration <= exports.HISTOGRAM_UPPER_BOUNDS_MS[mid])
|
|
37
|
+
hi = mid;
|
|
38
|
+
else
|
|
39
|
+
lo = mid + 1;
|
|
40
|
+
}
|
|
41
|
+
return lo;
|
|
42
|
+
}
|
|
43
|
+
function recordInto(agg, durationMs, statusCode) {
|
|
44
|
+
if (!Number.isFinite(durationMs))
|
|
45
|
+
return false;
|
|
46
|
+
const duration = Math.max(0, durationMs);
|
|
47
|
+
agg.requestCount++;
|
|
48
|
+
agg.durationSumMs += duration;
|
|
49
|
+
agg.durationMaxMs = agg.durationMaxMs === null ? duration : Math.max(agg.durationMaxMs, duration);
|
|
50
|
+
agg.durationMinMs = agg.durationMinMs === null ? duration : Math.min(agg.durationMinMs, duration);
|
|
51
|
+
if (statusCode >= 400 && statusCode < 500)
|
|
52
|
+
agg.clientErrorCount++;
|
|
53
|
+
else if (statusCode >= 500)
|
|
54
|
+
agg.serverErrorCount++;
|
|
55
|
+
agg.histogramCounts[histogramIndex(duration)]++;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const maxN = (a, b) => a === null ? b : b === null ? a : Math.max(a, b);
|
|
59
|
+
const minN = (a, b) => a === null ? b : b === null ? a : Math.min(a, b);
|
|
60
|
+
function mergeInto(t, s) {
|
|
61
|
+
if (t.histogramSchemaId !== s.histogramSchemaId || t.histogramCounts.length !== s.histogramCounts.length) {
|
|
62
|
+
throw new Error(`Cannot merge aggregates with different histogram schemas: ${t.histogramSchemaId} vs ${s.histogramSchemaId}`);
|
|
63
|
+
}
|
|
64
|
+
t.requestCount += s.requestCount;
|
|
65
|
+
t.clientErrorCount += s.clientErrorCount;
|
|
66
|
+
t.serverErrorCount += s.serverErrorCount;
|
|
67
|
+
t.durationSumMs += s.durationSumMs;
|
|
68
|
+
t.durationMaxMs = maxN(t.durationMaxMs, s.durationMaxMs);
|
|
69
|
+
t.durationMinMs = minN(t.durationMinMs, s.durationMinMs);
|
|
70
|
+
for (let i = 0; i < t.histogramCounts.length; i++)
|
|
71
|
+
t.histogramCounts[i] += s.histogramCounts[i];
|
|
72
|
+
}
|
|
73
|
+
function mergeAggregates(a, b) {
|
|
74
|
+
const out = { ...a, histogramCounts: [...a.histogramCounts] };
|
|
75
|
+
mergeInto(out, b);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function percentileFromAggregate(agg, q) {
|
|
79
|
+
if (agg.requestCount === 0)
|
|
80
|
+
return 0;
|
|
81
|
+
const target = Math.max(1, Math.ceil(q * agg.requestCount));
|
|
82
|
+
let cum = 0;
|
|
83
|
+
for (let i = 0; i < agg.histogramCounts.length; i++) {
|
|
84
|
+
cum += agg.histogramCounts[i];
|
|
85
|
+
if (cum >= target) {
|
|
86
|
+
const upper = i < exports.HISTOGRAM_UPPER_BOUNDS_MS.length ? exports.HISTOGRAM_UPPER_BOUNDS_MS[i] : Number.POSITIVE_INFINITY;
|
|
87
|
+
return Math.min(upper, agg.durationMaxMs ?? upper);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return agg.durationMaxMs ?? 0;
|
|
91
|
+
}
|
|
92
|
+
function countSlowerThan(agg, thresholdMs) {
|
|
93
|
+
let count = 0;
|
|
94
|
+
for (let i = 0; i < agg.histogramCounts.length; i++) {
|
|
95
|
+
const lower = i === 0 ? 0 : exports.HISTOGRAM_UPPER_BOUNDS_MS[i - 1];
|
|
96
|
+
if (lower >= thresholdMs)
|
|
97
|
+
count += agg.histogramCounts[i];
|
|
98
|
+
}
|
|
99
|
+
return count;
|
|
100
|
+
}
|
|
101
|
+
function deriveMetrics(agg, durationMs) {
|
|
102
|
+
const n = agg.requestCount;
|
|
103
|
+
return {
|
|
104
|
+
requestCount: n,
|
|
105
|
+
rps: durationMs > 0 ? n / (durationMs / 1000) : 0,
|
|
106
|
+
averageLatency: n > 0 ? agg.durationSumMs / n : 0,
|
|
107
|
+
minLatency: agg.durationMinMs ?? 0,
|
|
108
|
+
maxLatency: agg.durationMaxMs ?? 0,
|
|
109
|
+
p50: percentileFromAggregate(agg, 0.50),
|
|
110
|
+
p95: percentileFromAggregate(agg, 0.95),
|
|
111
|
+
p99: percentileFromAggregate(agg, 0.99),
|
|
112
|
+
errorRate: n > 0 ? (agg.clientErrorCount + agg.serverErrorCount) / n : 0,
|
|
113
|
+
clientErrorCount: agg.clientErrorCount,
|
|
114
|
+
serverErrorCount: agg.serverErrorCount,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const sumN = (a, b) => a === null ? b : b === null ? a : a + b;
|
|
118
|
+
function emptyRuntime() {
|
|
119
|
+
return {
|
|
120
|
+
cpuTimeUs: null, cpuUserUs: null, cpuSystemUs: null,
|
|
121
|
+
heapUsedBytes: null, heapTotalBytes: null, heapLimitBytes: null, rssBytes: null,
|
|
122
|
+
eventLoopSampleCount: 0, eventLoopDelaySumMs: null, eventLoopDelayMaxMs: null,
|
|
123
|
+
gcCount: null, gcDurationSumMs: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function mergeRuntimeInto(t, s, sIsLater) {
|
|
127
|
+
t.cpuTimeUs = sumN(t.cpuTimeUs, s.cpuTimeUs);
|
|
128
|
+
t.cpuUserUs = sumN(t.cpuUserUs, s.cpuUserUs);
|
|
129
|
+
t.cpuSystemUs = sumN(t.cpuSystemUs, s.cpuSystemUs);
|
|
130
|
+
t.eventLoopSampleCount += s.eventLoopSampleCount;
|
|
131
|
+
t.eventLoopDelaySumMs = sumN(t.eventLoopDelaySumMs, s.eventLoopDelaySumMs);
|
|
132
|
+
t.eventLoopDelayMaxMs = maxN(t.eventLoopDelayMaxMs, s.eventLoopDelayMaxMs);
|
|
133
|
+
t.gcCount = sumN(t.gcCount, s.gcCount);
|
|
134
|
+
t.gcDurationSumMs = sumN(t.gcDurationSumMs, s.gcDurationSumMs);
|
|
135
|
+
if (sIsLater) {
|
|
136
|
+
t.heapUsedBytes = s.heapUsedBytes;
|
|
137
|
+
t.heapTotalBytes = s.heapTotalBytes;
|
|
138
|
+
t.heapLimitBytes = s.heapLimitBytes;
|
|
139
|
+
t.rssBytes = s.rssBytes;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function deriveRuntime(rt, durationMs) {
|
|
143
|
+
const durationUs = durationMs * 1000;
|
|
144
|
+
const pct = (us) => us !== null && durationUs > 0 ? (us / durationUs) * 100 : null;
|
|
145
|
+
return {
|
|
146
|
+
cpuPercent: pct(rt.cpuTimeUs),
|
|
147
|
+
cpuUserPercent: pct(rt.cpuUserUs),
|
|
148
|
+
cpuSystemPercent: pct(rt.cpuSystemUs),
|
|
149
|
+
eventLoopDelayMeanMs: rt.eventLoopSampleCount > 0 && rt.eventLoopDelaySumMs !== null
|
|
150
|
+
? rt.eventLoopDelaySumMs / rt.eventLoopSampleCount : null,
|
|
151
|
+
eventLoopDelayMaxMs: rt.eventLoopDelayMaxMs,
|
|
152
|
+
gcCount: rt.gcCount,
|
|
153
|
+
gcDurationSumMs: rt.gcDurationSumMs,
|
|
154
|
+
gcPauseAverageMs: rt.gcCount && rt.gcDurationSumMs !== null ? rt.gcDurationSumMs / rt.gcCount : null,
|
|
155
|
+
heapUsedBytes: rt.heapUsedBytes,
|
|
156
|
+
heapTotalBytes: rt.heapTotalBytes,
|
|
157
|
+
heapLimitBytes: rt.heapLimitBytes,
|
|
158
|
+
rssBytes: rt.rssBytes,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function deepFreeze(v) {
|
|
162
|
+
if (v !== null && typeof v === 'object' && !Object.isFrozen(v)) {
|
|
163
|
+
Object.freeze(v);
|
|
164
|
+
for (const x of Object.values(v))
|
|
165
|
+
deepFreeze(x);
|
|
166
|
+
}
|
|
167
|
+
return v;
|
|
168
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type MetricBucket, type EndpointAggregate } from './bucket-metric';
|
|
2
|
+
export interface BucketView {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
durationMs: number;
|
|
5
|
+
quality: string[];
|
|
6
|
+
rps: {
|
|
7
|
+
value: number;
|
|
8
|
+
count: number;
|
|
9
|
+
perEndpoints: Map<string, number>;
|
|
10
|
+
};
|
|
11
|
+
latency: {
|
|
12
|
+
average: number;
|
|
13
|
+
p50: number;
|
|
14
|
+
p95: number;
|
|
15
|
+
p99: number;
|
|
16
|
+
max: number;
|
|
17
|
+
min: number;
|
|
18
|
+
};
|
|
19
|
+
error: {
|
|
20
|
+
rate: number;
|
|
21
|
+
clientCount: number;
|
|
22
|
+
serverCount: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function toBucketView(b: MetricBucket): BucketView;
|
|
26
|
+
export interface EndpointView {
|
|
27
|
+
method: string;
|
|
28
|
+
route: string;
|
|
29
|
+
rps: number;
|
|
30
|
+
requestCount: number;
|
|
31
|
+
averageLatency: number;
|
|
32
|
+
minLatency: number;
|
|
33
|
+
p50: number;
|
|
34
|
+
p95: number;
|
|
35
|
+
p99: number;
|
|
36
|
+
errorRate: number;
|
|
37
|
+
impactedRequests: number;
|
|
38
|
+
status: 'HEALTHY' | 'DEGRADED';
|
|
39
|
+
}
|
|
40
|
+
export interface EndpointSeriesInput {
|
|
41
|
+
durationMs: number;
|
|
42
|
+
endpoints?: readonly EndpointAggregate[];
|
|
43
|
+
}
|
|
44
|
+
export declare function deriveEndpointView(buckets: readonly EndpointSeriesInput[], slowLatencyMs: number): EndpointView[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toBucketView = toBucketView;
|
|
4
|
+
exports.deriveEndpointView = deriveEndpointView;
|
|
5
|
+
const bucket_metric_1 = require("./bucket-metric");
|
|
6
|
+
function toBucketView(b) {
|
|
7
|
+
const m = (0, bucket_metric_1.deriveMetrics)(b.requests, b.durationMs);
|
|
8
|
+
return {
|
|
9
|
+
timestamp: Date.parse(b.startTime),
|
|
10
|
+
durationMs: b.durationMs,
|
|
11
|
+
quality: b.qualityFlags,
|
|
12
|
+
rps: {
|
|
13
|
+
value: m.rps,
|
|
14
|
+
count: m.requestCount,
|
|
15
|
+
perEndpoints: new Map(b.endpoints.map(e => [`${e.method}:${e.route}`, e.requestCount])),
|
|
16
|
+
},
|
|
17
|
+
latency: {
|
|
18
|
+
average: m.averageLatency,
|
|
19
|
+
p50: m.p50,
|
|
20
|
+
p95: m.p95,
|
|
21
|
+
p99: m.p99,
|
|
22
|
+
max: b.requests.durationMaxMs ?? 0,
|
|
23
|
+
min: b.requests.durationMinMs ?? 0
|
|
24
|
+
},
|
|
25
|
+
error: {
|
|
26
|
+
rate: m.errorRate,
|
|
27
|
+
clientCount: m.clientErrorCount,
|
|
28
|
+
serverCount: m.serverErrorCount
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function deriveEndpointView(buckets, slowLatencyMs) {
|
|
33
|
+
const merged = new Map();
|
|
34
|
+
for (const b of buckets) {
|
|
35
|
+
for (const e of b.endpoints ?? []) {
|
|
36
|
+
const key = `${e.method}:${e.route}`;
|
|
37
|
+
let m = merged.get(key);
|
|
38
|
+
if (!m) {
|
|
39
|
+
m = { method: e.method, route: e.route, agg: (0, bucket_metric_1.createAggregate)() };
|
|
40
|
+
merged.set(key, m);
|
|
41
|
+
}
|
|
42
|
+
(0, bucket_metric_1.mergeInto)(m.agg, e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const last = buckets[buckets.length - 1];
|
|
46
|
+
return [...merged.entries()].map(([key, m]) => {
|
|
47
|
+
const all = (0, bucket_metric_1.deriveMetrics)(m.agg, 1000);
|
|
48
|
+
const cur = last?.endpoints?.find(e => `${e.method}:${e.route}` === key);
|
|
49
|
+
return {
|
|
50
|
+
method: m.method, route: m.route,
|
|
51
|
+
rps: cur && last ? (0, bucket_metric_1.deriveMetrics)(cur, last.durationMs).rps : 0,
|
|
52
|
+
requestCount: all.requestCount,
|
|
53
|
+
averageLatency: all.averageLatency,
|
|
54
|
+
minLatency: all.minLatency,
|
|
55
|
+
p50: all.p50,
|
|
56
|
+
p95: all.p95,
|
|
57
|
+
p99: all.p99,
|
|
58
|
+
errorRate: all.errorRate,
|
|
59
|
+
impactedRequests: all.serverErrorCount + (0, bucket_metric_1.countSlowerThan)(m.agg, slowLatencyMs),
|
|
60
|
+
status: all.clientErrorCount + all.serverErrorCount > 0 ? 'DEGRADED' : 'HEALTHY',
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -1,40 +1,43 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
latency: {
|
|
12
|
-
average: number;
|
|
13
|
-
p50: number;
|
|
14
|
-
p95: number;
|
|
15
|
-
p99: number;
|
|
16
|
-
};
|
|
17
|
-
error: {
|
|
18
|
-
rate: number;
|
|
19
|
-
clientCount: number;
|
|
20
|
-
serverCount: number;
|
|
21
|
-
};
|
|
22
|
-
endpoints: EndpointTelemetry[];
|
|
23
|
-
health: ReturnType<HealthStore["get"]>;
|
|
1
|
+
import { type MetricBucket, type Origin } from './bucket-metric';
|
|
2
|
+
import type { RuntimeStore } from "./runtime.store";
|
|
3
|
+
export interface BucketStoreOptions {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
instanceId: string;
|
|
6
|
+
intervalMs: number;
|
|
7
|
+
historySize: number;
|
|
8
|
+
maxEndpoints: number;
|
|
9
|
+
originProvider?: () => Origin;
|
|
24
10
|
}
|
|
25
11
|
export declare class BucketStore {
|
|
26
|
-
private readonly
|
|
27
|
-
private readonly
|
|
28
|
-
private readonly metricsStore;
|
|
12
|
+
private readonly runtime;
|
|
13
|
+
private readonly options;
|
|
29
14
|
private readonly history;
|
|
30
|
-
private
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
15
|
+
private readonly intervalMs;
|
|
16
|
+
private readonly maxEndpoints;
|
|
17
|
+
private sequence;
|
|
18
|
+
private lastCommitted;
|
|
19
|
+
private readonly committed;
|
|
20
|
+
private open;
|
|
21
|
+
private sealed;
|
|
22
|
+
private _droppedAfterSeal;
|
|
23
|
+
constructor(runtime: RuntimeStore, options: BucketStoreOptions);
|
|
24
|
+
record(input: {
|
|
25
|
+
method: string;
|
|
26
|
+
route: string;
|
|
27
|
+
duration: number;
|
|
28
|
+
statusCode: number;
|
|
29
|
+
origin?: Origin;
|
|
30
|
+
}): void;
|
|
31
|
+
flush(opts?: {
|
|
32
|
+
force?: boolean;
|
|
33
|
+
seal?: boolean;
|
|
34
|
+
}): MetricBucket | null;
|
|
35
|
+
get instanceId(): string;
|
|
36
|
+
markCommitted(sequence: number): void;
|
|
37
|
+
isCommitted(sequence: number): boolean;
|
|
38
|
+
get lastCommittedSequence(): number;
|
|
39
|
+
get droppedAfterSeal(): number;
|
|
40
|
+
getHistory(): MetricBucket[];
|
|
41
|
+
latest(): MetricBucket | undefined;
|
|
42
|
+
private openInterval;
|
|
40
43
|
}
|
|
@@ -2,74 +2,126 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.BucketStore = void 0;
|
|
4
4
|
const utility_1 = require("../utility");
|
|
5
|
+
const bucket_metric_1 = require("./bucket-metric");
|
|
5
6
|
class BucketStore {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
metricsStore;
|
|
7
|
+
runtime;
|
|
8
|
+
options;
|
|
9
9
|
history;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
10
|
+
intervalMs;
|
|
11
|
+
maxEndpoints;
|
|
12
|
+
sequence = 0;
|
|
13
|
+
lastCommitted = 0;
|
|
14
|
+
committed = new Set();
|
|
15
|
+
open;
|
|
16
|
+
sealed = false;
|
|
17
|
+
_droppedAfterSeal = 0;
|
|
18
|
+
constructor(runtime, options) {
|
|
19
|
+
this.runtime = runtime;
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.intervalMs = options.intervalMs;
|
|
22
|
+
this.maxEndpoints = options.maxEndpoints;
|
|
23
|
+
this.history = new utility_1.RingBuffer(options.historySize);
|
|
24
|
+
this.open = this.openInterval(Date.now(), process.hrtime.bigint());
|
|
21
25
|
}
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
const count = this.endpointRequests.get(key) ?? 0;
|
|
26
|
-
this.endpointRequests.set(key, count + 1);
|
|
27
|
-
}
|
|
28
|
-
flush() {
|
|
29
|
-
const now = this.getSecond();
|
|
30
|
-
// same second
|
|
31
|
-
if (now === this.currentSecond) {
|
|
26
|
+
record(input) {
|
|
27
|
+
if (this.sealed) {
|
|
28
|
+
this._droppedAfterSeal++;
|
|
32
29
|
return;
|
|
33
30
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.currentSecond = now;
|
|
60
|
-
}
|
|
61
|
-
getHistory() {
|
|
62
|
-
return this.history.values();
|
|
31
|
+
const cur = this.open;
|
|
32
|
+
if (!(0, bucket_metric_1.recordInto)(cur.requests, input.duration, input.statusCode))
|
|
33
|
+
return;
|
|
34
|
+
const origin = input.origin ?? this.options.originProvider?.() ?? 'real';
|
|
35
|
+
if (origin === 'synthetic')
|
|
36
|
+
cur.syntheticSeen = true;
|
|
37
|
+
else
|
|
38
|
+
cur.realSeen = true;
|
|
39
|
+
let key = `${input.method}:${input.route}`;
|
|
40
|
+
let entry = cur.endpoints.get(key);
|
|
41
|
+
if (!entry && cur.endpoints.size >= this.maxEndpoints) {
|
|
42
|
+
cur.flags.add('ENDPOINT_OVERFLOW');
|
|
43
|
+
key = 'OTHER:__overflow__';
|
|
44
|
+
entry = cur.endpoints.get(key);
|
|
45
|
+
}
|
|
46
|
+
if (!entry) {
|
|
47
|
+
const overflow = key === 'OTHER:__overflow__';
|
|
48
|
+
entry = {
|
|
49
|
+
method: overflow ? 'OTHER' : input.method,
|
|
50
|
+
route: overflow ? '__overflow__' : input.route,
|
|
51
|
+
aggregate: (0, bucket_metric_1.createAggregate)()
|
|
52
|
+
};
|
|
53
|
+
cur.endpoints.set(key, entry);
|
|
54
|
+
}
|
|
55
|
+
(0, bucket_metric_1.recordInto)(entry.aggregate, input.duration, input.statusCode);
|
|
63
56
|
}
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
flush(opts = {}) {
|
|
58
|
+
if (this.sealed)
|
|
59
|
+
return null;
|
|
60
|
+
if (opts.seal)
|
|
61
|
+
this.sealed = true;
|
|
62
|
+
const nowHr = process.hrtime.bigint();
|
|
63
|
+
const cur = this.open;
|
|
64
|
+
const elapsedMs = Number(nowHr - cur.startHr) / 1e6;
|
|
65
|
+
if (!opts.force && elapsedMs < this.intervalMs)
|
|
66
|
+
return null;
|
|
67
|
+
if (opts.force && elapsedMs < 1 && cur.requests.requestCount === 0)
|
|
68
|
+
return null;
|
|
69
|
+
const endWall = Date.now();
|
|
70
|
+
const flags = new Set(cur.flags);
|
|
71
|
+
if (elapsedMs > this.intervalMs * 1.5)
|
|
72
|
+
flags.add('LATE_CLOSE');
|
|
73
|
+
if (opts.force && elapsedMs < this.intervalMs)
|
|
74
|
+
flags.add('PARTIAL');
|
|
75
|
+
if (cur.syntheticSeen && cur.realSeen)
|
|
76
|
+
flags.add('MIXED_ORIGIN');
|
|
77
|
+
if (Math.abs((endWall - cur.startWall) - elapsedMs) > 500)
|
|
78
|
+
flags.add('CLOCK_SKEW');
|
|
79
|
+
const sequence = ++this.sequence;
|
|
80
|
+
const origin = cur.syntheticSeen ? 'synthetic' : cur.realSeen ? 'real' : (this.options.originProvider?.() ?? 'real');
|
|
81
|
+
const runtime = this.runtime.closeInterval();
|
|
82
|
+
const bucket = (0, bucket_metric_1.deepFreeze)({
|
|
83
|
+
schemaVersion: bucket_metric_1.BUCKET_SCHEMA_VERSION,
|
|
84
|
+
bucketId: `${this.options.sessionId}:${sequence}`,
|
|
85
|
+
sessionId: this.options.sessionId,
|
|
86
|
+
instanceId: this.options.instanceId,
|
|
87
|
+
sequence,
|
|
88
|
+
startTime: new Date(cur.startWall).toISOString(),
|
|
89
|
+
endTime: new Date(endWall).toISOString(),
|
|
90
|
+
durationMs: Math.round(elapsedMs * 1000) / 1000,
|
|
91
|
+
origin,
|
|
92
|
+
requests: cur.requests,
|
|
93
|
+
runtime,
|
|
94
|
+
endpoints: [...cur.endpoints.values()].map(e => ({ method: e.method, route: e.route, ...e.aggregate })),
|
|
95
|
+
qualityFlags: [...flags],
|
|
96
|
+
});
|
|
97
|
+
this.history.push(bucket);
|
|
98
|
+
this.open = this.openInterval(endWall, nowHr);
|
|
99
|
+
return bucket;
|
|
66
100
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.
|
|
101
|
+
get instanceId() { return this.options.instanceId; }
|
|
102
|
+
markCommitted(sequence) {
|
|
103
|
+
this.committed.add(sequence);
|
|
104
|
+
if (sequence > this.lastCommitted)
|
|
105
|
+
this.lastCommitted = sequence;
|
|
106
|
+
const oldest = this.history.values()[0]?.sequence ?? sequence;
|
|
107
|
+
for (const s of this.committed)
|
|
108
|
+
if (s < oldest)
|
|
109
|
+
this.committed.delete(s);
|
|
70
110
|
}
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
isCommitted(sequence) { return this.committed.has(sequence); }
|
|
112
|
+
get lastCommittedSequence() { return this.lastCommitted; }
|
|
113
|
+
get droppedAfterSeal() { return this._droppedAfterSeal; }
|
|
114
|
+
getHistory() { return this.history.values(); }
|
|
115
|
+
latest() { return this.history.latest(); }
|
|
116
|
+
openInterval(startWall, startHr) {
|
|
117
|
+
return {
|
|
118
|
+
startHr, startWall,
|
|
119
|
+
requests: (0, bucket_metric_1.createAggregate)(),
|
|
120
|
+
endpoints: new Map(),
|
|
121
|
+
flags: new Set(),
|
|
122
|
+
syntheticSeen: false,
|
|
123
|
+
realSeen: false
|
|
124
|
+
};
|
|
73
125
|
}
|
|
74
126
|
}
|
|
75
127
|
exports.BucketStore = BucketStore;
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import type { DashboardData } from '../../domain';
|
|
1
|
+
import type { DashboardData, DashboardTickData } from '../../domain';
|
|
2
2
|
import type { BucketStore } from './bucket.store';
|
|
3
3
|
import type { HealthStore } from './health.store';
|
|
4
|
-
import type { EndpointStore } from './endpoint.store';
|
|
5
4
|
import type { AlertStore } from './alert.store';
|
|
6
5
|
declare class DashboardStore {
|
|
7
6
|
private readonly buckets;
|
|
8
7
|
private readonly health;
|
|
9
|
-
private readonly endpoints;
|
|
10
8
|
private readonly alerts;
|
|
11
|
-
constructor(buckets: BucketStore, health: HealthStore,
|
|
9
|
+
constructor(buckets: BucketStore, health: HealthStore, alerts: AlertStore);
|
|
10
|
+
private metrics;
|
|
11
|
+
private buildCurrent;
|
|
12
|
+
private buildHistory;
|
|
13
|
+
private buildHistoryTick;
|
|
14
|
+
private buildCharts;
|
|
15
|
+
private buildChartsTick;
|
|
12
16
|
get(): DashboardData;
|
|
17
|
+
getTickData(): DashboardTickData;
|
|
18
|
+
private fillRest;
|
|
19
|
+
private getTotalRequestCount;
|
|
13
20
|
private getImpactEndpoints;
|
|
14
21
|
}
|
|
15
22
|
export { DashboardStore };
|