qa360 1.4.5 → 2.0.1
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 +1 -1
- package/dist/commands/ai.d.ts +41 -0
- package/dist/commands/ai.js +499 -0
- package/dist/commands/ask.js +12 -12
- package/dist/commands/coverage.d.ts +8 -0
- package/dist/commands/coverage.js +252 -0
- package/dist/commands/explain.d.ts +27 -0
- package/dist/commands/explain.js +630 -0
- package/dist/commands/flakiness.d.ts +73 -0
- package/dist/commands/flakiness.js +435 -0
- package/dist/commands/generate.d.ts +66 -0
- package/dist/commands/generate.js +438 -0
- package/dist/commands/init.d.ts +56 -9
- package/dist/commands/init.js +217 -10
- package/dist/commands/monitor.d.ts +27 -0
- package/dist/commands/monitor.js +225 -0
- package/dist/commands/ollama.d.ts +40 -0
- package/dist/commands/ollama.js +301 -0
- package/dist/commands/pack.d.ts +37 -9
- package/dist/commands/pack.js +240 -141
- package/dist/commands/regression.d.ts +8 -0
- package/dist/commands/regression.js +340 -0
- package/dist/commands/repair.d.ts +26 -0
- package/dist/commands/repair.js +307 -0
- package/dist/commands/retry.d.ts +43 -0
- package/dist/commands/retry.js +275 -0
- package/dist/commands/run.d.ts +8 -3
- package/dist/commands/run.js +45 -31
- package/dist/commands/slo.d.ts +8 -0
- package/dist/commands/slo.js +327 -0
- package/dist/core/adapters/playwright-native-api.d.ts +183 -0
- package/dist/core/adapters/playwright-native-api.js +461 -0
- package/dist/core/adapters/playwright-ui.d.ts +7 -0
- package/dist/core/adapters/playwright-ui.js +29 -1
- package/dist/core/ai/anthropic-provider.d.ts +50 -0
- package/dist/core/ai/anthropic-provider.js +211 -0
- package/dist/core/ai/deepseek-provider.d.ts +81 -0
- package/dist/core/ai/deepseek-provider.js +254 -0
- package/dist/core/ai/index.d.ts +60 -0
- package/dist/core/ai/index.js +18 -0
- package/dist/core/ai/llm-client.d.ts +45 -0
- package/dist/core/ai/llm-client.js +7 -0
- package/dist/core/ai/mock-provider.d.ts +49 -0
- package/dist/core/ai/mock-provider.js +121 -0
- package/dist/core/ai/ollama-provider.d.ts +78 -0
- package/dist/core/ai/ollama-provider.js +192 -0
- package/dist/core/ai/openai-provider.d.ts +48 -0
- package/dist/core/ai/openai-provider.js +188 -0
- package/dist/core/ai/provider-factory.d.ts +160 -0
- package/dist/core/ai/provider-factory.js +269 -0
- package/dist/core/auth/api-key-provider.d.ts +16 -0
- package/dist/core/auth/api-key-provider.js +63 -0
- package/dist/core/auth/aws-iam-provider.d.ts +35 -0
- package/dist/core/auth/aws-iam-provider.js +177 -0
- package/dist/core/auth/azure-ad-provider.d.ts +15 -0
- package/dist/core/auth/azure-ad-provider.js +99 -0
- package/dist/core/auth/basic-auth-provider.d.ts +26 -0
- package/dist/core/auth/basic-auth-provider.js +111 -0
- package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
- package/dist/core/auth/gcp-adc-provider.js +126 -0
- package/dist/core/auth/index.d.ts +238 -0
- package/dist/core/auth/index.js +82 -0
- package/dist/core/auth/jwt-provider.d.ts +19 -0
- package/dist/core/auth/jwt-provider.js +160 -0
- package/dist/core/auth/manager.d.ts +84 -0
- package/dist/core/auth/manager.js +230 -0
- package/dist/core/auth/oauth2-provider.d.ts +17 -0
- package/dist/core/auth/oauth2-provider.js +114 -0
- package/dist/core/auth/totp-provider.d.ts +31 -0
- package/dist/core/auth/totp-provider.js +134 -0
- package/dist/core/auth/ui-login-provider.d.ts +26 -0
- package/dist/core/auth/ui-login-provider.js +198 -0
- package/dist/core/cache/index.d.ts +7 -0
- package/dist/core/cache/index.js +6 -0
- package/dist/core/cache/lru-cache.d.ts +203 -0
- package/dist/core/cache/lru-cache.js +397 -0
- package/dist/core/coverage/analyzer.d.ts +101 -0
- package/dist/core/coverage/analyzer.js +415 -0
- package/dist/core/coverage/collector.d.ts +74 -0
- package/dist/core/coverage/collector.js +459 -0
- package/dist/core/coverage/config.d.ts +37 -0
- package/dist/core/coverage/config.js +156 -0
- package/dist/core/coverage/index.d.ts +11 -0
- package/dist/core/coverage/index.js +15 -0
- package/dist/core/coverage/types.d.ts +267 -0
- package/dist/core/coverage/types.js +6 -0
- package/dist/core/coverage/vault.d.ts +95 -0
- package/dist/core/coverage/vault.js +405 -0
- package/dist/core/dashboard/assets.d.ts +6 -0
- package/dist/core/dashboard/assets.js +690 -0
- package/dist/core/dashboard/index.d.ts +6 -0
- package/dist/core/dashboard/index.js +5 -0
- package/dist/core/dashboard/server.d.ts +72 -0
- package/dist/core/dashboard/server.js +354 -0
- package/dist/core/dashboard/types.d.ts +70 -0
- package/dist/core/dashboard/types.js +5 -0
- package/dist/core/discoverer/index.d.ts +115 -0
- package/dist/core/discoverer/index.js +250 -0
- package/dist/core/flakiness/index.d.ts +228 -0
- package/dist/core/flakiness/index.js +384 -0
- package/dist/core/generation/code-formatter.d.ts +111 -0
- package/dist/core/generation/code-formatter.js +307 -0
- package/dist/core/generation/code-generator.d.ts +144 -0
- package/dist/core/generation/code-generator.js +293 -0
- package/dist/core/generation/generator.d.ts +40 -0
- package/dist/core/generation/generator.js +76 -0
- package/dist/core/generation/index.d.ts +30 -0
- package/dist/core/generation/index.js +28 -0
- package/dist/core/generation/pack-generator.d.ts +107 -0
- package/dist/core/generation/pack-generator.js +416 -0
- package/dist/core/generation/prompt-builder.d.ts +132 -0
- package/dist/core/generation/prompt-builder.js +672 -0
- package/dist/core/generation/source-analyzer.d.ts +213 -0
- package/dist/core/generation/source-analyzer.js +657 -0
- package/dist/core/generation/test-optimizer.d.ts +117 -0
- package/dist/core/generation/test-optimizer.js +328 -0
- package/dist/core/generation/types.d.ts +214 -0
- package/dist/core/generation/types.js +4 -0
- package/dist/core/index.d.ts +23 -1
- package/dist/core/index.js +39 -0
- package/dist/core/pack/validator.js +31 -1
- package/dist/core/pack-v2/index.d.ts +9 -0
- package/dist/core/pack-v2/index.js +8 -0
- package/dist/core/pack-v2/loader.d.ts +62 -0
- package/dist/core/pack-v2/loader.js +231 -0
- package/dist/core/pack-v2/migrator.d.ts +56 -0
- package/dist/core/pack-v2/migrator.js +455 -0
- package/dist/core/pack-v2/validator.d.ts +61 -0
- package/dist/core/pack-v2/validator.js +577 -0
- package/dist/core/regression/detector.d.ts +107 -0
- package/dist/core/regression/detector.js +497 -0
- package/dist/core/regression/index.d.ts +9 -0
- package/dist/core/regression/index.js +11 -0
- package/dist/core/regression/trend-analyzer.d.ts +102 -0
- package/dist/core/regression/trend-analyzer.js +345 -0
- package/dist/core/regression/types.d.ts +222 -0
- package/dist/core/regression/types.js +7 -0
- package/dist/core/regression/vault.d.ts +87 -0
- package/dist/core/regression/vault.js +289 -0
- package/dist/core/repair/engine/fixer.d.ts +24 -0
- package/dist/core/repair/engine/fixer.js +226 -0
- package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
- package/dist/core/repair/engine/suggestion-engine.js +187 -0
- package/dist/core/repair/index.d.ts +10 -0
- package/dist/core/repair/index.js +13 -0
- package/dist/core/repair/repairer.d.ts +90 -0
- package/dist/core/repair/repairer.js +284 -0
- package/dist/core/repair/types.d.ts +91 -0
- package/dist/core/repair/types.js +6 -0
- package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
- package/dist/core/repair/utils/error-analyzer.js +264 -0
- package/dist/core/retry/flakiness-integration.d.ts +60 -0
- package/dist/core/retry/flakiness-integration.js +228 -0
- package/dist/core/retry/index.d.ts +14 -0
- package/dist/core/retry/index.js +16 -0
- package/dist/core/retry/retry-engine.d.ts +80 -0
- package/dist/core/retry/retry-engine.js +296 -0
- package/dist/core/retry/types.d.ts +178 -0
- package/dist/core/retry/types.js +52 -0
- package/dist/core/retry/vault.d.ts +77 -0
- package/dist/core/retry/vault.js +304 -0
- package/dist/core/runner/e2e-helpers.d.ts +102 -0
- package/dist/core/runner/e2e-helpers.js +153 -0
- package/dist/core/runner/phase3-runner.d.ts +101 -2
- package/dist/core/runner/phase3-runner.js +559 -24
- package/dist/core/self-healing/assertion-healer.d.ts +97 -0
- package/dist/core/self-healing/assertion-healer.js +371 -0
- package/dist/core/self-healing/engine.d.ts +122 -0
- package/dist/core/self-healing/engine.js +538 -0
- package/dist/core/self-healing/index.d.ts +10 -0
- package/dist/core/self-healing/index.js +11 -0
- package/dist/core/self-healing/selector-healer.d.ts +103 -0
- package/dist/core/self-healing/selector-healer.js +372 -0
- package/dist/core/self-healing/types.d.ts +152 -0
- package/dist/core/self-healing/types.js +6 -0
- package/dist/core/slo/config.d.ts +107 -0
- package/dist/core/slo/config.js +360 -0
- package/dist/core/slo/index.d.ts +11 -0
- package/dist/core/slo/index.js +15 -0
- package/dist/core/slo/sli-calculator.d.ts +92 -0
- package/dist/core/slo/sli-calculator.js +364 -0
- package/dist/core/slo/slo-tracker.d.ts +148 -0
- package/dist/core/slo/slo-tracker.js +379 -0
- package/dist/core/slo/types.d.ts +281 -0
- package/dist/core/slo/types.js +7 -0
- package/dist/core/slo/vault.d.ts +102 -0
- package/dist/core/slo/vault.js +427 -0
- package/dist/core/tui/index.d.ts +7 -0
- package/dist/core/tui/index.js +6 -0
- package/dist/core/tui/monitor.d.ts +92 -0
- package/dist/core/tui/monitor.js +271 -0
- package/dist/core/tui/renderer.d.ts +33 -0
- package/dist/core/tui/renderer.js +218 -0
- package/dist/core/tui/types.d.ts +63 -0
- package/dist/core/tui/types.js +5 -0
- package/dist/core/types/pack-v2.d.ts +425 -0
- package/dist/core/types/pack-v2.js +8 -0
- package/dist/core/vault/index.d.ts +116 -0
- package/dist/core/vault/index.js +400 -5
- package/dist/core/watch/index.d.ts +7 -0
- package/dist/core/watch/index.js +6 -0
- package/dist/core/watch/watch-mode.d.ts +213 -0
- package/dist/core/watch/watch-mode.js +389 -0
- package/dist/index.js +68 -68
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +136 -0
- package/package.json +5 -1
- package/dist/core/adapters/playwright-api.d.ts +0 -82
- package/dist/core/adapters/playwright-api.js +0 -264
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLI Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates Service Level Indicators from various data sources.
|
|
5
|
+
* Supports ratio-based, threshold-based, and percentile-based SLIs.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* SLI Calculator class
|
|
9
|
+
*/
|
|
10
|
+
export class SLICalculator {
|
|
11
|
+
/**
|
|
12
|
+
* Calculate SLI from good/bad events (ratio-based)
|
|
13
|
+
*/
|
|
14
|
+
calculateFromEvents(sli, events) {
|
|
15
|
+
const total = events.good + events.bad;
|
|
16
|
+
const value = total > 0 ? (events.good / total) * 100 : 0;
|
|
17
|
+
return {
|
|
18
|
+
sliId: sli.id,
|
|
19
|
+
timestamp: Date.now(),
|
|
20
|
+
value,
|
|
21
|
+
total,
|
|
22
|
+
good: events.good,
|
|
23
|
+
bad: events.bad,
|
|
24
|
+
unit: sli.unit,
|
|
25
|
+
windowStart: Date.now(),
|
|
26
|
+
windowEnd: Date.now()
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Calculate SLI from time series data
|
|
31
|
+
*/
|
|
32
|
+
calculateFromTimeSeries(sli, data, windowMs) {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const windowStart = now - windowMs;
|
|
35
|
+
// Filter data within window
|
|
36
|
+
const windowData = data.filter(p => p.timestamp >= windowStart && p.timestamp <= now);
|
|
37
|
+
if (windowData.length === 0) {
|
|
38
|
+
return this.emptyMeasurement(sli, windowStart, now);
|
|
39
|
+
}
|
|
40
|
+
let value;
|
|
41
|
+
switch (sli.aggregation) {
|
|
42
|
+
case 'count':
|
|
43
|
+
value = windowData.length;
|
|
44
|
+
break;
|
|
45
|
+
case 'sum':
|
|
46
|
+
value = windowData.reduce((sum, p) => sum + p.value, 0);
|
|
47
|
+
break;
|
|
48
|
+
case 'avg':
|
|
49
|
+
value = windowData.reduce((sum, p) => sum + p.value, 0) / windowData.length;
|
|
50
|
+
break;
|
|
51
|
+
case 'max':
|
|
52
|
+
value = Math.max(...windowData.map(p => p.value));
|
|
53
|
+
break;
|
|
54
|
+
case 'min':
|
|
55
|
+
value = Math.min(...windowData.map(p => p.value));
|
|
56
|
+
break;
|
|
57
|
+
case 'p50':
|
|
58
|
+
value = this.percentile(windowData.map(p => p.value), 50);
|
|
59
|
+
break;
|
|
60
|
+
case 'p95':
|
|
61
|
+
value = this.percentile(windowData.map(p => p.value), 95);
|
|
62
|
+
break;
|
|
63
|
+
case 'p99':
|
|
64
|
+
value = this.percentile(windowData.map(p => p.value), 99);
|
|
65
|
+
break;
|
|
66
|
+
case 'p99_9':
|
|
67
|
+
value = this.percentile(windowData.map(p => p.value), 99.9);
|
|
68
|
+
break;
|
|
69
|
+
case 'rate':
|
|
70
|
+
// Calculate rate per second
|
|
71
|
+
const durationSec = windowMs / 1000;
|
|
72
|
+
value = windowData.length / durationSec;
|
|
73
|
+
break;
|
|
74
|
+
case 'ratio':
|
|
75
|
+
// Calculate success ratio
|
|
76
|
+
const successes = windowData.filter(p => p.success !== false).length;
|
|
77
|
+
value = (successes / windowData.length) * 100;
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
value = windowData[windowData.length - 1].value;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
sliId: sli.id,
|
|
84
|
+
timestamp: now,
|
|
85
|
+
value,
|
|
86
|
+
total: windowData.length,
|
|
87
|
+
unit: sli.unit,
|
|
88
|
+
windowStart,
|
|
89
|
+
windowEnd: now
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Calculate SLI from QA360 test results
|
|
94
|
+
*/
|
|
95
|
+
calculateFromTestResults(sli, results) {
|
|
96
|
+
const totalTests = results.total;
|
|
97
|
+
const passedTests = results.passed;
|
|
98
|
+
// For quality SLIs, we typically use passed/total
|
|
99
|
+
let value;
|
|
100
|
+
switch (sli.type) {
|
|
101
|
+
case 'quality':
|
|
102
|
+
case 'availability':
|
|
103
|
+
value = totalTests > 0 ? (passedTests / totalTests) * 100 : 0;
|
|
104
|
+
break;
|
|
105
|
+
case 'error_rate':
|
|
106
|
+
value = totalTests > 0 ? (results.failed / totalTests) * 100 : 0;
|
|
107
|
+
break;
|
|
108
|
+
case 'coverage':
|
|
109
|
+
// Assume results include coverage percentage
|
|
110
|
+
value = results.passed; // Treat passed as coverage percentage
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
value = totalTests > 0 ? (passedTests / totalTests) * 100 : 0;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
sliId: sli.id,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
value,
|
|
119
|
+
total: totalTests,
|
|
120
|
+
good: passedTests,
|
|
121
|
+
bad: results.failed,
|
|
122
|
+
unit: sli.unit,
|
|
123
|
+
windowStart: Date.now(),
|
|
124
|
+
windowEnd: Date.now()
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Calculate SLI from latency measurements
|
|
129
|
+
*/
|
|
130
|
+
calculateLatency(sli, latencies, percentile = 'p95') {
|
|
131
|
+
if (latencies.length === 0) {
|
|
132
|
+
return this.emptyMeasurement(sli, Date.now(), Date.now());
|
|
133
|
+
}
|
|
134
|
+
const value = this.percentile(latencies, this.parsePercentile(percentile));
|
|
135
|
+
return {
|
|
136
|
+
sliId: sli.id,
|
|
137
|
+
timestamp: Date.now(),
|
|
138
|
+
value,
|
|
139
|
+
total: latencies.length,
|
|
140
|
+
unit: sli.unit,
|
|
141
|
+
windowStart: Date.now(),
|
|
142
|
+
windowEnd: Date.now()
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Calculate multiple SLIs from a single data source
|
|
147
|
+
*/
|
|
148
|
+
calculateBatch(slis, data) {
|
|
149
|
+
const measurements = [];
|
|
150
|
+
for (const sli of slis) {
|
|
151
|
+
if (Array.isArray(data) && data.length > 0 && 'timestamp' in data[0]) {
|
|
152
|
+
measurements.push(this.calculateFromTimeSeries(sli, data, 86400000));
|
|
153
|
+
}
|
|
154
|
+
else if ('good' in data && 'bad' in data) {
|
|
155
|
+
measurements.push(this.calculateFromEvents(sli, data));
|
|
156
|
+
}
|
|
157
|
+
else if ('total' in data) {
|
|
158
|
+
measurements.push(this.calculateFromTestResults(sli, data));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return measurements;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Calculate percentile value
|
|
165
|
+
*/
|
|
166
|
+
percentile(values, p) {
|
|
167
|
+
if (values.length === 0)
|
|
168
|
+
return 0;
|
|
169
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
170
|
+
const index = (p / 100) * (sorted.length - 1);
|
|
171
|
+
if (index === Math.floor(index)) {
|
|
172
|
+
return sorted[index];
|
|
173
|
+
}
|
|
174
|
+
const lower = sorted[Math.floor(index)];
|
|
175
|
+
const upper = sorted[Math.ceil(index)];
|
|
176
|
+
return lower + (upper - lower) * (index - Math.floor(index));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Parse percentile string to number
|
|
180
|
+
*/
|
|
181
|
+
parsePercentile(p) {
|
|
182
|
+
const match = p.match(/p(\d+(\.\d+)?)/);
|
|
183
|
+
if (match) {
|
|
184
|
+
return parseFloat(match[1]);
|
|
185
|
+
}
|
|
186
|
+
return 95; // Default to p95
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Create empty measurement
|
|
190
|
+
*/
|
|
191
|
+
emptyMeasurement(sli, windowStart, windowEnd) {
|
|
192
|
+
return {
|
|
193
|
+
sliId: sli.id,
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
value: 0,
|
|
196
|
+
total: 0,
|
|
197
|
+
unit: sli.unit,
|
|
198
|
+
windowStart,
|
|
199
|
+
windowEnd
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Aggregate measurements over time
|
|
204
|
+
*/
|
|
205
|
+
aggregateMeasurements(measurements, aggregation) {
|
|
206
|
+
if (measurements.length === 0) {
|
|
207
|
+
throw new Error('No measurements to aggregate');
|
|
208
|
+
}
|
|
209
|
+
const values = measurements.map(m => m.value);
|
|
210
|
+
const sliId = measurements[0].sliId;
|
|
211
|
+
const unit = measurements[0].unit;
|
|
212
|
+
const windowStart = Math.min(...measurements.map(m => m.windowStart));
|
|
213
|
+
const windowEnd = Math.max(...measurements.map(m => m.windowEnd));
|
|
214
|
+
let value;
|
|
215
|
+
switch (aggregation) {
|
|
216
|
+
case 'sum':
|
|
217
|
+
value = values.reduce((sum, v) => sum + v, 0);
|
|
218
|
+
break;
|
|
219
|
+
case 'avg':
|
|
220
|
+
value = values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
221
|
+
break;
|
|
222
|
+
case 'max':
|
|
223
|
+
value = Math.max(...values);
|
|
224
|
+
break;
|
|
225
|
+
case 'min':
|
|
226
|
+
value = Math.min(...values);
|
|
227
|
+
break;
|
|
228
|
+
case 'p50':
|
|
229
|
+
value = this.percentile(values, 50);
|
|
230
|
+
break;
|
|
231
|
+
case 'p95':
|
|
232
|
+
value = this.percentile(values, 95);
|
|
233
|
+
break;
|
|
234
|
+
case 'p99':
|
|
235
|
+
value = this.percentile(values, 99);
|
|
236
|
+
break;
|
|
237
|
+
case 'p99_9':
|
|
238
|
+
value = this.percentile(values, 99.9);
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
value = values[values.length - 1];
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
sliId,
|
|
245
|
+
timestamp: Date.now(),
|
|
246
|
+
value,
|
|
247
|
+
total: measurements.reduce((sum, m) => sum + (m.total || 0), 0),
|
|
248
|
+
unit,
|
|
249
|
+
windowStart,
|
|
250
|
+
windowEnd
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Check if measurement meets threshold
|
|
255
|
+
*/
|
|
256
|
+
meetsThreshold(measurement, threshold) {
|
|
257
|
+
const { operator, value } = threshold;
|
|
258
|
+
switch (operator) {
|
|
259
|
+
case 'gt':
|
|
260
|
+
return measurement.value > value;
|
|
261
|
+
case 'gte':
|
|
262
|
+
return measurement.value >= value;
|
|
263
|
+
case 'lt':
|
|
264
|
+
return measurement.value < value;
|
|
265
|
+
case 'lte':
|
|
266
|
+
return measurement.value <= value;
|
|
267
|
+
case 'eq':
|
|
268
|
+
return measurement.value === value;
|
|
269
|
+
case 'ne':
|
|
270
|
+
return measurement.value !== value;
|
|
271
|
+
default:
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Calculate burn rate for error budget
|
|
277
|
+
*/
|
|
278
|
+
calculateBurnRate(measurements, target, windowMs) {
|
|
279
|
+
if (measurements.length < 2) {
|
|
280
|
+
return {
|
|
281
|
+
current: 0,
|
|
282
|
+
status: 'healthy',
|
|
283
|
+
history: []
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const now = Date.now();
|
|
287
|
+
const windowStart = now - windowMs;
|
|
288
|
+
// Filter measurements within window
|
|
289
|
+
const recentMeasurements = measurements.filter(m => m.timestamp >= windowStart);
|
|
290
|
+
if (recentMeasurements.length === 0) {
|
|
291
|
+
return {
|
|
292
|
+
current: 0,
|
|
293
|
+
status: 'healthy',
|
|
294
|
+
history: []
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
// Calculate error budget burn
|
|
298
|
+
// Each measurement below target contributes to burn
|
|
299
|
+
let totalBudgetDeficit = 0;
|
|
300
|
+
for (const m of recentMeasurements) {
|
|
301
|
+
if (m.value < target) {
|
|
302
|
+
totalBudgetDeficit += (target - m.value);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Expected burn rate (assuming uniform distribution)
|
|
306
|
+
const expectedDeficit = (100 - target) * recentMeasurements.length;
|
|
307
|
+
const burnRate = expectedDeficit > 0 ? totalBudgetDeficit / expectedDeficit : 0;
|
|
308
|
+
// Determine status
|
|
309
|
+
let status;
|
|
310
|
+
if (burnRate >= 5) {
|
|
311
|
+
status = 'critical';
|
|
312
|
+
}
|
|
313
|
+
else if (burnRate >= 2) {
|
|
314
|
+
status = 'warning';
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
status = 'healthy';
|
|
318
|
+
}
|
|
319
|
+
// Project exhaustion if burning fast
|
|
320
|
+
let projectedExhaustion;
|
|
321
|
+
if (burnRate > 1) {
|
|
322
|
+
const remainingBudget = 100 - target - totalBudgetDeficit / recentMeasurements.length;
|
|
323
|
+
if (remainingBudget > 0 && burnRate > 0) {
|
|
324
|
+
const burnPerMs = (100 - target) / windowMs * burnRate;
|
|
325
|
+
projectedExhaustion = now + (remainingBudget / burnPerMs);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Build history
|
|
329
|
+
const history = recentMeasurements.map(m => {
|
|
330
|
+
const deficit = Math.max(0, target - m.value);
|
|
331
|
+
const rate = deficit > 0 ? deficit / (100 - target) : 0;
|
|
332
|
+
return {
|
|
333
|
+
timestamp: m.timestamp,
|
|
334
|
+
rate
|
|
335
|
+
};
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
current: burnRate,
|
|
339
|
+
status,
|
|
340
|
+
projectedExhaustion,
|
|
341
|
+
history
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Determine SLO status from current value and target
|
|
346
|
+
*/
|
|
347
|
+
determineStatus(current, target, warningThreshold = 0.95) {
|
|
348
|
+
if (current >= target) {
|
|
349
|
+
return 'healthy';
|
|
350
|
+
}
|
|
351
|
+
else if (current >= target * warningThreshold) {
|
|
352
|
+
return 'warning';
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
return 'breached';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Create an SLI calculator
|
|
361
|
+
*/
|
|
362
|
+
export function createSLICalculator() {
|
|
363
|
+
return new SLICalculator();
|
|
364
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLO Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks Service Level Objectives and manages error budgets.
|
|
5
|
+
* Monitors SLO compliance and generates reports.
|
|
6
|
+
*/
|
|
7
|
+
import type { SLO, SLI, SLIMeasurement, SLOResult, SLOReport, SLOViolation, ErrorBudget, BurnRate } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* SLO Tracker Options
|
|
10
|
+
*/
|
|
11
|
+
export interface SLOTrackerOptions {
|
|
12
|
+
/** Warning threshold as % of target */
|
|
13
|
+
warningThreshold?: number;
|
|
14
|
+
/** Enable violation tracking */
|
|
15
|
+
trackViolations?: boolean;
|
|
16
|
+
/** Enable alerting */
|
|
17
|
+
enableAlerts?: boolean;
|
|
18
|
+
/** Alert callback */
|
|
19
|
+
onAlert?: (alert: SLOAlert) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* SLO Alert
|
|
23
|
+
*/
|
|
24
|
+
export interface SLOAlert {
|
|
25
|
+
sloId: string;
|
|
26
|
+
sloName: string;
|
|
27
|
+
severity: 'warning' | 'critical';
|
|
28
|
+
message: string;
|
|
29
|
+
currentValue: number;
|
|
30
|
+
target: number;
|
|
31
|
+
errorBudgetRemaining: number;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SLO Tracker class
|
|
36
|
+
*/
|
|
37
|
+
export declare class SLOTracker {
|
|
38
|
+
private slos;
|
|
39
|
+
private slis;
|
|
40
|
+
private measurements;
|
|
41
|
+
private violations;
|
|
42
|
+
private calculator;
|
|
43
|
+
private options;
|
|
44
|
+
constructor(options?: SLOTrackerOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Register an SLO
|
|
47
|
+
*/
|
|
48
|
+
registerSLO(slo: SLO): void;
|
|
49
|
+
/**
|
|
50
|
+
* Register an SLI
|
|
51
|
+
*/
|
|
52
|
+
registerSLI(sli: SLI): void;
|
|
53
|
+
/**
|
|
54
|
+
* Register multiple SLOs
|
|
55
|
+
*/
|
|
56
|
+
registerSLOs(slos: SLO[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Register multiple SLIs
|
|
59
|
+
*/
|
|
60
|
+
registerSLIs(slis: SLI[]): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get an SLO by ID
|
|
63
|
+
*/
|
|
64
|
+
getSLO(id: string): SLO | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Get an SLI by ID
|
|
67
|
+
*/
|
|
68
|
+
getSLI(id: string): SLI | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Get all registered SLOs
|
|
71
|
+
*/
|
|
72
|
+
getAllSLOs(): SLO[];
|
|
73
|
+
/**
|
|
74
|
+
* Get all registered SLIs
|
|
75
|
+
*/
|
|
76
|
+
getAllSLIs(): SLI[];
|
|
77
|
+
/**
|
|
78
|
+
* Record a measurement for an SLI
|
|
79
|
+
*/
|
|
80
|
+
recordMeasurement(measurement: SLIMeasurement): void;
|
|
81
|
+
/**
|
|
82
|
+
* Get measurements for an SLI
|
|
83
|
+
*/
|
|
84
|
+
getMeasurements(sliId: string, windowMs?: number): SLIMeasurement[];
|
|
85
|
+
/**
|
|
86
|
+
* Calculate SLO result
|
|
87
|
+
*/
|
|
88
|
+
calculateSLOResult(sloId: string): SLOResult | null;
|
|
89
|
+
/**
|
|
90
|
+
* Calculate all SLO results
|
|
91
|
+
*/
|
|
92
|
+
calculateAllResults(): SLOResult[];
|
|
93
|
+
/**
|
|
94
|
+
* Generate SLO report
|
|
95
|
+
*/
|
|
96
|
+
generateReport(period?: {
|
|
97
|
+
start: number;
|
|
98
|
+
end: number;
|
|
99
|
+
}): SLOReport;
|
|
100
|
+
/**
|
|
101
|
+
* Get error budget for an SLO
|
|
102
|
+
*/
|
|
103
|
+
getErrorBudget(sloId: string): ErrorBudget | null;
|
|
104
|
+
/**
|
|
105
|
+
* Calculate burn rate for an SLO
|
|
106
|
+
*/
|
|
107
|
+
calculateBurnRate(sloId: string): BurnRate | null;
|
|
108
|
+
/**
|
|
109
|
+
* Get violations for an SLO
|
|
110
|
+
*/
|
|
111
|
+
getViolations(sloId?: string): SLOViolation[];
|
|
112
|
+
/**
|
|
113
|
+
* Clear violations
|
|
114
|
+
*/
|
|
115
|
+
clearViolations(sloId?: string): void;
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a violation
|
|
118
|
+
*/
|
|
119
|
+
resolveViolation(violationId: string): void;
|
|
120
|
+
/**
|
|
121
|
+
* Calculate error budget from current value and target
|
|
122
|
+
*/
|
|
123
|
+
private calculateErrorBudget;
|
|
124
|
+
/**
|
|
125
|
+
* Update SLOs that depend on an SLI
|
|
126
|
+
*/
|
|
127
|
+
private updateDependentSLOs;
|
|
128
|
+
/**
|
|
129
|
+
* Check for SLO violation
|
|
130
|
+
*/
|
|
131
|
+
private checkForViolation;
|
|
132
|
+
/**
|
|
133
|
+
* Generate alerts for SLO status
|
|
134
|
+
*/
|
|
135
|
+
private generateAlerts;
|
|
136
|
+
/**
|
|
137
|
+
* Get the longest window from all SLOs
|
|
138
|
+
*/
|
|
139
|
+
private getLongestWindow;
|
|
140
|
+
/**
|
|
141
|
+
* Clear all data
|
|
142
|
+
*/
|
|
143
|
+
clear(): void;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Create an SLO tracker
|
|
147
|
+
*/
|
|
148
|
+
export declare function createSLOTracker(options?: SLOTrackerOptions): SLOTracker;
|