Skip to content

Commit d700bb0

Browse files
perf(trace-serializer): pre-allocate serialization buffer (#1949)
# Motivation When serializing spans, we start from a zero sized buffer. Pre-allocate the buffer to prevent small re-allocs. # Changes * Save the length of the payload we serialised * When creating a new buffer allocate capacity for the length of the previous payload * Also add a 1KB minimum buffer size.
1 parent fff4fd3 commit d700bb0

3 files changed

Lines changed: 67 additions & 45 deletions

File tree

libdd-data-pipeline/src/trace_exporter/builder.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::trace_exporter::TelemetryConfig;
1414
use crate::trace_exporter::TraceExporterWorkers;
1515
use crate::trace_exporter::{
1616
add_path, StatsComputationStatus, TelemetryInstrumentationSessions, TraceExporter,
17-
TraceExporterError, TraceExporterInputFormat, TraceExporterOutputFormat, TracerMetadata,
18-
INFO_ENDPOINT,
17+
TraceExporterError, TraceExporterInputFormat, TraceExporterOutputFormat, TraceSerializer,
18+
TracerMetadata, INFO_ENDPOINT,
1919
};
2020
use arc_swap::ArcSwap;
2121
use libdd_capabilities::{HttpClientTrait, MaybeSend};
@@ -400,6 +400,7 @@ impl TraceExporterBuilder {
400400
},
401401
input_format: self.input_format,
402402
output_format: self.output_format,
403+
serializer: TraceSerializer::new(self.output_format),
403404
client_computed_top_level: self.client_computed_top_level,
404405
shared_runtime,
405406
dogstatsd,
@@ -484,6 +485,7 @@ impl TraceExporterBuilder {
484485
},
485486
input_format: self.input_format,
486487
output_format: self.output_format,
488+
serializer: TraceSerializer::new(self.output_format),
487489
client_computed_top_level: self.client_computed_top_level,
488490
shared_runtime,
489491
dogstatsd,

libdd-data-pipeline/src/trace_exporter/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ pub struct TraceExporter<H: HttpClientTrait + MaybeSend + Sync + 'static> {
215215
metadata: TracerMetadata,
216216
input_format: TraceExporterInputFormat,
217217
output_format: TraceExporterOutputFormat,
218+
serializer: TraceSerializer,
218219
shared_runtime: Arc<SharedRuntime>,
219220
/// None if dogstatsd is disabled
220221
dogstatsd: Option<Client>,
@@ -616,11 +617,11 @@ impl<H: HttpClientTrait + MaybeSend + Sync + 'static> TraceExporter<H> {
616617
return self.send_otlp_traces_inner(traces, config).await;
617618
}
618619

