@miragon/pulse-analytics-client 0.0.0-stage → 0.21.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/LICENSE +21 -0
- package/README.md +84 -2
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/metric-names.d.ts +43 -0
- package/dist/metric-names.d.ts.map +1 -0
- package/dist/metric-names.js +53 -0
- package/dist/metric-names.js.map +1 -0
- package/dist/metrics-contract-labels.test.d.ts +2 -0
- package/dist/metrics-contract-labels.test.d.ts.map +1 -0
- package/dist/metrics-contract-labels.test.js +169 -0
- package/dist/metrics-contract-labels.test.js.map +1 -0
- package/dist/metrics-contract.test.d.ts +2 -0
- package/dist/metrics-contract.test.d.ts.map +1 -0
- package/dist/metrics-contract.test.js +247 -0
- package/dist/metrics-contract.test.js.map +1 -0
- package/dist/prometheus-client.test.d.ts +2 -0
- package/dist/prometheus-client.test.d.ts.map +1 -0
- package/dist/prometheus-client.test.js +265 -0
- package/dist/prometheus-client.test.js.map +1 -0
- package/dist/prometheus.d.ts +100 -0
- package/dist/prometheus.d.ts.map +1 -0
- package/dist/prometheus.js +295 -0
- package/dist/prometheus.js.map +1 -0
- package/dist/prometheus.test.d.ts +2 -0
- package/dist/prometheus.test.d.ts.map +1 -0
- package/dist/prometheus.test.js +54 -0
- package/dist/prometheus.test.js.map +1 -0
- package/dist/promql-labels.test-support.d.ts +53 -0
- package/dist/promql-labels.test-support.d.ts.map +1 -0
- package/dist/promql-labels.test-support.js +237 -0
- package/dist/promql-labels.test-support.js.map +1 -0
- package/dist/promql-labels.test.d.ts +2 -0
- package/dist/promql-labels.test.d.ts.map +1 -0
- package/dist/promql-labels.test.js +190 -0
- package/dist/promql-labels.test.js.map +1 -0
- package/dist/promql-parse.test-support.d.ts +57 -0
- package/dist/promql-parse.test-support.d.ts.map +1 -0
- package/dist/promql-parse.test-support.js +284 -0
- package/dist/promql-parse.test-support.js.map +1 -0
- package/dist/promql-reading-rules.test-support.d.ts +29 -0
- package/dist/promql-reading-rules.test-support.d.ts.map +1 -0
- package/dist/promql-reading-rules.test-support.js +79 -0
- package/dist/promql-reading-rules.test-support.js.map +1 -0
- package/dist/promql-reading-rules.test.d.ts +2 -0
- package/dist/promql-reading-rules.test.d.ts.map +1 -0
- package/dist/promql-reading-rules.test.js +61 -0
- package/dist/promql-reading-rules.test.js.map +1 -0
- package/dist/promql-sources.test-support.d.ts +29 -0
- package/dist/promql-sources.test-support.d.ts.map +1 -0
- package/dist/promql-sources.test-support.js +112 -0
- package/dist/promql-sources.test-support.js.map +1 -0
- package/dist/promql-sources.test.d.ts +2 -0
- package/dist/promql-sources.test.d.ts.map +1 -0
- package/dist/promql-sources.test.js +93 -0
- package/dist/promql-sources.test.js.map +1 -0
- package/dist/queries/as-of.test.d.ts +2 -0
- package/dist/queries/as-of.test.d.ts.map +1 -0
- package/dist/queries/as-of.test.js +80 -0
- package/dist/queries/as-of.test.js.map +1 -0
- package/dist/queries/cluster-compare.d.ts +62 -0
- package/dist/queries/cluster-compare.d.ts.map +1 -0
- package/dist/queries/cluster-compare.js +70 -0
- package/dist/queries/cluster-compare.js.map +1 -0
- package/dist/queries/cluster-compare.test.d.ts +2 -0
- package/dist/queries/cluster-compare.test.d.ts.map +1 -0
- package/dist/queries/cluster-compare.test.js +81 -0
- package/dist/queries/cluster-compare.test.js.map +1 -0
- package/dist/queries/dashboard.d.ts +60 -0
- package/dist/queries/dashboard.d.ts.map +1 -0
- package/dist/queries/dashboard.js +292 -0
- package/dist/queries/dashboard.js.map +1 -0
- package/dist/queries/dashboard.test.d.ts +2 -0
- package/dist/queries/dashboard.test.d.ts.map +1 -0
- package/dist/queries/dashboard.test.js +423 -0
- package/dist/queries/dashboard.test.js.map +1 -0
- package/dist/queries/element.d.ts +68 -0
- package/dist/queries/element.d.ts.map +1 -0
- package/dist/queries/element.js +98 -0
- package/dist/queries/element.js.map +1 -0
- package/dist/queries/element.test.d.ts +2 -0
- package/dist/queries/element.test.d.ts.map +1 -0
- package/dist/queries/element.test.js +105 -0
- package/dist/queries/element.test.js.map +1 -0
- package/dist/queries/engine-compare.d.ts +52 -0
- package/dist/queries/engine-compare.d.ts.map +1 -0
- package/dist/queries/engine-compare.js +57 -0
- package/dist/queries/engine-compare.js.map +1 -0
- package/dist/queries/engine-compare.test.d.ts +2 -0
- package/dist/queries/engine-compare.test.d.ts.map +1 -0
- package/dist/queries/engine-compare.test.js +159 -0
- package/dist/queries/engine-compare.test.js.map +1 -0
- package/dist/queries/engine-landscape.d.ts +92 -0
- package/dist/queries/engine-landscape.d.ts.map +1 -0
- package/dist/queries/engine-landscape.js +183 -0
- package/dist/queries/engine-landscape.js.map +1 -0
- package/dist/queries/engine-landscape.test.d.ts +2 -0
- package/dist/queries/engine-landscape.test.d.ts.map +1 -0
- package/dist/queries/engine-landscape.test.js +171 -0
- package/dist/queries/engine-landscape.test.js.map +1 -0
- package/dist/queries/failures.d.ts +33 -0
- package/dist/queries/failures.d.ts.map +1 -0
- package/dist/queries/failures.js +31 -0
- package/dist/queries/failures.js.map +1 -0
- package/dist/queries/health.d.ts +77 -0
- package/dist/queries/health.d.ts.map +1 -0
- package/dist/queries/health.js +126 -0
- package/dist/queries/health.js.map +1 -0
- package/dist/queries/health.test.d.ts +2 -0
- package/dist/queries/health.test.d.ts.map +1 -0
- package/dist/queries/health.test.js +187 -0
- package/dist/queries/health.test.js.map +1 -0
- package/dist/queries/helpers.d.ts +184 -0
- package/dist/queries/helpers.d.ts.map +1 -0
- package/dist/queries/helpers.js +219 -0
- package/dist/queries/helpers.js.map +1 -0
- package/dist/queries/helpers.test.d.ts +2 -0
- package/dist/queries/helpers.test.d.ts.map +1 -0
- package/dist/queries/helpers.test.js +53 -0
- package/dist/queries/helpers.test.js.map +1 -0
- package/dist/queries/index.d.ts +19 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +10 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/performance.d.ts +103 -0
- package/dist/queries/performance.d.ts.map +1 -0
- package/dist/queries/performance.js +173 -0
- package/dist/queries/performance.js.map +1 -0
- package/dist/queries/performance.test.d.ts +2 -0
- package/dist/queries/performance.test.d.ts.map +1 -0
- package/dist/queries/performance.test.js +265 -0
- package/dist/queries/performance.test.js.map +1 -0
- package/dist/queries/version-compare.d.ts +41 -0
- package/dist/queries/version-compare.d.ts.map +1 -0
- package/dist/queries/version-compare.js +44 -0
- package/dist/queries/version-compare.js.map +1 -0
- package/dist/queries/version-compare.test.d.ts +2 -0
- package/dist/queries/version-compare.test.d.ts.map +1 -0
- package/dist/queries/version-compare.test.js +162 -0
- package/dist/queries/version-compare.test.js.map +1 -0
- package/dist/queries/windows.d.ts +40 -0
- package/dist/queries/windows.d.ts.map +1 -0
- package/dist/queries/windows.js +56 -0
- package/dist/queries/windows.js.map +1 -0
- package/dist/query-honesty.test.d.ts +2 -0
- package/dist/query-honesty.test.d.ts.map +1 -0
- package/dist/query-honesty.test.js +294 -0
- package/dist/query-honesty.test.js.map +1 -0
- package/dist/query-scenarios.test-support.d.ts +49 -0
- package/dist/query-scenarios.test-support.d.ts.map +1 -0
- package/dist/query-scenarios.test-support.js +146 -0
- package/dist/query-scenarios.test-support.js.map +1 -0
- package/dist/schemas/cluster-compare.d.ts +11 -0
- package/dist/schemas/cluster-compare.d.ts.map +1 -0
- package/dist/schemas/cluster-compare.js +35 -0
- package/dist/schemas/cluster-compare.js.map +1 -0
- package/dist/schemas/engine-compare.d.ts +10 -0
- package/dist/schemas/engine-compare.d.ts.map +1 -0
- package/dist/schemas/engine-compare.js +27 -0
- package/dist/schemas/engine-compare.js.map +1 -0
- package/dist/schemas/engine-landscape.d.ts +5 -0
- package/dist/schemas/engine-landscape.d.ts.map +1 -0
- package/dist/schemas/engine-landscape.js +8 -0
- package/dist/schemas/engine-landscape.js.map +1 -0
- package/dist/schemas/failures.d.ts +8 -0
- package/dist/schemas/failures.d.ts.map +1 -0
- package/dist/schemas/failures.js +9 -0
- package/dist/schemas/failures.js.map +1 -0
- package/dist/schemas/health.d.ts +5 -0
- package/dist/schemas/health.d.ts.map +1 -0
- package/dist/schemas/health.js +6 -0
- package/dist/schemas/health.js.map +1 -0
- package/dist/schemas/index.d.ts +10 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +10 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/path.d.ts +15 -0
- package/dist/schemas/path.d.ts.map +1 -0
- package/dist/schemas/path.js +21 -0
- package/dist/schemas/path.js.map +1 -0
- package/dist/schemas/performance.d.ts +23 -0
- package/dist/schemas/performance.d.ts.map +1 -0
- package/dist/schemas/performance.js +21 -0
- package/dist/schemas/performance.js.map +1 -0
- package/dist/schemas/shared.d.ts +34 -0
- package/dist/schemas/shared.d.ts.map +1 -0
- package/dist/schemas/shared.js +34 -0
- package/dist/schemas/shared.js.map +1 -0
- package/dist/schemas/version-compare.d.ts +10 -0
- package/dist/schemas/version-compare.d.ts.map +1 -0
- package/dist/schemas/version-compare.js +25 -0
- package/dist/schemas/version-compare.js.map +1 -0
- package/dist/widgets.d.ts +138 -0
- package/dist/widgets.d.ts.map +1 -0
- package/dist/widgets.js +2 -0
- package/dist/widgets.js.map +1 -0
- package/metrics-contract.json +174 -0
- package/package.json +72 -3
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { GAUGE_LABELS, METRIC_NAMES } from "./metric-names.js";
|
|
6
|
+
const here = (rel) => fileURLToPath(new URL(rel, import.meta.url));
|
|
7
|
+
// src/ → analytics-client → analytics → connectors → packages → repo root
|
|
8
|
+
const repoRoot = here("../../../../..");
|
|
9
|
+
const contract = JSON.parse(readFileSync(here("../metrics-contract.json"), "utf8"));
|
|
10
|
+
/** All Prometheus series names the contract covers (histograms expand to _sum/_count/_bucket). */
|
|
11
|
+
const coveredSeries = new Set(contract.metrics.flatMap((m) => m.type === "histogram"
|
|
12
|
+
? [m.promName, `${m.promName}_sum`, `${m.promName}_count`, `${m.promName}_bucket`]
|
|
13
|
+
: [m.promName]));
|
|
14
|
+
const extractSeries = (text) => [...new Set(text.match(/camunda_[a-zA-Z0-9_]+/g) ?? [])];
|
|
15
|
+
/** Strip block and line comments — doc comments may legitimately mention series names. */
|
|
16
|
+
const stripComments = (src) => src
|
|
17
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
18
|
+
.split("\n")
|
|
19
|
+
.filter((line) => !line.trim().startsWith("//"))
|
|
20
|
+
.join("\n");
|
|
21
|
+
/**
|
|
22
|
+
* Replace `${M.foo}` / `${METRIC_NAMES.foo}` interpolations and bare
|
|
23
|
+
* `M.foo` arguments (`engineState(M.foo, sel)`) with the real series name, so
|
|
24
|
+
* the plain-text scans below see which series the queries read.
|
|
25
|
+
*/
|
|
26
|
+
const resolveMetricInterpolations = (code) => code.replace(/\$\{(?:M|METRIC_NAMES)\.([A-Za-z0-9_]+)\}|\b(?:M|METRIC_NAMES)\.([A-Za-z0-9_]+)/g, (_, braced, bare) => {
|
|
27
|
+
const key = braced ?? bare ?? "";
|
|
28
|
+
const name = METRIC_NAMES[key];
|
|
29
|
+
if (name === undefined)
|
|
30
|
+
throw new Error(`unknown METRIC_NAMES key "${key}"`);
|
|
31
|
+
return name;
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Every string value in a JSON document — Grafana dashboards keep their PromQL
|
|
35
|
+
* in string fields (`expr`, templating `query`, …); collecting all string
|
|
36
|
+
* values also undoes the JSON escaping (`\"` → `"`) that would defeat the
|
|
37
|
+
* label-matcher regexes.
|
|
38
|
+
*/
|
|
39
|
+
const jsonStringValues = (node) => {
|
|
40
|
+
if (typeof node === "string")
|
|
41
|
+
return [node];
|
|
42
|
+
if (Array.isArray(node))
|
|
43
|
+
return node.flatMap(jsonStringValues);
|
|
44
|
+
if (node !== null && typeof node === "object")
|
|
45
|
+
return Object.values(node).flatMap(jsonStringValues);
|
|
46
|
+
return [];
|
|
47
|
+
};
|
|
48
|
+
const queriesDir = here("./queries");
|
|
49
|
+
const dashboardsDir = join(repoRoot, "playground/docker/grafana/dashboards");
|
|
50
|
+
const alertsFile = join(repoRoot, "playground/docker/prometheus/alerts.yml");
|
|
51
|
+
/** Query sources, comment-stripped, with `METRIC_NAMES` interpolations resolved. */
|
|
52
|
+
const querySources = () => readdirSync(queriesDir)
|
|
53
|
+
.filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts"))
|
|
54
|
+
.map((f) => ({
|
|
55
|
+
name: `queries/${f}`,
|
|
56
|
+
text: resolveMetricInterpolations(stripComments(readFileSync(join(queriesDir, f), "utf8"))),
|
|
57
|
+
}));
|
|
58
|
+
const alertsSource = () => ({
|
|
59
|
+
name: "alerts.yml",
|
|
60
|
+
text: readFileSync(alertsFile, "utf8"),
|
|
61
|
+
});
|
|
62
|
+
const dashboardSources = () => readdirSync(dashboardsDir)
|
|
63
|
+
.filter((f) => f.endsWith(".json"))
|
|
64
|
+
.map((f) => ({
|
|
65
|
+
name: `dashboards/${f}`,
|
|
66
|
+
text: jsonStringValues(JSON.parse(readFileSync(join(dashboardsDir, f), "utf8"))).join("\n"),
|
|
67
|
+
}));
|
|
68
|
+
/** Every place that consumes the metrics: TS queries, alert rules, dashboards. */
|
|
69
|
+
const allConsumerSources = () => [
|
|
70
|
+
...querySources(),
|
|
71
|
+
alertsSource(),
|
|
72
|
+
...dashboardSources(),
|
|
73
|
+
];
|
|
74
|
+
describe("metrics contract", () => {
|
|
75
|
+
it("declares well-formed metrics", () => {
|
|
76
|
+
expect(contract.metrics.length).toBeGreaterThan(0);
|
|
77
|
+
for (const m of contract.metrics) {
|
|
78
|
+
expect(m.otelName, m.promName).toMatch(/^camunda\.[a-z._]+$/);
|
|
79
|
+
expect(["counter", "histogram", "gauge"]).toContain(m.type);
|
|
80
|
+
expect(m.labels.length, `${m.promName} must carry engine_id`).toBeGreaterThan(0);
|
|
81
|
+
expect(m.labels).toContain("engine_id");
|
|
82
|
+
for (const label of Object.keys(m.knownValues ?? {})) {
|
|
83
|
+
expect(m.labels, `knownValues key ${label} of ${m.promName}`).toContain(label);
|
|
84
|
+
}
|
|
85
|
+
if (m.type === "histogram") {
|
|
86
|
+
// Whole milliseconds (contract rule 1), with explicit ascending bounds.
|
|
87
|
+
expect(m.unit, `${m.promName} unit`).toBe("ms");
|
|
88
|
+
expect(m.promName, m.otelName).toMatch(/_milliseconds$/);
|
|
89
|
+
const buckets = m.buckets ?? [];
|
|
90
|
+
expect(buckets.length, `${m.promName} declares its buckets`).toBeGreaterThan(0);
|
|
91
|
+
expect(buckets.every((b, i) => Number.isInteger(b) && b > 0 && (i === 0 || b > buckets[i - 1])), `${m.promName} buckets are ascending whole ms`).toBe(true);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
expect(m.unit, `${m.promName} unit`).toBe("");
|
|
95
|
+
expect(m.buckets, `${m.promName} is no histogram`).toBeUndefined();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it("GAUGE_LABELS mirrors every gauge's contract labels exactly", () => {
|
|
100
|
+
const gauges = contract.metrics.filter((m) => m.type === "gauge");
|
|
101
|
+
expect(Object.keys(GAUGE_LABELS).sort()).toEqual(gauges.map((m) => m.promName).sort());
|
|
102
|
+
for (const m of gauges) {
|
|
103
|
+
expect([...GAUGE_LABELS[m.promName]].sort(), m.promName).toEqual([...m.labels].sort());
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
it("METRIC_NAMES mirrors the contract exactly", () => {
|
|
107
|
+
const contractNames = contract.metrics.map((m) => m.promName).sort();
|
|
108
|
+
const constNames = Object.values(METRIC_NAMES).sort();
|
|
109
|
+
expect(constNames).toEqual(contractNames);
|
|
110
|
+
});
|
|
111
|
+
it("covers every camunda_* metric used in the Prometheus alert rules", () => {
|
|
112
|
+
const alerts = readFileSync(alertsFile, "utf8");
|
|
113
|
+
const used = extractSeries(alerts);
|
|
114
|
+
expect(used.length).toBeGreaterThan(0);
|
|
115
|
+
for (const series of used) {
|
|
116
|
+
expect(coveredSeries.has(series), `${series} (alerts.yml) missing from contract`).toBe(true);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
it("covers every camunda_* metric used in the Grafana dashboards", () => {
|
|
120
|
+
const dashboards = readdirSync(dashboardsDir).filter((f) => f.endsWith(".json"));
|
|
121
|
+
expect(dashboards.length).toBeGreaterThan(0);
|
|
122
|
+
for (const file of dashboards) {
|
|
123
|
+
const used = extractSeries(readFileSync(join(dashboardsDir, file), "utf8"));
|
|
124
|
+
expect(used.length, `${file} should reference camunda_* metrics`).toBeGreaterThan(0);
|
|
125
|
+
for (const series of used) {
|
|
126
|
+
expect(coveredSeries.has(series), `${series} (${file}) missing from contract`).toBe(true);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
it("query sources contain no raw camunda_* literals (METRIC_NAMES only)", () => {
|
|
131
|
+
const sources = readdirSync(queriesDir).filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts"));
|
|
132
|
+
expect(sources.length).toBeGreaterThan(0);
|
|
133
|
+
for (const file of sources) {
|
|
134
|
+
const code = stripComments(readFileSync(join(queriesDir, file), "utf8"));
|
|
135
|
+
const literals = extractSeries(code);
|
|
136
|
+
expect(literals, `queries/${file} must use METRIC_NAMES`).toEqual([]);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
it("hardcoded label values in queries, alert rules and dashboards are declared knownValues", () => {
|
|
140
|
+
const sources = allConsumerSources();
|
|
141
|
+
const knownValues = new Map();
|
|
142
|
+
for (const m of contract.metrics) {
|
|
143
|
+
for (const [label, values] of Object.entries(m.knownValues ?? {})) {
|
|
144
|
+
const set = knownValues.get(label) ?? new Set();
|
|
145
|
+
values.forEach((v) => set.add(v));
|
|
146
|
+
knownValues.set(label, set);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
expect(knownValues.size).toBeGreaterThan(0);
|
|
150
|
+
/** Regex-matcher alternatives that are regex syntax or Grafana variables, not values. */
|
|
151
|
+
const isWildcard = (alt) => alt === ".*" || alt === ".+" || alt.startsWith("$");
|
|
152
|
+
let exactChecked = 0;
|
|
153
|
+
let regexChecked = 0;
|
|
154
|
+
for (const [label, values] of knownValues) {
|
|
155
|
+
// Exact-match selectors (`label="literal"`); the lookbehind keeps
|
|
156
|
+
// e.g. `alertstate="firing"` from matching the `state` label.
|
|
157
|
+
const exact = new RegExp(`(?<![A-Za-z_])${label}="([A-Za-z_][A-Za-z0-9_]*)"`, "g");
|
|
158
|
+
// Regex selectors (`label=~"total|unassigned"`): every alternative must
|
|
159
|
+
// be a knownValue, except wildcards (`.*`/`.+`) and Grafana variables.
|
|
160
|
+
const regexSel = new RegExp(`(?<![A-Za-z_])${label}=~"([^"]*)"`, "g");
|
|
161
|
+
for (const { name, text } of sources) {
|
|
162
|
+
for (const match of text.matchAll(exact)) {
|
|
163
|
+
exactChecked++;
|
|
164
|
+
expect(values.has(match[1]), `${label}="${match[1]}" (${name}) not in knownValues`).toBe(true);
|
|
165
|
+
}
|
|
166
|
+
for (const match of text.matchAll(regexSel)) {
|
|
167
|
+
for (const alt of match[1].split("|")) {
|
|
168
|
+
if (isWildcard(alt))
|
|
169
|
+
continue;
|
|
170
|
+
regexChecked++;
|
|
171
|
+
expect(/^[A-Za-z_][A-Za-z0-9_]*$/.test(alt), `${label}=~"${match[1]}" (${name}): alternative "${alt}" is neither a plain value nor a recognised wildcard — extend the parser if the pattern is legitimate`).toBe(true);
|
|
172
|
+
expect(values.has(alt), `${label}=~"${match[1]}" (${name}): "${alt}" not in knownValues`).toBe(true);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
expect(exactChecked).toBeGreaterThan(0);
|
|
178
|
+
// The Grafana dashboard pins `status=~"total|unassigned"` — if this reads
|
|
179
|
+
// 0 the regex-matcher scan silently broke, not the dashboard.
|
|
180
|
+
expect(regexChecked).toBeGreaterThan(0);
|
|
181
|
+
});
|
|
182
|
+
it("PromQL grouping labels in alert rules and dashboards are declared on the metric they group", () => {
|
|
183
|
+
const seriesToMetric = new Map();
|
|
184
|
+
for (const m of contract.metrics) {
|
|
185
|
+
seriesToMetric.set(m.promName, m);
|
|
186
|
+
if (m.type === "histogram") {
|
|
187
|
+
for (const suffix of ["_sum", "_count", "_bucket"]) {
|
|
188
|
+
seriesToMetric.set(`${m.promName}${suffix}`, m);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// `sum by (a, b)(max by (…)(camunda_x{…}))` — each by-clause attributed to
|
|
193
|
+
// the first camunda_* series after it (the aggregated expression's, behind
|
|
194
|
+
// any nested calls, parentheses and inner by-clauses). Two flat regexes and
|
|
195
|
+
// a slice, not one nested pattern: that one backtracked (CodeQL js/redos).
|
|
196
|
+
const byClause = /\bby\s*\(([^)]*)\)/g;
|
|
197
|
+
const firstSeries = /camunda_[A-Za-z0-9_]+/;
|
|
198
|
+
let checked = 0;
|
|
199
|
+
for (const { name, text } of [alertsSource(), ...dashboardSources()]) {
|
|
200
|
+
for (const clause of text.matchAll(byClause)) {
|
|
201
|
+
const labelList = clause[1];
|
|
202
|
+
const series = firstSeries.exec(text.slice(clause.index + clause[0].length))?.[0];
|
|
203
|
+
// Every by-clause must be attributable to a camunda_* series.
|
|
204
|
+
expect(series, `unattributable "${clause[0]}" clause in ${name}`).toBeDefined();
|
|
205
|
+
if (series === undefined)
|
|
206
|
+
continue;
|
|
207
|
+
const metric = seriesToMetric.get(series);
|
|
208
|
+
expect(metric, `${series} (${name}) missing from contract`).toBeDefined();
|
|
209
|
+
if (!metric)
|
|
210
|
+
continue;
|
|
211
|
+
const labels = labelList
|
|
212
|
+
.split(",")
|
|
213
|
+
.map((l) => l.trim())
|
|
214
|
+
.filter((l) => l !== "" && l !== "le"); // `le` is the histogram bucket label
|
|
215
|
+
for (const label of labels) {
|
|
216
|
+
checked++;
|
|
217
|
+
expect(metric.labels.includes(label), `${name} groups ${series} by "${label}" — not a declared label of ${metric.promName}`).toBe(true);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
expect(checked).toBeGreaterThan(0);
|
|
222
|
+
});
|
|
223
|
+
it("every contract metric has at least one consumer (queries, alerts or dashboards)", () => {
|
|
224
|
+
// Emitted by the engine plugin for future dashboards but consumed nowhere
|
|
225
|
+
// yet. Remove an entry from this allowlist the moment a consumer appears —
|
|
226
|
+
// the stale-entry check below fails to remind you. Do NOT add new entries
|
|
227
|
+
// without the same "deliberately emitted ahead of consumption" rationale.
|
|
228
|
+
const KNOWN_UNCONSUMED = new Set([
|
|
229
|
+
"camunda_usertask_created_total",
|
|
230
|
+
"camunda_usertask_completed_total",
|
|
231
|
+
]);
|
|
232
|
+
const used = new Set(allConsumerSources().flatMap(({ text }) => extractSeries(text)));
|
|
233
|
+
for (const m of contract.metrics) {
|
|
234
|
+
const series = m.type === "histogram"
|
|
235
|
+
? [m.promName, `${m.promName}_sum`, `${m.promName}_count`, `${m.promName}_bucket`]
|
|
236
|
+
: [m.promName];
|
|
237
|
+
const consumed = series.some((s) => used.has(s));
|
|
238
|
+
if (KNOWN_UNCONSUMED.has(m.promName)) {
|
|
239
|
+
expect(consumed, `${m.promName} is consumed now — remove it from KNOWN_UNCONSUMED`).toBe(false);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
expect(consumed, `${m.promName} has no consumer (TS queries, alerts.yml, Grafana dashboards) — dead contract entry?`).toBe(true);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
//# sourceMappingURL=metrics-contract.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-contract.test.js","sourceRoot":"","sources":["../src/metrics-contract.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prometheus-client.test.d.ts","sourceRoot":"","sources":["../src/prometheus-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { createPrometheusClient, withCallerSignal } from "./prometheus.js";
|
|
4
|
+
/**
|
|
5
|
+
* Guard for #325 on the Prometheus side: the client runs against a REAL
|
|
6
|
+
* local HTTP server that answers like the Prometheus HTTP API. Pins auth
|
|
7
|
+
* (bearer, basic, URL userinfo, custom headers), the per-query deadline and
|
|
8
|
+
* caller cancellation, and the model-facing error texts — which must never
|
|
9
|
+
* carry credentials or unbounded upstream bodies.
|
|
10
|
+
*/
|
|
11
|
+
const TOKEN = "s3cr3t-token";
|
|
12
|
+
const PASSWORD = "hunter2-pass";
|
|
13
|
+
/** Last request's headers, for the auth assertions. */
|
|
14
|
+
let lastHeaders = {};
|
|
15
|
+
const success = {
|
|
16
|
+
status: "success",
|
|
17
|
+
data: {
|
|
18
|
+
resultType: "vector",
|
|
19
|
+
result: [
|
|
20
|
+
{ metric: { engine_id: "prod-a" }, value: [1, "42"] },
|
|
21
|
+
{ metric: { engine_id: "prod-b" }, value: [1, "NaN"] },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
/** The credential of an Authorization header: the bearer token / the decoded password. */
|
|
26
|
+
function credentialOf(authorization = "") {
|
|
27
|
+
const [scheme, value = ""] = authorization.split(" ");
|
|
28
|
+
return scheme === "Basic" ? Buffer.from(value, "base64").toString().split(":")[1] : value;
|
|
29
|
+
}
|
|
30
|
+
/** Upstreams that quote a credential back — status, content type, body per query. */
|
|
31
|
+
const echoTable = {
|
|
32
|
+
// A misbehaving proxy: the whole Authorization header…
|
|
33
|
+
"echo-auth": (auth) => [401, "text/plain", `denied: ${auth ?? ""}`],
|
|
34
|
+
// …or only the credential itself.
|
|
35
|
+
"echo-credential": (auth) => [401, "text/plain", `invalid credential ${credentialOf(auth)}`],
|
|
36
|
+
// Envelopes whose errorType is no Prometheus error type.
|
|
37
|
+
"echo-type": (auth) => [
|
|
38
|
+
400,
|
|
39
|
+
"application/json",
|
|
40
|
+
JSON.stringify({ status: "error", errorType: `${"t".repeat(2000)} ${auth}`, error: "nope" }),
|
|
41
|
+
],
|
|
42
|
+
"api-echo-type": () => [
|
|
43
|
+
200,
|
|
44
|
+
"application/json",
|
|
45
|
+
JSON.stringify({ status: "error", errorType: `bad ${TOKEN}`, error: "nope" }),
|
|
46
|
+
],
|
|
47
|
+
// A short, Prometheus-shaped errorType that happens to be a configured header value.
|
|
48
|
+
"header-type": () => [
|
|
49
|
+
400,
|
|
50
|
+
"application/json",
|
|
51
|
+
JSON.stringify({ status: "error", errorType: "tenantsecret", error: "nope" }),
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
// Matched by comparison against the fixed table, never looked up by the query
|
|
55
|
+
// string (no dispatch through inherited properties like "constructor").
|
|
56
|
+
const echoes = Object.entries(echoTable);
|
|
57
|
+
const prometheus = http.createServer((req, res) => {
|
|
58
|
+
lastHeaders = req.headers;
|
|
59
|
+
const query = new URL(req.url ?? "/", "http://x").searchParams.get("query") ?? "";
|
|
60
|
+
const json = (status, body) => {
|
|
61
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
62
|
+
res.end(JSON.stringify(body));
|
|
63
|
+
};
|
|
64
|
+
const echo = echoes.find(([q]) => q === query)?.[1];
|
|
65
|
+
if (echo) {
|
|
66
|
+
const [status, type, body] = echo(req.headers.authorization);
|
|
67
|
+
res.writeHead(status, { "Content-Type": type });
|
|
68
|
+
return res.end(body);
|
|
69
|
+
}
|
|
70
|
+
if (query === "hang")
|
|
71
|
+
return;
|
|
72
|
+
if (query === "bad") {
|
|
73
|
+
return json(400, {
|
|
74
|
+
status: "error",
|
|
75
|
+
errorType: "bad_data",
|
|
76
|
+
error: `invalid parameter "query": 1:1: parse error: unexpected ${"x".repeat(2000)}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (query === "text") {
|
|
80
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
81
|
+
return res.end(`bad gateway ${"y".repeat(2000)}`);
|
|
82
|
+
}
|
|
83
|
+
if (query === "empty") {
|
|
84
|
+
res.writeHead(503);
|
|
85
|
+
return res.end();
|
|
86
|
+
}
|
|
87
|
+
if (query === "html") {
|
|
88
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
89
|
+
return res.end("<!DOCTYPE html><html><body>Sign in</body></html>");
|
|
90
|
+
}
|
|
91
|
+
if (query === "api-error") {
|
|
92
|
+
return json(200, { status: "error", errorType: "timeout", error: "query timed out" });
|
|
93
|
+
}
|
|
94
|
+
json(200, success);
|
|
95
|
+
});
|
|
96
|
+
let url = "";
|
|
97
|
+
let closedUrl = "";
|
|
98
|
+
beforeAll(async () => {
|
|
99
|
+
await new Promise((resolve) => prometheus.listen(0, "127.0.0.1", resolve));
|
|
100
|
+
url = `http://127.0.0.1:${prometheus.address().port}`;
|
|
101
|
+
const probe = http.createServer();
|
|
102
|
+
await new Promise((resolve) => probe.listen(0, "127.0.0.1", resolve));
|
|
103
|
+
const closedPort = probe.address().port;
|
|
104
|
+
await new Promise((resolve) => probe.close(() => resolve()));
|
|
105
|
+
closedUrl = `http://127.0.0.1:${closedPort}`;
|
|
106
|
+
});
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
lastHeaders = {};
|
|
109
|
+
});
|
|
110
|
+
afterAll(async () => {
|
|
111
|
+
prometheus.closeAllConnections();
|
|
112
|
+
await new Promise((resolve) => prometheus.close(() => resolve()));
|
|
113
|
+
});
|
|
114
|
+
const client = (config = {}) => createPrometheusClient({ url, ...config });
|
|
115
|
+
/** The rejection message of a query. */
|
|
116
|
+
async function failure(promise) {
|
|
117
|
+
const error = await promise.then(() => {
|
|
118
|
+
throw new Error("expected the query to fail");
|
|
119
|
+
}, (e) => e);
|
|
120
|
+
expect(error).toBeInstanceOf(Error);
|
|
121
|
+
return error.message;
|
|
122
|
+
}
|
|
123
|
+
describe("Prometheus queries", () => {
|
|
124
|
+
it("map the labeled samples and drop NaN", async () => {
|
|
125
|
+
expect(await client().instant("up")).toEqual([{ metric: { engine_id: "prod-a" }, value: 42 }]);
|
|
126
|
+
});
|
|
127
|
+
it("tolerate a trailing slash on the URL", async () => {
|
|
128
|
+
expect(await client({ url: `${url}/` }).instant("up")).toHaveLength(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("Prometheus auth", () => {
|
|
132
|
+
it("sends no Authorization header by default", async () => {
|
|
133
|
+
await client().instant("up");
|
|
134
|
+
expect(lastHeaders.authorization).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
it("sends a bearer token", async () => {
|
|
137
|
+
await client({ bearerToken: TOKEN }).instant("up");
|
|
138
|
+
expect(lastHeaders.authorization).toBe(`Bearer ${TOKEN}`);
|
|
139
|
+
});
|
|
140
|
+
it("sends basic auth from username/password", async () => {
|
|
141
|
+
await client({ username: "grafana", password: PASSWORD }).instant("up");
|
|
142
|
+
expect(lastHeaders.authorization).toBe(`Basic ${Buffer.from(`grafana:${PASSWORD}`).toString("base64")}`);
|
|
143
|
+
});
|
|
144
|
+
it("moves URL userinfo into a Basic header (fetch rejects credentialed URLs)", async () => {
|
|
145
|
+
const credentialed = url.replace("http://", `http://grafana:${encodeURIComponent("p@ss:w")}@`);
|
|
146
|
+
await client({ url: credentialed }).instant("up");
|
|
147
|
+
expect(lastHeaders.authorization).toBe(`Basic ${Buffer.from("grafana:p@ss:w").toString("base64")}`);
|
|
148
|
+
});
|
|
149
|
+
it("sends custom headers (e.g. a tenant id) next to the auth header", async () => {
|
|
150
|
+
await client({ bearerToken: TOKEN, headers: { "X-Scope-OrgID": "tenant-a" } }).instant("up");
|
|
151
|
+
expect(lastHeaders["x-scope-orgid"]).toBe("tenant-a");
|
|
152
|
+
expect(lastHeaders.authorization).toBe(`Bearer ${TOKEN}`);
|
|
153
|
+
});
|
|
154
|
+
it.each([
|
|
155
|
+
["bearer + basic", { bearerToken: TOKEN, username: "u", password: "p" }],
|
|
156
|
+
["a username without password", { username: "u" }],
|
|
157
|
+
["a password without username", { password: "p" }],
|
|
158
|
+
["URL userinfo + explicit credentials", { url: "http://u:p@prom:9090", bearerToken: TOKEN }],
|
|
159
|
+
[
|
|
160
|
+
"an Authorization header + a bearer token",
|
|
161
|
+
{ bearerToken: TOKEN, headers: { authorization: "x" } },
|
|
162
|
+
],
|
|
163
|
+
])("rejects ambiguous auth at construction: %s", (_, config) => {
|
|
164
|
+
expect(() => client(config)).toThrow(/Prometheus/);
|
|
165
|
+
});
|
|
166
|
+
it("rejects an invalid header without echoing its value", () => {
|
|
167
|
+
expect(() => client({ headers: { "Bad Header": "v4lue-secret" } })).toThrow(/Bad Header/);
|
|
168
|
+
expect(() => client({ headers: { "X-Key": "line\nbreak-secret" } })).toThrow(/X-Key/);
|
|
169
|
+
try {
|
|
170
|
+
client({ headers: { "X-Key": "line\nbreak-secret" } });
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
expect(e.message).not.toContain("break-secret");
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
it("rejects an invalid URL without echoing it (it may carry credentials)", () => {
|
|
177
|
+
expect(() => client({ url: `not a url ${PASSWORD}` })).toThrow(/not a valid URL/);
|
|
178
|
+
try {
|
|
179
|
+
client({ url: `not a url ${PASSWORD}` });
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
expect(e.message).not.toContain(PASSWORD);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe("Prometheus errors reach the model actionable and credential-free", () => {
|
|
187
|
+
it("unreachable: the OS code, no URL, no userinfo", async () => {
|
|
188
|
+
const credentialed = closedUrl.replace("http://", `http://grafana:${PASSWORD}@`);
|
|
189
|
+
const message = await failure(client({ url: credentialed }).instant("up"));
|
|
190
|
+
expect(message).toBe("Prometheus unreachable (ECONNREFUSED)");
|
|
191
|
+
});
|
|
192
|
+
it("a PromQL error: status, errorType, the truncated error text", async () => {
|
|
193
|
+
const message = await failure(client().instant("bad"));
|
|
194
|
+
expect(message).toMatch(/^Prometheus query failed \(400 bad_data\): invalid parameter "query": 1:1: parse error: unexpected x+…$/);
|
|
195
|
+
expect(message.length).toBeLessThan(600);
|
|
196
|
+
});
|
|
197
|
+
it("a text body is truncated", async () => {
|
|
198
|
+
const message = await failure(client().instant("text"));
|
|
199
|
+
expect(message).toMatch(/^Prometheus query failed \(502\): bad gateway y+…$/);
|
|
200
|
+
expect(message.length).toBeLessThan(600);
|
|
201
|
+
});
|
|
202
|
+
it("an empty body falls back to the status line", async () => {
|
|
203
|
+
expect(await failure(client().instant("empty"))).toBe("Prometheus query failed (503): Service Unavailable — empty response body");
|
|
204
|
+
});
|
|
205
|
+
it("401: a credential echoed by the upstream is redacted, and the remedy is named", async () => {
|
|
206
|
+
const bearer = await failure(client({ bearerToken: TOKEN }).instant("echo-auth"));
|
|
207
|
+
expect(bearer).not.toContain(TOKEN);
|
|
208
|
+
expect(bearer).toBe("Prometheus query failed (401): denied: Bearer *** — check the configured Prometheus credentials");
|
|
209
|
+
const basic = await failure(client({ username: "grafana", password: PASSWORD }).instant("echo-auth"));
|
|
210
|
+
expect(basic).not.toContain(Buffer.from(`grafana:${PASSWORD}`).toString("base64"));
|
|
211
|
+
expect(basic).toContain("denied: Basic ***");
|
|
212
|
+
});
|
|
213
|
+
it("masks a credential echoed alone, also when configured with stray whitespace", async () => {
|
|
214
|
+
// A token read verbatim from a secret file keeps its trailing newline; the
|
|
215
|
+
// header is sent trimmed, so the echo carries the trimmed form.
|
|
216
|
+
expect(await failure(client({ bearerToken: `${TOKEN}\n` }).instant("echo-credential"))).toBe("Prometheus query failed (401): invalid credential *** — check the configured Prometheus credentials");
|
|
217
|
+
// Whitespace inside a password survives the echo but not the collapse.
|
|
218
|
+
const spaced = "hunter2 pass\tword";
|
|
219
|
+
const basic = await failure(client({ username: "grafana", password: spaced }).instant("echo-credential"));
|
|
220
|
+
expect(basic).toBe("Prometheus query failed (401): invalid credential *** — check the configured Prometheus credentials");
|
|
221
|
+
});
|
|
222
|
+
it("never echoes an errorType that is no Prometheus error type", async () => {
|
|
223
|
+
expect(await failure(client({ bearerToken: TOKEN }).instant("echo-type"))).toBe("Prometheus query failed (400): nope");
|
|
224
|
+
expect(await failure(client({ bearerToken: TOKEN }).instant("api-echo-type"))).toBe("Prometheus query error: nope");
|
|
225
|
+
});
|
|
226
|
+
it("masks a Prometheus-shaped errorType that is a configured secret", async () => {
|
|
227
|
+
const tenant = client({ headers: { "X-Scope-OrgID": "tenantsecret" } });
|
|
228
|
+
expect(await failure(tenant.instant("header-type"))).toBe("Prometheus query failed (400 ***): nope");
|
|
229
|
+
});
|
|
230
|
+
it("a 200 that is not JSON (a login page) says so instead of a JSON parse error", async () => {
|
|
231
|
+
expect(await failure(client().instant("html"))).toBe("Prometheus returned a non-JSON response (HTTP 200, text/html) — is a proxy or login page in front of it?");
|
|
232
|
+
});
|
|
233
|
+
it("an API-level error", async () => {
|
|
234
|
+
expect(await failure(client().instant("api-error"))).toBe("Prometheus query error (timeout): query timed out");
|
|
235
|
+
});
|
|
236
|
+
it("a hung Prometheus ends at the per-query deadline", async () => {
|
|
237
|
+
const started = Date.now();
|
|
238
|
+
const message = await failure(client({ timeoutMs: 50 }).instant("hang"));
|
|
239
|
+
expect(Date.now() - started).toBeLessThan(2_000);
|
|
240
|
+
expect(message).toBe("Prometheus did not respond within 50 ms (timeout)");
|
|
241
|
+
});
|
|
242
|
+
it("rejects a deadline a timer cannot hold", () => {
|
|
243
|
+
for (const timeoutMs of [0, -1, 1.5, Number.NaN, 2_147_483_648]) {
|
|
244
|
+
expect(() => client({ timeoutMs })).toThrow(RangeError);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
describe("caller cancellation", () => {
|
|
249
|
+
it("aborts the query when the caller's signal fires", async () => {
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
setTimeout(() => controller.abort(), 30);
|
|
252
|
+
const message = await failure(withCallerSignal(client(), controller.signal).instant("hang"));
|
|
253
|
+
expect(message).toBe("Prometheus query cancelled by the caller");
|
|
254
|
+
});
|
|
255
|
+
it("a per-call signal wins over the bound one", async () => {
|
|
256
|
+
const bound = withCallerSignal(client(), new AbortController().signal);
|
|
257
|
+
const message = await failure(bound.instant("hang", { signal: AbortSignal.abort() }));
|
|
258
|
+
expect(message).toBe("Prometheus query cancelled by the caller");
|
|
259
|
+
});
|
|
260
|
+
it("returns the client unchanged without a signal", () => {
|
|
261
|
+
const c = client();
|
|
262
|
+
expect(withCallerSignal(c, undefined)).toBe(c);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
//# sourceMappingURL=prometheus-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prometheus-client.test.js","sourceRoot":"","sources":["../src/prometheus-client.test.ts"],"names":[],"mappings":"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"}
|