@checkstack/healthcheck-script-backend 0.7.13 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/package.json +7 -7
- package/src/execute-collector.test.ts +42 -2
- package/src/execute-collector.ts +45 -11
- package/src/inline-script-collector.ts +16 -5
- package/src/strategy.ts +20 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
# @checkstack/healthcheck-script-backend
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8cad340: Fix collectors hard-failing on successful-but-non-OK application results.
|
|
8
|
+
|
|
9
|
+
A health-check collector must fail only when the TRANSPORT fails (the probe
|
|
10
|
+
could not complete: DNS/connect/TLS failure, timeout, aborted, unspawnable
|
|
11
|
+
process). A successfully-received result that is simply "not what you hoped" is
|
|
12
|
+
an assertable metric, not a collector failure - the user's assertions (or the
|
|
13
|
+
no-assertion default) decide health.
|
|
14
|
+
|
|
15
|
+
BREAKING CHANGE: checks that previously relied on a collector auto-failing on a
|
|
16
|
+
non-OK result will now report healthy unless an explicit assertion is added.
|
|
17
|
+
Affected collectors:
|
|
18
|
+
|
|
19
|
+
- HTTP request collector: a received response (including 4xx/5xx) is now a
|
|
20
|
+
successful collection. `statusCode` / `statusText` / `success` are exposed as
|
|
21
|
+
metrics; the collector no longer sets `error` on a non-2xx. Add a
|
|
22
|
+
`statusCode equals 200` assertion to fail on non-200 (or `statusCode equals
|
|
23
|
+
404` for a check that wants a 404). Only a real transport failure fails the
|
|
24
|
+
collector.
|
|
25
|
+
- gRPC health collector: a completed health RPC returning `NOT_SERVING` /
|
|
26
|
+
`SERVICE_UNKNOWN` / `UNKNOWN` is now a successful collection. `serving` /
|
|
27
|
+
`status` are assertable metrics; only a real RPC transport error fails the
|
|
28
|
+
collector.
|
|
29
|
+
- Jenkins node-health collector: offline nodes are now an assertable metric
|
|
30
|
+
(`offlineNodes`); a successful all-nodes API call no longer fails the
|
|
31
|
+
collector when some nodes are offline.
|
|
32
|
+
- Script (shell) execute collector: a non-zero exit code is now an assertable
|
|
33
|
+
metric (`exitCode` / `success`); the collector no longer hard-fails on a
|
|
34
|
+
non-zero exit. A timeout or a script that could not be spawned still fails the
|
|
35
|
+
collector (those are transport failures). Add a `success is true` (or
|
|
36
|
+
`exitCode equals 0`) assertion to fail on a non-zero exit.
|
|
37
|
+
|
|
38
|
+
Other strategies (DNS, TCP, TLS, ping, ssh, mysql, postgres, redis, rcon,
|
|
39
|
+
hardware, and the inline-script collector) were audited and already failed only
|
|
40
|
+
on genuine transport failures.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 8cad340: Retune anomaly-detection defaults across every health-check strategy and the
|
|
45
|
+
hardware collector for a low-noise, problem-focused out-of-the-box experience.
|
|
46
|
+
|
|
47
|
+
The detection engine already learns a per-metric baseline, debounces with a
|
|
48
|
+
confirmation window, and applies practical-significance floors. This pass tunes
|
|
49
|
+
the per-metric **defaults** so a fresh install alerts only on genuine,
|
|
50
|
+
statistically-significant, problem-mapping deviations instead of flooding on
|
|
51
|
+
every metric that wiggles. 264 metrics were reviewed:
|
|
52
|
+
|
|
53
|
+
- **Default-disabled** the high-noise and un-baselineable classes that were
|
|
54
|
+
alerting for no good reason: raw identifiers and counts (status codes, error
|
|
55
|
+
and row counts, build counts, player and executor counts), config echoes and
|
|
56
|
+
near-constants (probe packet counts, CPU core count, total/swap memory),
|
|
57
|
+
payload-size and other run-to-run-volatile values, and deterministic values
|
|
58
|
+
like certificate days-remaining (governed by the check's own static-threshold
|
|
59
|
+
health logic, not statistics). These stay chartable and can be re-enabled per
|
|
60
|
+
field.
|
|
61
|
+
- **Hardened** the signals that should alert - latency/response/execution time
|
|
62
|
+
and availability/success/saturation percentages - with confirmation windows
|
|
63
|
+
and absolute + relative floors so brief spikes and sub-threshold jitter no
|
|
64
|
+
longer flap, and prefer percentage metrics over their absolute twins.
|
|
65
|
+
|
|
66
|
+
No detection-engine or schema changes; only per-metric `x-anomaly-*` defaults.
|
|
67
|
+
Users who had opted into any now-disabled metric keep their explicit override.
|
|
68
|
+
|
|
69
|
+
- Updated dependencies [8cad340]
|
|
70
|
+
- Updated dependencies [8cad340]
|
|
71
|
+
- Updated dependencies [8cad340]
|
|
72
|
+
- Updated dependencies [8cad340]
|
|
73
|
+
- Updated dependencies [8cad340]
|
|
74
|
+
- Updated dependencies [8cad340]
|
|
75
|
+
- Updated dependencies [8cad340]
|
|
76
|
+
- Updated dependencies [8cad340]
|
|
77
|
+
- Updated dependencies [8cad340]
|
|
78
|
+
- @checkstack/script-packages-backend@0.3.15
|
|
79
|
+
- @checkstack/backend-api@0.25.0
|
|
80
|
+
- @checkstack/healthcheck-common@1.8.0
|
|
81
|
+
- @checkstack/common@0.17.0
|
|
82
|
+
- @checkstack/secrets-common@0.2.5
|
|
83
|
+
|
|
84
|
+
## 0.7.14
|
|
85
|
+
|
|
86
|
+
### Patch Changes
|
|
87
|
+
|
|
88
|
+
- Updated dependencies [2ec8f64]
|
|
89
|
+
- @checkstack/backend-api@0.24.1
|
|
90
|
+
- @checkstack/script-packages-backend@0.3.14
|
|
91
|
+
|
|
3
92
|
## 0.7.13
|
|
4
93
|
|
|
5
94
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-script-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"checkstack": {
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
"pack": "bunx @checkstack/scripts plugin-pack"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@checkstack/backend-api": "0.
|
|
18
|
-
"@checkstack/common": "0.
|
|
19
|
-
"@checkstack/healthcheck-common": "1.
|
|
20
|
-
"@checkstack/script-packages-backend": "0.3.
|
|
21
|
-
"@checkstack/secrets-common": "0.2.
|
|
17
|
+
"@checkstack/backend-api": "0.25.0",
|
|
18
|
+
"@checkstack/common": "0.17.0",
|
|
19
|
+
"@checkstack/healthcheck-common": "1.8.0",
|
|
20
|
+
"@checkstack/script-packages-backend": "0.3.15",
|
|
21
|
+
"@checkstack/secrets-common": "0.2.5"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/bun": "^1.0.0",
|
|
25
25
|
"typescript": "^5.0.0",
|
|
26
26
|
"@checkstack/tsconfig": "0.0.7",
|
|
27
|
-
"@checkstack/scripts": "0.6.
|
|
27
|
+
"@checkstack/scripts": "0.6.3"
|
|
28
28
|
},
|
|
29
29
|
"description": "Checkstack healthcheck-script-backend plugin",
|
|
30
30
|
"author": {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, mock } from "bun:test";
|
|
2
|
+
import { evaluateAssertions } from "@checkstack/backend-api";
|
|
2
3
|
import { ExecuteCollector, type ExecuteConfig } from "./execute-collector";
|
|
3
4
|
import type { ScriptTransportClient } from "./transport-client";
|
|
4
5
|
|
|
@@ -47,7 +48,13 @@ describe("ExecuteCollector", () => {
|
|
|
47
48
|
expect(result.error).toBeUndefined();
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
// Regression: a script that ran to completion and exited non-zero is a
|
|
52
|
+
// SUCCESSFUL collection - the command executed and reported a result. The
|
|
53
|
+
// collector must NOT set `error` (the executor treats that as a transport
|
|
54
|
+
// failure and hard-fails the run). `exitCode` / `success` are assertable
|
|
55
|
+
// metrics. Previously this asserted the wrong behavior (`error` contains
|
|
56
|
+
// "Exit code: 1").
|
|
57
|
+
it("does not hard-fail the collector on a non-zero exit code", async () => {
|
|
51
58
|
const collector = new ExecuteCollector();
|
|
52
59
|
const client = createMockClient({
|
|
53
60
|
exitCode: 1,
|
|
@@ -62,7 +69,40 @@ describe("ExecuteCollector", () => {
|
|
|
62
69
|
|
|
63
70
|
expect(result.result.exitCode).toBe(1);
|
|
64
71
|
expect(result.result.success).toBe(false);
|
|
65
|
-
|
|
72
|
+
// Transport succeeded: no error field is set.
|
|
73
|
+
expect(result.error).toBeUndefined();
|
|
74
|
+
|
|
75
|
+
// A "success is true" assertion fails - the user decides this is
|
|
76
|
+
// unhealthy, not the collector.
|
|
77
|
+
const failed = evaluateAssertions(
|
|
78
|
+
[{ field: "success", operator: "isTrue" }],
|
|
79
|
+
result.result as Record<string, unknown>,
|
|
80
|
+
);
|
|
81
|
+
expect(failed).not.toBeNull();
|
|
82
|
+
|
|
83
|
+
// A check that WANTS exit code 1 (e.g. "this file should be absent") can
|
|
84
|
+
// be green by asserting on the exit code.
|
|
85
|
+
const failedWanted = evaluateAssertions(
|
|
86
|
+
[{ field: "exitCode", operator: "equals", value: 1 }],
|
|
87
|
+
result.result as Record<string, unknown>,
|
|
88
|
+
);
|
|
89
|
+
expect(failedWanted).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// A timeout IS a transport failure: the script could not complete. The
|
|
93
|
+
// collector must surface it as an `error`.
|
|
94
|
+
it("hard-fails the collector on a timeout (transport failure)", async () => {
|
|
95
|
+
const collector = new ExecuteCollector();
|
|
96
|
+
const client = createMockClient({ timedOut: true, exitCode: -1 });
|
|
97
|
+
|
|
98
|
+
const result = await collector.execute({
|
|
99
|
+
config: { script: "sleep 999", timeout: 100 },
|
|
100
|
+
client,
|
|
101
|
+
pluginId: "test",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(result.result.timedOut).toBe(true);
|
|
105
|
+
expect(result.error).toContain("timed out");
|
|
66
106
|
});
|
|
67
107
|
|
|
68
108
|
it("should handle timeout", async () => {
|
package/src/execute-collector.ts
CHANGED
|
@@ -174,8 +174,11 @@ const executeResultSchema = healthResultSchema({
|
|
|
174
174
|
exitCode: healthResultNumber({
|
|
175
175
|
"x-chart-type": "counter",
|
|
176
176
|
"x-chart-label": "Exit Code",
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
// Exit codes are arbitrary integers (0, 1, 2, 127, -1, ...) with no
|
|
178
|
+
// stable distribution; the pass/fail signal they carry is already
|
|
179
|
+
// covered by `success`. Charting it stays useful; alerting on it just
|
|
180
|
+
// double-fires (and flaps for scripts that vary their nonzero codes).
|
|
181
|
+
"x-anomaly-enabled": false,
|
|
179
182
|
}),
|
|
180
183
|
stdout: healthResultString({
|
|
181
184
|
"x-chart-type": "text",
|
|
@@ -193,7 +196,9 @@ const executeResultSchema = healthResultSchema({
|
|
|
193
196
|
"x-chart-unit": "ms",
|
|
194
197
|
"x-anomaly-enabled": true,
|
|
195
198
|
"x-anomaly-direction": "lower-is-better",
|
|
196
|
-
|
|
199
|
+
// Err wider than the default band: only sustained, materially-larger
|
|
200
|
+
// run times should alert, not normal jitter.
|
|
201
|
+
"x-anomaly-sensitivity": 2.5,
|
|
197
202
|
"x-anomaly-confirmation-window": 3,
|
|
198
203
|
"x-anomaly-min-absolute-delta": 50,
|
|
199
204
|
"x-anomaly-min-relative-delta": 0.5,
|
|
@@ -207,8 +212,10 @@ const executeResultSchema = healthResultSchema({
|
|
|
207
212
|
timedOut: healthResultBoolean({
|
|
208
213
|
"x-chart-type": "boolean",
|
|
209
214
|
"x-chart-label": "Timed Out",
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
// A timeout always implies `success: false`, so alerting here on top of
|
|
216
|
+
// `success` double-fires on the same incident. Keep it chartable, let
|
|
217
|
+
// `success` carry the alert.
|
|
218
|
+
"x-anomaly-enabled": false,
|
|
212
219
|
}),
|
|
213
220
|
});
|
|
214
221
|
|
|
@@ -222,6 +229,10 @@ const executeAggregatedFields = {
|
|
|
222
229
|
"x-chart-unit": "ms",
|
|
223
230
|
"x-anomaly-enabled": true,
|
|
224
231
|
"x-anomaly-direction": "lower-is-better",
|
|
232
|
+
"x-anomaly-sensitivity": 2.5,
|
|
233
|
+
"x-anomaly-confirmation-window": 3,
|
|
234
|
+
"x-anomaly-min-absolute-delta": 50,
|
|
235
|
+
"x-anomaly-min-relative-delta": 0.5,
|
|
225
236
|
}),
|
|
226
237
|
successRate: aggregatedRate({
|
|
227
238
|
"x-chart-type": "gauge",
|
|
@@ -229,6 +240,10 @@ const executeAggregatedFields = {
|
|
|
229
240
|
"x-chart-unit": "%",
|
|
230
241
|
"x-anomaly-enabled": true,
|
|
231
242
|
"x-anomaly-direction": "higher-is-better",
|
|
243
|
+
// Only a real drop in availability should alert, not single-sample
|
|
244
|
+
// jitter in the rate.
|
|
245
|
+
"x-anomaly-confirmation-window": 3,
|
|
246
|
+
"x-anomaly-min-absolute-delta": 5,
|
|
232
247
|
}),
|
|
233
248
|
};
|
|
234
249
|
|
|
@@ -253,8 +268,12 @@ export type ExecuteAggregatedResult = InferAggregatedResult<
|
|
|
253
268
|
* awk -v l="$load" 'BEGIN { exit (l+0 > 0.60) ? 1 : 0 }'
|
|
254
269
|
* ```
|
|
255
270
|
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
271
|
+
* The exit code is exposed as the assertable `exitCode` / `success` metric
|
|
272
|
+
* (`success` is true when the exit code is 0). It does NOT hard-fail the
|
|
273
|
+
* collector: add an assertion (e.g. "success is true", or "exitCode equals 0")
|
|
274
|
+
* to turn a non-zero exit into an unhealthy result. Only a genuine transport
|
|
275
|
+
* failure (the script could not be spawned, or it timed out) fails the
|
|
276
|
+
* collector itself. stdout / stderr are captured and reported with the run.
|
|
258
277
|
*/
|
|
259
278
|
export class ExecuteCollector implements CollectorStrategy<
|
|
260
279
|
ScriptTransportClient,
|
|
@@ -264,7 +283,8 @@ export class ExecuteCollector implements CollectorStrategy<
|
|
|
264
283
|
> {
|
|
265
284
|
id = "execute";
|
|
266
285
|
displayName = "Shell Script";
|
|
267
|
-
description =
|
|
286
|
+
description =
|
|
287
|
+
"Run a shell script and expose its exit code as an assertable metric";
|
|
268
288
|
|
|
269
289
|
supportedPlugins = [pluginMetadata];
|
|
270
290
|
|
|
@@ -354,7 +374,23 @@ export class ExecuteCollector implements CollectorStrategy<
|
|
|
354
374
|
});
|
|
355
375
|
|
|
356
376
|
const executionTimeMs = Date.now() - startTime;
|
|
377
|
+
// `success` (exit code 0) and the raw `exitCode` are ASSERTABLE METRICS,
|
|
378
|
+
// not a collector-failure signal. A script that ran to completion and
|
|
379
|
+
// exited non-zero is a SUCCESSFUL collection: the command executed and
|
|
380
|
+
// reported a result. Whether a non-zero exit makes the check unhealthy is
|
|
381
|
+
// the user's decision via assertions (e.g. "exitCode equals 0", or
|
|
382
|
+
// "exitCode equals 1" when a non-zero exit is the wanted state). We must
|
|
383
|
+
// NOT set the `error` field on a non-zero exit, otherwise the executor
|
|
384
|
+
// hard-fails the run before assertions get to decide.
|
|
385
|
+
//
|
|
386
|
+
// Only a genuine transport failure fails the collector: `masked.error`
|
|
387
|
+
// (the script could not be spawned / a runner-level error) or `timedOut`
|
|
388
|
+
// (the script could not complete within the timeout). Those propagate as
|
|
389
|
+
// the collector `error`.
|
|
357
390
|
const success = response.exitCode === 0 && !response.timedOut;
|
|
391
|
+
const transportError = response.timedOut
|
|
392
|
+
? (masked.error ?? "Script execution timed out")
|
|
393
|
+
: masked.error;
|
|
358
394
|
|
|
359
395
|
return {
|
|
360
396
|
result: {
|
|
@@ -365,9 +401,7 @@ export class ExecuteCollector implements CollectorStrategy<
|
|
|
365
401
|
success,
|
|
366
402
|
timedOut: response.timedOut,
|
|
367
403
|
},
|
|
368
|
-
error:
|
|
369
|
-
masked.error ??
|
|
370
|
-
(success ? undefined : `Exit code: ${response.exitCode}`),
|
|
404
|
+
error: transportError,
|
|
371
405
|
};
|
|
372
406
|
}
|
|
373
407
|
|
|
@@ -153,8 +153,12 @@ const inlineScriptResultSchema = healthResultSchema({
|
|
|
153
153
|
value: healthResultNumber({
|
|
154
154
|
"x-chart-type": "line",
|
|
155
155
|
"x-chart-label": "Value",
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
// Free-form user-returned number with no stable baseline and no
|
|
157
|
+
// universal good/bad direction (it could be a load average, a queue
|
|
158
|
+
// depth, a row count, anything). A learned band over an arbitrary
|
|
159
|
+
// value is the core alert-fatigue case, so do not alert by default.
|
|
160
|
+
// It stays fully chartable and a user can opt in per check.
|
|
161
|
+
"x-anomaly-enabled": false,
|
|
158
162
|
}).optional(),
|
|
159
163
|
executionTimeMs: healthResultNumber({
|
|
160
164
|
"x-chart-type": "line",
|
|
@@ -162,7 +166,7 @@ const inlineScriptResultSchema = healthResultSchema({
|
|
|
162
166
|
"x-chart-unit": "ms",
|
|
163
167
|
"x-anomaly-enabled": true,
|
|
164
168
|
"x-anomaly-direction": "lower-is-better",
|
|
165
|
-
"x-anomaly-sensitivity": 2,
|
|
169
|
+
"x-anomaly-sensitivity": 2.5,
|
|
166
170
|
"x-anomaly-confirmation-window": 3,
|
|
167
171
|
"x-anomaly-min-absolute-delta": 50,
|
|
168
172
|
"x-anomaly-min-relative-delta": 0.5,
|
|
@@ -170,8 +174,9 @@ const inlineScriptResultSchema = healthResultSchema({
|
|
|
170
174
|
timedOut: healthResultBoolean({
|
|
171
175
|
"x-chart-type": "boolean",
|
|
172
176
|
"x-chart-label": "Timed Out",
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
// Always implies `success: false`; alerting here as well double-fires
|
|
178
|
+
// on the same incident. Keep chartable, let `success` carry the alert.
|
|
179
|
+
"x-anomaly-enabled": false,
|
|
175
180
|
}),
|
|
176
181
|
});
|
|
177
182
|
|
|
@@ -185,6 +190,10 @@ const inlineScriptAggregatedFields = {
|
|
|
185
190
|
"x-chart-unit": "ms",
|
|
186
191
|
"x-anomaly-enabled": true,
|
|
187
192
|
"x-anomaly-direction": "lower-is-better",
|
|
193
|
+
"x-anomaly-sensitivity": 2.5,
|
|
194
|
+
"x-anomaly-confirmation-window": 3,
|
|
195
|
+
"x-anomaly-min-absolute-delta": 50,
|
|
196
|
+
"x-anomaly-min-relative-delta": 0.5,
|
|
188
197
|
}),
|
|
189
198
|
successRate: aggregatedRate({
|
|
190
199
|
"x-chart-type": "gauge",
|
|
@@ -192,6 +201,8 @@ const inlineScriptAggregatedFields = {
|
|
|
192
201
|
"x-chart-unit": "%",
|
|
193
202
|
"x-anomaly-enabled": true,
|
|
194
203
|
"x-anomaly-direction": "higher-is-better",
|
|
204
|
+
"x-anomaly-confirmation-window": 3,
|
|
205
|
+
"x-anomaly-min-absolute-delta": 5,
|
|
195
206
|
}),
|
|
196
207
|
};
|
|
197
208
|
|
package/src/strategy.ts
CHANGED
|
@@ -73,7 +73,7 @@ const scriptResultSchema = healthResultSchema({
|
|
|
73
73
|
"x-chart-unit": "ms",
|
|
74
74
|
"x-anomaly-enabled": true,
|
|
75
75
|
"x-anomaly-direction": "lower-is-better",
|
|
76
|
-
"x-anomaly-sensitivity": 2,
|
|
76
|
+
"x-anomaly-sensitivity": 2.5,
|
|
77
77
|
"x-anomaly-confirmation-window": 3,
|
|
78
78
|
"x-anomaly-min-absolute-delta": 50,
|
|
79
79
|
"x-anomaly-min-relative-delta": 0.5,
|
|
@@ -81,8 +81,9 @@ const scriptResultSchema = healthResultSchema({
|
|
|
81
81
|
exitCode: healthResultNumber({
|
|
82
82
|
"x-chart-type": "counter",
|
|
83
83
|
"x-chart-label": "Exit Code",
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
// Arbitrary integer with no stable distribution; the pass/fail signal
|
|
85
|
+
// it carries is already covered by `success`. Keep chartable only.
|
|
86
|
+
"x-anomaly-enabled": false,
|
|
86
87
|
}).optional(),
|
|
87
88
|
success: healthResultBoolean({
|
|
88
89
|
"x-chart-type": "boolean",
|
|
@@ -93,8 +94,9 @@ const scriptResultSchema = healthResultSchema({
|
|
|
93
94
|
timedOut: healthResultBoolean({
|
|
94
95
|
"x-chart-type": "boolean",
|
|
95
96
|
"x-chart-label": "Timed Out",
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
// Always implies `success: false`; alerting here as well double-fires
|
|
98
|
+
// on the same incident. Keep chartable, let `success` carry the alert.
|
|
99
|
+
"x-anomaly-enabled": false,
|
|
98
100
|
}),
|
|
99
101
|
error: healthResultString({
|
|
100
102
|
"x-chart-type": "status",
|
|
@@ -113,6 +115,10 @@ const scriptAggregatedFields = {
|
|
|
113
115
|
"x-chart-unit": "ms",
|
|
114
116
|
"x-anomaly-enabled": true,
|
|
115
117
|
"x-anomaly-direction": "lower-is-better",
|
|
118
|
+
"x-anomaly-sensitivity": 2.5,
|
|
119
|
+
"x-anomaly-confirmation-window": 3,
|
|
120
|
+
"x-anomaly-min-absolute-delta": 50,
|
|
121
|
+
"x-anomaly-min-relative-delta": 0.5,
|
|
116
122
|
}),
|
|
117
123
|
successRate: aggregatedRate({
|
|
118
124
|
"x-chart-type": "gauge",
|
|
@@ -120,18 +126,23 @@ const scriptAggregatedFields = {
|
|
|
120
126
|
"x-chart-unit": "%",
|
|
121
127
|
"x-anomaly-enabled": true,
|
|
122
128
|
"x-anomaly-direction": "higher-is-better",
|
|
129
|
+
"x-anomaly-confirmation-window": 3,
|
|
130
|
+
"x-anomaly-min-absolute-delta": 5,
|
|
123
131
|
}),
|
|
124
132
|
errorCount: aggregatedCounter({
|
|
125
133
|
"x-chart-type": "counter",
|
|
126
134
|
"x-chart-label": "Errors",
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
// Raw per-bucket count is the absolute twin of `successRate`; it scales
|
|
136
|
+
// with bucket volume and has no stable baseline. Prefer the percent
|
|
137
|
+
// form (`successRate`) for alerting and keep this chartable only.
|
|
138
|
+
"x-anomaly-enabled": false,
|
|
129
139
|
}),
|
|
130
140
|
timeoutCount: aggregatedCounter({
|
|
131
141
|
"x-chart-type": "counter",
|
|
132
142
|
"x-chart-label": "Timeouts",
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
// Same as errorCount: a volume-scaled absolute twin of the failure
|
|
144
|
+
// rate already expressed by `successRate`. Chartable only.
|
|
145
|
+
"x-anomaly-enabled": false,
|
|
135
146
|
}),
|
|
136
147
|
};
|
|
137
148
|
|