619-
let serializer = TraceSerializer::new(
620-
self.output_format,
620+
let prepared = match self.serializer.prepare_traces_payload(
621+
traces,
622+
header_tags,
621623
self.agent_payload_response_version.as_ref(),
622-
);
623-
let prepared = match serializer.prepare_traces_payload(traces, header_tags) {
624+
) {
624625
Ok(p) => p,
625626
Err(e) => {
626627
error!("Error serializing traces: {e}");

libdd-data-pipeline/src/trace_exporter/trace_serializer.rs

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4+
use std::sync::atomic::{AtomicUsize, Ordering};
5+
46
use crate::trace_exporter::agent_response::{
57
AgentResponsePayloadVersion, DATADOG_RATES_PAYLOAD_VERSION,
68
};
@@ -16,6 +18,9 @@ use libdd_trace_utils::span::{v04::Span, TraceData};
1618
use libdd_trace_utils::trace_utils::{self, TracerHeaderTags};
1719
use libdd_trace_utils::tracer_payload;
1820

21+
/// Minimal capacity of fresh buffers allocated to encode traces, in bytes.
22+
const MIN_BUFFER_CAPACITY: usize = 1024;
23+
1924
/// Prepared traces payload ready for sending to the agent
2025
pub(super) struct PreparedTracesPayload {
2126
/// Serialized msgpack payload
@@ -27,20 +32,18 @@ pub(super) struct PreparedTracesPayload {
2732
}
2833

2934
/// Trace serialization client for handling payload preparation
30-
pub(super) struct TraceSerializer<'a> {
35+
#[derive(Debug)]
36+
pub(super) struct TraceSerializer {
37+
previous_serialised_len: AtomicUsize,
3138
output_format: TraceExporterOutputFormat,
32-
agent_payload_response_version: Option<&'a AgentResponsePayloadVersion>,
3339
}
3440

35-
impl<'a> TraceSerializer<'a> {
41+
impl TraceSerializer {
3642
/// Create a new trace serializer
37-
pub(super) fn new(
38-
output_format: TraceExporterOutputFormat,
39-
agent_payload_response_version: Option<&'a AgentResponsePayloadVersion>,
40-
) -> Self {
43+
pub(super) fn new(output_format: TraceExporterOutputFormat) -> Self {
4144
Self {
45+
previous_serialised_len: AtomicUsize::new(MIN_BUFFER_CAPACITY),
4246
output_format,
43-
agent_payload_response_version,
4447
}
4548
}
4649

@@ -49,10 +52,12 @@ impl<'a> TraceSerializer<'a> {
4952
&self,
5053
traces: Vec<Vec<Span<T>>>,
5154
header_tags: TracerHeaderTags,
55+
agent_payload_response_version: Option<&AgentResponsePayloadVersion>,
5256
) -> Result<PreparedTracesPayload, TraceExporterError> {
5357
let payload = self.collect_and_process_traces(traces)?;
5458
let chunks = payload.size();
55-
let headers = self.build_traces_headers(header_tags, chunks);
59+
let headers =
60+
self.build_traces_headers(header_tags, chunks, agent_payload_response_version);
5661
let mp_payload = self.serialize_payload(&payload)?;
5762

5863
Ok(PreparedTracesPayload {
@@ -77,13 +82,18 @@ impl<'a> TraceSerializer<'a> {
7782
}
7883

7984
/// Build HTTP headers for traces request
80-
fn build_traces_headers(&self, header_tags: TracerHeaderTags, chunk_count: usize) -> HeaderMap {
85+
fn build_traces_headers(
86+
&self,
87+
header_tags: TracerHeaderTags,
88+
chunk_count: usize,
89+
agent_payload_response_version: Option<&AgentResponsePayloadVersion>,
90+
) -> HeaderMap {
8191
let mut headers: HeaderMap = header_tags.into();
8292
headers.reserve(4);
8393
headers.insert(DATADOG_SEND_REAL_HTTP_STATUS, HeaderValue::from_static("1"));
8494
headers.insert(DATADOG_TRACE_COUNT, chunk_count.into());
8595
headers.insert(CONTENT_TYPE, APPLICATION_MSGPACK);
86-
if let Some(agent_payload_response_version) = &self.agent_payload_response_version {
96+
if let Some(agent_payload_response_version) = agent_payload_response_version {
8797
// should never fail, as the version should only contain visible ascii chars
8898
let _ = HeaderValue::try_from(agent_payload_response_version.header_value())
8999
.map(|v| headers.insert(DATADOG_RATES_PAYLOAD_VERSION, v));
@@ -96,12 +106,24 @@ impl<'a> TraceSerializer<'a> {
96106
&self,
97107
payload: &tracer_payload::TraceChunks<T>,
98108
) -> Result<Vec<u8>, TraceExporterError> {
99-
match payload {
100-
tracer_payload::TraceChunks::V04(p) => Ok(msgpack_encoder::v04::to_vec(p)),
109+
let capacity = self
110+
.previous_serialised_len
111+
.load(Ordering::Relaxed)
112+
.max(MIN_BUFFER_CAPACITY);
113+
let buff = match payload {
114+
tracer_payload::TraceChunks::V04(p) => {
115+
msgpack_encoder::v04::to_vec_with_capacity(p, capacity as u32)
116+
}
101117
tracer_payload::TraceChunks::V05(p) => {
102-
rmp_serde::to_vec(p).map_err(TraceExporterError::Serialization)
118+
let mut buff = Vec::with_capacity(capacity);
119+
rmp_serde::encode::write(&mut buff, p)
120+
.map_err(TraceExporterError::Serialization)?;
121+
buff
103122
}
104-
}
123+
};
124+
self.previous_serialised_len
125+
.store(buff.len(), Ordering::Relaxed);
126+
Ok(buff)
105127
}
106128
}
107129

@@ -146,30 +168,27 @@ mod tests {
146168

147169
#[test]
148170
fn test_trace_serializer_new() {
149-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
171+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
150172
assert!(matches!(
151173
serializer.output_format,
152174
TraceExporterOutputFormat::V04
153175
));
154-
assert!(serializer.agent_payload_response_version.is_none());
155176
}
156177

157178
#[test]
158179
fn test_trace_serializer_new_with_agent_version() {
159-
let agent_version = AgentResponsePayloadVersion::new();
160-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05, Some(&agent_version));
180+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05);
161181
assert!(matches!(
162182
serializer.output_format,
163183
TraceExporterOutputFormat::V05
164184
));
165-
assert!(serializer.agent_payload_response_version.is_some());
166185
}
167186

168187
#[test]
169188
fn test_build_traces_headers() {
170-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
189+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
171190
let header_tags = create_test_header_tags();
172-
let headers = serializer.build_traces_headers(header_tags, 3);
191+
let headers = serializer.build_traces_headers(header_tags, 3, None);
173192

174193
// Check basic headers are present
175194
assert_eq!(headers.get(DATADOG_SEND_REAL_HTTP_STATUS).unwrap(), "1");
@@ -197,9 +216,9 @@ mod tests {
197216
#[test]
198217
fn test_build_traces_headers_with_agent_version() {
199218
let agent_version = AgentResponsePayloadVersion::new();
200-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, Some(&agent_version));
219+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
201220
let header_tags = create_test_header_tags();
202-
let headers = serializer.build_traces_headers(header_tags, 2);
221+
let headers = serializer.build_traces_headers(header_tags, 2, Some(&agent_version));
203222

204223
// Check that agent payload version header is included
205224
assert!(headers.contains_key(DATADOG_RATES_PAYLOAD_VERSION));
@@ -208,7 +227,7 @@ mod tests {
208227

209228
#[test]
210229
fn test_collect_and_process_traces_v04() {
211-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
230+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
212231
let traces = vec![vec![create_test_span()]];
213232

214233
let result = serializer.collect_and_process_traces(traces);
@@ -221,7 +240,7 @@ mod tests {
221240

222241
#[test]
223242
fn test_collect_and_process_traces_v05() {
224-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05, None);
243+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05);
225244
let traces = vec![vec![create_test_span()]];
226245

227246
let result = serializer.collect_and_process_traces(traces);
@@ -234,7 +253,7 @@ mod tests {
234253

235254
#[test]
236255
fn test_collect_and_process_traces_multiple_chunks() {
237-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
256+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
238257
let traces = vec![
239258
vec![create_test_span()],
240259
vec![create_test_span(), create_test_span()],
@@ -250,7 +269,7 @@ mod tests {
250269

251270
#[test]
252271
fn test_serialize_payload_v04() {
253-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
272+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
254273
let original_traces = vec![vec![create_test_span()]];
255274
let payload = serializer
256275
.collect_and_process_traces(original_traces.clone())
@@ -285,7 +304,7 @@ mod tests {
285304

286305
#[test]
287306
fn test_serialize_payload_v05() {
288-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05, None);
307+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05);
289308
let original_traces = vec![vec![create_test_span()]];
290309
let payload = serializer
291310
.collect_and_process_traces(original_traces.clone())
@@ -320,14 +339,14 @@ mod tests {
320339

321340
#[test]
322341
fn test_prepare_traces_payload_v04() {
323-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
342+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
324343
let traces = vec![
325344
vec![create_test_span()],
326345
vec![create_test_span(), create_test_span()],
327346
];
328347
let header_tags = create_test_header_tags();
329348

330-
let result = serializer.prepare_traces_payload(traces, header_tags);
349+
let result = serializer.prepare_traces_payload(traces, header_tags, None);
331350
assert!(result.is_ok());
332351

333352
let prepared = result.unwrap();
@@ -342,11 +361,11 @@ mod tests {
342361

343362
#[test]
344363
fn test_prepare_traces_payload_v05() {
345-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05, None);
364+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V05);
346365
let traces = vec![vec![create_test_span()]];
347366
let header_tags = create_test_header_tags();
348367

349-
let result = serializer.prepare_traces_payload(traces, header_tags);
368+
let result = serializer.prepare_traces_payload(traces, header_tags, None);
350369
assert!(result.is_ok());
351370

352371
let prepared = result.unwrap();
@@ -358,11 +377,11 @@ mod tests {
358377
#[test]
359378
fn test_prepare_traces_payload_with_agent_version() {
360379
let agent_version = AgentResponsePayloadVersion::new();
361-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, Some(&agent_version));
380+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
362381
let traces = vec![vec![create_test_span()]];
363382
let header_tags = create_test_header_tags();
364383

365-
let result = serializer.prepare_traces_payload(traces, header_tags);
384+
let result = serializer.prepare_traces_payload(traces, header_tags, Some(&agent_version));
366385
assert!(result.is_ok());
367386

368387
let prepared = result.unwrap();
@@ -372,11 +391,11 @@ mod tests {
372391

373392
#[test]
374393
fn test_prepare_traces_payload_empty_traces() {
375-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
394+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
376395
let traces: Vec<Vec<SpanBytes>> = vec![];
377396
let header_tags = create_test_header_tags();
378397

379-
let result = serializer.prepare_traces_payload(traces, header_tags);
398+
let result = serializer.prepare_traces_payload(traces, header_tags, None);
380399
assert!(result.is_ok());
381400

382401
let prepared = result.unwrap();
@@ -398,8 +417,8 @@ mod tests {
398417
..Default::default()
399418
};
400419

401-
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04, None);
402-
let headers = serializer.build_traces_headers(header_tags, 1);
420+
let serializer = TraceSerializer::new(TraceExporterOutputFormat::V04);
421+
let headers = serializer.build_traces_headers(header_tags, 1, None);
403422

404423
assert_eq!(headers.get("datadog-meta-lang").unwrap(), "python");
405424
assert_eq!(headers.get("datadog-meta-lang-version").unwrap(), "3.9.0");

0 commit comments

Comments
 (0)