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.
Files changed (177) hide show
  1. package/README.md +54 -23
  2. package/dashboard-out/404/index.html +1 -1
  3. package/dashboard-out/404.html +1 -1
  4. package/dashboard-out/__next.__PAGE__.txt +4 -4
  5. package/dashboard-out/__next._full.txt +17 -17
  6. package/dashboard-out/__next._head.txt +4 -4
  7. package/dashboard-out/__next._index.txt +10 -10
  8. package/dashboard-out/__next._tree.txt +2 -2
  9. package/dashboard-out/_next/static/chunks/{2xjcsu4pgp14_.js → 062oipeclh9bq.js} +1 -1
  10. package/dashboard-out/_next/static/chunks/0ncbimuip0-o7.css +3 -0
  11. package/dashboard-out/_next/static/chunks/0xtgenoch3q77.js +1 -0
  12. package/dashboard-out/_next/static/chunks/1xln1-s87x5yp.js +27 -0
  13. package/dashboard-out/_not-found/__next._full.txt +15 -15
  14. package/dashboard-out/_not-found/__next._head.txt +4 -4
  15. package/dashboard-out/_not-found/__next._index.txt +10 -10
  16. package/dashboard-out/_not-found/__next._not-found.__PAGE__.txt +2 -2
  17. package/dashboard-out/_not-found/__next._not-found.txt +3 -3
  18. package/dashboard-out/_not-found/__next._tree.txt +2 -2
  19. package/dashboard-out/_not-found/index.html +1 -1
  20. package/dashboard-out/_not-found/index.txt +15 -15
  21. package/dashboard-out/index.html +1 -1
  22. package/dashboard-out/index.txt +17 -17
  23. package/dist/adapters/session-export.handler.d.ts +4 -0
  24. package/dist/adapters/session-export.handler.js +29 -0
  25. package/dist/adapters/socket.options.d.ts +3 -0
  26. package/dist/adapters/socket.options.js +16 -0
  27. package/dist/config/config.manager.d.ts +80 -3
  28. package/dist/config/config.manager.js +269 -89
  29. package/dist/config/config.types.d.ts +89 -13
  30. package/dist/core/delivery/buckets-request.d.ts +2 -0
  31. package/dist/core/delivery/buckets-request.js +18 -0
  32. package/dist/core/delivery/index.d.ts +1 -0
  33. package/dist/core/delivery/index.js +1 -0
  34. package/dist/core/delivery/metrics.publisher.d.ts +23 -10
  35. package/dist/core/delivery/metrics.publisher.js +22 -22
  36. package/dist/core/delivery/websocket.events.d.ts +14 -3
  37. package/dist/core/delivery/websocket.events.js +13 -2
  38. package/dist/core/domain/correlation.types.d.ts +37 -0
  39. package/dist/core/domain/correlation.types.js +2 -0
  40. package/dist/core/domain/dashboard.types.d.ts +16 -69
  41. package/dist/core/domain/dashboard.types.js +29 -0
  42. package/dist/core/domain/endpoints.types.d.ts +3 -0
  43. package/dist/core/domain/event.types.d.ts +14 -0
  44. package/dist/core/domain/event.types.js +2 -0
  45. package/dist/core/domain/incident.types.d.ts +50 -0
  46. package/dist/core/domain/incident.types.js +2 -0
  47. package/dist/core/domain/index.d.ts +5 -0
  48. package/dist/core/domain/index.js +5 -0
  49. package/dist/core/domain/live-bucket.types.d.ts +86 -0
  50. package/dist/core/domain/live-bucket.types.js +2 -0
  51. package/dist/core/domain/metrics.types.d.ts +41 -11
  52. package/dist/core/domain/session.types.d.ts +93 -0
  53. package/dist/core/domain/session.types.js +2 -0
  54. package/dist/core/domain/telemetry.types.d.ts +1 -0
  55. package/dist/core/organizers/event.manager.d.ts +19 -0
  56. package/dist/core/organizers/event.manager.js +73 -0
  57. package/dist/core/organizers/index.d.ts +2 -0
  58. package/dist/core/organizers/index.js +18 -0
  59. package/dist/core/organizers/interval.manager.d.ts +18 -0
  60. package/dist/core/organizers/interval.manager.js +54 -0
  61. package/dist/core/storage/index.d.ts +1 -0
  62. package/dist/core/storage/index.js +1 -0
  63. package/dist/core/storage/local.repository.d.ts +12 -12
  64. package/dist/core/storage/local.repository.js +38 -38
  65. package/dist/core/storage/persistence/persistence-writer.d.ts +48 -0
  66. package/dist/core/storage/persistence/persistence-writer.js +191 -0
  67. package/dist/core/storage/persistence/segment-index.d.ts +27 -0
  68. package/dist/core/storage/persistence/segment-index.js +206 -0
  69. package/dist/core/storage/persistence/session-registry.d.ts +28 -0
  70. package/dist/core/storage/persistence/session-registry.js +127 -0
  71. package/dist/core/storage/persistence/session-summary.d.ts +3 -0
  72. package/dist/core/storage/persistence/session-summary.js +112 -0
  73. package/dist/core/storage/persistence.layer.d.ts +69 -0
  74. package/dist/core/storage/persistence.layer.js +404 -0
  75. package/dist/core/storage/runtime/anomaly-detector.d.ts +11 -0
  76. package/dist/core/storage/runtime/anomaly-detector.js +42 -0
  77. package/dist/core/storage/stores/analytics.store.d.ts +5 -5
  78. package/dist/core/storage/stores/analytics.store.js +39 -12
  79. package/dist/core/storage/stores/bucket-metric.d.ts +85 -0
  80. package/dist/core/storage/stores/bucket-metric.js +168 -0
  81. package/dist/core/storage/stores/bucket-view.d.ts +44 -0
  82. package/dist/core/storage/stores/bucket-view.js +63 -0
  83. package/dist/core/storage/stores/bucket.store.d.ts +39 -36
  84. package/dist/core/storage/stores/bucket.store.js +112 -60
  85. package/dist/core/storage/stores/dashboard.store.d.ts +11 -4
  86. package/dist/core/storage/stores/dashboard.store.js +179 -47
  87. package/dist/core/storage/stores/endpoint.store.d.ts +1 -0
  88. package/dist/core/storage/stores/endpoint.store.js +21 -5
  89. package/dist/core/storage/stores/index.d.ts +0 -7
  90. package/dist/core/storage/stores/index.js +0 -7
  91. package/dist/core/storage/stores/metrics.store.d.ts +1 -1
  92. package/dist/core/storage/stores/metrics.store.js +1 -0
  93. package/dist/core/storage/stores/runtime.store.d.ts +77 -0
  94. package/dist/core/storage/stores/runtime.store.js +222 -0
  95. package/dist/core/storage/utility/common.d.ts +1 -0
  96. package/dist/core/storage/utility/common.js +11 -0
  97. package/dist/core/storage/utility/file-buffer.d.ts +31 -0
  98. package/dist/core/storage/utility/file-buffer.js +238 -0
  99. package/dist/core/storage/utility/histogram.d.ts +1 -2
  100. package/dist/core/storage/utility/histogram.js +1 -2
  101. package/dist/core/storage/utility/index.d.ts +2 -0
  102. package/dist/core/storage/utility/index.js +2 -0
  103. package/dist/core/storage/utility/persistence-files.d.ts +3 -0
  104. package/dist/core/storage/utility/persistence-files.js +34 -0
  105. package/dist/core/storage/utility/persistence-writer.d.ts +39 -0
  106. package/dist/core/storage/utility/persistence-writer.js +183 -0
  107. package/dist/core/storage/utility/session-export.d.ts +3 -0
  108. package/dist/core/storage/utility/session-export.js +31 -0
  109. package/dist/core/storage/utility/session-registry.d.ts +21 -0
  110. package/dist/core/storage/utility/session-registry.js +91 -0
  111. package/dist/core/storage/utility/session-window.d.ts +8 -0
  112. package/dist/core/storage/utility/session-window.js +43 -0
  113. package/dist/core/telemetry/analytics.service.d.ts +18 -0
  114. package/dist/core/telemetry/analytics.service.js +100 -0
  115. package/dist/core/telemetry/collector.service.d.ts +17 -15
  116. package/dist/core/telemetry/collector.service.js +23 -80
  117. package/dist/core/telemetry/correlation.service.d.ts +57 -9
  118. package/dist/core/telemetry/correlation.service.js +246 -54
  119. package/dist/core/telemetry/dashboard-live.service.d.ts +10 -0
  120. package/dist/core/telemetry/dashboard-live.service.js +110 -0
  121. package/dist/core/telemetry/dashboard.service.d.ts +20 -0
  122. package/dist/core/telemetry/dashboard.service.js +140 -0
  123. package/dist/core/telemetry/incident.service.d.ts +71 -0
  124. package/dist/core/telemetry/incident.service.js +239 -0
  125. package/dist/core/telemetry/logger.d.ts +4 -0
  126. package/dist/core/telemetry/logger.js +5 -2
  127. package/dist/core/telemetry/runtime.service.d.ts +14 -0
  128. package/dist/core/telemetry/runtime.service.js +56 -0
  129. package/dist/core/telemetry/telemetry-query.service.d.ts +82 -0
  130. package/dist/core/telemetry/telemetry-query.service.js +231 -0
  131. package/dist/core/telemetry/telemetry.service.d.ts +10 -4
  132. package/dist/core/telemetry/telemetry.service.js +60 -10
  133. package/dist/core/telemetry/utility/correlation-analysis.d.ts +9 -0
  134. package/dist/core/telemetry/utility/correlation-analysis.js +143 -0
  135. package/dist/core/telemetry/utility/correlation-finding.d.ts +70 -0
  136. package/dist/core/telemetry/utility/correlation-finding.js +41 -0
  137. package/dist/core/telemetry/utility/correlation-input.d.ts +59 -0
  138. package/dist/core/telemetry/utility/correlation-input.js +139 -0
  139. package/dist/core/telemetry/utility/correlation-replay.d.ts +24 -0
  140. package/dist/core/telemetry/utility/correlation-replay.js +46 -0
  141. package/dist/core/utility/correlation.analysis.d.ts +9 -0
  142. package/dist/core/utility/correlation.analysis.js +144 -0
  143. package/dist/core/utility/statistics.d.ts +65 -0
  144. package/dist/core/utility/statistics.js +141 -0
  145. package/dist/express/index.d.ts +1 -1
  146. package/dist/express/index.js +7 -5
  147. package/dist/express/metrics-websocket.adapter.d.ts +8 -2
  148. package/dist/express/metrics-websocket.adapter.js +52 -17
  149. package/dist/express/metrics.adapter.d.ts +1 -0
  150. package/dist/express/metrics.adapter.js +8 -1
  151. package/dist/express/metrics.bootstrap.d.ts +6 -1
  152. package/dist/express/metrics.bootstrap.js +48 -22
  153. package/dist/express/metrics.dashboard.d.ts +2 -1
  154. package/dist/express/metrics.dashboard.js +4 -1
  155. package/dist/express/metrics.middleware.d.ts +2 -1
  156. package/dist/express/metrics.middleware.js +23 -20
  157. package/dist/nest/metrics-websocket.adapter.d.ts +19 -3
  158. package/dist/nest/metrics-websocket.adapter.js +101 -21
  159. package/dist/nest/metrics.adapter.d.ts +4 -1
  160. package/dist/nest/metrics.adapter.js +24 -1
  161. package/dist/nest/metrics.bootstrap.service.d.ts +7 -3
  162. package/dist/nest/metrics.bootstrap.service.js +56 -21
  163. package/dist/nest/metrics.interceptor.d.ts +3 -3
  164. package/dist/nest/metrics.interceptor.js +11 -8
  165. package/dist/nest/metrics.module.d.ts +3 -0
  166. package/dist/nest/metrics.module.js +27 -9
  167. package/dist/runtime/index.d.ts +32 -0
  168. package/dist/runtime/index.js +104 -0
  169. package/dist/simulation/simulation.d.ts +5 -16
  170. package/dist/simulation/simulation.js +5 -8
  171. package/package.json +2 -1
  172. package/dashboard-out/_next/static/chunks/20t9qbodfrw_z.js +0 -3
  173. package/dashboard-out/_next/static/chunks/23soo6qkun-yv.js +0 -1
  174. package/dashboard-out/_next/static/chunks/3xoopocxi2vi8.css +0 -3
  175. /package/dashboard-out/_next/static/{u8vU2V6fhASeGVCpWU3eW → 17egwqgk36_DSRF55LJZG}/_buildManifest.js +0 -0
  176. /package/dashboard-out/_next/static/{u8vU2V6fhASeGVCpWU3eW → 17egwqgk36_DSRF55LJZG}/_clientMiddlewareManifest.js +0 -0
  177. /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 { EndpointTelemetry } from '../../domain';
2
- import { HealthStore } from "../stores/health.store";
3
- import type { EndpointStore } from "../stores/endpoint.store";
4
- import type { MetricsStore } from "../stores/metrics.store";
5
- export interface TelemetryBucket {
6
- timestamp: number;
7
- rps: {
8
- count: number;
9
- perEndpoints: Map<string, number>;
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 endpointStore;
27
- private readonly healthStore;
28
- private readonly metricsStore;
12
+ private readonly runtime;
13
+ private readonly options;
29
14
  private readonly history;
30
- private currentSecond;
31
- private requestCount;
32
- private endpointRequests;
33
- constructor(endpointStore: EndpointStore, healthStore: HealthStore, metricsStore: MetricsStore, bufferSize?: number);
34
- recordRequest(method: string, route: string): void;
35
- flush(): void;
36
- getHistory(): TelemetryBucket[];
37
- latest(): TelemetryBucket | undefined;
38
- private reset;
39
- private getSecond;
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
- endpointStore;
7
- healthStore;
8
- metricsStore;
7
+ runtime;
8
+ options;
9
9
  history;
10
- currentSecond;
11
- requestCount;
12
- endpointRequests;
13
- constructor(endpointStore, healthStore, metricsStore, bufferSize = 60) {
14
- this.endpointStore = endpointStore;
15
- this.healthStore = healthStore;
16
- this.metricsStore = metricsStore;
17
- this.history = new utility_1.RingBuffer(bufferSize);
18
- this.currentSecond = this.getSecond();
19
- this.requestCount = 0;
20
- this.endpointRequests = new Map();
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
- recordRequest(method, route) {
23
- this.requestCount++;
24
- const key = `${method}:${route}`;
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 endpoints = this.endpointStore.all();
35
- const metrics = this.metricsStore.snapshot();
36
- const bucket = {
37
- timestamp: this.currentSecond,
38
- rps: {
39
- count: this.requestCount,
40
- perEndpoints: new Map(this.endpointRequests),
41
- },
42
- latency: {
43
- average: metrics.averageLatency,
44
- p50: metrics.latency.p50,
45
- p95: metrics.latency.p95,
46
- p99: metrics.latency.p99,
47
- },
48
- error: {
49
- rate: metrics.errorRate,
50
- clientCount: metrics.errorClientCount,
51
- serverCount: metrics.errorServerCount,
52
- },
53
- endpoints,
54
- health: this.healthStore.get(),
55
- };
56
- this.history.push(bucket);
57
- this.reset();
58
- this.metricsStore.reset();
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
- latest() {
65
- return this.history.latest();
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
- reset() {
68
- this.requestCount = 0;
69
- this.endpointRequests.clear();
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
- getSecond() {
72
- return (Math.floor(Date.now() / 1000) * 1000);
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, endpoints: EndpointStore, alerts: AlertStore);
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 };