-
Notifications
You must be signed in to change notification settings - Fork 59
[otap-df-pdata]Add zero-copy view for OTAP logs format #1467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1467 +/- ##
==========================================
- Coverage 83.58% 83.45% -0.14%
==========================================
Files 412 413 +1
Lines 112106 113233 +1127
==========================================
+ Hits 93700 94493 +793
- Misses 17872 18206 +334
Partials 534 534
🚀 New features to boost your workflow:
|
| use otap_df_engine::terminal_state::TerminalState; | ||
| use otap_df_pdata::otlp::OtlpProtoBytes; | ||
| // TODO: Uncomment when geneva-uploader supports pdata-views (after 0.3.0) | ||
| // use otap_df_pdata::views::otap::OtapLogsView; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The integration code using OtapLogsView is currently commented out because the geneva-uploader crate requires an update to support iteration over this new view type. I have included the commented-out code solely to demonstrate the intended usage pattern. This will be uncommented and enabled once a compatible version of geneva-uploader is published. Please feel free to ignore the commented block for this review.
jmacd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent!
I have one thought, non-blocking feedback looking for additional ways to test this.
I was thinking that since the former releases of geneva_uploader were based on OTLP protocol message objects, that I'd see OTLP test message objects used as input and corresponding test expectations written for the Geneva output we expect.
Possibly, when the geneva_uploader component releases with view support, we could see a few tests with input OlpProtoMessages and output Geneva structs added, so that we're testing an end-to-end without hand-crafted OtapArrowRecords.
| // typically only 1-10 resources/scopes). The actual attribute strings (~200 KB) | ||
| // remain in the Arrow RecordBatch (zero-copy). | ||
| // | ||
| // TODO: Consider HashMap instead of BTreeMap for log_attrs_map when log count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe explain why you start with a BTreeMap instead of using a HashMap right away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion! I've expanded the comment focusing on the memory vs speed trade-off and the need to profile before switching.
lquerel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good, made few comments.
I’m wondering whether all cases have been tested, especially those where no attributes are present.
@albertlockett could take a look at this PR.
| self.current_idx += 1; | ||
|
|
||
| let log_attrs_cols = self.log_attrs?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we increment this index before knowing if log_attrs or get_attribute_key are None?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lquerel - Incrementing before these checks is safe here. When log_attrs is None, matching_rows is empty so we never reach the increment. For get_attribute_key returning None (malformed data), stopping iteration is the correct behavior.
I have now added test test_missing_attributes_iterator to validate this.
| None | ||
| } | ||
|
|
||
| #[cfg(test)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you test scenarios with log batches that contain no resource attributes or fields, or no scope attributes/fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have now added test_missing_attributes_iterator to cover this. It tests log batches with no resource attributes, no scope attributes, and no log attributes (all passed as None). The logs batch contains resource and scope ID fields per the OTAP schema, but has zero attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding the test @lalitb
I think we should update the test to exclude those ID columns. These columns are optional and if there are no attributes, we don't don't include them in the Logs RecordBatch.
Good suggestion! I'll look into adding OTLP → Geneva struct tests that validate the encoding without needing backend connectivity. That would give us better test coverage of the full conversion path. |
| struct OtapLogsColumns<'a> { | ||
| // Logs batch columns | ||
| id: Option<&'a UInt16Array>, | ||
| time_unix_nano: Option<&'a TimestampNanosecondArray>, | ||
| observed_time_unix_nano: Option<&'a TimestampNanosecondArray>, | ||
| severity_number: Option<Int32ArrayAccessor<'a>>, | ||
| severity_text: Option<StringArrayAccessor<'a>>, | ||
| body_columns: Option<OtapBodyColumns<'a>>, | ||
| dropped_attributes_count: Option<&'a UInt32Array>, | ||
| flags: Option<&'a UInt32Array>, | ||
| trace_id: Option<&'a FixedSizeBinaryArray>, | ||
| span_id: Option<&'a FixedSizeBinaryArray>, | ||
| event_name: Option<StringArrayAccessor<'a>>, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a similar struct that already exists here. Maybe we should use that to avoid duplicating the code?
otel-arrow/rust/otap-dataflow/crates/pdata/src/otlp/logs.rs
Lines 35 to 48 in 2d49406
| struct LogsArrays<'a> { | |
| id: Option<&'a UInt16Array>, | |
| schema_url: Option<StringArrayAccessor<'a>>, | |
| time_unix_nano: Option<&'a TimestampNanosecondArray>, | |
| observed_time_unix_nano: Option<&'a TimestampNanosecondArray>, | |
| trace_id: Option<ByteArrayAccessor<'a>>, | |
| span_id: Option<ByteArrayAccessor<'a>>, | |
| severity_number: Option<Int32ArrayAccessor<'a>>, | |
| severity_text: Option<StringArrayAccessor<'a>>, | |
| body: Option<LogBodyArrays<'a>>, | |
| dropped_attributes_count: Option<&'a UInt32Array>, | |
| flags: Option<&'a UInt32Array>, | |
| event_name: Option<StringArrayAccessor<'a>>, | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, we have these structs to represent logs body and attributes which we might be able to use in place of OtapAttributeColumns OtapBodyColumns?
otel-arrow/rust/otap-dataflow/crates/pdata/src/otlp/logs.rs
Lines 119 to 122 in 2d49406
| struct LogBodyArrays<'a> { | |
| body: &'a StructArray, | |
| anyval_arrays: AnyValueArrays<'a>, | |
| } |
otel-arrow/rust/otap-dataflow/crates/pdata/src/otlp/attributes.rs
Lines 44 to 51 in 2d49406
| pub(crate) type Attribute16Arrays<'a> = AttributeArrays<'a, UInt16Type>; | |
| pub(crate) type Attribute32Arrays<'a> = AttributeArrays<'a, UInt32Type>; | |
| pub(crate) struct AttributeArrays<'a, T: ArrowPrimitiveType> { | |
| pub parent_id: MaybeDictArrayAccessor<'a, PrimitiveArray<T>>, | |
| pub attr_key: MaybeDictArrayAccessor<'a, StringArray>, | |
| pub anyval_arrays: AnyValueArrays<'a>, | |
| } |
| } | ||
|
|
||
| /// Iterator over attributes for a resource | ||
| pub struct OtapResourceAttributeIter<'a> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like OtapResourceAttributeIter, OtapScopeAttributeIter and OtapAttributeIter all have a similar implementation. Is there any reason we couldn't combine these into one type?
|
overall looks really good @lalitb! Thanks a lot for taking this on. I had a few minor comments about where we could reuse some code to avoid duplication. I'm happy to approve as-is if these suggestions aren't sensible so let me know what you think :) |
|
|
||
| #[inline] | ||
| fn dropped_attributes_count(&self) -> u32 { | ||
| 0 // TODO: implement if stored in OTAP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created an issue to track the methods we don't currently implement: #1475
Fixes: #1441
Implements
OtapLogsView- a zero-copy view over Arrow RecordBatch data for OTAP logs.Use Cases
This view is useful for exporters that need to iterate over the OTAP log data:
Geneva Exporter: Enables the exporter to iterate directly over Arrow data to generate custom payloads, avoiding the costly round-trip of converting to OTLP bytes and decoding them back to objects.
Log Analytics: Allows efficient, zero-copy access to log fields for serialization to JSON.
General Data Access: Provides a standard, efficient way for any component to read the hierarchical OTLP structure (Resource → Scope → Log) without needing to understand the underlying Arrow complexity.
Without views: OTAP → OTLP bytes → decode → iterate (slower)
With views: OTAP → view → iterate (faster, zero-copy)
Design
Benchmark Results
System: Apple M4 Pro, 12 cores, 24 GB RAM
OTAP→OTLP→Process = full cost without views (OTAP→OTLP conversion + OTLP decode + iterate)
Changes
OtapLogsViewwith hierarchical iteration (Resource → Scope → LogRecord → Attributes)