From 1d8176918cd2876cea06da2a33135e1934147307 Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:08:44 +0100 Subject: [PATCH] fix(telemetry): bake legend brackets into cloud + Alloy collector labels Mirror the local collector change on the Grafana Cloud collector config and the in-repo Alloy config: build the group-label VALUES with brackets and comma separators (resource_group="[node, mainnet]", iac_group="[branch, role, work-item]") in both trace and metric resource statements, so span-derived and native metrics carry the same bracketed values. This lets the dashboards use a bracket-free Display name and avoids the empty "[]" that a templated bracket would leave when a group is absent (Grafana's renameByRegex runs pre-interpolation and cannot strip it). Separator changes from "-" to ", ". Co-Authored-By: Claude Opus 4.8 (1M context) --- docker/telemetry/alloy/config.alloy | 12 ++++---- .../otel-collector-config.grafanacloud.yaml | 30 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docker/telemetry/alloy/config.alloy b/docker/telemetry/alloy/config.alloy index ffbe499159..994f84fa62 100644 --- a/docker/telemetry/alloy/config.alloy +++ b/docker/telemetry/alloy/config.alloy @@ -157,9 +157,9 @@ otelcol.processor.transform "tier" { // TRACES and go straight to the batch processor, bypassing the metric // resource statements. Building the labels on the span resource keeps // span-based dashboard panels' legends consistent with native metrics. - `set(attributes["resource_group"], attributes["service.instance.id"]) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != ""`, - `set(attributes["resource_group"], Concat([attributes["resource_group"], attributes["xrpl.network.type"]], "-")) where attributes["resource_group"] != nil and attributes["xrpl.network.type"] != nil and attributes["xrpl.network.type"] != ""`, - `set(attributes["iac_group"], Concat([attributes["xrpl.branch"], attributes["xrpl.node.role"], attributes["xrpl.work.item"]], "-")) where attributes["xrpl.branch"] != nil and attributes["xrpl.branch"] != "" and attributes["xrpl.node.role"] != nil and attributes["xrpl.node.role"] != "" and attributes["xrpl.work.item"] != nil and attributes["xrpl.work.item"] != ""`, + `set(attributes["resource_group"], Concat(["[", attributes["service.instance.id"], "]"], "")) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != ""`, + `set(attributes["resource_group"], Concat(["[", attributes["service.instance.id"], ", ", attributes["xrpl.network.type"], "]"], "")) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != "" and attributes["xrpl.network.type"] != nil and attributes["xrpl.network.type"] != ""`, + `set(attributes["iac_group"], Concat(["[", Concat([attributes["xrpl.branch"], attributes["xrpl.node.role"], attributes["xrpl.work.item"]], ", "), "]"], "")) where attributes["xrpl.branch"] != nil and attributes["xrpl.branch"] != "" and attributes["xrpl.node.role"] != nil and attributes["xrpl.node.role"] != "" and attributes["xrpl.work.item"] != nil and attributes["xrpl.work.item"] != ""`, ] } @@ -181,9 +181,9 @@ otelcol.processor.transform "tier" { // it is built only on perf-comparison nodes. The guard checks != "" // as well as != nil because OTTL Concat emits a stray separator for an // empty element. - `set(attributes["resource_group"], attributes["service.instance.id"]) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != ""`, - `set(attributes["resource_group"], Concat([attributes["resource_group"], attributes["xrpl.network.type"]], "-")) where attributes["resource_group"] != nil and attributes["xrpl.network.type"] != nil and attributes["xrpl.network.type"] != ""`, - `set(attributes["iac_group"], Concat([attributes["xrpl.branch"], attributes["xrpl.node.role"], attributes["xrpl.work.item"]], "-")) where attributes["xrpl.branch"] != nil and attributes["xrpl.branch"] != "" and attributes["xrpl.node.role"] != nil and attributes["xrpl.node.role"] != "" and attributes["xrpl.work.item"] != nil and attributes["xrpl.work.item"] != ""`, + `set(attributes["resource_group"], Concat(["[", attributes["service.instance.id"], "]"], "")) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != ""`, + `set(attributes["resource_group"], Concat(["[", attributes["service.instance.id"], ", ", attributes["xrpl.network.type"], "]"], "")) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != "" and attributes["xrpl.network.type"] != nil and attributes["xrpl.network.type"] != ""`, + `set(attributes["iac_group"], Concat(["[", Concat([attributes["xrpl.branch"], attributes["xrpl.node.role"], attributes["xrpl.work.item"]], ", "), "]"], "")) where attributes["xrpl.branch"] != nil and attributes["xrpl.branch"] != "" and attributes["xrpl.node.role"] != nil and attributes["xrpl.node.role"] != "" and attributes["xrpl.work.item"] != nil and attributes["xrpl.work.item"] != ""`, ] } diff --git a/docker/telemetry/otel-collector-config.grafanacloud.yaml b/docker/telemetry/otel-collector-config.grafanacloud.yaml index 3f269dc9ee..1f9740d8ff 100644 --- a/docker/telemetry/otel-collector-config.grafanacloud.yaml +++ b/docker/telemetry/otel-collector-config.grafanacloud.yaml @@ -130,25 +130,27 @@ processors: - set(attributes["resource_group"], resource.attributes["resource_group"]) where resource.attributes["resource_group"] != nil - set(attributes["iac_group"], resource.attributes["iac_group"]) where resource.attributes["iac_group"] != nil # Build grouped legend labels from the tier / perf-iac resource attributes so - # dashboards reference one pre-built label per group instead of a per-panel - # renameByRegex chain. On the local scrape these resource attributes are - # promoted to labels by resource_to_telemetry_conversion; on the Cloud path + # dashboards reference one pre-built label per group. On the local scrape these + # are promoted to labels by resource_to_telemetry_conversion; on the Cloud path # transform/cloudlabels (above) copies them onto datapoint attributes. - # - iac_group: perf-iac branch/role/work-item, stamped together (all or - # none), so one guard covers them. Unset outside perf runs -> the legend - # shows nothing for it. - # - resource_group: node id, optionally suffixed with network type. - # Concat is guarded on presence (!= nil and != "") because OTTL Concat renders - # "" for an absent attribute and a stray separator for an empty one. The - # perf-iac collector stamps these with an empty-string default when a run has - # no work item, so the empty-string check matters, not just the nil check. + # The BRACKETS and comma separators are baked into the label VALUE here (e.g. + # "[node, mainnet]") rather than added in the dashboard Display name: a template + # like "... [${__field.labels.iac_group}]" renders an empty "[]" when the label + # is absent, and Grafana's renameByRegex runs pre-interpolation so it cannot + # strip it. Baking brackets into the value means an absent group is just an + # absent label -> the Display name renders nothing for it. + # - iac_group: "[branch, role, work-item]", built only on perf runs. + # - resource_group: "[node]" or "[node, network]". + # Guards check != nil and != "" because OTTL Concat renders "" for an + # absent attribute and the perf-iac collector stamps these with an empty-string + # default outside a comparison run. transform/legendgroups: metric_statements: - context: resource statements: - - set(attributes["iac_group"], Concat([attributes["xrpl.branch"], attributes["xrpl.node.role"], attributes["xrpl.work.item"]], "-")) where attributes["xrpl.branch"] != nil and attributes["xrpl.branch"] != "" and attributes["xrpl.node.role"] != nil and attributes["xrpl.node.role"] != "" and attributes["xrpl.work.item"] != nil and attributes["xrpl.work.item"] != "" - - set(attributes["resource_group"], attributes["service.instance.id"]) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != "" - - set(attributes["resource_group"], Concat([attributes["resource_group"], attributes["xrpl.network.type"]], "-")) where attributes["resource_group"] != nil and attributes["xrpl.network.type"] != nil and attributes["xrpl.network.type"] != "" + - set(attributes["iac_group"], Concat(["[", Concat([attributes["xrpl.branch"], attributes["xrpl.node.role"], attributes["xrpl.work.item"]], ", "), "]"], "")) where attributes["xrpl.branch"] != nil and attributes["xrpl.branch"] != "" and attributes["xrpl.node.role"] != nil and attributes["xrpl.node.role"] != "" and attributes["xrpl.work.item"] != nil and attributes["xrpl.work.item"] != "" + - set(attributes["resource_group"], Concat(["[", attributes["service.instance.id"], "]"], "")) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != "" + - set(attributes["resource_group"], Concat(["[", attributes["service.instance.id"], ", ", attributes["xrpl.network.type"], "]"], "")) where attributes["service.instance.id"] != nil and attributes["service.instance.id"] != "" and attributes["xrpl.network.type"] != nil and attributes["xrpl.network.type"] != "" connectors: spanmetrics: