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
@@ -1,20 +1,70 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const local_repository_1 = require("../storage/local.repository");
4
- const config_1 = require("../../config");
3
+ exports.TelemetryService = void 0;
4
+ function globToRegExp(glob) {
5
+ let source = '';
6
+ for (let i = 0; i < glob.length; i++) {
7
+ const char = glob[i];
8
+ if (char === '*') {
9
+ if (glob[i + 1] === '*') {
10
+ const slash = glob[i + 2] === '/';
11
+ source += slash ? '(?:.*/)?' : '.*';
12
+ i += slash ? 2 : 1;
13
+ }
14
+ else {
15
+ source += '[^/]*';
16
+ }
17
+ }
18
+ else if (char === '?') {
19
+ source += '[^/]';
20
+ }
21
+ else {
22
+ source += char.replace(/[.+^${}()|[\]\\]/g, '\\$&');
23
+ }
24
+ }
25
+ return new RegExp(`^${source}$`);
26
+ }
5
27
  class TelemetryService {
6
28
  storage;
7
- constructor(storage = local_repository_1.storage) {
29
+ excludePatterns;
30
+ inFlight = 0;
31
+ drainWaiters = [];
32
+ constructor(storage, config) {
8
33
  this.storage = storage;
34
+ this.excludePatterns = config.collection.excludePaths.map(globToRegExp);
9
35
  }
10
36
  isPathExcluded(endpoint) {
11
37
  const cleanPath = endpoint.split('?')[0];
12
- const userConfig = (0, config_1.getConfig)();
13
- const configuredPaths = userConfig?.excludePaths || [];
14
- const allPatterns = [...configuredPaths];
15
- return allPatterns.some(pattern => {
16
- const cleanPattern = pattern.replace(/\*/g, '');
17
- return cleanPath.includes(cleanPattern);
38
+ return this.excludePatterns.some(pattern => pattern.test(cleanPath));
39
+ }
40
+ track() {
41
+ this.inFlight++;
42
+ let done = false;
43
+ return () => {
44
+ if (done)
45
+ return;
46
+ done = true;
47
+ this.inFlight--;
48
+ if (this.inFlight === 0) {
49
+ const waiters = this.drainWaiters;
50
+ this.drainWaiters = [];
51
+ for (const w of waiters)
52
+ w();
53
+ }
54
+ };
55
+ }
56
+ get inFlightCount() { return this.inFlight; }
57
+ whenDrained(timeoutMs) {
58
+ if (this.inFlight === 0)
59
+ return Promise.resolve(true);
60
+ return new Promise(resolve => {
61
+ const timer = setTimeout(() => {
62
+ this.drainWaiters = this.drainWaiters.filter(w => w !== onDrain);
63
+ resolve(false);
64
+ }, timeoutMs);
65
+ timer.unref();
66
+ const onDrain = () => { clearTimeout(timer); resolve(true); };
67
+ this.drainWaiters.push(onDrain);
18
68
  });
19
69
  }
20
70
  record(data, startHrTime) {
@@ -33,4 +83,4 @@ class TelemetryService {
33
83
  return telemetryRequest;
34
84
  }
35
85
  }
36
- exports.default = TelemetryService;
86
+ exports.TelemetryService = TelemetryService;
@@ -0,0 +1,9 @@
1
+ import { CorrelationAnalysis } from '../../domain';
2
+ interface AnalyzeCorrelationOptions {
3
+ strongThreshold?: number;
4
+ maxLag?: number;
5
+ xLabel?: string;
6
+ yLabel?: string;
7
+ }
8
+ export declare function analyzeCorrelation(xInput: number[], yInput: number[], requiredN: number, options?: AnalyzeCorrelationOptions): CorrelationAnalysis;
9
+ export {};
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeCorrelation = analyzeCorrelation;
4
+ const statistics_1 = require("../../utility/statistics");
5
+ const DEFAULT_STRONG_THRESHOLD = 0.7;
6
+ const DEFAULT_MAX_LAG = 5;
7
+ const MIN_POINTS = 5;
8
+ const VARIATION_TOLERANCE = 1e-9;
9
+ function keepFinitePairs(x, y) {
10
+ const xs = [];
11
+ const ys = [];
12
+ for (let i = 0; i < x.length; i++) {
13
+ if (Number.isFinite(x[i]) && Number.isFinite(y[i])) {
14
+ xs.push(x[i]);
15
+ ys.push(y[i]);
16
+ }
17
+ }
18
+ return { x: xs, y: ys };
19
+ }
20
+ function isConstant(series) {
21
+ return series.every((value) => value === series[0]);
22
+ }
23
+ function hasNoVariation(series, reference) {
24
+ const scale = Math.max(1, reference.reduce((max, v) => Math.max(max, Math.abs(v)), 0));
25
+ return (0, statistics_1.standardDeviation)(series) <= VARIATION_TOLERANCE * scale;
26
+ }
27
+ function insufficient(sampleSize, requiredSampleSize, recommendation, partial = {}) {
28
+ return {
29
+ status: 'INSUFFICIENT_DATA',
30
+ direction: 'NONE',
31
+ pearsonR: 0,
32
+ spearmanR: 0,
33
+ rawPearsonR: 0,
34
+ differencePearsonR: 0,
35
+ determination: 0,
36
+ lag: 0,
37
+ laggedPearsonR: 0,
38
+ sampleSize,
39
+ effectiveSampleSize: 0,
40
+ requiredSampleSize,
41
+ autocorrelationX: 0,
42
+ autocorrelationY: 0,
43
+ trendDriven: false,
44
+ recommendation,
45
+ ...partial
46
+ };
47
+ }
48
+ function signOf(value) {
49
+ return value > 0 ? 'POSITIVE' : value < 0 ? 'NEGATIVE' : 'NONE';
50
+ }
51
+ function analyzeCorrelation(xInput, yInput, requiredN, options = {}) {
52
+ if (xInput.length !== yInput.length) {
53
+ throw new RangeError(`Series must have the same length (got ${xInput.length} and ${yInput.length})`);
54
+ }
55
+ const strong = options.strongThreshold ?? DEFAULT_STRONG_THRESHOLD;
56
+ const maxLag = options.maxLag ?? DEFAULT_MAX_LAG;
57
+ const xLabel = options.xLabel ?? 'X';
58
+ const yLabel = options.yLabel ?? 'Y';
59
+ const { x, y } = keepFinitePairs(xInput, yInput);
60
+ const n = x.length;
61
+ if (n < Math.max(requiredN, MIN_POINTS)) {
62
+ return insufficient(n, requiredN, `Insufficient data points. At least ${requiredN} valid pairs of ${xLabel} and ${yLabel} are required.`);
63
+ }
64
+ if (isConstant(x) || isConstant(y)) {
65
+ return insufficient(n, requiredN, `One of the series (${xLabel} or ${yLabel}) is constant. Correlation cannot be computed.`);
66
+ }
67
+ const dx = (0, statistics_1.linearDetrend)(x);
68
+ const dy = (0, statistics_1.linearDetrend)(y);
69
+ if (hasNoVariation(dx, x) || hasNoVariation(dy, y)) {
70
+ return insufficient(n, requiredN, `One of the series (${xLabel} or ${yLabel}) has no variation after detrending. Correlation cannot be computed.`);
71
+ }
72
+ const autocorrelationX = (0, statistics_1.autocorrelation)(dx, 1);
73
+ const autocorrelationY = (0, statistics_1.autocorrelation)(dy, 1);
74
+ const nEff = (0, statistics_1.effectiveSampleSize)(n, autocorrelationX, autocorrelationY);
75
+ if (nEff < requiredN) {
76
+ return insufficient(n, requiredN, `Effective sample size is too small. At least ${requiredN} effective pairs of ${xLabel} and ${yLabel} are required.`, {
77
+ effectiveSampleSize: nEff,
78
+ autocorrelationX,
79
+ autocorrelationY
80
+ });
81
+ }
82
+ const pearsonR = (0, statistics_1.pearsonCorrelation)(dx, dy);
83
+ const spearmanR = (0, statistics_1.spearmanCorrelation)(dx, dy);
84
+ const rawPearsonR = (0, statistics_1.pearsonCorrelation)(x, y);
85
+ const differencePearsonR = (0, statistics_1.pearsonCorrelation)((0, statistics_1.difference)(x), (0, statistics_1.difference)(y));
86
+ const { determination } = (0, statistics_1.determinationAndAlienation)(pearsonR);
87
+ const lagged = (0, statistics_1.laggedCorrelation)(dx, dy, maxLag);
88
+ let status;
89
+ let direction;
90
+ if (Math.abs(pearsonR) >= strong) {
91
+ status = 'STRONG_LINEAR_ASSOCIATION';
92
+ direction = signOf(pearsonR);
93
+ }
94
+ else if (Math.abs(spearmanR) >= strong) {
95
+ status = 'STRONG_MONOTONIC_NONLINEAR_ASSOCIATION';
96
+ direction = signOf(spearmanR);
97
+ }
98
+ else {
99
+ status = 'NO_STRONG_ASSOCIATION';
100
+ direction = 'NONE';
101
+ }
102
+ const trendDriven = Math.abs(rawPearsonR) >= strong && Math.abs(pearsonR) < strong;
103
+ return {
104
+ status,
105
+ direction,
106
+ pearsonR,
107
+ spearmanR,
108
+ rawPearsonR,
109
+ differencePearsonR,
110
+ determination,
111
+ lag: lagged.lag,
112
+ laggedPearsonR: lagged.r,
113
+ sampleSize: n,
114
+ effectiveSampleSize: nEff,
115
+ requiredSampleSize: requiredN,
116
+ autocorrelationX,
117
+ autocorrelationY,
118
+ trendDriven,
119
+ recommendation: describe(status, direction, { pearsonR, spearmanR, rawPearsonR, lag: lagged.lag, laggedR: lagged.r, strong, trendDriven, xLabel, yLabel })
120
+ };
121
+ }
122
+ function describe(status, direction, ctx) {
123
+ const sign = direction === 'NEGATIVE' ? 'negative' : 'positive';
124
+ const parts = [];
125
+ if (status === 'STRONG_LINEAR_ASSOCIATION') {
126
+ parts.push(`In the observed period a strong linear correlation (${sign}, r=${ctx.pearsonR.toFixed(2)}) was detected between ${ctx.xLabel} and ${ctx.yLabel}. Correlation does not prove causality.`);
127
+ }
128
+ else if (status === 'STRONG_MONOTONIC_NONLINEAR_ASSOCIATION') {
129
+ parts.push(`In the observed period a strong monotonic nonlinear correlation (${sign}, ρ=${ctx.spearmanR.toFixed(2)}, r=${ctx.pearsonR.toFixed(2)}) was detected between ${ctx.xLabel} and ${ctx.yLabel}. The relationship could be logarithmic, threshold-based, saturating, or due to deviations; correlation does not prove causality.`);
130
+ }
131
+ else {
132
+ parts.push(`In the observed period no strong correlational relationship was detected between ${ctx.xLabel} and ${ctx.yLabel}.`);
133
+ }
134
+ if (ctx.trendDriven) {
135
+ parts.push(`The correlation is strong only on the raw series (r=${ctx.rawPearsonR.toFixed(2)}); it is possible that it is driven by a common trend.`);
136
+ }
137
+ if (ctx.lag !== 0 && Math.abs(ctx.laggedR) >= ctx.strong && status === 'NO_STRONG_ASSOCIATION') {
138
+ const leader = ctx.lag > 0 ? ctx.xLabel : ctx.yLabel;
139
+ const follower = ctx.lag > 0 ? ctx.yLabel : ctx.xLabel;
140
+ parts.push(`At a lag of ${Math.abs(ctx.lag)} samples (${leader} leads ${follower}), a strong correlation (r=${ctx.laggedR.toFixed(2)}) was observed.`);
141
+ }
142
+ return parts.join(' ');
143
+ }
@@ -0,0 +1,70 @@
1
+ import type { analyzeCorrelation } from "./correlation-analysis";
2
+ export declare const CORRELATION_METHOD_VERSION = "cor-2.0.0";
3
+ export declare const INPUT_SCHEMA_VERSION = 2;
4
+ export type CorrelationTransform = 'raw' | 'detrend' | 'difference';
5
+ export type CorrelationResult = ReturnType<typeof analyzeCorrelation>;
6
+ export interface CorrelationParameters {
7
+ requiredSampleSize: number;
8
+ minCorrelation: number;
9
+ alpha: number;
10
+ power: number;
11
+ strongThreshold: number;
12
+ maxLag: number;
13
+ maxWindow: number;
14
+ minCoverage: number;
15
+ gapPolicy: 'compact';
16
+ }
17
+ export interface CorrelationScope {
18
+ method?: string;
19
+ route?: string;
20
+ }
21
+ export interface InputIdentity {
22
+ sessionId: string;
23
+ bucketCount: number;
24
+ ranges: Array<[number, number]>;
25
+ digest: string;
26
+ }
27
+ export type CorrelationFinding = {
28
+ findingId: string;
29
+ evaluationKey: string;
30
+ sessionId: string;
31
+ instanceId: string;
32
+ scope: CorrelationScope;
33
+ metricX: string;
34
+ metricY: string;
35
+ windowStart: string;
36
+ windowEnd: string;
37
+ resolutionMs: number | null;
38
+ transform: CorrelationTransform;
39
+ methodVersion: string;
40
+ parameters: CorrelationParameters;
41
+ input: InputIdentity;
42
+ inputSchemaVersion: number;
43
+ createdAt: string;
44
+ source: 'live' | 'replay';
45
+ validPairCount: number;
46
+ droppedPairCount: number;
47
+ coverage: number;
48
+ qualityFlags: string[];
49
+ result: CorrelationResult;
50
+ };
51
+ export declare function stableStringify(v: unknown): string;
52
+ export declare const sha256: (s: string) => string;
53
+ export declare const scopeKey: (s: CorrelationScope) => string;
54
+ export declare const parametersHash: (p: CorrelationParameters) => string;
55
+ export declare function seriesKey(a: {
56
+ sessionId: string;
57
+ scope: CorrelationScope;
58
+ pairId: string;
59
+ resolutionMs: number | null;
60
+ transform: CorrelationTransform;
61
+ parameters: CorrelationParameters;
62
+ }): string;
63
+ export declare function evaluationKey(series: string, windowEndSequence: number, inputDigest: string): string;
64
+ export declare function findingIdFor(a: {
65
+ sessionId: string;
66
+ scope: CorrelationScope;
67
+ pairId: string;
68
+ windowEndSequence: number;
69
+ evaluationKey: string;
70
+ }): string;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parametersHash = exports.scopeKey = exports.sha256 = exports.INPUT_SCHEMA_VERSION = exports.CORRELATION_METHOD_VERSION = void 0;
4
+ exports.stableStringify = stableStringify;
5
+ exports.seriesKey = seriesKey;
6
+ exports.evaluationKey = evaluationKey;
7
+ exports.findingIdFor = findingIdFor;
8
+ const crypto_1 = require("crypto");
9
+ exports.CORRELATION_METHOD_VERSION = 'cor-2.0.0';
10
+ exports.INPUT_SCHEMA_VERSION = 2;
11
+ function stableStringify(v) {
12
+ if (v === null || typeof v !== 'object')
13
+ return JSON.stringify(v);
14
+ if (Array.isArray(v))
15
+ return `[${v.map(stableStringify).join(',')}]`;
16
+ const o = v;
17
+ return `{${Object.keys(o).sort().map(k => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(',')}}`;
18
+ }
19
+ const sha256 = (s) => (0, crypto_1.createHash)('sha256').update(s).digest('hex');
20
+ exports.sha256 = sha256;
21
+ const scopeKey = (s) => s.method || s.route ? `${s.method ?? '*'} ${s.route ?? '*'}` : 'service';
22
+ exports.scopeKey = scopeKey;
23
+ const parametersHash = (p) => (0, exports.sha256)(stableStringify(p)).slice(0, 12);
24
+ exports.parametersHash = parametersHash;
25
+ function seriesKey(a) {
26
+ return [
27
+ a.sessionId,
28
+ (0, exports.scopeKey)(a.scope),
29
+ a.pairId,
30
+ a.resolutionMs ?? 'raw',
31
+ a.transform,
32
+ exports.CORRELATION_METHOD_VERSION,
33
+ (0, exports.parametersHash)(a.parameters),
34
+ ].join('|');
35
+ }
36
+ function evaluationKey(series, windowEndSequence, inputDigest) {
37
+ return `${series}|end=${windowEndSequence}|in=${inputDigest}`;
38
+ }
39
+ function findingIdFor(a) {
40
+ return `${a.sessionId}:${encodeURIComponent((0, exports.scopeKey)(a.scope))}:${a.pairId}:${a.windowEndSequence}:${(0, exports.sha256)(a.evaluationKey).slice(0, 8)}`;
41
+ }
@@ -0,0 +1,59 @@
1
+ import type { SeriesPoint } from '../telemetry-query.service';
2
+ import type { CorrelationDirection } from '../../domain';
3
+ import { type CorrelationParameters, type CorrelationResult, type CorrelationTransform } from './correlation-finding';
4
+ export interface Metric {
5
+ label: string;
6
+ select: (p: SeriesPoint) => number | null;
7
+ }
8
+ export declare const METRICS: {
9
+ rps: {
10
+ label: string;
11
+ select: (p: SeriesPoint) => number | null;
12
+ };
13
+ errorRate: {
14
+ label: string;
15
+ select: (p: SeriesPoint) => number | null;
16
+ };
17
+ heapUsage: {
18
+ label: string;
19
+ select: (p: SeriesPoint) => number | null;
20
+ };
21
+ p95Latency: {
22
+ label: string;
23
+ select: (p: SeriesPoint) => number | null;
24
+ };
25
+ averageLatency: {
26
+ label: string;
27
+ select: (p: SeriesPoint) => number | null;
28
+ };
29
+ eventLoopLag: {
30
+ label: string;
31
+ select: (p: SeriesPoint) => number | null;
32
+ };
33
+ };
34
+ export type MetricKey = keyof typeof METRICS;
35
+ export interface PairConfig {
36
+ x: MetricKey;
37
+ y: MetricKey;
38
+ expectedDirection: Exclude<CorrelationDirection, 'NONE'>;
39
+ }
40
+ export declare const PAIRS: readonly PairConfig[];
41
+ export declare const pairId: (x: MetricKey, y: MetricKey) => string;
42
+ export interface PreparedSeries {
43
+ xs: number[];
44
+ ys: number[];
45
+ sequences: number[];
46
+ ranges: Array<[number, number]>;
47
+ bucketCount: number;
48
+ windowStart: string;
49
+ windowEnd: string;
50
+ windowEndSequence: number;
51
+ expectedIntervals: number;
52
+ validPairCount: number;
53
+ droppedPairCount: number;
54
+ coverage: number;
55
+ qualityFlags: string[];
56
+ digest: string;
57
+ }
58
+ export declare function prepareSeries(points: readonly SeriesPoint[], pair: Pick<PairConfig, 'x' | 'y'>, transform: CorrelationTransform, minCoverage: number): PreparedSeries;
59
+ export declare function runAnalysis(prepared: Pick<PreparedSeries, 'xs' | 'ys'>, pair: Pick<PairConfig, 'x' | 'y'>, params: CorrelationParameters): CorrelationResult;
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pairId = exports.PAIRS = exports.METRICS = void 0;
4
+ exports.prepareSeries = prepareSeries;
5
+ exports.runAnalysis = runAnalysis;
6
+ const correlation_analysis_1 = require("./correlation-analysis");
7
+ const bucket_metric_1 = require("../../storage/stores/bucket-metric");
8
+ const correlation_finding_1 = require("./correlation-finding");
9
+ const finite = (n) => typeof n === 'number' && Number.isFinite(n) ? n : null;
10
+ const whenRequests = (p, pick) => p.requests.requestCount > 0 ? finite(pick((0, bucket_metric_1.deriveMetrics)(p.requests, p.durationMs))) : null;
11
+ exports.METRICS = {
12
+ rps: { label: 'RPS', select: p => finite((0, bucket_metric_1.deriveMetrics)(p.requests, p.durationMs).rps) },
13
+ errorRate: { label: 'error rate', select: p => whenRequests(p, m => m.errorRate) },
14
+ heapUsage: { label: 'heap memory', select: p => finite(p.runtime.heapUsedBytes) },
15
+ p95Latency: { label: 'p95 latency', select: p => whenRequests(p, m => m.p95) },
16
+ averageLatency: { label: 'average latency', select: p => whenRequests(p, m => m.averageLatency) },
17
+ eventLoopLag: { label: 'event loop lag', select: p => finite((0, bucket_metric_1.deriveRuntime)(p.runtime, p.durationMs).eventLoopDelayMeanMs) },
18
+ };
19
+ exports.PAIRS = [
20
+ { x: 'eventLoopLag', y: 'p95Latency', expectedDirection: 'POSITIVE' },
21
+ { x: 'eventLoopLag', y: 'averageLatency', expectedDirection: 'POSITIVE' },
22
+ { x: 'heapUsage', y: 'eventLoopLag', expectedDirection: 'POSITIVE' },
23
+ { x: 'heapUsage', y: 'p95Latency', expectedDirection: 'POSITIVE' },
24
+ { x: 'rps', y: 'errorRate', expectedDirection: 'POSITIVE' },
25
+ { x: 'rps', y: 'p95Latency', expectedDirection: 'POSITIVE' }
26
+ ];
27
+ const pairId = (x, y) => `${x}->${y}`;
28
+ exports.pairId = pairId;
29
+ const PROPAGATED_FLAGS = ['LATE_CLOSE', 'PARTIAL', 'MIXED_ORIGIN', 'CLOCK_SKEW'];
30
+ function toRanges(points) {
31
+ const ranges = [];
32
+ for (const p of points) {
33
+ const last = ranges[ranges.length - 1];
34
+ if (last && p.sequenceFrom === last[1] + 1)
35
+ last[1] = p.sequenceTo;
36
+ else
37
+ ranges.push([p.sequenceFrom, p.sequenceTo]);
38
+ }
39
+ return ranges;
40
+ }
41
+ function detrend(seq, values) {
42
+ const n = values.length;
43
+ if (n < 2)
44
+ return values.slice();
45
+ const t0 = seq[0];
46
+ let st = 0, sv = 0, stt = 0, stv = 0;
47
+ for (let i = 0; i < n; i++) {
48
+ const t = seq[i] - t0;
49
+ st += t;
50
+ sv += values[i];
51
+ stt += t * t;
52
+ stv += t * values[i];
53
+ }
54
+ const denom = n * stt - st * st;
55
+ const slope = denom === 0 ? 0 : (n * stv - st * sv) / denom;
56
+ const intercept = (sv - slope * st) / n;
57
+ return values.map((v, i) => v - (intercept + slope * (seq[i] - t0)));
58
+ }
59
+ function prepareSeries(points, pair, transform, minCoverage) {
60
+ if (points.length === 0)
61
+ throw new Error('prepareSeries: empty window');
62
+ const x = exports.METRICS[pair.x];
63
+ const y = exports.METRICS[pair.y];
64
+ let seq = [];
65
+ let xs = [];
66
+ let ys = [];
67
+ for (const p of points) {
68
+ const vx = x.select(p);
69
+ const vy = y.select(p);
70
+ if (vx === null || vy === null)
71
+ continue;
72
+ seq.push(p.sequenceTo);
73
+ xs.push(vx);
74
+ ys.push(vy);
75
+ }
76
+ const afterPairing = seq.length;
77
+ if (transform === 'difference') {
78
+ const s2 = [], x2 = [], y2 = [];
79
+ for (let i = 1; i < seq.length; i++) {
80
+ if (seq[i] !== seq[i - 1] + 1)
81
+ continue;
82
+ s2.push(seq[i]);
83
+ x2.push(xs[i] - xs[i - 1]);
84
+ y2.push(ys[i] - ys[i - 1]);
85
+ }
86
+ seq = s2;
87
+ xs = x2;
88
+ ys = y2;
89
+ }
90
+ else if (transform === 'detrend') {
91
+ xs = detrend(seq, xs);
92
+ ys = detrend(seq, ys);
93
+ }
94
+ const first = points[0];
95
+ const last = points[points.length - 1];
96
+ const expectedIntervals = last.sequenceTo - first.sequenceFrom + 1;
97
+ const presentIntervals = points.reduce((s, p) => s + (p.sequenceTo - p.sequenceFrom + 1), 0);
98
+ const flags = new Set();
99
+ if (presentIntervals < expectedIntervals)
100
+ flags.add('GAPS');
101
+ if (afterPairing < points.length)
102
+ flags.add('INVALID_VALUES');
103
+ if (seq.length < afterPairing)
104
+ flags.add('TRANSFORM_LOSS');
105
+ if (seq.length < expectedIntervals && transform !== 'difference')
106
+ flags.add('COMPACTED_SERIES');
107
+ for (const p of points)
108
+ for (const f of p.qualityFlags) {
109
+ if (PROPAGATED_FLAGS.includes(f))
110
+ flags.add(f);
111
+ }
112
+ const coverage = expectedIntervals > 0 ? seq.length / expectedIntervals : 0;
113
+ if (coverage < minCoverage)
114
+ flags.add('LOW_COVERAGE');
115
+ const digest = (0, correlation_finding_1.sha256)(JSON.stringify({ t: transform, s: seq, x: xs, y: ys }));
116
+ return {
117
+ xs, ys,
118
+ sequences: seq,
119
+ ranges: toRanges(points),
120
+ bucketCount: points.reduce((s, p) => s + p.bucketCount, 0),
121
+ windowStart: first.startTime,
122
+ windowEnd: last.endTime,
123
+ windowEndSequence: last.sequenceTo,
124
+ expectedIntervals,
125
+ validPairCount: seq.length,
126
+ droppedPairCount: Math.max(0, expectedIntervals - seq.length),
127
+ coverage,
128
+ qualityFlags: [...flags].sort(),
129
+ digest,
130
+ };
131
+ }
132
+ function runAnalysis(prepared, pair, params) {
133
+ return (0, correlation_analysis_1.analyzeCorrelation)(prepared.xs, prepared.ys, params.requiredSampleSize, {
134
+ strongThreshold: params.strongThreshold,
135
+ maxLag: params.maxLag,
136
+ xLabel: exports.METRICS[pair.x].label,
137
+ yLabel: exports.METRICS[pair.y].label,
138
+ });
139
+ }
@@ -0,0 +1,24 @@
1
+ import type { TelemetryQueryService } from '../telemetry-query.service';
2
+ import { type CorrelationFinding } from './correlation-finding';
3
+ export type ReplayOutcome = {
4
+ status: 'REPRODUCED';
5
+ replayed: CorrelationFinding;
6
+ } | {
7
+ status: 'INPUT_MISSING';
8
+ expectedBuckets: number;
9
+ foundBuckets: number;
10
+ } | {
11
+ status: 'INPUT_CHANGED';
12
+ storedDigest: string;
13
+ replayedDigest: string;
14
+ } | {
15
+ status: 'RESULT_DIFFERS';
16
+ replayed: CorrelationFinding;
17
+ } | {
18
+ status: 'UNSUPPORTED';
19
+ reason: string;
20
+ } | {
21
+ status: 'NOT_FOUND';
22
+ findingId: string;
23
+ };
24
+ export declare function verifyFinding(queries: TelemetryQueryService, finding: CorrelationFinding): Promise<ReplayOutcome>;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyFinding = verifyFinding;
4
+ const correlation_finding_1 = require("./correlation-finding");
5
+ const correlation_input_1 = require("./correlation-input");
6
+ const inRanges = (seqFrom, seqTo, ranges) => ranges.some(([a, b]) => seqFrom >= a && seqTo <= b);
7
+ async function verifyFinding(queries, finding) {
8
+ if (finding.inputSchemaVersion !== correlation_finding_1.INPUT_SCHEMA_VERSION) {
9
+ return { status: 'UNSUPPORTED', reason: `input schema ${finding.inputSchemaVersion}` };
10
+ }
11
+ const pair = correlation_input_1.PAIRS.find(p => p.x === finding.metricX && p.y === finding.metricY);
12
+ if (!pair)
13
+ return { status: 'UNSUPPORTED', reason: `unknown pair ${finding.metricX}->${finding.metricY}` };
14
+ const result = await queries.query({
15
+ sessionId: finding.sessionId,
16
+ from: finding.windowStart,
17
+ to: finding.windowEnd,
18
+ method: finding.scope.method,
19
+ route: finding.scope.route,
20
+ resolutionMs: finding.resolutionMs ?? undefined,
21
+ consistency: 'committed',
22
+ });
23
+ const points = result.points.filter(p => inRanges(p.sequenceFrom, p.sequenceTo, finding.input.ranges));
24
+ const found = points.reduce((s, p) => s + p.bucketCount, 0);
25
+ if (found < finding.input.bucketCount) {
26
+ return { status: 'INPUT_MISSING', expectedBuckets: finding.input.bucketCount, foundBuckets: found };
27
+ }
28
+ const prepared = (0, correlation_input_1.prepareSeries)(points, pair, finding.transform, finding.parameters.minCoverage);
29
+ if (prepared.digest !== finding.input.digest) {
30
+ return { status: 'INPUT_CHANGED', storedDigest: finding.input.digest, replayedDigest: prepared.digest };
31
+ }
32
+ const replayedResult = (0, correlation_input_1.runAnalysis)(prepared, pair, finding.parameters);
33
+ const replayed = {
34
+ ...finding,
35
+ createdAt: new Date().toISOString(),
36
+ source: 'replay',
37
+ validPairCount: prepared.validPairCount,
38
+ droppedPairCount: prepared.droppedPairCount,
39
+ coverage: prepared.coverage,
40
+ qualityFlags: prepared.qualityFlags,
41
+ result: replayedResult,
42
+ };
43
+ return (0, correlation_finding_1.stableStringify)(replayedResult) === (0, correlation_finding_1.stableStringify)(finding.result)
44
+ ? { status: 'REPRODUCED', replayed }
45
+ : { status: 'RESULT_DIFFERS', replayed };
46
+ }
@@ -0,0 +1,9 @@
1
+ import { CorrelationAnalysis } from '../domain';
2
+ interface AnalyzeCorrelationOptions {
3
+ strongThreshold?: number;
4
+ maxLag?: number;
5
+ xLabel?: string;
6
+ yLabel?: string;
7
+ }
8
+ export declare function analyzeCorrelation(xInput: number[], yInput: number[], requiredN: number, options?: AnalyzeCorrelationOptions): CorrelationAnalysis;
9
+ export {};