Skip to content

fix(otlp): preserve empty metric descriptors under non-matching policies - #303

Open
detail-app[bot] wants to merge 1 commit into
masterfrom
detail/bug-fix/fix-otlp-preserve-empty-metric-descriptors-under-n-233876
Open

detail-app[bot] wants to merge 1 commit into
masterfrom
detail/bug-fix/fix-otlp-preserve-empty-metric-descriptors-under-n-233876

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 16, 2026

Copy link
Copy Markdown

Detail bug report: View on Detail

Fixes ENG-704

Bug

When any metric policy was loaded (even one matching nothing), the OTLP proxy silently dropped metric descriptors whose data oneof was set but whose data_points list was empty — a valid OTLP wire form (a named metric with type/unit/description but no measurements this batch). dropped_count stayed 0, so the loss was invisible to monitoring.

Two compounding defects in src/signals/otlp/metrics.zig:

  • filterMetricsInPlace pruned any metric with data != null and dataPointCount == 0, but checked only the post-filter count — it could not distinguish a metric that arrived empty (a valid descriptor) from one emptied by policy. This contradicted the code's own comment ("A metric with no data at all is left alone").
  • processProtobufMetrics always re-encoded (no "nothing-modified" fast path), so the prune ran even when no policy matched — unlike processProtobufLogs, which returns the original bytes verbatim when nothing was modified.

Fix

  • Track the pre-filter count in filterMetricsInPlace: capture original_dp = dataPointCount(metric) before per-data-point filtering, and prune only when original_dp > 0 and dataPointCount == 0 — i.e. only metrics emptied by policy are pruned; originally-empty descriptors survive. This fix is shared by the protobuf and JSON paths.
  • Add the nothing-modified fast path to processProtobufMetrics, mirroring processProtobufLogs: when counts.dropped_count == 0, return the original bytes without re-encoding. This restores logs/metrics parity and prevents the prune from firing for non-matching policies.

Testing

  • Unit tests (zig build test --summary all, Debug and ReleaseSafe): 522/523 pass, 1 skip (the pre-existing S3/MinIO e2e test that self-skips via return error.SkipZigTest; unrelated to this change). Added 3 regression tests:
    • empty descriptor survives a non-matching policy on the protobuf path,
    • a policy-emptied metric is still pruned while an originally-empty metric alongside it survives (the key invariant of the prune fix),
    • empty descriptor survives a non-matching policy on the JSON path.
  • Negative verification: reverting only the prune condition (keeping the fast path) makes the partial-filter test and the JSON repro fail by name, confirming the prune fix is load-bearing.
  • Build / lint / format: zig build, zig build -Doptimize=ReleaseSafe, zig fmt --check src/, and ziglint all pass clean.
  • End-to-end: built edge-otlp and the repo's echo-server (as a live upstream), loaded a non-matching metric policy, and generated valid OTLP payloads with protobuf + opentelemetry-proto. The upstream captured both the populated gauge and the empty-gauge descriptor (53 bytes, byte-identical to input — confirming the streaming path returns .keep, not .replace). Repeating with a policy that drops one metric by name confirmed the emptied metric was pruned while the originally-empty descriptor survived. As a final check, rebuilding edge-otlp with the fix reverted reproduced the bug end-to-end (upstream received only the populated gauge, 36 bytes), then the restored fix forwarded both metrics again.

Automatic Fixes PRs can be configured here.

@macroscopeapp

macroscopeapp Bot commented Sep 16, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved at 809ec56

Macroscope's review found this PR approvable — This is a narrowly scoped OTLP metrics bug fix with regression coverage for both protobuf and JSON paths. It preserves valid empty descriptors while retaining existing policy-driven pruning, without introducing schema, infrastructure, or sensitive-data changes.

You can add or adjust custom eligibility rules. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant