Skip to content

otlp_metadata, cat: keep metadata and metric semantics across mixed-version pipelines - #315

Merged
edsiper merged 7 commits into
masterfrom
otlp-metadata-compat
Oct 23, 2026
Merged

edsiper merged 7 commits into
masterfrom
otlp-metadata-compat

Conversation

@edsiper

@edsiper edsiper commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

This fixes OTLP metadata and cmt_cat() problems that affect Fluent Bit fleets running mixed versions, and adds tests for the OTLP, MessagePack and forward paths. It is intended to be bundled in Fluent Bit 5.2 (fluent/fluent-bit#12589) instead of v2.3.0.

This is a scoped correctness release, not complete mixed-version compatibility. See Compatibility and Not covered.

Fixes

  • Point metadata bound by identity (otlp_metadata).
    • Before: the v2.3.0 native metadata sidecar matched point metadata by position. A released Fluent Bit relaying a chunk keeps the sidecar but can add, remove or replace points, so flags, exemplars and typed attributes landed on the wrong series.
    • Now: the writer adds point_ids (series hash, timestamp, label values, a value fingerprint and, when set, start timestamp per point) and the reader binds by identity. v2.3.0 readers ignore the new key.
    • Every point is looked up through identifiers sorted once per family, so restoring takes O(n log n) time. A reversed 40,000-point family went from 8 s to 0.13 s to decode. Points whose identity was recorded more than once get no metadata instead of another point's, and the same is true of the legacy entries they share. A relay that changes a value at the same timestamp no longer keeps the old metadata.
    • Legacy external metadata is dropped only for points that native metadata replaced, so a 5.2 reader no longer keeps less than 5.1 did.
  • cmt_cat() preserves family semantics. This is what the labels, metrics_selector and cumulative_to_delta processors use. It:
    • no longer fails the whole context when an OTLP metric's points have different attribute key sets, such as error.type only on failures
    • keeps delta temporality for sums and histograms
    • keeps non-monotonic sums and allow_reset
    • keeps the unit
    • no longer adds a zero point to families that had none
    • clears stale point metadata when the source sample has none
    • rejects contributors with samples whose explicit unit differs from the family's, since values are never converted
    • sets unit, temporality and monotonicity from the contributors while a family holds no samples, and never changes them afterwards. An empty contributor never changes a family that holds samples. A contributor whose samples have conflicting temporality or monotonicity fails before the destination changes. Unspecified temporality, as written by Fluent Bit 5.0.x, merges with either.
    • accepts partial label sets only from a family with the same label keys, and otherwise fails before changing the destination, as before.
  • OTLP decode keeps points whose attributes hold empty nested AnyValues. Before, they were reported as an allocation failure and the request was dropped.
  • MessagePack decode no longer sizes bucket and quantile allocations from an oversized declared count. Before, a corrupt chunk could request gigabytes before being rejected.

Compatibility

  • No public header or ABI changes relative to v2.3.0. This does not revert the structure changes v2.3.0 already made.
  • No new top-level MessagePack keys. A new test fails if the encoder writes a key released readers reject.
  • Output decodes in the CMetrics bundled with Fluent Bit 5.0.11 and 5.1.5. Fluent Bit 5.0.0 already rejected the unit key written since 5.0.1, and that is unchanged.
  • Payloads written by Fluent Bit 5.0.0, 5.0.11 and 5.1.5 decode with full content, including in mixed forward batches.

Behaviour changes in Fluent Bit processors

These are fixes, but dashboards built on the old output may shift:

  • Delta and non-monotonic metrics keep their type after the processors that use cmt_cat().
  • Units now come through, which can change exporter metric names that carry unit suffixes.
  • Metrics with mixed attribute sets, which were dropped before, now come through.
  • Families with no points no longer report a 0.
  • Merging a family into one of the same name with conflicting temporality or monotonicity now fails instead of silently mixing them.

Known limitations

These are pinned by tests and left for follow-up:

  • OTLP histograms with no explicit bounds are rejected.
  • cmt_cat() merges series whose attributes differ only by value type (7, "7", 7.0).
  • An empty metric description becomes "-".
  • cmt_cat() does not carry resource and scope metadata.
  • Points identical in labels, time window and value but differing in attribute type lose their typed attributes, including on an unedited hop.

Tests

  • tests/otlp_pipeline.c: 23 cases that compare every OTLP data point field after four paths:

    • direct
    • MessagePack chunk
    • two-hop forward
    • cmt_cat()

    The cases cover all metric types, integer and float extremes, NaN, Inf and staleness markers, typed attributes, resources and scopes, exemplars, malformed requests and empty containers.

  • tests/msgpack_compat.c: 7 cases:

    • released-version fixtures
    • mixed batches
    • encoder key allowlist
    • scalar extremes
    • every truncation and byte flip
    • oversized counts
    • strict format version
  • tests/opentelemetry.c: relay emulation tests for identity binding.

Checks run

  • scripts/agent-verify.sh: 25/25 passed.
  • Every commit builds and passes the test suite on its own.
  • All suites under AddressSanitizer and UndefinedBehaviorSanitizer are clean, except opentelemetry_zero_length_bytes_attribute. That test already exists, and UBSan reports memcpy(dst, NULL, 0) inside the bundled protobuf-c (lib/fluent-otel-proto), not in CMetrics.
  • Valgrind on the five touched suites is clean.
  • Decoding of this branch's output was checked by hand against real builds of the CMetrics bundled with Fluent Bit 5.0.11 and 5.1.5. This includes a chunk whose point ids carry colliding label sets, a NULL label, a label containing a NUL byte and a start time. This is not automated yet.
  • The "forward" pipeline in tests/otlp_pipeline.c runs two round trips through the current codec. Old relays are emulated in tests/opentelemetry.c; no released Fluent Bit binary runs in CTest.

Not covered

  • Allocation-failure injection for the new metadata paths.
  • A CI job that decodes current output with each released Fluent Bit's bundled CMetrics.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T20:41:16.725754Z dfc9332 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@edsiper
edsiper force-pushed the otlp-metadata-compat branch from dfc9332 to 067a54b Compare October 22, 2026 20:39

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dfc93324e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cmt_otlp_metadata.c Outdated
Comment on lines +245 to +247
mpack_start_array(writer, 2);
mpack_write_uint(writer, sample->hash);
mpack_write_uint(writer, cmt_metric_get_timestamp(sample));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the start timestamp in point identities

When two points have the same labels and time_unix_nano but different start_time_unix_nano, CMetrics treats them as distinct data points, yet this identifier is identical for both. If a released relay removes or reorders one of those points while retaining the sidecar, the positional fast path can attach the removed point's flags, exemplars, and typed attributes to the survivor. Include start-timestamp presence and value in the identity so relay edits cannot conflate distinct OTLP windows.

AGENTS.md reference: AGENTS.md:L101-L104

Useful? React with 👍 / 👎.

@edsiper
edsiper force-pushed the otlp-metadata-compat branch from 3941cf9 to 23e0b08 Compare October 22, 2026 20:53
Replacing a sample with one that carries no OTLP metadata kept the
destination's old flags, exemplars and attributes on the new value.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
An AnyValue with no value set inside an array or kvlist attribute was
reported as an allocation failure, dropping the whole request. It is now
treated as absent metadata and the point keeps its label-derived form.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
Bucket, quantile and exponential bucket decoders allocate from the peeked
count before cmt_mpack_unpack_array() rejects counts above
CMT_MPACK_MAX_ARRAY_ENTRY_COUNT, so a corrupt chunk could request
gigabytes before failing. Peeking now reports such counts as zero and the
array read still fails.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
The v2.3.0 native metadata sidecar matched point metadata by position.
Released readers relay a chunk with the sidecar unchanged but can add,
remove, replace or reorder points, so flags, exemplars and typed
attributes landed on other series.

The writer now records an identity for every point under a new
"point_ids" key, which v2.3.0 readers ignore: series hash, time, label
values written as the codec writes labels, a fingerprint of the complete
value as the codec carries it, and the start time when set. The series
hash only accelerates the comparison, since it concatenates label values
without boundaries. The reader looks every point up through the
identifiers sorted once per family, so restoring takes n log n time, and
an identity recorded more than once binds nothing. Sidecars without
identifiers keep the positional rule.

Legacy external entries, keyed by series hash and time only, are pruned
in one pass. With identifiers, an entry stays only when every point
recorded under its key was bound by a unique identity and has no native
metadata that reads back. Without them, only entries of restored points
are dropped. Whether the sidecar is restored, skipped, rejected or
absent, an entry whose key several decoded samples share is dropped.
Families whose metadata exists only in the legacy tree keep it.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
Fluent Bit processors rebuild contexts with cmt_cat(), which looks up
destination families by name only. It:

- failed the whole context when an OTLP metric's points carried
  different attribute key sets
- turned delta sums and histograms into cumulative ones and
  non-monotonic sums into counters
- dropped the unit
- added a zero point to families that had none

Samples with fewer values than the label keys are accepted from a
family with the same keys, the shape OTLP points with partial attributes
take. Any other label count mismatch fails before the destination
changes.

Unit, temporality and monotonicity are taken from contributors while a
family holds no samples and never changed afterwards. A contributor
with samples and a conflicting temporality, monotonicity or explicit
unit fails before the destination changes, since values are never
converted. Unspecified temporality, written by releases that did not
record it, and an empty unit are compatible with any.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
Compare every OTLP data point field after direct, MessagePack chunk,
two-hop forward and cmt_cat() pipelines: all metric types, integer and
float extremes, NaN, infinities and staleness markers, typed attributes,
resources and scopes, exemplars, malformed requests and empty
containers, plus points that differ only by attribute type relayed by a
released reader.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
Add fixtures written by the CMetrics bundled with Fluent Bit 5.0.0,
5.0.11 and 5.1.5 and check their content after decode, re-encode,
cmt_cat() and mixed forward batches. Also check that the encoder only
writes keys released readers accept, scalar extremes, every truncation
and byte flip of a chunk, oversized declared counts and the strict
format version.

Signed-off-by: Eduardo Silva <eduardo@chronosphere.io>
@edsiper
edsiper force-pushed the otlp-metadata-compat branch from ee82dfe to ec49ff8 Compare October 22, 2026 23:13
@edsiper
edsiper merged commit 99c0dab into master Oct 23, 2026
20 checks passed
@edsiper
edsiper deleted the otlp-metadata-compat branch October 23, 2026 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant