Skip to content

Commit 560dc0b

Browse files
committed
perf: add a reserve method to Injector to avoid repeated resizing
1 parent 00f97c9 commit 560dc0b

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

opentelemetry-http/src/lib.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ impl Injector for HeaderInjector<'_> {
2222
}
2323
}
2424
}
25+
26+
/// Reserves capacity for at least `additional` more entries to be inserted.
27+
fn reserve(&mut self, additional: usize) {
28+
self.0.reserve(additional);
29+
}
2530
}
2631

2732
/// Helper for extracting headers from HTTP Requests. This is used for OpenTelemetry context
@@ -303,4 +308,52 @@ mod tests {
303308
assert!(got.contains(&"headername1"));
304309
assert!(got.contains(&"headername2"));
305310
}
311+
312+
#[test]
313+
fn http_headers_reserve() {
314+
let mut carrier = http::HeaderMap::new();
315+
316+
// Test that reserve doesn't panic and works correctly
317+
{
318+
let mut injector = HeaderInjector(&mut carrier);
319+
injector.reserve(10);
320+
321+
// Verify the HeaderMap still works after reserve
322+
injector.set("test-header", "test-value".to_string());
323+
}
324+
assert_eq!(
325+
HeaderExtractor(&carrier).get("test-header"),
326+
Some("test-value")
327+
);
328+
329+
// Test reserve with zero capacity
330+
{
331+
let mut injector = HeaderInjector(&mut carrier);
332+
injector.reserve(0);
333+
injector.set("another-header", "another-value".to_string());
334+
}
335+
assert_eq!(
336+
HeaderExtractor(&carrier).get("another-header"),
337+
Some("another-value")
338+
);
339+
340+
// Test that capacity is actually reserved (at least the requested amount)
341+
let mut new_carrier = http::HeaderMap::new();
342+
{
343+
let mut new_injector = HeaderInjector(&mut new_carrier);
344+
new_injector.reserve(5);
345+
}
346+
let initial_capacity = new_carrier.capacity();
347+
348+
// Add some headers and verify capacity doesn't decrease
349+
{
350+
let mut new_injector = HeaderInjector(&mut new_carrier);
351+
for i in 0..3 {
352+
new_injector.set(&format!("header-{}", i), format!("value-{}", i));
353+
}
354+
}
355+
356+
assert!(new_carrier.capacity() >= initial_capacity);
357+
assert!(new_carrier.capacity() >= 5);
358+
}
306359
}

opentelemetry/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## vNext
44

5+
- Add `reserve` method to `opentelemetry::propagation::Injector` to hint at the number of elements that will be added to avoid multiple resize operations of the underlying data structure. Has an empty default implementation.
6+
57
## v0.31.0
68

79
Released 2025-Sep-25

opentelemetry/src/propagation/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub use text_map_propagator::TextMapPropagator;
3131
pub trait Injector {
3232
/// Add a key and value to the underlying data.
3333
fn set(&mut self, key: &str, value: String);
34+
35+
#[allow(unused_variables)]
36+
/// Hint to reserve capacity for at least `additional` more entries to be inserted.
37+
fn reserve(&mut self, additional: usize) {}
3438
}
3539

3640
/// Extractor provides an interface for removing fields from an underlying struct like `HashMap`
@@ -52,6 +56,11 @@ impl<S: std::hash::BuildHasher> Injector for HashMap<String, String, S> {
5256
fn set(&mut self, key: &str, value: String) {
5357
self.insert(key.to_lowercase(), value);
5458
}
59+
60+
/// Reserves capacity for at least `additional` more entries to be inserted.
61+
fn reserve(&mut self, additional: usize) {
62+
self.reserve(additional);
63+
}
5564
}
5665

5766
impl<S: std::hash::BuildHasher> Extractor for HashMap<String, String, S> {
@@ -117,4 +126,53 @@ mod tests {
117126
assert!(got.contains(&"headername1"));
118127
assert!(got.contains(&"headername2"));
119128
}
129+
130+
#[test]
131+
fn hash_map_injector_reserve() {
132+
let mut carrier = HashMap::new();
133+
134+
// Test that reserve doesn't panic and works correctly
135+
Injector::reserve(&mut carrier, 10);
136+
137+
// Verify the HashMap still works after reserve
138+
Injector::set(&mut carrier, "test_key", "test_value".to_string());
139+
assert_eq!(Extractor::get(&carrier, "test_key"), Some("test_value"));
140+
141+
// Test reserve with zero capacity
142+
Injector::reserve(&mut carrier, 0);
143+
Injector::set(&mut carrier, "another_key", "another_value".to_string());
144+
assert_eq!(
145+
Extractor::get(&carrier, "another_key"),
146+
Some("another_value")
147+
);
148+
149+
// Test that capacity is actually reserved (at least the requested amount)
150+
let mut new_carrier = HashMap::new();
151+
Injector::reserve(&mut new_carrier, 5);
152+
let initial_capacity = new_carrier.capacity();
153+
154+
// Add some elements and verify capacity doesn't decrease
155+
for i in 0..3 {
156+
Injector::set(
157+
&mut new_carrier,
158+
&format!("key{}", i),
159+
format!("value{}", i),
160+
);
161+
}
162+
163+
assert!(new_carrier.capacity() >= initial_capacity);
164+
assert!(new_carrier.capacity() >= 5);
165+
}
166+
167+
#[test]
168+
fn injector_reserve() {
169+
// Test to have full line coverage of default method
170+
struct TestInjector();
171+
impl Injector for TestInjector {
172+
fn set(&mut self, _key: &str, _value: String) {}
173+
}
174+
let mut test_injector = TestInjector();
175+
Injector::reserve(&mut test_injector, 4711);
176+
Injector::set(&mut test_injector, "key", "value".to_string());
177+
}
120178
}

0 commit comments

Comments
 (0)