Skip to content

Commit 179f777

Browse files
Copilotlalitbutpilla
authored
fix: [Geneva Exporter] Replace approximation with exact size calculation for schema buffer pre-allocation (#418)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: lalitb <[email protected]> Co-authored-by: Lalit Kumar Bhasin <[email protected]> Co-authored-by: utpilla <[email protected]>
1 parent 5b6bf7e commit 179f777

File tree

1 file changed

+145
-1
lines changed
  • opentelemetry-exporter-geneva/geneva-uploader/src/payload_encoder

1 file changed

+145
-1
lines changed

opentelemetry-exporter-geneva/geneva-uploader/src/payload_encoder/bond_encoder.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,113 @@ impl DynamicSchema {
122122
}
123123
}
124124

125+
/// Calculate the fixed overhead size for Bond schema encoding
126+
const fn calculate_fixed_overhead() -> usize {
127+
// Bond header: "SP" + version
128+
let mut size = 4; // [0x53, 0x50, 0x01, 0x00]
129+
130+
// Number of structs
131+
size += 4; // 1u32
132+
133+
// Struct definition header (excluding variable name lengths)
134+
size += 4; // attributes (0u32)
135+
size += 1; // modifier (0u8)
136+
137+
// Default values block
138+
size += 8; // default_uint (u64)
139+
size += 8; // default_int (i64)
140+
size += 8; // default_double (f64)
141+
size += 4; // default_string (u32)
142+
size += 4; // default_wstring (u32)
143+
size += 1; // default_nothing (u8)
144+
145+
// Base def
146+
size += 4; // 0u32
147+
148+
// Field count header
149+
size += 3; // 3 zero bytes
150+
size += 4; // field count (u32)
151+
152+
// Post-fields padding
153+
size += 8; // alignment padding
154+
155+
// Root typedef
156+
size += 1; // BondDataType::BT_STRUCT
157+
size += 2; // struct index (u16)
158+
size += 1; // element (u8)
159+
size += 1; // key (u8)
160+
size += 1; // bonded (u8)
161+
162+
// Final padding
163+
size += 9; // 9 zero bytes
164+
165+
size
166+
}
167+
168+
/// Calculate the fixed overhead per field (excluding variable field name length)
169+
const fn calculate_per_field_fixed_overhead() -> usize {
170+
// Empty qualified name
171+
let mut size = 4; // 0u32 for empty string
172+
173+
// Field attributes and data
174+
size += 4; // attributes (0u32)
175+
size += 1; // modifier (0u8)
176+
177+
// Default values block for field
178+
size += 8; // default_uint (u64)
179+
size += 8; // default_int (i64)
180+
size += 8; // default_double (f64)
181+
size += 4; // default_string (u32)
182+
size += 4; // default_wstring (u32)
183+
size += 1; // default_nothing (u8)
184+
185+
// Field metadata
186+
size += 3; // 3 padding bytes
187+
size += 2; // field_id (u16)
188+
size += 1; // type_id (u8)
189+
190+
// Additional type info
191+
size += 2; // struct_def (u16)
192+
size += 1; // element (u8)
193+
size += 1; // key (u8)
194+
size += 1; // bonded_type (u8)
195+
size += 1; // default_value_present (u8)
196+
197+
size
198+
}
199+
200+
/// Calculate the exact size of the encoded schema in bytes
201+
pub(crate) fn calculate_exact_encoded_size(&self) -> usize {
202+
// Start with fixed overhead
203+
let mut size = Self::calculate_fixed_overhead();
204+
205+
// Add variable struct name lengths
206+
size += 4 + self.struct_name.len(); // struct_name length + bytes
207+
size += 4 + self.qualified_name.len(); // qualified_name length + bytes
208+
209+
// Add field-specific sizes
210+
for (i, field) in self.fields.iter().enumerate() {
211+
let is_last = i == self.fields.len() - 1;
212+
213+
// Fixed overhead per field
214+
size += Self::calculate_per_field_fixed_overhead();
215+
216+
// Variable field name
217+
size += 4 + field.name.len(); // name length + bytes
218+
219+
// Padding after each field except the last
220+
if !is_last {
221+
size += 8;
222+
}
223+
}
224+
225+
size
226+
}
227+
125228
/// Encode the schema to Bond format
126229
pub(crate) fn encode(&self) -> Result<Vec<u8>> {
127-
let mut schema_bytes = Vec::new();
230+
let exact_size = self.calculate_exact_encoded_size();
231+
let mut schema_bytes = Vec::with_capacity(exact_size);
128232

129233
// Write header
130234
schema_bytes.write_all(&[0x53, 0x50])?; // 'S','P'
@@ -375,4 +479,44 @@ mod tests {
375479
let bytes = schema.as_bytes();
376480
assert!(!bytes.is_empty());
377481
}
482+
483+
#[test]
484+
fn test_schema_exact_size_calculation() {
485+
// Test that the exact size calculation matches the actual encoded size
486+
// This validates that pre-allocation is precise and no reallocations occur
487+
488+
// Test with different field counts and varying field name lengths
489+
let test_cases = vec![
490+
(0, "no fields"),
491+
(1, "single field"),
492+
(5, "few fields"),
493+
(10, "medium fields"),
494+
(20, "many fields"),
495+
];
496+
497+
for (field_count, description) in test_cases {
498+
// Create fields with varying name lengths to test the calculation accuracy
499+
let fields: Vec<FieldDef> = (0..field_count)
500+
.map(|i| FieldDef {
501+
name: Cow::Owned(format!("field_with_long_name_{i}")),
502+
type_id: BondDataType::BT_STRING,
503+
field_id: i as u16 + 1,
504+
})
505+
.collect();
506+
507+
let schema = DynamicSchema::new("TestStruct", "test.namespace", fields);
508+
let exact_size = schema.calculate_exact_encoded_size();
509+
let encoded = schema.encode().unwrap();
510+
511+
// Verify encoding succeeded
512+
assert!(!encoded.is_empty(), "Encoding failed for {description}");
513+
514+
// The exact calculation should match the actual encoded size precisely
515+
let actual_size = encoded.len();
516+
assert_eq!(
517+
exact_size, actual_size,
518+
"Exact size calculation {exact_size} does not match actual size {actual_size} for {description}"
519+
);
520+
}
521+
}
378522
}

0 commit comments

Comments
 (0)