diff --git a/docker/telemetry/workload/validate_telemetry.py b/docker/telemetry/workload/validate_telemetry.py index ef990dd8c3..a6d1974ecd 100644 --- a/docker/telemetry/workload/validate_telemetry.py +++ b/docker/telemetry/workload/validate_telemetry.py @@ -465,11 +465,16 @@ async def validate_spans( # function. Note the tag-values API is not service-scoped, so in a stack # where something other than xrpld also sent traces this list would be a # superset; on the harness only xrpld exports spans. + # + # The tag is the bare intrinsic `name`, NOT `span.name`. A span's name is a + # TraceQL intrinsic, not a span-scoped attribute, so `span.name` resolves to + # an attribute nothing sets and Tempo answers 200 with an empty tagValues + # list -- indistinguishable from an empty backend, which is how the wrong + # tag went unnoticed. Verified against tempo 2.9.4 holding one span: + # `span.name` -> {"tagValues":[]}, `name` -> that span's name. emitted_span_names: list[str] = [] try: - async with session.get( - f"{tempo_url}/api/v2/search/tag/span.name/values" - ) as resp: + async with session.get(f"{tempo_url}/api/v2/search/tag/name/values") as resp: ops_data = await resp.json() tag_values = ops_data.get("tagValues", []) emitted_span_names = [tv.get("value", "") for tv in tag_values] diff --git a/docs/telemetry-runbook.md b/docs/telemetry-runbook.md index 17303ac9d3..aecbad1c52 100644 --- a/docs/telemetry-runbook.md +++ b/docs/telemetry-runbook.md @@ -3621,14 +3621,14 @@ The counts are not hard-coded in the validator — it iterates the inventory fil so those files are authoritative. The figures below are the inventory as it stands today. -| Category | Checks | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Spans | Every **required** entry in `expected_spans.json` — 41 span types at the time of writing: 25 required, 16 marked `"optional": true` | Span name found in Tempo carrying its `required_attributes`, plus the declared parent-child relationships. An `"optional": true` entry that does not fire is recorded as a skip, not a failure — it needs traffic the harness may not generate (HTTP/JSON-RPC client, gRPC client, missing-ledger fetch, mode transitions) or that it deliberately no longer generates (path-finding RPC — see "Pathfinding is not exercised" in [the workload README](../docker/telemetry/workload/README.md)). | -| Metrics | Every entry in every asserted category of `expected_metrics.json` — 84 checks across 25 asserting categories at the time of writing: 79 metric names plus 5 `required_labels` checks | SpanMetrics, `beast::insight` gauges/counters exported over OTLP, and the `MetricsRegistry` OTLP metrics. Each must have > 0 Prometheus series; none are optional. A category may also declare `required_labels`, and each label there becomes one additional check that at least one of that category's series carries it with a non-empty value (matched as `