Skip to content

Commit c4c0160

Browse files
committed
perf: add a reserve method to Injector to avoid repeated resizing
1 parent 5250df2 commit c4c0160

File tree

3 files changed

+100
-0
lines changed

3 files changed

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

opentelemetry/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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.
56
- *Breaking* Change return type of `opentelemetry::global::set_tracer_provider` to Unit to align with metrics counterpart
67
- Add `get_all` method to `opentelemetry::propagation::Extractor` to return all values of the given propagation key and provide a default implementation.
78

opentelemetry/src/propagation/mod.rs

Lines changed: 46 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 reserves 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,41 @@ 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+
}
120166
}

0 commit comments

Comments
 (0)