apm-optima 1.0.5 → 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 -19
- 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/23soo6qkun-yv.js +0 -1
- package/dashboard-out/_next/static/chunks/3xoopocxi2vi8.css +0 -3
- package/dashboard-out/_next/static/chunks/3z-nzyp9z8vih.js +0 -3
- /package/dashboard-out/_next/static/{kFIRZt4fuL4Zsh38-jGui → 17egwqgk36_DSRF55LJZG}/_buildManifest.js +0 -0
- /package/dashboard-out/_next/static/{kFIRZt4fuL4Zsh38-jGui → 17egwqgk36_DSRF55LJZG}/_clientMiddlewareManifest.js +0 -0
- /package/dashboard-out/_next/static/{kFIRZt4fuL4Zsh38-jGui → 17egwqgk36_DSRF55LJZG}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PersistenceRepository = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
const config_1 = require("../../config");
|
|
11
|
+
const utility_1 = require("./utility");
|
|
12
|
+
const persistence_writer_1 = require("./persistence/persistence-writer");
|
|
13
|
+
const session_registry_1 = require("./persistence/session-registry");
|
|
14
|
+
const organizers_1 = require("../organizers");
|
|
15
|
+
const logger_1 = __importDefault(require("../telemetry/logger"));
|
|
16
|
+
const bucket_metric_1 = require("./stores/bucket-metric");
|
|
17
|
+
const segment_index_1 = require("./persistence/segment-index");
|
|
18
|
+
const session_summary_1 = require("./persistence/session-summary");
|
|
19
|
+
class PersistenceRepository {
|
|
20
|
+
conf;
|
|
21
|
+
httpBuffer = [];
|
|
22
|
+
enabled;
|
|
23
|
+
maxBufferSize;
|
|
24
|
+
baseDir;
|
|
25
|
+
persistRawRequests;
|
|
26
|
+
heartbeatIntervalMs;
|
|
27
|
+
writer;
|
|
28
|
+
registry;
|
|
29
|
+
ready;
|
|
30
|
+
heartbeatTimer = null;
|
|
31
|
+
shutdownPromise = null;
|
|
32
|
+
closing = false;
|
|
33
|
+
inFlight = new Set();
|
|
34
|
+
sessionId = (0, crypto_1.randomUUID)();
|
|
35
|
+
segments;
|
|
36
|
+
queries = null;
|
|
37
|
+
constructor(conf) {
|
|
38
|
+
this.conf = conf;
|
|
39
|
+
const settings = conf.persistence === false ? config_1.DEFAULT_CONFIG.persistence : conf.persistence;
|
|
40
|
+
this.enabled = conf.persistence !== false;
|
|
41
|
+
this.baseDir = settings.baseDir;
|
|
42
|
+
this.maxBufferSize = settings.maxBufferSize;
|
|
43
|
+
this.persistRawRequests = settings.persistRawRequests;
|
|
44
|
+
this.heartbeatIntervalMs = settings.heartbeatIntervalMs;
|
|
45
|
+
this.writer = new persistence_writer_1.PersistenceWriter({
|
|
46
|
+
maxQueue: settings.writer.maxQueue,
|
|
47
|
+
maxRetries: settings.writer.maxRetries,
|
|
48
|
+
retryDelayMs: settings.writer.retryDelayMs,
|
|
49
|
+
onStatusChange: (status) => logger_1.default.error(`PersistenceLayer: storage status is now ${status}`)
|
|
50
|
+
});
|
|
51
|
+
this.registry = new session_registry_1.SessionRegistry(path_1.default.join(this.baseDir, 'session-manifest.json'), this.writer);
|
|
52
|
+
this.segments = new segment_index_1.SegmentIndex(this.baseDir);
|
|
53
|
+
this.ready = this.initSession().catch(err => {
|
|
54
|
+
logger_1.default.error('Failed to initialize session:', err);
|
|
55
|
+
return null;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
attachQueryService(q) { this.queries = q; }
|
|
59
|
+
get isEnabled() { return this.enabled; }
|
|
60
|
+
get bucketSource() {
|
|
61
|
+
return { read: (s, f, t, onIssue) => this.readBuckets(s, f, t, onIssue) };
|
|
62
|
+
}
|
|
63
|
+
//#region Initialization
|
|
64
|
+
async initSession() {
|
|
65
|
+
if (!this.enabled)
|
|
66
|
+
return null;
|
|
67
|
+
const removed = await (0, utility_1.cleanupOrphanedTempFiles)(this.baseDir);
|
|
68
|
+
if (removed > 0) {
|
|
69
|
+
logger_1.default.debug(`PersistenceLayer: Removed ${removed} orphaned temporary files during initialization.`);
|
|
70
|
+
}
|
|
71
|
+
await this.segments.rebuild();
|
|
72
|
+
const session = await this.registry.start(this.sessionId);
|
|
73
|
+
logger_1.default.debug(`Session initialized: #${session.sessionNumber} (${session.sessionId}), recoveredFromCrash=${this.registry.recoveredFromCrash}`);
|
|
74
|
+
if (this.registry.recoveredFromCrash) {
|
|
75
|
+
void organizers_1.ApplicationEventManager.instance?.emit({
|
|
76
|
+
type: 'CRASH',
|
|
77
|
+
reason: 'Application recovered from an unexpected shutdown',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
this.heartbeatTimer = setInterval(() => { void this.registry.heartbeat(); }, this.heartbeatIntervalMs);
|
|
81
|
+
this.heartbeatTimer.unref();
|
|
82
|
+
return session;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region Writing Metrics
|
|
86
|
+
track(op) {
|
|
87
|
+
this.inFlight.add(op);
|
|
88
|
+
void op.finally(() => this.inFlight.delete(op)).catch(() => { });
|
|
89
|
+
return op;
|
|
90
|
+
}
|
|
91
|
+
async drainInFlight() {
|
|
92
|
+
while (this.inFlight.size > 0) {
|
|
93
|
+
await Promise.allSettled([...this.inFlight]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async enqueue(category, items) {
|
|
97
|
+
try {
|
|
98
|
+
await this.writer.enqueueAppend(this.baseDir, category, items);
|
|
99
|
+
this.registry.notePersisted();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// do nothing
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
onHttpRequest(metric) {
|
|
106
|
+
if (!this.enabled || !this.persistRawRequests || this.closing)
|
|
107
|
+
return;
|
|
108
|
+
if (metric.statusCode < 400)
|
|
109
|
+
return; // Only persisting errors for now
|
|
110
|
+
this.httpBuffer.push({ recordId: (0, crypto_1.randomUUID)(), payload: metric });
|
|
111
|
+
if (this.httpBuffer.length >= this.maxBufferSize) {
|
|
112
|
+
void this.flushHttpBuffer();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async flushHttpBuffer() {
|
|
116
|
+
if (!this.enabled || this.httpBuffer.length === 0)
|
|
117
|
+
return;
|
|
118
|
+
const batch = this.httpBuffer;
|
|
119
|
+
this.httpBuffer = [];
|
|
120
|
+
await this.track(this.enqueue('http_requests', batch));
|
|
121
|
+
}
|
|
122
|
+
onBucketClosed(bucket) {
|
|
123
|
+
if (!this.enabled)
|
|
124
|
+
return Promise.resolve(false);
|
|
125
|
+
return this.track((async () => {
|
|
126
|
+
const session = await this.ready;
|
|
127
|
+
if (!session)
|
|
128
|
+
return false;
|
|
129
|
+
try {
|
|
130
|
+
await this.writer.enqueueAppend(this.baseDir, 'metric_buckets', [{ recordId: bucket.bucketId, payload: bucket }], { date: new Date(bucket.endTime) });
|
|
131
|
+
this.segments.noteWritten(bucket);
|
|
132
|
+
this.registry.notePersisted(bucket.sequence);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
})());
|
|
139
|
+
}
|
|
140
|
+
saveCorrelationFinding(finding) {
|
|
141
|
+
if (!this.enabled)
|
|
142
|
+
return Promise.resolve(false);
|
|
143
|
+
return this.track((async () => {
|
|
144
|
+
const session = await this.ready;
|
|
145
|
+
if (!session)
|
|
146
|
+
return false;
|
|
147
|
+
try {
|
|
148
|
+
await this.writer.enqueueAppend(this.baseDir, 'correlation', [{ recordId: finding.findingId, payload: finding }]);
|
|
149
|
+
this.registry.notePersisted();
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
})());
|
|
156
|
+
}
|
|
157
|
+
onApplicationEvent(event) {
|
|
158
|
+
if (!this.enabled)
|
|
159
|
+
return Promise.resolve();
|
|
160
|
+
const record = { recordId: (0, crypto_1.randomUUID)(), payload: { ...event } };
|
|
161
|
+
return this.track((async () => {
|
|
162
|
+
const session = await this.ready;
|
|
163
|
+
if (!session)
|
|
164
|
+
return;
|
|
165
|
+
await this.enqueue('events', [record]);
|
|
166
|
+
})());
|
|
167
|
+
}
|
|
168
|
+
getStorageStatus() {
|
|
169
|
+
return {
|
|
170
|
+
status: this.writer.status,
|
|
171
|
+
lostRecords: this.writer.lostRecords,
|
|
172
|
+
pending: this.writer.pending + this.inFlight.size,
|
|
173
|
+
lastError: this.writer.lastError
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region Reading Metrics
|
|
178
|
+
async getSessionManifest() {
|
|
179
|
+
if (!this.enabled)
|
|
180
|
+
return { version: 2, sessions: [] };
|
|
181
|
+
await this.ready;
|
|
182
|
+
return { version: 2, sessions: [...this.registry.all] };
|
|
183
|
+
}
|
|
184
|
+
getSessionRecord() {
|
|
185
|
+
if (!this.enabled) {
|
|
186
|
+
return {
|
|
187
|
+
sessionId: 'disabled',
|
|
188
|
+
sessionNumber: 0,
|
|
189
|
+
startedAt: new Date().toISOString(),
|
|
190
|
+
endedAt: null,
|
|
191
|
+
lastPersistedAt: null,
|
|
192
|
+
lastCommittedSequence: 0,
|
|
193
|
+
status: 'RUNNING'
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return this.registry.current;
|
|
197
|
+
}
|
|
198
|
+
async findSession(sessionNumber) {
|
|
199
|
+
await this.ready;
|
|
200
|
+
return this.registry.all.find(s => s.sessionNumber === sessionNumber) ?? null;
|
|
201
|
+
}
|
|
202
|
+
async resolveExistingFile(category, date) {
|
|
203
|
+
const dir = path_1.default.join(this.baseDir, category);
|
|
204
|
+
const plain = path_1.default.join(dir, `${date}.ndjson`);
|
|
205
|
+
const gz = `${plain}.gz`;
|
|
206
|
+
if (await promises_1.default.access(plain).then(() => true).catch(() => false))
|
|
207
|
+
return plain;
|
|
208
|
+
if (await promises_1.default.access(gz).then(() => true).catch(() => false))
|
|
209
|
+
return gz;
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
requireQueries() {
|
|
213
|
+
if (!this.queries)
|
|
214
|
+
throw new Error('PersistenceRepository: query service not attached');
|
|
215
|
+
return this.queries;
|
|
216
|
+
}
|
|
217
|
+
sessionQuery(session) {
|
|
218
|
+
const window = session_registry_1.SessionRegistry.window(session);
|
|
219
|
+
return {
|
|
220
|
+
sessionId: session.sessionId,
|
|
221
|
+
from: window.start.toISOString(),
|
|
222
|
+
to: window.end.toISOString(),
|
|
223
|
+
consistency: session.status === 'RUNNING' ? 'live' : 'committed',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async getSessionSummary(sessionNumber) {
|
|
227
|
+
const session = await this.findSession(sessionNumber);
|
|
228
|
+
if (!session)
|
|
229
|
+
return null;
|
|
230
|
+
const incidents = await this.sessionIncidents(session);
|
|
231
|
+
const dashboard = this.conf.dashboard === false ? config_1.DEFAULT_CONFIG.dashboard : this.conf.dashboard;
|
|
232
|
+
return (0, session_summary_1.buildSessionSummary)(this.requireQueries(), session, dashboard.sessionSummaryWindowHours, this.conf.thresholds.slowLatencyMs, incidents);
|
|
233
|
+
}
|
|
234
|
+
async sessionIncidents(session) {
|
|
235
|
+
if (!this.enabled)
|
|
236
|
+
return [];
|
|
237
|
+
const byId = new Map();
|
|
238
|
+
const events = this.sessionRecords('events', session, e => (e.type === 'INCIDENT_OPENED' || e.type === 'INCIDENT_RESOLVED')
|
|
239
|
+
&& e.details?.sessionId === session.sessionId);
|
|
240
|
+
for await (const event of events) {
|
|
241
|
+
const incident = event.details;
|
|
242
|
+
const prev = byId.get(incident.incidentId);
|
|
243
|
+
if (!prev || prev.status !== 'RESOLVED')
|
|
244
|
+
byId.set(incident.incidentId, incident);
|
|
245
|
+
}
|
|
246
|
+
return [...byId.values()].sort((a, b) => b.firedAt.localeCompare(a.firedAt));
|
|
247
|
+
}
|
|
248
|
+
async findCorrelationFinding(findingId) {
|
|
249
|
+
if (!this.enabled)
|
|
250
|
+
return null;
|
|
251
|
+
await this.ready;
|
|
252
|
+
const sessionId = findingId.split(':')[0];
|
|
253
|
+
const session = this.registry.all.find(s => s.sessionId === sessionId);
|
|
254
|
+
if (!session)
|
|
255
|
+
return null;
|
|
256
|
+
for (const date of session_registry_1.SessionRegistry.enumerateDates(session_registry_1.SessionRegistry.window(session))) {
|
|
257
|
+
const file = await this.resolveExistingFile('correlation', date);
|
|
258
|
+
if (!file)
|
|
259
|
+
continue;
|
|
260
|
+
for await (const rec of (0, persistence_writer_1.dedupeRecords)((0, utility_1.readRecords)(file))) {
|
|
261
|
+
if (rec.id === findingId)
|
|
262
|
+
return rec.payload;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
async *readBuckets(sessionId, fromMs, toMs, onIssue) {
|
|
268
|
+
for (const date of this.segments.find(sessionId, fromMs, toMs)) {
|
|
269
|
+
const file = await this.resolveExistingFile('metric_buckets', date);
|
|
270
|
+
if (!file) {
|
|
271
|
+
onIssue({ type: 'SOURCE_ERROR', detail: `Missing segment ${date}` });
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const records = (0, persistence_writer_1.dedupeRecords)((0, utility_1.readRecords)(file), id => onIssue({ type: 'CONFLICT', bucketId: id }));
|
|
275
|
+
for await (const rec of records) {
|
|
276
|
+
const b = rec.payload;
|
|
277
|
+
if (b?.schemaVersion !== bucket_metric_1.BUCKET_SCHEMA_VERSION || b.sessionId !== sessionId)
|
|
278
|
+
continue;
|
|
279
|
+
if (Date.parse(b.startTime) > toMs || Date.parse(b.endTime) < fromMs)
|
|
280
|
+
continue;
|
|
281
|
+
yield b;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region Shutdown
|
|
287
|
+
async shutdown() {
|
|
288
|
+
if (!this.enabled)
|
|
289
|
+
return;
|
|
290
|
+
if (this.shutdownPromise)
|
|
291
|
+
return this.shutdownPromise;
|
|
292
|
+
this.shutdownPromise = (async () => {
|
|
293
|
+
logger_1.default.debug('PersistenceLayer: Shutdown initiated.');
|
|
294
|
+
this.closing = true;
|
|
295
|
+
if (this.heartbeatTimer)
|
|
296
|
+
clearInterval(this.heartbeatTimer);
|
|
297
|
+
try {
|
|
298
|
+
await this.ready;
|
|
299
|
+
await this.flushHttpBuffer();
|
|
300
|
+
await this.drainInFlight();
|
|
301
|
+
await this.writer.whenIdle();
|
|
302
|
+
await this.segments.persistClosed();
|
|
303
|
+
await this.registry.complete();
|
|
304
|
+
await this.writer.close();
|
|
305
|
+
const removed = await (0, utility_1.cleanupOrphanedTempFiles)(this.baseDir);
|
|
306
|
+
if (removed > 0) {
|
|
307
|
+
logger_1.default.debug(`PersistenceLayer: Removed ${removed} orphaned temporary files during shutdown.`);
|
|
308
|
+
}
|
|
309
|
+
logger_1.default.debug(`PersistenceLayer: Shutdown complete (lostRecords=${this.writer.lostRecords}).`);
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
logger_1.default.error("Error during persistence shutdown:", err);
|
|
313
|
+
}
|
|
314
|
+
})();
|
|
315
|
+
return this.shutdownPromise;
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region Archiving
|
|
319
|
+
async archiveOldCategory(category) {
|
|
320
|
+
const dir = path_1.default.join(this.baseDir, category);
|
|
321
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
322
|
+
const files = await promises_1.default.readdir(dir).catch(() => []);
|
|
323
|
+
for (const file of files) {
|
|
324
|
+
if (!file.endsWith('.ndjson'))
|
|
325
|
+
continue;
|
|
326
|
+
if (file.startsWith(today))
|
|
327
|
+
continue;
|
|
328
|
+
const fullPath = path_1.default.join(dir, file);
|
|
329
|
+
await (0, utility_1.writeChecksum)(fullPath);
|
|
330
|
+
await (0, utility_1.archiveFile)(fullPath);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
archiveAllCategories() {
|
|
334
|
+
const categories = ['http_requests', 'events', 'correlation', 'metric_buckets'];
|
|
335
|
+
for (const category of categories) {
|
|
336
|
+
this.archiveOldCategory(category).catch(err => {
|
|
337
|
+
logger_1.default.error(`Error archiving old ${category} files`, err);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
void this.segments.persistClosed();
|
|
341
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region Session Export
|
|
344
|
+
async writeChunk(out, chunk) {
|
|
345
|
+
if (!out.write(chunk))
|
|
346
|
+
await new Promise(resolve => out.once('drain', resolve));
|
|
347
|
+
}
|
|
348
|
+
async streamJsonArray(out, name, items, last = false) {
|
|
349
|
+
await this.writeChunk(out, `${JSON.stringify(name)}:[`);
|
|
350
|
+
let first = true;
|
|
351
|
+
for await (const item of items) {
|
|
352
|
+
await this.writeChunk(out, (first ? '' : ',') + JSON.stringify(item));
|
|
353
|
+
first = false;
|
|
354
|
+
}
|
|
355
|
+
await this.writeChunk(out, last ? ']' : '],');
|
|
356
|
+
}
|
|
357
|
+
async *sessionRecords(category, session, belongs) {
|
|
358
|
+
const window = session_registry_1.SessionRegistry.window(session);
|
|
359
|
+
for (const date of session_registry_1.SessionRegistry.enumerateDates(window)) {
|
|
360
|
+
const file = await this.resolveExistingFile(category, date);
|
|
361
|
+
if (!file)
|
|
362
|
+
continue;
|
|
363
|
+
for await (const rec of (0, persistence_writer_1.dedupeRecords)((0, utility_1.readRecords)(file))) {
|
|
364
|
+
if (belongs(rec.payload))
|
|
365
|
+
yield rec.payload;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async streamSessionExport(out, sessionNumber) {
|
|
370
|
+
const session = await this.findSession(sessionNumber);
|
|
371
|
+
if (!session)
|
|
372
|
+
return false;
|
|
373
|
+
const queries = this.requireQueries();
|
|
374
|
+
const window = session_registry_1.SessionRegistry.window(session);
|
|
375
|
+
const inWindow = (iso) => {
|
|
376
|
+
const t = iso ? Date.parse(iso) : NaN;
|
|
377
|
+
return t >= window.start.getTime() && t <= window.end.getTime();
|
|
378
|
+
};
|
|
379
|
+
const incidents = await this.sessionIncidents(session);
|
|
380
|
+
const summary = await (0, session_summary_1.buildSessionSummary)(queries, session, Number.POSITIVE_INFINITY, this.conf.thresholds.slowLatencyMs, incidents);
|
|
381
|
+
const issues = [];
|
|
382
|
+
const query = this.sessionQuery(session);
|
|
383
|
+
const buckets = (async function* () {
|
|
384
|
+
for await (const e of queries.buckets(query, i => issues.push(i)))
|
|
385
|
+
yield e.bucket;
|
|
386
|
+
})();
|
|
387
|
+
await this.writeChunk(out, '{');
|
|
388
|
+
await this.writeChunk(out, `"sessionId":${JSON.stringify(session.sessionId)},`);
|
|
389
|
+
await this.writeChunk(out, `"sessionNumber":${JSON.stringify(session.sessionNumber)},`);
|
|
390
|
+
await this.writeChunk(out, `"status":${JSON.stringify(session.status)},`);
|
|
391
|
+
await this.writeChunk(out, `"startedAt":${JSON.stringify(session.startedAt)},`);
|
|
392
|
+
await this.writeChunk(out, `"endedAt":${JSON.stringify(session.endedAt)},`);
|
|
393
|
+
await this.writeChunk(out, `"lastCommittedSequence":${JSON.stringify(session.lastCommittedSequence)},`);
|
|
394
|
+
await this.writeChunk(out, `"summary":${JSON.stringify(summary)},`);
|
|
395
|
+
await this.streamJsonArray(out, 'metric_buckets', buckets);
|
|
396
|
+
await this.writeChunk(out, `"bucketIssues":${JSON.stringify(issues)},`);
|
|
397
|
+
await this.streamJsonArray(out, 'events', this.sessionRecords('events', session, e => inWindow(e.timestamp)));
|
|
398
|
+
await this.streamJsonArray(out, 'http_requests', this.sessionRecords('http_requests', session, r => inWindow(r.timestamp)));
|
|
399
|
+
await this.streamJsonArray(out, 'correlation', this.sessionRecords('correlation', session, f => f.sessionId === session.sessionId), true);
|
|
400
|
+
await this.writeChunk(out, '}');
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
exports.PersistenceRepository = PersistenceRepository;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class AnomalyDetector {
|
|
2
|
+
private readonly minSamples;
|
|
3
|
+
private readonly lowerLimitMs;
|
|
4
|
+
private readonly zThreshold;
|
|
5
|
+
private readonly windowCap;
|
|
6
|
+
private n;
|
|
7
|
+
private mean;
|
|
8
|
+
private m2;
|
|
9
|
+
constructor(minSamples?: number, lowerLimitMs?: number, zThreshold?: number, windowCap?: number);
|
|
10
|
+
record(duration: number): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnomalyDetector = void 0;
|
|
4
|
+
class AnomalyDetector {
|
|
5
|
+
minSamples;
|
|
6
|
+
lowerLimitMs;
|
|
7
|
+
zThreshold;
|
|
8
|
+
windowCap;
|
|
9
|
+
n = 0;
|
|
10
|
+
mean = 0;
|
|
11
|
+
m2 = 0;
|
|
12
|
+
constructor(minSamples = 60, lowerLimitMs = 180, zThreshold = 3, windowCap = 5000) {
|
|
13
|
+
this.minSamples = minSamples;
|
|
14
|
+
this.lowerLimitMs = lowerLimitMs;
|
|
15
|
+
this.zThreshold = zThreshold;
|
|
16
|
+
this.windowCap = windowCap;
|
|
17
|
+
}
|
|
18
|
+
record(duration) {
|
|
19
|
+
if (!Number.isFinite(duration))
|
|
20
|
+
return false;
|
|
21
|
+
let anomalyDetected = false;
|
|
22
|
+
if (this.n >= this.minSamples && duration >= this.lowerLimitMs) {
|
|
23
|
+
const std = Math.sqrt(this.m2 / (this.n - 1));
|
|
24
|
+
anomalyDetected = std > 0 ? (duration - this.mean) / std > this.zThreshold : duration > this.mean;
|
|
25
|
+
}
|
|
26
|
+
if (this.n < this.windowCap) {
|
|
27
|
+
this.n++;
|
|
28
|
+
const delta = duration - this.mean;
|
|
29
|
+
this.mean += delta / this.n;
|
|
30
|
+
this.m2 += delta * (duration - this.mean);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const alpha = 1 / this.windowCap;
|
|
34
|
+
const delta = duration - this.mean;
|
|
35
|
+
const variance = (this.m2 / (this.n - 1) + alpha * delta * delta) * (1 - alpha);
|
|
36
|
+
this.mean += alpha * delta;
|
|
37
|
+
this.m2 = variance * (this.n - 1);
|
|
38
|
+
}
|
|
39
|
+
return anomalyDetected;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.AnomalyDetector = AnomalyDetector;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { AnalyticsData } from "../../domain";
|
|
2
|
-
import { EndpointStore } from "./endpoint.store";
|
|
1
|
+
import type { AnalyticsData, AnalyticsFilterSettings } from "../../domain";
|
|
3
2
|
import type { BucketStore } from "./bucket.store";
|
|
4
3
|
declare class AnalyticsStore {
|
|
5
4
|
private readonly buckets;
|
|
6
|
-
private readonly endpoints;
|
|
7
5
|
private readonly slowLatencyThreshold;
|
|
8
|
-
constructor(buckets: BucketStore,
|
|
9
|
-
|
|
6
|
+
constructor(buckets: BucketStore, slowLatencyThreshold?: number);
|
|
7
|
+
private matchesFilters;
|
|
8
|
+
private toTelemetry;
|
|
9
|
+
get(page?: number, pageSize?: number, filters?: AnalyticsFilterSettings | undefined): AnalyticsData;
|
|
10
10
|
private calculateSummary;
|
|
11
11
|
private latencyDistribution;
|
|
12
12
|
private requestVolume;
|
|
@@ -2,18 +2,45 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AnalyticsStore = void 0;
|
|
4
4
|
const utility_1 = require("../../utility");
|
|
5
|
+
const bucket_view_1 = require("./bucket-view");
|
|
6
|
+
const bucket_metric_1 = require("./bucket-metric");
|
|
5
7
|
class AnalyticsStore {
|
|
6
8
|
buckets;
|
|
7
|
-
endpoints;
|
|
8
9
|
slowLatencyThreshold;
|
|
9
|
-
constructor(buckets,
|
|
10
|
+
constructor(buckets, slowLatencyThreshold = 500) {
|
|
10
11
|
this.buckets = buckets;
|
|
11
|
-
this.endpoints = endpoints;
|
|
12
12
|
this.slowLatencyThreshold = slowLatencyThreshold;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
matchesFilters(endpoint, filters) {
|
|
15
|
+
if (!filters)
|
|
16
|
+
return true;
|
|
17
|
+
if (filters.method !== 'ALL' && endpoint.method !== filters.method)
|
|
18
|
+
return false;
|
|
19
|
+
if (filters.query && !endpoint.route.includes(filters.query))
|
|
20
|
+
return false;
|
|
21
|
+
//! REMOVE STATUS FILTERING FOR NOW
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
toTelemetry(endpoint, durationMs) {
|
|
25
|
+
const metrics = (0, bucket_metric_1.deriveMetrics)(endpoint, durationMs);
|
|
26
|
+
return {
|
|
27
|
+
method: endpoint.method,
|
|
28
|
+
route: endpoint.route,
|
|
29
|
+
rps: metrics.rps,
|
|
30
|
+
requestCount: metrics.requestCount,
|
|
31
|
+
averageLatency: metrics.averageLatency,
|
|
32
|
+
minLatency: metrics.minLatency,
|
|
33
|
+
p95: metrics.p95,
|
|
34
|
+
p99: metrics.p99,
|
|
35
|
+
errorRate: metrics.errorRate,
|
|
36
|
+
status: metrics.errorRate > 0 ? 'DEGRADED' : 'HEALTHY',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
get(page = 1, pageSize = 20, filters = undefined) {
|
|
15
40
|
const history = this.buckets.getHistory();
|
|
16
|
-
const
|
|
41
|
+
const endpointViews = (0, bucket_view_1.deriveEndpointView)(history);
|
|
42
|
+
const endpointList = endpointViews.filter(e => this.matchesFilters(e, filters))
|
|
43
|
+
.sort((a, b) => a.method.localeCompare(b.method));
|
|
17
44
|
const pagination = (0, utility_1.paginate)(endpointList, page, pageSize);
|
|
18
45
|
return {
|
|
19
46
|
summary: this.calculateSummary(endpointList),
|
|
@@ -23,14 +50,14 @@ class AnalyticsStore {
|
|
|
23
50
|
data: pagination.data,
|
|
24
51
|
pagination: pagination.meta,
|
|
25
52
|
},
|
|
26
|
-
history: history.map(bucket => bucket.endpoints)
|
|
53
|
+
history: history.map(bucket => bucket.endpoints.map(endpoint => this.toTelemetry(endpoint, bucket.durationMs))) //! FIX HISTORY
|
|
27
54
|
};
|
|
28
55
|
}
|
|
29
56
|
calculateSummary(endpoints) {
|
|
30
57
|
return {
|
|
31
58
|
totalEndpoints: endpoints.length,
|
|
32
|
-
healthyEndpoints: endpoints.filter(e => e.errorRate === 0 &&
|
|
33
|
-
slowEndpoints: endpoints.filter(e =>
|
|
59
|
+
healthyEndpoints: endpoints.filter(e => e.errorRate === 0 && e.p95 < this.slowLatencyThreshold).length,
|
|
60
|
+
slowEndpoints: endpoints.filter(e => e.p95 >= this.slowLatencyThreshold).length,
|
|
34
61
|
slowEndpointsThreshold: this.slowLatencyThreshold,
|
|
35
62
|
errorEndpoints: endpoints.filter(e => e.errorRate > 0).length,
|
|
36
63
|
};
|
|
@@ -38,16 +65,16 @@ class AnalyticsStore {
|
|
|
38
65
|
latencyDistribution(endpoints) {
|
|
39
66
|
return endpoints.map(endpoint => ({
|
|
40
67
|
endpoint: this.endpointName(endpoint),
|
|
41
|
-
p50: endpoint.
|
|
42
|
-
p95: endpoint.p95
|
|
43
|
-
p99: endpoint.p99
|
|
68
|
+
p50: endpoint.p50,
|
|
69
|
+
p95: endpoint.p95,
|
|
70
|
+
p99: endpoint.p99,
|
|
44
71
|
}));
|
|
45
72
|
}
|
|
46
73
|
requestVolume(endpoints) {
|
|
47
74
|
return endpoints
|
|
48
75
|
.map(endpoint => ({
|
|
49
76
|
endpoint: this.endpointName(endpoint),
|
|
50
|
-
volume: endpoint.requestCount
|
|
77
|
+
volume: endpoint.requestCount,
|
|
51
78
|
rps: endpoint.rps,
|
|
52
79
|
}))
|
|
53
80
|
.sort((a, b) => b.volume - a.volume);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export type Origin = 'real' | 'synthetic';
|
|
2
|
+
export type RequestAggregate = {
|
|
3
|
+
requestCount: number;
|
|
4
|
+
clientErrorCount: number;
|
|
5
|
+
serverErrorCount: number;
|
|
6
|
+
durationSumMs: number;
|
|
7
|
+
durationMaxMs: number | null;
|
|
8
|
+
durationMinMs: number | null;
|
|
9
|
+
histogramSchemaId: string;
|
|
10
|
+
histogramCounts: number[];
|
|
11
|
+
};
|
|
12
|
+
export type RuntimeInterval = {
|
|
13
|
+
cpuTimeUs: number | null;
|
|
14
|
+
cpuUserUs: number | null;
|
|
15
|
+
cpuSystemUs: number | null;
|
|
16
|
+
heapUsedBytes: number | null;
|
|
17
|
+
heapTotalBytes: number | null;
|
|
18
|
+
heapLimitBytes: number | null;
|
|
19
|
+
rssBytes: number | null;
|
|
20
|
+
eventLoopSampleCount: number;
|
|
21
|
+
eventLoopDelaySumMs: number | null;
|
|
22
|
+
eventLoopDelayMaxMs: number | null;
|
|
23
|
+
gcCount: number | null;
|
|
24
|
+
gcDurationSumMs: number | null;
|
|
25
|
+
};
|
|
26
|
+
export type EndpointAggregate = RequestAggregate & {
|
|
27
|
+
method: string;
|
|
28
|
+
route: string;
|
|
29
|
+
};
|
|
30
|
+
export type MetricBucket = {
|
|
31
|
+
schemaVersion: typeof BUCKET_SCHEMA_VERSION;
|
|
32
|
+
bucketId: string;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
instanceId: string;
|
|
35
|
+
sequence: number;
|
|
36
|
+
startTime: string;
|
|
37
|
+
endTime: string;
|
|
38
|
+
durationMs: number;
|
|
39
|
+
origin: Origin;
|
|
40
|
+
requests: RequestAggregate;
|
|
41
|
+
endpoints: EndpointAggregate[];
|
|
42
|
+
runtime: RuntimeInterval;
|
|
43
|
+
qualityFlags: string[];
|
|
44
|
+
};
|
|
45
|
+
export declare const BUCKET_SCHEMA_VERSION: 3;
|
|
46
|
+
export declare const HISTOGRAM_SCHEMA_ID = "lat-v1";
|
|
47
|
+
export declare const HISTOGRAM_UPPER_BOUNDS_MS: readonly number[];
|
|
48
|
+
export declare const HISTOGRAM_BUCKET_COUNT: number;
|
|
49
|
+
export declare function createAggregate(): RequestAggregate;
|
|
50
|
+
export declare function histogramIndex(duration: number): number;
|
|
51
|
+
export declare function recordInto(agg: RequestAggregate, durationMs: number, statusCode: number): boolean;
|
|
52
|
+
export declare function mergeInto(t: RequestAggregate, s: RequestAggregate): void;
|
|
53
|
+
export declare function mergeAggregates(a: RequestAggregate, b: RequestAggregate): RequestAggregate;
|
|
54
|
+
export declare function percentileFromAggregate(agg: RequestAggregate, q: number): number;
|
|
55
|
+
export declare function countSlowerThan(agg: RequestAggregate, thresholdMs: number): number;
|
|
56
|
+
export declare function deriveMetrics(agg: RequestAggregate, durationMs: number): {
|
|
57
|
+
requestCount: number;
|
|
58
|
+
rps: number;
|
|
59
|
+
averageLatency: number;
|
|
60
|
+
minLatency: number;
|
|
61
|
+
maxLatency: number;
|
|
62
|
+
p50: number;
|
|
63
|
+
p95: number;
|
|
64
|
+
p99: number;
|
|
65
|
+
errorRate: number;
|
|
66
|
+
clientErrorCount: number;
|
|
67
|
+
serverErrorCount: number;
|
|
68
|
+
};
|
|
69
|
+
export declare function emptyRuntime(): RuntimeInterval;
|
|
70
|
+
export declare function mergeRuntimeInto(t: RuntimeInterval, s: RuntimeInterval, sIsLater: boolean): void;
|
|
71
|
+
export declare function deriveRuntime(rt: RuntimeInterval, durationMs: number): {
|
|
72
|
+
cpuPercent: number | null;
|
|
73
|
+
cpuUserPercent: number | null;
|
|
74
|
+
cpuSystemPercent: number | null;
|
|
75
|
+
eventLoopDelayMeanMs: number | null;
|
|
76
|
+
eventLoopDelayMaxMs: number | null;
|
|
77
|
+
gcCount: number | null;
|
|
78
|
+
gcDurationSumMs: number | null;
|
|
79
|
+
gcPauseAverageMs: number | null;
|
|
80
|
+
heapUsedBytes: number | null;
|
|
81
|
+
heapTotalBytes: number | null;
|
|
82
|
+
heapLimitBytes: number | null;
|
|
83
|
+
rssBytes: number | null;
|
|
84
|
+
};
|
|
85
|
+
export declare function deepFreeze<T>(v: T): T;
|