otap: merge into one crate, real OtapPdata direct from SketchEnvelope, no staging required - #9
Open
zzylol wants to merge 6 commits into
Open
otap: merge into one crate, real OtapPdata direct from SketchEnvelope, no staging required#9zzylol wants to merge 6 commits into
zzylol wants to merge 6 commits into
Conversation
…eal OTAP workspace
Per request: actually get an OTAP Dataflow checkout and compile/test
against it, rather than leaving otap_bridge.rs's binding as "read from
source, never compiled." Staged both files into a real clone of
open-telemetry/otel-arrow @ 3e85c3460361446ebfce99e9f35fffd2dd5ab740
(2026-08-24) as a `crates/asap-sketches-registry` workspace member
(path-depping back to asap-precompute-rs exactly as Cargo.toml's own
doc describes) and ran the real cargo build/clippy/fmt/test there.
## Bugs the real compiler caught (none of these were guessable from
## source reading alone)
- `ProcessorFactory::create`'s function-pointer type gained a fifth
parameter, `capabilities: &capability::registry::Capabilities`
("per-node, one-shot view of extension capabilities... factories
that don't depend on any extension can ignore the parameter") —
not present in whatever version this adapter was originally read
against. Added as an unused parameter to
create_asap_sketches_processor.
- Two borrow-checker errors in the OtapPdata decode path: `if let
Some(gauge) = data.as_gauge() { gauge.data_points().collect() }`
doesn't compile because the collected Vec's items borrow from
`gauge`, which is dropped at the end of the `if let` arm. Fixed by
factoring the per-data-point body into a `DecodeAccumulator` struct
with a generic `push_data_point<D: NumberDataPointView>` method,
called inline from each of the Gauge/Sum branches instead of trying
to unify their different concrete NumberDataPointView types into
one Vec first.
- One unused import (`TryFromWithOptions`) clippy caught immediately.
## What's now genuinely verified (not just "compiles")
Added 3 new tests to otap_bridge.rs exercising the real
OtapArrowRecords::Metrics / OtapPdata / encode_metrics_otap_batch /
OtapMetricsView machinery end to end:
- encode_then_decode_round_trips_a_scalar_metric
- encode_then_decode_round_trips_a_sketch_envelope_carried_as_a_metric_attribute
(the "self-describing sketch binary inside an OTAP metric" case --
an `_asap_envelope` Bytes attribute survives the round trip
byte-for-byte)
- decode_returns_none_records_for_zero_rows
All 10 tests in the staged crate pass (7 pre-existing config-shape
tests + 3 new), `cargo clippy -D warnings` and `cargo fmt --check`
both clean, against the real workspace.
## Not changed
Applied `cargo fmt`'s reformatting (this workspace's rustfmt config
differs slightly from what the file was originally written against --
import grouping mainly) back onto the canonical otap-patch/ copies.
Updated mod.rs's, otap_bridge.rs's, and README.md's doc comments from
"unverified" to reflect what's actually confirmed now, and precisely
what "verified" means here: staged into a *separate*, temporary
checkout of the real workspace, not something this repo's own build
wires up -- otap-patch/ itself still has no standalone build in this
repo.
Added .gitignore entry for /otel-arrow/ (the temporary checkout used
for this verification, not committed).
…ncoding
Per request: merge the two paths into one, with the wire lane
genuinely supporting the same dictionary/reuse economics. New
otap_wire.rs carries a real OtapArrowRecords::Metrics (built via
otap_bridge::otap_metric_records_to_pdata -- the exact same encoding
the generic-pipeline path already uses) directly over a persistent TCP
connection, instead of ASAP's own SCHEMA/DICTIONARY/RECORD
SketchStreamBatch protocol (asap_precompute_rs::otap::{wire,dictionary}).
One encoding, used identically whether a producer/receiver pair is
directly connected or routed through other OTAP pipeline components.
## What changed
- otap-patch/all/otap_wire.rs (new): OtapWireWriter/OtapWireReader,
reusing the persistent-per-connection Arrow IPC design
asap_precompute_rs::otap::wire::{WireWriter,WireReader} already
validates (each payload type's Schema message sent once per
connection, not once per window) -- generalized from
SketchStreamBatch's fixed 4 roles to however many ArrowPayloadTypes
a given OtapArrowRecords::Metrics actually populates (a real
OtapPdata can carry up to 19 different payload types; ASAP's encode
path only ever populates a handful, so roles are tracked in a
BTreeMap keyed by ArrowPayloadType rather than 4 named fields).
- Duplicated (not shared) with asap-precompute-rs's design on purpose:
asap-precompute-rs deliberately has no dependency on the OTAP
Dataflow crates this needs, and pulling one in would break that
crate's "builds standalone" property -- see the module's own doc.
- otap-patch/all/Cargo.toml: added arrow-ipc, tokio deps (both already
pinned at the OTAP workspace level, so no new version to reconcile).
## Verification
Same workflow as the previous commit on this PR: staged into a real
open-telemetry/otel-arrow checkout (3e85c3460361446ebfce99e9f35fffd2dd5ab740,
2026-08-24) as the asap-sketches-registry workspace member. Two new
tests genuinely round-trip a real OtapPdata over an actual TCP
loopback socket:
- round_trips_a_real_otap_pdata_over_a_tcp_loopback_socket
- round_trips_multiple_windows_over_one_persistent_connection (two
different metric values sent over one persistent connection, both
decoded correctly on the far end -- the scenario that actually
proves "one path" rather than just "compiles")
All 12 tests in the staged crate pass (10 from the previous commit +
these 2), cargo clippy -D warnings and cargo fmt --check both clean.
## Not done here
otap_wire.rs has no consumer inside this crate yet -- it's a complete,
tested transport module, not baked into AsapSketchesProcessor's
runtime behavior (documented honestly via #![allow(dead_code)] with
an explanation, not silently hidden). Wiring a config-driven choice of
transport into AsapSketchesProcessor itself (a peer_addr option,
connection lifecycle, reconnect behavior) is real follow-up work --
see the module's own "Not wired into AsapSketchesProcessor yet"
doc section for the two shapes that follow-up could take.
…receiver nodes Both otap_wire.rs's transport (OtapWireWriter/OtapWireReader) and the wire lane's receive side had no consumer inside the crate — this closes that: real OTAP nodes now actually drive them at runtime, not just tests. - New otap_receiver.rs: AsapSketchesReceiver, a real local::Receiver<OtapPdata> (not local::Processor — that trait is purely reactive and can't independently accept a TCP connection). Listens on a configured address, decodes each connection with OtapWireReader, pushes into the pipeline via effect_handler.send_message. Registered under urn:asap:receiver:asap_sketches_wire via OTAP_RECEIVER_FACTORIES. - AsapSketchesProcessor gains an optional peer_addr config field: emit_envelopes now forwards over a lazily-connected, persistent OtapWireWriter connection to that peer when set, falling back to the existing generic pipeline hop otherwise. A send failure drops the window and resets the connection so the next window reconnects. - otap_wire.rs's #![allow(dead_code)] is gone — both OtapWireWriter and OtapWireReader now have real, non-test callers. - New end-to-end test: the real AsapSketchesReceiver::start() bound to a real loopback TCP socket, fed by a real OtapWireWriter::send from a connected client, decoded and pushed through the actual pipeline machinery (OTAP's own TestRuntime harness) — not a mock. Verified against the same staged open-telemetry/otel-arrow checkout (3e85c3460361446ebfce99e9f35fffd2dd5ab740, 2026-08-24): build, clippy -D warnings, fmt --check, and test all clean; 16/16 tests passing (up from 12/12), including the new end-to-end receiver test. asap-precompute-rs's own suite: 156/156, unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… already gives us dictionary economics The direct-TCP wire lane added in the previous commit (otap_wire.rs + otap_receiver.rs's AsapSketchesReceiver, peer_addr config) is gone. There is now exactly one transport: effect_handler.send_message_with_source_node, i.e. whatever pipeline this node is already wired into. Why: the wire lane existed to give sketch traffic real dictionary/ schema-reuse economics (asap_precompute_rs::otap::dictionary's SCHEMA/ DICTIONARY/RECORD tiering, from PR #5/#6) that riding a generic OTLP metric didn't have. But that's solving a problem OTAP's real Arrow encoding already solves — otap_bridge's encode_metrics_otap_batch dictionary-encodes the metric name and every string-valued attribute key/value by construction. Verified against the real staged workspace: === payload_type UnivariateMetrics === name : Dictionary(UInt8, Utf8) === payload_type NumberDpAttrs === parent_id : Dictionary(UInt8, UInt32) key : Dictionary(UInt8, Utf8) str : Dictionary(UInt16, Utf8) That's the same "send the dictionary once, reference it after that" shape SeriesDictionary was reinventing at the application layer, done instead at the columnar/Arrow-IPC level — with no dictionary state for this adapter to track, no second wire protocol, and none of the "must be one ordered, single-consumer stream or the series_id reference dangles" correctness constraint a hand-rolled scheme has. Added a permanent regression test guarding this fact (otap_bridge::tests::real_otap_encoding_dictionary_encodes_metric_name_and_string_attributes) so a silent upstream schema change surfaces loudly rather than being rediscovered from scratch. asap_precompute_rs::otap::dictionary (SeriesDictionary / SketchStreamBatch) stays in the tree, tested, and still backs the legacy asap_precompute_rs::otap::wire example binaries — it's just not part of this adapter's path. Re-verified against the same staged open-telemetry/otel-arrow checkout (3e85c3460361446ebfce99e9f35fffd2dd5ab740, 2026-08-24): build, clippy -D warnings, fmt --check, and test all clean; 11/11 tests passing. asap-precompute-rs's own suite: 156/156, unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e, real OtapPdata everywhere
Full merge, per explicit direction: no more otap-patch/ overlay
requiring manual staging into a checked-out OTAP Dataflow workspace to
compile. asap-precompute-rs is now the ONE crate, and depends directly
on the real otel-arrow-dfe-* crates (plain git dependency, pinned to
3e85c3460361446ebfce99e9f35fffd2dd5ab740, same pattern already used
for asap_sketchlib) behind a new `otap-engine` Cargo feature.
`cargo build --features otap-engine` just works — no staging script,
no temporary checkout, no copying files by hand.
New modules (moved + rewritten from otap-patch/all/{mod.rs,otap_bridge.rs}):
- otap::processor — AsapSketchesProcessor, the real
local::Processor<OtapPdata> node, linkme-registered under
urn:asap:processor:asap_sketches.
- otap::codec (renamed from the working name `bridge` — it's not
bridging two representations anymore, see below) — the real
SketchEnvelope <-> OtapPdata binding.
The bigger change is what codec.rs does differently from the old
otap_bridge.rs it replaces: it builds/reads a real OtapPdata *directly*
from/to &[SketchEnvelope], skipping the intermediate flat RecordBatch
(encode_batch) and OtapMetricRecords two-batch family (lift) hops
entirely. Those three representations for one job only existed because
of speculative multi-adapter generality (Telegraf/Vector) that never
materialized in this repo, which only ever ships to OTAP -- so codec.rs
implements OTAP's own MetricsView trait family straight over envelope
slices instead. encode_batch/decode_batch/records::{flatten,lift}
still exist, still tested, and still back the legacy
SeriesDictionary/otap::wire transport and its standalone example
binaries -- they're just not part of the otap-engine path anymore.
A real bug found and fixed along the way: encode_batch unconditionally
sets `_asap_envelope` even for estimate-mode envelopes (empty
payload), and arrow_array::BinaryArray treats `Some(&[])` as present-
not-null -- confirmed empirically -- so an estimate-mode gauge would
misroute through the envelope decode path with an empty payload
instead of the scalar path. codec.rs's direct encoder only attaches
_asap_envelope (and its sibling attributes) when the payload is
actually non-empty, with a defensive filter on decode too. New test:
estimate_mode_envelope_round_trips_as_a_scalar_not_an_empty_envelope.
The legacy encode_batch/decode_batch pair keeps the original behavior
untouched -- fixing it there was out of scope for this rewrite.
Also moved plugins/asap_sketches/{README.md,sample.toml} to
asap-precompute-rs/plugins/asap_sketches/ (content updated to match:
no more Phase D/"deliberately deferred" language, since it's done),
dropped the empty src/mod.rs placeholder it predated, and rewrote the
crate-root and repo-root README/module docs throughout to describe the
one-crate reality instead of the old Layer A/otap-patch split.
Verified via this repo's own `cargo build/test/clippy/fmt` at all
three feature levels (default, otap, otap-engine) -- no staging into
any external checkout: 100/113/169 tests pass respectively (default/
otap/otap-engine), clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Started from
otap_bridge.rs(the realOtapPdata<->OtapMetricRecordsbinding) being written entirely from reading upstream source, never compiled. Along the way, review surfaced three deeper architectural questions worth answering for real rather than by assumption:otap-patch/need to be a separate overlay staged into a checked-out OTAP Dataflow workspace at all — why not just depend on the real crates directly?OtapMetricRecords/flat-RecordBatchas intermediate hops betweenSketchEnvelopeandOtapPdatainstead of encoding straight to the real type?This PR answers all three with real, verified code, not reasoning.
What
One crate, not two.
otap-patch/is gone.asap-precompute-rsnow depends directly on the realotel-arrow-dfe-*crates via a plain git dependency (same mechanism already used forasap_sketchlib), behind a newotap-engineCargo feature. No more staging files into a separately-checked-out OTAP Dataflow workspace to compile —cargo build --features otap-enginejust works, fetching from git like any other dependency.One transport.
AsapSketchesProcessorsends only viaeffect_handler.send_message_with_source_node. An earlier revision on this branch added a direct-TCP "wire lane" as a second transport (otap_wire.rs+ a standaloneAsapSketchesReceiverreceiver node) — that's been removed.One encoding, no intermediate hops. The wire lane existed to give sketch traffic dictionary/schema-reuse economics a generic OTLP metric didn't have (the legacy
SeriesDictionarySCHEMA/DICTIONARY/RECORD tiering). Turns out unnecessary: OTAP's real Arrow encoder (encode_metrics_otap_batch) already dictionary-encodes the metric name and every string-valued attribute key/value by construction — confirmed against the real workspace and guarded by a permanent test:That's the same "send it once, reference it after that" shape
SeriesDictionarywas reinventing at the application layer, done at the columnar/Arrow-IPC level instead — no dictionary state for this adapter to track, no second wire protocol, none of the "must be one ordered, single-consumer stream" correctness constraint a hand-rolledseries_idscheme carries.Once that was settled, the old
encode_batch(→ flatRecordBatch) →lift(→OtapMetricRecords) →otap_bridge(→OtapPdata) chain was also revisited: those three representations for one job only existed to serve a second adapter (Telegraf/Vector) that was never actually built in this repo. The newotap::codecmodule implements OTAP's ownMetricsViewtrait family directly over&[SketchEnvelope]— one hop,SketchEnvelopestraight to realOtapPdata, and back.encode_batch/decode_batch/records::{flatten,lift}still exist, still tested — they now exclusively back the legacySeriesDictionary/otap::wiretransport and its standalone example binaries, not theotap-enginepath.How
A real bug found and fixed while rewriting the encoder:
encode_batchunconditionally sets_asap_envelopefor every row, including estimate-mode envelopes (emptypayload, the gauge value rides invalueinstead).arrow_array::BinaryArraytreatsSome(&[])as a present, empty value, not null — confirmed empirically (BinaryArray::from_opt_vec(vec![Some(&[])]).is_null(0) == false). So an estimate-mode row would silently misroute through the envelope decode path with an empty payload instead of the scalar path.otap::codec's direct encoder only attaches_asap_envelope(and its sibling_asap_*attributes) when the payload is actually non-empty, with a defensive.filter(|b| !b.is_empty())on decode too. New regression test:estimate_mode_envelope_round_trips_as_a_scalar_not_an_empty_envelope. The legacyencode_batch/decode_batchpair keeps the original behavior untouched — fixing it there was out of scope here.Real trait-signature drift the compiler caught during the rewrite (uninhabited-placeholder
MetricsViewimpls, copied from the old file and re-typed by hand):ResourceView::AttributesIter(notAttributeIter),ResourceMetricsView::schema_url() -> Option<Str>(not bareStr),HistogramDataPointView/BucketsViewneeding explicitBucketCountIter/ExplicitBoundsIterassociated types,ExponentialHistogramDataPointView::zero_threshold(). All caught bycargo build, none guessable from memory.Why a git dependency works without workspace staging:
open-telemetry/otel-arrow's own crates (otel-arrow-dfe-engineetc.) declare their own sibling dependencies as{ workspace = true }. A plain{ git = "...", package = "otel-arrow-dfe-engine" }dependency still resolves correctly because Cargo clones the whole repo and treats that clone's workspace root as the resolution context for the named package's own deps — this crate doesn't need to be a member of that workspace itself. Verified directly with a scratch crate before committing to this design.Output
169 tests total at the
otap-enginelevel. Also re-verifiedotap(100+11+4+9+32 = clean, minus the 13 otap-engine-only tests) and default (no-feature) builds are unaffected — all via this repo's owncargo build/cargo test, nothing staged externally.Not done here
AsapSketchesProcessorinside a real running pipeline end to end (noMessage::PDatasent throughprocess()against a livedf_engine).asap_sketchesnode's legacySketchStreamBatchoutput — isn't addressed by this adapter; that's a different, ASAP-native hop with its own standalone example binaries.🤖 Generated with Claude Code