@intentius/chant-lexicon-otel 0.124.46 → 0.124.48
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 +2 -2
- package/dist/build/capability-plugin.d.ts +10 -0
- package/dist/build/capability-plugin.d.ts.map +1 -0
- package/dist/build/capability-plugin.js +19 -0
- package/dist/build/ocb-build.d.ts +38 -0
- package/dist/build/ocb-build.d.ts.map +1 -0
- package/dist/build/ocb-build.js +49 -0
- package/dist/clickhouse-tables.d.ts +73 -0
- package/dist/clickhouse-tables.d.ts.map +1 -0
- package/dist/clickhouse-tables.js +199 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/docs.js +4 -2
- package/dist/codegen/generate.d.ts +29 -10
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/codegen/generate.js +82 -17
- package/dist/components/common.d.ts +3 -1
- package/dist/components/common.d.ts.map +1 -1
- package/dist/components/common.js +2 -0
- package/dist/components/connectors.d.ts +25 -90
- package/dist/components/connectors.d.ts.map +1 -1
- package/dist/components/connectors.js +3 -0
- package/dist/components/exporters.d.ts +78 -34
- package/dist/components/exporters.d.ts.map +1 -1
- package/dist/components/exporters.js +26 -1
- package/dist/components/extensions.d.ts +16 -33
- package/dist/components/extensions.d.ts.map +1 -1
- package/dist/components/filtering.d.ts +13 -60
- package/dist/components/filtering.d.ts.map +1 -1
- package/dist/components/k8s-receivers.d.ts +5 -53
- package/dist/components/k8s-receivers.d.ts.map +1 -1
- package/dist/components/overlay.d.ts +25 -0
- package/dist/components/overlay.d.ts.map +1 -0
- package/dist/components/overlay.js +19 -0
- package/dist/components/processors.d.ts +18 -79
- package/dist/components/processors.d.ts.map +1 -1
- package/dist/components/receivers.d.ts +17 -71
- package/dist/components/receivers.d.ts.map +1 -1
- package/dist/components/receivers.js +3 -1
- package/dist/components/sampling.d.ts +19 -35
- package/dist/components/sampling.d.ts.map +1 -1
- package/dist/config-hygiene.d.ts +10 -8
- package/dist/config-hygiene.d.ts.map +1 -1
- package/dist/config-hygiene.js +36 -13
- package/dist/coverage.d.ts +50 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +92 -0
- package/dist/define.d.ts +8 -5
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +10 -6
- package/dist/distribution.d.ts +125 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +227 -0
- package/dist/generated/index.d.ts +4234 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +23 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/init-templates.js +2 -2
- package/dist/integrity.json +11 -9
- package/dist/lint/audit-catalog.d.ts +3 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-catalog.js +5 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +4 -0
- package/dist/lint/post-synth/otel-helpers.d.ts +2 -1
- package/dist/lint/post-synth/otel-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/otel-helpers.js +1 -1
- package/dist/lint/post-synth/otel119.d.ts +1 -1
- package/dist/lint/post-synth/otel119.js +1 -1
- package/dist/lint/post-synth/otel125.d.ts +1 -1
- package/dist/lint/post-synth/otel125.js +1 -1
- package/dist/lint/post-synth/otel126.d.ts +1 -1
- package/dist/lint/post-synth/otel126.js +1 -1
- package/dist/lint/post-synth/otel127.d.ts +1 -1
- package/dist/lint/post-synth/otel127.js +1 -1
- package/dist/lint/post-synth/otel128.d.ts +8 -0
- package/dist/lint/post-synth/otel128.d.ts.map +1 -0
- package/dist/lint/post-synth/otel128.js +17 -0
- package/dist/lint/post-synth/otel129.d.ts +15 -0
- package/dist/lint/post-synth/otel129.d.ts.map +1 -0
- package/dist/lint/post-synth/otel129.js +92 -0
- package/dist/lsp/completions.d.ts +10 -1
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +29 -1
- package/dist/lsp/hover.d.ts +5 -1
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +16 -3
- package/dist/lsp/properties.d.ts +34 -0
- package/dist/lsp/properties.d.ts.map +1 -0
- package/dist/lsp/properties.js +135 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +5 -0
- package/dist/metric-names.d.ts +36 -8
- package/dist/metric-names.d.ts.map +1 -1
- package/dist/metric-names.js +42 -9
- package/dist/model.d.ts +1 -1
- package/dist/model.js +1 -1
- package/dist/okf/index.md +3 -0
- package/dist/okf/rules/OTEL128.md +11 -0
- package/dist/okf/rules/OTEL129.md +11 -0
- package/dist/okf/types/ClickHouseExporter.md +9 -0
- package/dist/op/activities/collector-audit.d.ts +18 -6
- package/dist/op/activities/collector-audit.d.ts.map +1 -1
- package/dist/op/activities/collector-audit.js +37 -21
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +51 -8
- package/dist/rules/otel-helpers.ts +1 -1
- package/dist/rules/otel119.ts +1 -1
- package/dist/rules/otel125.ts +1 -1
- package/dist/rules/otel126.ts +1 -1
- package/dist/rules/otel127.ts +1 -1
- package/dist/rules/otel128.ts +22 -0
- package/dist/rules/otel129.ts +99 -0
- package/dist/serializer.d.ts +3 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +12 -2
- package/dist/spec/builtins.d.ts +17 -0
- package/dist/spec/builtins.d.ts.map +1 -0
- package/dist/spec/builtins.js +45 -0
- package/dist/spec/fetch.d.ts +120 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/fetch.js +282 -0
- package/dist/spec/parse.d.ts +77 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/parse.js +337 -0
- package/dist/spec/pin.d.ts +82 -0
- package/dist/spec/pin.d.ts.map +1 -0
- package/dist/spec/pin.js +126 -0
- package/dist/spec/snapshot-cli.d.ts +13 -0
- package/dist/spec/snapshot-cli.d.ts.map +1 -0
- package/dist/spec/snapshot-cli.js +31 -0
- package/dist/spec/snapshot.d.ts +9 -0
- package/dist/spec/snapshot.d.ts.map +1 -0
- package/dist/spec/snapshot.js +14 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/validate-config.d.ts +1 -1
- package/dist/validate-config.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +1 -0
- package/dist/workspace-links.d.ts +25 -0
- package/dist/workspace-links.d.ts.map +1 -0
- package/dist/workspace-links.js +49 -0
- package/package.json +3 -2
- package/src/build/capability-plugin.ts +30 -0
- package/src/build/ocb-build.ts +73 -0
- package/src/clickhouse-exporter.e2e.test.ts +208 -0
- package/src/clickhouse-tables.test.ts +216 -0
- package/src/clickhouse-tables.ts +223 -0
- package/src/codegen/docs.ts +4 -2
- package/src/codegen/generate.ts +95 -19
- package/src/components/common.ts +4 -2
- package/src/components/connectors.ts +30 -77
- package/src/components/exporters.ts +128 -61
- package/src/components/extensions.ts +22 -36
- package/src/components/filtering.ts +17 -58
- package/src/components/k8s-receivers.test.ts +1 -1
- package/src/components/k8s-receivers.ts +3 -45
- package/src/components/overlay.ts +24 -0
- package/src/components/processors.ts +18 -63
- package/src/components/receivers.ts +20 -60
- package/src/components/sampling.ts +24 -37
- package/src/config-hygiene.ts +36 -13
- package/src/coverage.test.ts +37 -0
- package/src/coverage.ts +121 -0
- package/src/define.test.ts +2 -0
- package/src/define.ts +10 -6
- package/src/distribution.test.ts +250 -0
- package/src/distribution.ts +305 -0
- package/src/generated/index.ts +4576 -0
- package/src/generated/lexicon-otel.json +5 -0
- package/src/generated/properties.json +1 -0
- package/src/import/roundtrip.test.ts +16 -0
- package/src/import/testdata/clickhouse.yaml +37 -0
- package/src/import/testdata/fixtures.ts +17 -2
- package/src/import/testdata/upstream/clickhouse-exporter.yaml +60 -0
- package/src/index.ts +69 -0
- package/src/init-templates.ts +2 -2
- package/src/lint/audit-catalog.ts +15 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/otel-helpers.ts +1 -1
- package/src/lint/post-synth/otel119.ts +1 -1
- package/src/lint/post-synth/otel125.ts +1 -1
- package/src/lint/post-synth/otel126.ts +1 -1
- package/src/lint/post-synth/otel127.ts +1 -1
- package/src/lint/post-synth/otel128.ts +22 -0
- package/src/lint/post-synth/otel129.ts +99 -0
- package/src/lsp/completions.ts +30 -1
- package/src/lsp/hover.ts +14 -3
- package/src/lsp/properties.test.ts +72 -0
- package/src/lsp/properties.ts +141 -0
- package/src/metric-names.test.ts +11 -0
- package/src/metric-names.ts +64 -16
- package/src/model.ts +1 -1
- package/src/op/activities/collector-audit.test.ts +29 -22
- package/src/op/activities/collector-audit.ts +38 -18
- package/src/plugin.test.ts +2 -0
- package/src/plugin.ts +57 -6
- package/src/serializer.ts +11 -2
- package/src/spec/builtins.ts +56 -0
- package/src/spec/fetch.ts +332 -0
- package/src/spec/otel-schemas.snapshot.json +13932 -0
- package/src/spec/parse.ts +364 -0
- package/src/spec/pin.ts +155 -0
- package/src/spec/snapshot-cli.ts +32 -0
- package/src/spec/snapshot.ts +19 -0
- package/src/spec/spec.test.ts +144 -0
- package/src/validate-config.ts +4 -3
- package/src/validate.ts +1 -0
- package/src/workspace-links.e2e.test.ts +91 -0
- package/src/workspace-links.ts +50 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property-level completion and hover for the built-in components (#3361),
|
|
3
|
+
* from the descriptions in upstream's `config.schema.yaml`.
|
|
4
|
+
*
|
|
5
|
+
* Generation writes each built-in's property tree to
|
|
6
|
+
* `src/generated/properties.json`. Inside `new OtlpReceiver({ … })` the
|
|
7
|
+
* cursor's position is resolved to a path of keys (`protocols.grpc`), and the
|
|
8
|
+
* properties at that path are offered, with upstream's description and a short
|
|
9
|
+
* type. A word under the cursor at such a position hovers with the same text.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from "fs";
|
|
13
|
+
import { dirname, join } from "path";
|
|
14
|
+
import { moduleSourceDir } from "@intentius/chant/module-source";
|
|
15
|
+
import type { PropertyDoc } from "../spec/parse";
|
|
16
|
+
|
|
17
|
+
type PropertyTrees = Record<string, Record<string, PropertyDoc>>;
|
|
18
|
+
|
|
19
|
+
let cached: PropertyTrees | undefined;
|
|
20
|
+
|
|
21
|
+
/** The generated property trees, or an empty set before `generate` has run. */
|
|
22
|
+
export function propertyTrees(): PropertyTrees {
|
|
23
|
+
if (cached) return cached;
|
|
24
|
+
const file = join(dirname(moduleSourceDir(import.meta.url)), "generated", "properties.json");
|
|
25
|
+
cached = existsSync(file) ? (JSON.parse(readFileSync(file, "utf-8")) as PropertyTrees) : {};
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Test seam: replace the loaded trees. */
|
|
30
|
+
export function setPropertyTrees(trees: PropertyTrees | undefined): void {
|
|
31
|
+
cached = trees;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Where the cursor is inside a constructor's config object. */
|
|
35
|
+
export interface PropertyPosition {
|
|
36
|
+
className: string;
|
|
37
|
+
/** The keys of the objects enclosing the cursor, outermost first. */
|
|
38
|
+
path: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the text before the cursor to the built-in whose constructor
|
|
43
|
+
* argument holds it and the keys of the objects around it. Undefined when the
|
|
44
|
+
* cursor is not inside `new X({ … })`.
|
|
45
|
+
*/
|
|
46
|
+
export function propertyPosition(before: string): PropertyPosition | undefined {
|
|
47
|
+
const re = /\bnew\s+(\w+)\s*\(\s*\{/g;
|
|
48
|
+
let match: RegExpExecArray | null;
|
|
49
|
+
let last: { className: string; start: number } | undefined;
|
|
50
|
+
while ((match = re.exec(before)) !== null) last = { className: match[1], start: match.index + match[0].length };
|
|
51
|
+
if (!last) return undefined;
|
|
52
|
+
|
|
53
|
+
// Walk from the constructor's `{`: each `{` opens the object of the key
|
|
54
|
+
// written just before it; `[` opens a list whose objects share that key.
|
|
55
|
+
const stack: Array<string | null> = [];
|
|
56
|
+
let pendingKey: string | null = null;
|
|
57
|
+
let i = last.start;
|
|
58
|
+
const text = before;
|
|
59
|
+
while (i < text.length) {
|
|
60
|
+
const ch = text[i];
|
|
61
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
62
|
+
const end = skipString(text, i);
|
|
63
|
+
const literal = text.slice(i + 1, end - 1);
|
|
64
|
+
// A quoted key: `"k8s.pod.name": …`
|
|
65
|
+
if (/^\s*:/.test(text.slice(end))) pendingKey = literal;
|
|
66
|
+
i = end;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
70
|
+
const nl = text.indexOf("\n", i);
|
|
71
|
+
i = nl < 0 ? text.length : nl;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
75
|
+
const close = text.indexOf("*/", i + 2);
|
|
76
|
+
i = close < 0 ? text.length : close + 2;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const ident = /^[A-Za-z_$][\w$]*/.exec(text.slice(i));
|
|
80
|
+
if (ident) {
|
|
81
|
+
if (/^\s*:/.test(text.slice(i + ident[0].length))) pendingKey = ident[0];
|
|
82
|
+
i += ident[0].length;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === "{" || ch === "[") {
|
|
86
|
+
// A list keeps its key, so its objects resolve to the list's items.
|
|
87
|
+
stack.push(ch === "[" ? `[${pendingKey ?? ""}` : pendingKey);
|
|
88
|
+
pendingKey = null;
|
|
89
|
+
} else if (ch === "}" || ch === "]") {
|
|
90
|
+
if (stack.length === 0) return undefined; // the constructor's object closed
|
|
91
|
+
stack.pop();
|
|
92
|
+
} else if (ch === "," || ch === "(" || ch === ")") {
|
|
93
|
+
pendingKey = null;
|
|
94
|
+
}
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const path: string[] = [];
|
|
99
|
+
let inList: string | undefined;
|
|
100
|
+
for (const entry of stack) {
|
|
101
|
+
if (entry === null) {
|
|
102
|
+
if (inList === undefined) return undefined; // an object whose key is unknown
|
|
103
|
+
inList = undefined; // an item of the list just entered
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (entry.startsWith("[")) {
|
|
107
|
+
inList = entry.slice(1);
|
|
108
|
+
if (inList) path.push(inList);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
path.push(entry);
|
|
112
|
+
inList = undefined;
|
|
113
|
+
}
|
|
114
|
+
if (inList !== undefined) return undefined; // directly inside a list, not an item
|
|
115
|
+
return { className: last.className, path };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function skipString(text: string, start: number): number {
|
|
119
|
+
const quote = text[start];
|
|
120
|
+
let i = start + 1;
|
|
121
|
+
while (i < text.length && text[i] !== quote) i += text[i] === "\\" ? 2 : 1;
|
|
122
|
+
return Math.min(i + 1, text.length);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The properties at a path of a built-in's tree, or undefined when the path leaves it. */
|
|
126
|
+
export function propertiesAt(position: PropertyPosition): Record<string, PropertyDoc> | undefined {
|
|
127
|
+
let level: Record<string, PropertyDoc> | undefined = propertyTrees()[position.className];
|
|
128
|
+
for (const key of position.path) {
|
|
129
|
+
level = level?.[key]?.properties;
|
|
130
|
+
if (!level) return undefined;
|
|
131
|
+
}
|
|
132
|
+
return level;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Markdown for one property: its type, deprecation and upstream description. */
|
|
136
|
+
export function propertyMarkdown(name: string, doc: PropertyDoc, className: string): string {
|
|
137
|
+
const lines = [`**${name}**: \`${doc.type}\`${doc.deprecated ? " (deprecated)" : ""}`];
|
|
138
|
+
if (doc.description) lines.push("", doc.description);
|
|
139
|
+
lines.push("", `\`${className}\` config, from upstream config.schema.yaml.`);
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
package/src/metric-names.test.ts
CHANGED
|
@@ -57,6 +57,17 @@ describe("spanMetricsNames", () => {
|
|
|
57
57
|
expect(spanMetricsNames(connector, { add_metric_suffixes: false }).duration?.prometheus).toBe("spans_duration");
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
test("the prometheus exporter's translation_strategy decides suffixes; it ignores add_metric_suffixes at the pin", () => {
|
|
61
|
+
const connector = new SpanMetricsConnector({ namespace: "spans", histogram: { unit: "s" } });
|
|
62
|
+
const without = new PrometheusExporter({ endpoint: "0.0.0.0:8889", translation_strategy: "UnderscoreEscapingWithoutSuffixes" });
|
|
63
|
+
expect(spanMetricsNames(connector, without).duration?.prometheus).toBe("spans_duration");
|
|
64
|
+
const ignored = new PrometheusExporter({ endpoint: "0.0.0.0:8889", add_metric_suffixes: false });
|
|
65
|
+
expect(spanMetricsNames(connector, ignored).duration?.prometheus).toBe("spans_duration_seconds");
|
|
66
|
+
const utf8 = new PrometheusExporter({ endpoint: "0.0.0.0:8889", translation_strategy: "NoUTF8EscapingWithSuffixes" });
|
|
67
|
+
expect(spanMetricsNames(connector, utf8).calls.prometheus).toBe("spans.calls_total");
|
|
68
|
+
expect(spanMetricsNames(connector, { translation_strategy: "NoTranslation" }).calls.prometheus).toBe("spans.calls");
|
|
69
|
+
});
|
|
70
|
+
|
|
60
71
|
test("only a spanmetrics connector", () => {
|
|
61
72
|
expect(() => spanMetricsNames(new PrometheusExporter({ endpoint: "0.0.0.0:8889" }) as never)).toThrow(/spanmetrics/);
|
|
62
73
|
});
|
package/src/metric-names.ts
CHANGED
|
@@ -11,9 +11,16 @@
|
|
|
11
11
|
* with the exporter's defaults: `.` and any other character Prometheus does
|
|
12
12
|
* not allow become `_`; counters take `_total`; a unit becomes a suffix
|
|
13
13
|
* (`ms` is `_milliseconds`, `s` is `_seconds`); the exporter's `namespace`
|
|
14
|
-
* is a prefix joined with `_
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* is a prefix joined with `_`. The exporter's `translation_strategy` decides
|
|
15
|
+
* the escaping and the suffixes: the default, `UnderscoreEscapingWithSuffixes`,
|
|
16
|
+
* is what is described here; `UnderscoreEscapingWithoutSuffixes` drops the
|
|
17
|
+
* suffixes; `NoUTF8EscapingWithSuffixes` keeps the name's characters;
|
|
18
|
+
* `NoTranslation` does neither. From collector-contrib v0.154.0 the
|
|
19
|
+
* `prometheus` exporter ignores the deprecated `add_metric_suffixes`, so a
|
|
20
|
+
* name read from that exporter follows `translation_strategy` alone; the
|
|
21
|
+
* `prometheusremotewrite` exporter still honours it when no strategy is set.
|
|
22
|
+
* Attribute names become label names the same way (`service.name` is
|
|
23
|
+
* `service_name`).
|
|
17
24
|
*
|
|
18
25
|
* This module holds types and string functions only, so a lexicon that
|
|
19
26
|
* builds queries from it does not load the collector components.
|
|
@@ -52,14 +59,54 @@ const UNIT_WORDS: Record<string, string> = {
|
|
|
52
59
|
"1": "ratio",
|
|
53
60
|
};
|
|
54
61
|
|
|
62
|
+
/** The `translation_strategy` values the Prometheus exporters take. */
|
|
63
|
+
export type PrometheusTranslationStrategy =
|
|
64
|
+
| "UnderscoreEscapingWithSuffixes"
|
|
65
|
+
| "UnderscoreEscapingWithoutSuffixes"
|
|
66
|
+
| "NoUTF8EscapingWithSuffixes"
|
|
67
|
+
| "NoTranslation";
|
|
68
|
+
|
|
55
69
|
/** How the `prometheus` exporter is configured, as far as naming goes. */
|
|
56
70
|
export interface PrometheusNaming {
|
|
57
71
|
/** The exporter's `namespace`, put in front of every name and joined with `_`. */
|
|
58
72
|
namespace?: string;
|
|
59
|
-
/** The exporter's `
|
|
73
|
+
/** The exporter's `translation_strategy`. Default `UnderscoreEscapingWithSuffixes`. */
|
|
74
|
+
translation_strategy?: PrometheusTranslationStrategy | (string & {});
|
|
75
|
+
/**
|
|
76
|
+
* `false` drops the unit and `_total` suffixes when no `translation_strategy`
|
|
77
|
+
* is set, as the `prometheusremotewrite` exporter reads it. The `prometheus`
|
|
78
|
+
* exporter ignores it at the pinned release, and {@link exporterNaming}
|
|
79
|
+
* leaves it out for that exporter.
|
|
80
|
+
*
|
|
81
|
+
* @deprecated Upstream deprecates it for `translation_strategy`.
|
|
82
|
+
*/
|
|
60
83
|
add_metric_suffixes?: boolean;
|
|
61
84
|
}
|
|
62
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The naming a declared `prometheus` exporter (or its config) applies: its
|
|
88
|
+
* `namespace` and `translation_strategy`. `add_metric_suffixes` is left out,
|
|
89
|
+
* since the exporter ignores it at the pinned release. A plain
|
|
90
|
+
* `PrometheusNaming` is returned as given.
|
|
91
|
+
*/
|
|
92
|
+
export function exporterNaming(exporter: PrometheusNaming | { props?: PrometheusNaming; componentType?: unknown } | undefined): PrometheusNaming {
|
|
93
|
+
if (!exporter) return {};
|
|
94
|
+
if ("props" in exporter && typeof exporter.props === "object" && exporter.props !== null) {
|
|
95
|
+
const { add_metric_suffixes: _ignored, ...rest } = exporter.props;
|
|
96
|
+
return { ...(rest.namespace !== undefined ? { namespace: rest.namespace } : {}), ...(rest.translation_strategy !== undefined ? { translation_strategy: rest.translation_strategy } : {}) };
|
|
97
|
+
}
|
|
98
|
+
return exporter as PrometheusNaming;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Whether a naming escapes names to `[A-Za-z0-9_:]` and whether it adds suffixes. */
|
|
102
|
+
function strategyOf(naming: PrometheusNaming): { escape: boolean; suffixes: boolean } {
|
|
103
|
+
const strategy = naming.translation_strategy ?? (naming.add_metric_suffixes === false ? "UnderscoreEscapingWithoutSuffixes" : "UnderscoreEscapingWithSuffixes");
|
|
104
|
+
return {
|
|
105
|
+
escape: strategy.startsWith("UnderscoreEscaping"),
|
|
106
|
+
suffixes: strategy !== "UnderscoreEscapingWithoutSuffixes" && strategy !== "NoTranslation",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
63
110
|
/** An attribute name as a Prometheus label: `service.name` is `service_name`. */
|
|
64
111
|
export function prometheusLabel(attribute: string): string {
|
|
65
112
|
const label = attribute.replace(/[^A-Za-z0-9_]/g, "_");
|
|
@@ -73,9 +120,11 @@ export function prometheusLabel(attribute: string): string {
|
|
|
73
120
|
* @param unit The metric's unit, e.g. `ms`. Units in braces (`{call}`) add nothing.
|
|
74
121
|
*/
|
|
75
122
|
export function prometheusMetricName(name: string, type: "sum" | "histogram", unit?: string, naming: PrometheusNaming = {}): string {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
123
|
+
const { escape, suffixes } = strategyOf(naming);
|
|
124
|
+
const clean = (s: string) => (escape ? s.replace(/[^A-Za-z0-9_:]/g, "_") : s);
|
|
125
|
+
let out = clean(name);
|
|
126
|
+
if (naming.namespace) out = `${clean(naming.namespace)}_${out}`;
|
|
127
|
+
if (!suffixes) return out;
|
|
79
128
|
const word = unit && !unit.startsWith("{") ? (UNIT_WORDS[unit] ?? unit.replace(/[^A-Za-z0-9_:]/g, "_")) : undefined;
|
|
80
129
|
if (word && !out.endsWith(`_${word}`)) out = `${out}_${word}`;
|
|
81
130
|
if (type === "sum" && !out.endsWith("_total")) out = `${out}_total`;
|
|
@@ -85,11 +134,11 @@ export function prometheusMetricName(name: string, type: "sum" | "histogram", un
|
|
|
85
134
|
/** The parts of a spanmetrics config the metric names depend on. */
|
|
86
135
|
export interface SpanMetricsNamingConfig {
|
|
87
136
|
namespace?: string;
|
|
88
|
-
dimensions?: Array<{ name
|
|
89
|
-
calls_dimensions?: Array<{ name
|
|
137
|
+
dimensions?: Array<{ name?: string }>;
|
|
138
|
+
calls_dimensions?: Array<{ name?: string }>;
|
|
90
139
|
exclude_dimensions?: string[];
|
|
91
|
-
histogram?: { disable?: boolean; unit?: "ms" | "s"; dimensions?: Array<{ name
|
|
92
|
-
events?: { enabled?: boolean; dimensions?: Array<{ name
|
|
140
|
+
histogram?: { disable?: boolean; unit?: "ms" | "s"; dimensions?: Array<{ name?: string }> };
|
|
141
|
+
events?: { enabled?: boolean; dimensions?: Array<{ name?: string }> };
|
|
93
142
|
}
|
|
94
143
|
|
|
95
144
|
/** The metrics one `spanmetrics` connector emits, and the labels a query selects them by. */
|
|
@@ -139,13 +188,13 @@ export function spanMetricsNames(
|
|
|
139
188
|
exporter?: PrometheusNaming | { props?: PrometheusNaming },
|
|
140
189
|
): SpanMetricsNames {
|
|
141
190
|
const c = configOf(connector);
|
|
142
|
-
const naming
|
|
143
|
-
exporter && "props" in exporter && typeof exporter.props === "object" ? (exporter.props as PrometheusNaming) : ((exporter ?? {}) as PrometheusNaming);
|
|
191
|
+
const naming = exporterNaming(exporter);
|
|
144
192
|
const namespace = c.namespace ?? SPANMETRICS_DEFAULT_NAMESPACE;
|
|
145
193
|
const metricName = (n: string) => (namespace === "" ? n : `${namespace}.${n}`);
|
|
146
194
|
const excluded = new Set(c.exclude_dimensions ?? []);
|
|
147
195
|
const defaults = SPANMETRICS_DEFAULT_DIMENSIONS.filter((d) => !excluded.has(d));
|
|
148
|
-
|
|
196
|
+
// A `glob` dimension names no single attribute, so it adds no fixed label.
|
|
197
|
+
const names = (extra?: Array<{ name?: string }>) => (extra ?? []).flatMap((d) => (d.name === undefined ? [] : [d.name]));
|
|
149
198
|
const common = [...defaults, ...names(c.dimensions)];
|
|
150
199
|
|
|
151
200
|
const metric = (n: string, type: "sum" | "histogram", dims: string[], unit?: string): CollectorMetric => ({
|
|
@@ -215,8 +264,7 @@ export function serviceGraphNames(
|
|
|
215
264
|
throw new Error(`serviceGraphNames: expected a servicegraph connector, got ${d.componentType}`);
|
|
216
265
|
}
|
|
217
266
|
const c = (typeof d.componentType === "string" ? (d.props ?? {}) : connector) as ServiceGraphNamingConfig;
|
|
218
|
-
const naming
|
|
219
|
-
exporter && "props" in exporter && typeof exporter.props === "object" ? (exporter.props as PrometheusNaming) : ((exporter ?? {}) as PrometheusNaming);
|
|
267
|
+
const naming = exporterNaming(exporter);
|
|
220
268
|
const dimensions = [
|
|
221
269
|
"client",
|
|
222
270
|
"server",
|
package/src/model.ts
CHANGED
|
@@ -125,7 +125,7 @@ export interface ComponentTypeAlias {
|
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* The renamed built-ins. chant emits `builtin`, which the pinned collector
|
|
128
|
-
* (`COLLECTOR_PIN
|
|
128
|
+
* (`COLLECTOR_PIN`) still accepts as each component's deprecated type. A config
|
|
129
129
|
* written for a newer collector may use `type`, so code that reads a config
|
|
130
130
|
* (the importer, the checks, the k8s composites) accepts both.
|
|
131
131
|
*/
|
|
@@ -61,17 +61,19 @@ describe("collectorAudit's pieces", () => {
|
|
|
61
61
|
]);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
test("bumpPins moves only the two pin versions in define.ts", () => {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
64
|
+
test("bumpPins moves only the two pin versions, in spec/pin.ts and define.ts", () => {
|
|
65
|
+
const define = readFileSync(join(import.meta.dirname, "..", "..", "define.ts"), "utf8");
|
|
66
|
+
const pin = readFileSync(join(import.meta.dirname, "..", "..", "spec", "pin.ts"), "utf8");
|
|
67
|
+
const out = bumpPins({ define, pin }, { collector: "v0.170.0", genai: "v1.50.0" });
|
|
67
68
|
const block = (text: string, name: string) => new RegExp(`export const ${name}: SchemaPin = Object\\.freeze\\(\\{[^}]*\\}`).exec(text)![0];
|
|
68
|
-
expect(
|
|
69
|
-
expect(block(out, "GENAI_SEMCONV_PIN")).toContain('version: "v1.50.0"');
|
|
70
|
-
// SEMCONV_PIN, and the custom-component pin in define.ts's doc comment
|
|
71
|
-
expect(block(out, "SEMCONV_PIN")).toBe(block(
|
|
72
|
-
const changed =
|
|
73
|
-
expect(changed).toHaveLength(
|
|
74
|
-
expect((
|
|
69
|
+
expect(out.pin).toContain('export const OTELCOL_CONTRIB_VERSION = "v0.170.0";');
|
|
70
|
+
expect(block(out.define, "GENAI_SEMCONV_PIN")).toContain('version: "v1.50.0"');
|
|
71
|
+
// SEMCONV_PIN, the k8s distribution pin and the custom-component pin in define.ts's doc comment are left alone.
|
|
72
|
+
expect(block(out.define, "SEMCONV_PIN")).toBe(block(define, "SEMCONV_PIN"));
|
|
73
|
+
const changed = (a: string, b: string) => a.split("\n").filter((l, i) => l !== b.split("\n")[i]);
|
|
74
|
+
expect(changed(out.define, define)).toHaveLength(1);
|
|
75
|
+
expect(changed(out.pin, pin)).toHaveLength(1);
|
|
76
|
+
expect(() => bumpPins({ define: "nothing", pin: "nothing" }, { collector: "v1.0.0" })).toThrow(/no OTELCOL_CONTRIB_VERSION/);
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
test("semver ordering", () => {
|
|
@@ -80,6 +82,11 @@ describe("collectorAudit's pieces", () => {
|
|
|
80
82
|
});
|
|
81
83
|
});
|
|
82
84
|
|
|
85
|
+
/** The releases after the pin, whatever it is. */
|
|
86
|
+
const minorAfter = (v: string, n: number) => v.replace(/^v(\d+)\.(\d+)\.\d+$/, (_, major: string, minor: string) => `v${major}.${Number(minor) + n}.0`);
|
|
87
|
+
const NEXT = minorAfter(COLLECTOR_PIN.version, 1);
|
|
88
|
+
const AFTER_NEXT = minorAfter(COLLECTOR_PIN.version, 2);
|
|
89
|
+
|
|
83
90
|
describe("collectorAudit", () => {
|
|
84
91
|
test("current pins and no stability reading: no findings, report mode does nothing else", async () => {
|
|
85
92
|
const { f } = fakeFetch([COLLECTOR_PIN.version, "v0.129.0"], [GENAI_SEMCONV_PIN.version]);
|
|
@@ -90,30 +97,30 @@ describe("collectorAudit", () => {
|
|
|
90
97
|
});
|
|
91
98
|
|
|
92
99
|
test("pins behind the newest releases are findings, prereleases ignored", async () => {
|
|
93
|
-
const { f } = fakeFetch([
|
|
100
|
+
const { f } = fakeFetch([AFTER_NEXT, NEXT, COLLECTOR_PIN.version], ["v1.42.0", GENAI_SEMCONV_PIN.version]);
|
|
94
101
|
const r = await collectorAudit({ stability: false, _fetch: f });
|
|
95
102
|
expect(r.findings.map((x) => x.kind)).toEqual(["collector-pin-behind", "semconv-pin-behind"]);
|
|
96
|
-
expect(r.collector).toEqual({ pin: COLLECTOR_PIN.version, latest:
|
|
103
|
+
expect(r.collector).toEqual({ pin: COLLECTOR_PIN.version, latest: AFTER_NEXT, behind: 2 });
|
|
97
104
|
expect(r.semconv.behind).toBe(1);
|
|
98
105
|
});
|
|
99
106
|
|
|
100
107
|
test("component stability at the pin and at the newest release, contrib then core, within the budget", async () => {
|
|
101
108
|
const meta = {
|
|
102
109
|
// otlp is a core component: contrib answers 404 for it.
|
|
103
|
-
[`opentelemetry-collector/
|
|
110
|
+
[`opentelemetry-collector/${NEXT}/receiver/otlpreceiver`]: stability({ stable: ["traces"], deprecated: ["logs"] }),
|
|
104
111
|
[`opentelemetry-collector/${COLLECTOR_PIN.version}/receiver/otlpreceiver`]: stability({ stable: ["traces", "logs"] }),
|
|
105
112
|
};
|
|
106
|
-
const { f, urls } = fakeFetch([
|
|
113
|
+
const { f, urls } = fakeFetch([NEXT, COLLECTOR_PIN.version], [GENAI_SEMCONV_PIN.version], meta);
|
|
107
114
|
const r = await collectorAudit({ _fetch: f, stabilityBudget: 500 });
|
|
108
|
-
expect(r.findings).toContainEqual({ kind: "deprecated", subject: "receiver/otlp", detail:
|
|
115
|
+
expect(r.findings).toContainEqual({ kind: "deprecated", subject: "receiver/otlp", detail: `logs deprecated at ${NEXT}` });
|
|
109
116
|
expect(r.unchecked).not.toContain("receiver/otlp");
|
|
110
117
|
expect(r.unchecked.length).toBe(builtinComponents().length - 1);
|
|
111
118
|
// Looked for in contrib first, then core.
|
|
112
|
-
const otlp = urls.filter((u) => u.includes(
|
|
119
|
+
const otlp = urls.filter((u) => u.includes(`/${NEXT}/receiver/otlpreceiver/`));
|
|
113
120
|
expect(otlp[0]).toContain("opentelemetry-collector-contrib");
|
|
114
121
|
expect(otlp[1]).toContain("open-telemetry/opentelemetry-collector/");
|
|
115
122
|
|
|
116
|
-
const capped = fakeFetch([
|
|
123
|
+
const capped = fakeFetch([NEXT, COLLECTOR_PIN.version], [GENAI_SEMCONV_PIN.version], meta);
|
|
117
124
|
await collectorAudit({ _fetch: capped.f, stabilityBudget: 3 });
|
|
118
125
|
expect(capped.urls.filter((u) => u.includes("raw.githubusercontent.com"))).toHaveLength(3);
|
|
119
126
|
});
|
|
@@ -127,7 +134,7 @@ describe("collectorAudit", () => {
|
|
|
127
134
|
});
|
|
128
135
|
|
|
129
136
|
test("pull-request mode bumps the pins in a worktree and opens a pull request", async () => {
|
|
130
|
-
const { f } = fakeFetch([
|
|
137
|
+
const { f } = fakeFetch([NEXT, COLLECTOR_PIN.version], [GENAI_SEMCONV_PIN.version]);
|
|
131
138
|
const calls: string[] = [];
|
|
132
139
|
const lexiconDir = join(import.meta.dirname, "..", "..", "..");
|
|
133
140
|
const run: CommandRunner = async (bin, args) => {
|
|
@@ -143,7 +150,7 @@ describe("collectorAudit", () => {
|
|
|
143
150
|
});
|
|
144
151
|
|
|
145
152
|
test("pull-request mode end to end against a recorded git and gh", async () => {
|
|
146
|
-
const { f } = fakeFetch([
|
|
153
|
+
const { f } = fakeFetch([NEXT, COLLECTOR_PIN.version], [GENAI_SEMCONV_PIN.version]);
|
|
147
154
|
const calls: string[] = [];
|
|
148
155
|
const lexiconDir = join(import.meta.dirname, "..", "..", "..");
|
|
149
156
|
const { mkdirSync } = await import("node:fs");
|
|
@@ -151,15 +158,15 @@ describe("collectorAudit", () => {
|
|
|
151
158
|
calls.push(`${bin} ${args.join(" ")}`);
|
|
152
159
|
if (args[0] === "rev-parse") return join(lexiconDir, "..", "..") + "\n";
|
|
153
160
|
if (args[0] === "symbolic-ref") return "origin/main\n";
|
|
154
|
-
if (args[0] === "worktree" && args[1] === "add") mkdirSync(join(args[5], "lexicons", "otel", "src"), { recursive: true });
|
|
161
|
+
if (args[0] === "worktree" && args[1] === "add") mkdirSync(join(args[5], "lexicons", "otel", "src", "spec"), { recursive: true });
|
|
155
162
|
if (bin === "gh" && args[1] === "list") return "[]";
|
|
156
163
|
if (bin === "gh" && args[1] === "create") return "https://github.com/INTENTIUS/chant/pull/1\n";
|
|
157
164
|
return "";
|
|
158
165
|
};
|
|
159
166
|
const r = await collectorAudit({ mode: "pull-request", stability: false, lexiconDir, _fetch: f, _run: run });
|
|
160
167
|
expect(r.prUrl).toBe("https://github.com/INTENTIUS/chant/pull/1");
|
|
161
|
-
expect(calls).toContain("git add lexicons/otel/src/
|
|
162
|
-
expect(calls.some((c) => c.startsWith(
|
|
168
|
+
expect(calls).toContain("git add lexicons/otel/src/spec/pin.ts");
|
|
169
|
+
expect(calls.some((c) => c.startsWith(`git commit -m chore(otel): move COLLECTOR_PIN to ${NEXT}`))).toBe(true);
|
|
163
170
|
expect(calls.some((c) => c.startsWith("git push --force origin HEAD:refs/heads/chant/otel-pins"))).toBe(true);
|
|
164
171
|
expect(calls.some((c) => c.startsWith("gh pr create --head chant/otel-pins --base main"))).toBe(true);
|
|
165
172
|
});
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
*
|
|
17
17
|
* `mode` says what to do with the findings: `report` returns them;
|
|
18
18
|
* `issue` keeps one open issue current; `pull-request` also bumps the pins
|
|
19
|
-
* in
|
|
19
|
+
* (`OTELCOL_CONTRIB_VERSION` in `src/spec/pin.ts` for `COLLECTOR_PIN`, and
|
|
20
|
+
* `GENAI_SEMCONV_PIN` in `src/define.ts`) on a proposal branch and opens (or
|
|
20
21
|
* edits) a pull request with the findings as its body. Only the pin values
|
|
21
|
-
* change: the
|
|
22
|
-
*
|
|
22
|
+
* change: refreshing the schema snapshot the config types are generated from
|
|
23
|
+
* (`just snapshot`, #3361) is the reviewer's, which the body says, and
|
|
24
|
+
* generation refuses until it is done.
|
|
23
25
|
*/
|
|
24
26
|
|
|
25
27
|
import { mkdtempSync, readFileSync } from "node:fs";
|
|
@@ -164,17 +166,27 @@ function behindCount(releases: Release[], pin: string): number {
|
|
|
164
166
|
return releases.filter((r) => compareSemver(r.tag, pin) > 0).length;
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
/**
|
|
170
|
+
* The pin files with `COLLECTOR_PIN` and `GENAI_SEMCONV_PIN` moved to the
|
|
171
|
+
* given versions: `COLLECTOR_PIN` follows `OTELCOL_CONTRIB_VERSION` in
|
|
172
|
+
* `src/spec/pin.ts`, and `GENAI_SEMCONV_PIN` is in `src/define.ts`.
|
|
173
|
+
*/
|
|
174
|
+
export function bumpPins(
|
|
175
|
+
texts: { define: string; pin: string },
|
|
176
|
+
to: { collector?: string; genai?: string },
|
|
177
|
+
): { define: string; pin: string } {
|
|
178
|
+
let { define, pin } = texts;
|
|
179
|
+
if (to.collector) {
|
|
180
|
+
const re = /(export const OTELCOL_CONTRIB_VERSION = ")([^"]+)(")/;
|
|
181
|
+
if (!re.test(pin)) throw new Error("spec/pin.ts has no OTELCOL_CONTRIB_VERSION (COLLECTOR_PIN) to bump");
|
|
182
|
+
pin = pin.replace(re, `$1${to.collector}$3`);
|
|
183
|
+
}
|
|
184
|
+
if (to.genai) {
|
|
185
|
+
const re = /(export const GENAI_SEMCONV_PIN: SchemaPin = Object\.freeze\(\{[^}]*?version:\s*")([^"]+)(")/;
|
|
186
|
+
if (!re.test(define)) throw new Error("define.ts has no GENAI_SEMCONV_PIN with a version to bump");
|
|
187
|
+
define = define.replace(re, `$1${to.genai}$3`);
|
|
188
|
+
}
|
|
189
|
+
return { define, pin };
|
|
178
190
|
}
|
|
179
191
|
|
|
180
192
|
export function renderCollectorAuditSummary(r: Omit<CollectorAuditResult, "summary" | "mode">): string {
|
|
@@ -248,16 +260,24 @@ export async function collectorAudit(args: CollectorAuditArgs = {}): Promise<Col
|
|
|
248
260
|
const lexiconDir = resolve(args.lexiconDir ?? join(cwd, "lexicons", "otel"));
|
|
249
261
|
const root = (await run("git", ["rev-parse", "--show-toplevel"], lexiconDir)).trim();
|
|
250
262
|
const definePath = join(lexiconDir, "src", "define.ts");
|
|
251
|
-
const
|
|
263
|
+
const pinPath = join(lexiconDir, "src", "spec", "pin.ts");
|
|
264
|
+
const bumped = bumpPins({ define: readFileSync(definePath, "utf8"), pin: readFileSync(pinPath, "utf8") }, to);
|
|
265
|
+
const files = [
|
|
266
|
+
...(to.collector ? [{ path: relative(root, pinPath), content: bumped.pin }] : []),
|
|
267
|
+
...(to.genai ? [{ path: relative(root, definePath), content: bumped.define }] : []),
|
|
268
|
+
];
|
|
252
269
|
const moved = [to.collector ? `COLLECTOR_PIN to ${to.collector}` : "", to.genai ? `GENAI_SEMCONV_PIN to ${to.genai}` : ""].filter(Boolean).join(" and ");
|
|
253
270
|
result.prUrl = await proposePullRequest(run, {
|
|
254
271
|
root,
|
|
255
272
|
branch: args.branch ?? "chant/otel-pins",
|
|
256
273
|
title: `chore(otel): move ${moved}`,
|
|
257
274
|
body:
|
|
258
|
-
`${summary}\n\nThis moves the pin values only.
|
|
259
|
-
|
|
260
|
-
|
|
275
|
+
`${summary}\n\nThis moves the pin values only. ` +
|
|
276
|
+
(to.collector
|
|
277
|
+
? "Run `just snapshot` in lexicons/otel, paste the printed OTEL_SPEC_PIN and regenerate: the config types are generated from the snapshot, and generation refuses a snapshot taken at another version. "
|
|
278
|
+
: "") +
|
|
279
|
+
`The defaults and checks written against ${collector.pin} still need reading against the release notes before this merges.\n`,
|
|
280
|
+
files,
|
|
261
281
|
commitMessage: `chore(otel): move ${moved}`,
|
|
262
282
|
worktreeDir: join(mkdtempSync(join(tmpdir(), "chant-otel-pins-")), "wt"),
|
|
263
283
|
});
|
package/src/plugin.test.ts
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -18,6 +18,19 @@ import { compositeCatalog } from "./composites/catalog";
|
|
|
18
18
|
import { DEFAULT_TEMPLATE, GENAI_TEMPLATE, K8S_AGENT_TEMPLATE } from "./init-templates";
|
|
19
19
|
import { COLLECTOR_PIN } from "./define";
|
|
20
20
|
import { collectorTopologyOf } from "./topology";
|
|
21
|
+
import { otelWorkspaceLinks } from "./workspace-links";
|
|
22
|
+
|
|
23
|
+
/** opentelemetry-collector-releases also tags its tools (`cmd/builder/v0.163.2`); only the plain release tags are distributions. */
|
|
24
|
+
function releaseTag(tag: string): string | null {
|
|
25
|
+
return /^v\d+\.\d+\.\d+$/.test(tag) ? tag : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SNAPSHOT_MOVES =
|
|
29
|
+
"The schema snapshot and its digest move with the version: set the version in src/spec/pin.ts, run `just snapshot` " +
|
|
30
|
+
"(lexicons/otel) on a networked machine, paste the printed OTEL_SPEC_PIN into src/spec/pin.ts, run `npm run generate`, " +
|
|
31
|
+
"and read the diffs of src/spec/otel-schemas.snapshot.json and the generated types. Moving otelcol-contrib also moves " +
|
|
32
|
+
"COLLECTOR_PIN and COLLECTOR_IMAGE, the default image of the k8s, docker and fly collector composites, so it is a " +
|
|
33
|
+
"level-0 change that needs an entry on the exceptions list (#2525).";
|
|
21
34
|
|
|
22
35
|
const catalogResource: McpResourceContribution = {
|
|
23
36
|
uri: "otel:resource-catalog",
|
|
@@ -54,15 +67,47 @@ export const otelPlugin: LexiconPlugin = {
|
|
|
54
67
|
printValidationResult(await validate());
|
|
55
68
|
},
|
|
56
69
|
|
|
57
|
-
async coverage(
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
|
|
71
|
+
const { coverageReport, formatCoverage } = await import("./coverage");
|
|
72
|
+
const report = coverageReport();
|
|
73
|
+
console.error(formatCoverage(report));
|
|
74
|
+
if (options?.verbose) {
|
|
75
|
+
for (const d of report.distributions) {
|
|
76
|
+
console.error(` ${d.distribution}, reached through defineComponent: ${[...d.schemaNotBuiltin, ...d.noSchemaNotBuiltin].join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
63
79
|
console.error(" anything else: defineComponent (see docs: custom components)");
|
|
64
80
|
},
|
|
65
81
|
|
|
82
|
+
// #3361: coverage against the otelcol-contrib manifest, offline from the
|
|
83
|
+
// committed schema snapshot.
|
|
84
|
+
async coverageReport(): Promise<{ unaccountedKinds?: string[] }> {
|
|
85
|
+
const { coverageReport } = await import("./coverage");
|
|
86
|
+
return coverageReport();
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// The distribution manifests the generated config types are scoped by
|
|
90
|
+
// (#3361): otelcol-contrib, which COLLECTOR_PIN and COLLECTOR_IMAGE follow,
|
|
91
|
+
// and otelcol-k8s for the coverage report.
|
|
92
|
+
upstreamPin: {
|
|
93
|
+
label: "otelcol-contrib",
|
|
94
|
+
file: "src/spec/pin.ts",
|
|
95
|
+
pattern: /export const OTELCOL_CONTRIB_VERSION = "([^"]+)"/,
|
|
96
|
+
replace: (v: string, line: string) => line.replace(/= "[^"]+"/, `= "${v}"`),
|
|
97
|
+
alsoMoves: SNAPSHOT_MOVES,
|
|
98
|
+
upstream: { owner: "open-telemetry", repo: "opentelemetry-collector-releases", kind: "releases", tagVersion: releaseTag },
|
|
99
|
+
},
|
|
100
|
+
upstreamPins: [
|
|
101
|
+
{
|
|
102
|
+
label: "otelcol-k8s",
|
|
103
|
+
file: "src/spec/pin.ts",
|
|
104
|
+
pattern: /export const OTELCOL_K8S_VERSION = "([^"]+)"/,
|
|
105
|
+
replace: (v: string, line: string) => line.replace(/= "[^"]+"/, `= "${v}"`),
|
|
106
|
+
alsoMoves: SNAPSHOT_MOVES,
|
|
107
|
+
upstream: { owner: "open-telemetry", repo: "opentelemetry-collector-releases", kind: "releases", tagVersion: releaseTag },
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
|
|
66
111
|
async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
|
|
67
112
|
const { packageLexicon } = await import("./codegen/package");
|
|
68
113
|
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
@@ -88,6 +133,12 @@ export const otelPlugin: LexiconPlugin = {
|
|
|
88
133
|
return { collector: topology };
|
|
89
134
|
},
|
|
90
135
|
|
|
136
|
+
// The tables and columns a clickhouse exporter writes, as links to the sql
|
|
137
|
+
// member that declares them (#4397), for `chant workspace check`.
|
|
138
|
+
workspaceLinks(entities) {
|
|
139
|
+
return otelWorkspaceLinks(entities);
|
|
140
|
+
},
|
|
141
|
+
|
|
91
142
|
postSynthChecks() {
|
|
92
143
|
return postSynthChecks;
|
|
93
144
|
},
|
package/src/serializer.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* the order they are declared in, and each component's config keeps the key
|
|
8
8
|
* order it was written in, since collector docs and diffs read that way.
|
|
9
9
|
*
|
|
10
|
+
* A project that declares a `Distribution` also gets the ocb builder manifest,
|
|
11
|
+
* as `builder-config.yaml` beside the config (see `distribution.ts`).
|
|
12
|
+
*
|
|
10
13
|
* A collector config has no metadata channel, so there is no ownership marker
|
|
11
14
|
* to stamp. A custom component's schema pin is written as a `# chant:` comment
|
|
12
15
|
* line above the config (see `define.ts`).
|
|
@@ -17,6 +20,7 @@ import type { Serializer, SerializerResult } from "@intentius/chant/serializer";
|
|
|
17
20
|
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
18
21
|
import { buildCollectorConfig } from "./collector";
|
|
19
22
|
import { emitCollectorYaml } from "./yaml";
|
|
23
|
+
import { DISTRIBUTION_MANIFEST_FILE, distributionYaml, isDistributionEntity } from "./distribution";
|
|
20
24
|
|
|
21
25
|
export const otelSerializer: Serializer = {
|
|
22
26
|
name: "otel",
|
|
@@ -25,7 +29,12 @@ export const otelSerializer: Serializer = {
|
|
|
25
29
|
serialize(entities: Map<string, Declarable>, _outputs?: LexiconOutput[]): string | SerializerResult {
|
|
26
30
|
const built = buildCollectorConfig(entities);
|
|
27
31
|
const yaml = emitCollectorYaml(built.config, { header: built.header });
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
const warnings = [...built.warnings];
|
|
33
|
+
// A declared Distribution (#3374) adds the ocb builder manifest beside the config.
|
|
34
|
+
const distributions = [...entities.values()].filter(isDistributionEntity);
|
|
35
|
+
if (distributions.length > 1) warnings.push("otel: more than one Distribution is declared; the first one is written");
|
|
36
|
+
const files = distributions.length > 0 ? { [DISTRIBUTION_MANIFEST_FILE]: distributionYaml(distributions[0]) } : undefined;
|
|
37
|
+
if (warnings.length === 0 && !files) return yaml;
|
|
38
|
+
return { primary: yaml, ...(files ? { files } : {}), ...(warnings.length > 0 ? { warnings } : {}) };
|
|
30
39
|
},
|
|
31
40
|
};
|