Skip to content

Commit c45b69d

Browse files
committed
Add TracerTraceChunks FFI for grouping spans into traces
Introduce an opaque `TracerTraceChunks` handle wrapping `Vec<Vec<SpanBytes>>` and expose it to C callers via four FFI functions: - `ddog_tracer_trace_chunks_new`: create a container with optional capacity hint - `ddog_tracer_trace_chunks_free`: release the container - `ddog_tracer_trace_chunks_begin_chunk`: start a new trace chunk - `ddog_tracer_trace_chunks_push_span`: move a `TracerSpan` into the current chunk, consuming it Spans are consumed on push to enforce single-ownership and prevent double-use from C callers.
1 parent d13e986 commit c45b69d

2 files changed

Lines changed: 183 additions & 4 deletions

File tree

libdd-data-pipeline-ffi/cbindgen.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ includes = ["common.h"]
1212
after_includes = """
1313
typedef struct ddog_TraceExporter ddog_TraceExporter;
1414
typedef struct ddog_TracerSpan ddog_TracerSpan;
15+
typedef struct ddog_TracerTraceChunks ddog_TracerTraceChunks;
1516
"""
1617

1718
[export]
1819
prefix = "ddog_"
1920
renaming_overrides_prefixing = true
20-
exclude = ["TraceExporter", "TracerSpan"]
21+
exclude = ["TraceExporter", "TracerSpan", "TracerTraceChunks"]
2122

2223
[export.rename]
2324
"ByteSlice" = "ddog_ByteSlice"
@@ -29,6 +30,7 @@ exclude = ["TraceExporter", "TracerSpan"]
2930
"ExporterErrorCode" = "ddog_TraceExporterErrorCode"
3031
"ExporterError" = "ddog_TraceExporterError"
3132
"TracerSpan" = "ddog_TracerSpan"
33+
"TracerTraceChunks" = "ddog_TracerTraceChunks"
3234

3335
[export.mangle]
3436
rename_types = "PascalCase"

libdd-data-pipeline-ffi/src/tracer.rs

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

4-
//! FFI functions for creating and manipulating individual tracer spans.
4+
//! FFI functions for creating and manipulating tracer spans and trace chunks.
55
//!
6-
//! Provides an opaque [`TracerSpan`] handle wrapping a `Span<BytesData>`,
7-
//! allowing callers to construct spans field-by-field from C.
6+
//! Provides opaque handles for building trace data from C:
7+
//!
8+
//! - [`TracerSpan`] wraps a single `Span<BytesData>`, constructed
9+
//! field-by-field.
10+
//! - [`TracerTraceChunks`] wraps `Vec<Vec<SpanBytes>>`, grouping spans
11+
//! into trace chunks ready for export.
812
913
use crate::error::{ExporterError, ExporterErrorCode as ErrorCode};
1014
use crate::{catch_panic, gen_error};
@@ -194,6 +198,102 @@ pub unsafe extern "C" fn ddog_tracer_span_set_metric(
194198
)
195199
}
196200

201+
// ---------------------------------------------------------------------------
202+
// TracerTraceChunks
203+
// ---------------------------------------------------------------------------
204+
205+
/// Opaque handle wrapping `Vec<Vec<SpanBytes>>` — a list of trace chunks,
206+
/// each containing a list of spans.
207+
pub struct TracerTraceChunks(pub(crate) Vec<Vec<SpanBytes>>);
208+
209+
/// Create a new empty trace chunks container.
210+
///
211+
/// `capacity` is a hint for the expected number of chunks; pass 0 if
212+
/// unknown.
213+
///
214+
/// # Safety
215+
///
216+
/// `out_handle` must point to valid, writable memory for a
217+
/// `Box<TracerTraceChunks>`.
218+
#[no_mangle]
219+
pub unsafe extern "C" fn ddog_tracer_trace_chunks_new(
220+
capacity: usize,
221+
out_handle: NonNull<Box<TracerTraceChunks>>,
222+
) -> Option<Box<ExporterError>> {
223+
catch_panic!(
224+
{
225+
let chunks = if capacity > 0 {
226+
Vec::with_capacity(capacity)
227+
} else {
228+
Vec::new()
229+
};
230+
out_handle
231+
.as_ptr()
232+
.write(Box::new(TracerTraceChunks(chunks)));
233+
None
234+
},
235+
gen_error!(ErrorCode::Panic)
236+
)
237+
}
238+
239+
/// Free a trace chunks container and all its contents.
240+
///
241+
/// After this call the handle is invalid and must not be reused.
242+
///
243+
/// # Safety
244+
///
245+
/// `handle` must have been created by [`ddog_tracer_trace_chunks_new`].
246+
#[no_mangle]
247+
pub unsafe extern "C" fn ddog_tracer_trace_chunks_free(handle: Box<TracerTraceChunks>) {
248+
drop(handle);
249+
}
250+
251+
/// Start a new chunk (trace) inside the container.
252+
///
253+
/// Subsequent [`ddog_tracer_trace_chunks_push_span`] calls will append
254+
/// spans to this chunk until the next `begin_chunk` call.
255+
///
256+
/// # Safety
257+
///
258+
/// `handle` must be a valid pointer to a `TracerTraceChunks`.
259+
#[no_mangle]
260+
pub unsafe extern "C" fn ddog_tracer_trace_chunks_begin_chunk(
261+
handle: Option<&mut TracerTraceChunks>,
262+
) {
263+
if let Some(chunks) = handle {
264+
chunks.0.push(Vec::new());
265+
}
266+
}
267+
268+
/// Move a span into the current (last) chunk, consuming the span handle.
269+
///
270+
/// A chunk must have been started with
271+
/// [`ddog_tracer_trace_chunks_begin_chunk`] before calling this function.
272+
///
273+
/// # Safety
274+
///
275+
/// * `handle` must be a valid pointer to a `TracerTraceChunks`.
276+
/// * `span` is consumed and must not be used after this call.
277+
#[no_mangle]
278+
pub unsafe extern "C" fn ddog_tracer_trace_chunks_push_span(
279+
handle: Option<&mut TracerTraceChunks>,
280+
span: Box<TracerSpan>,
281+
) -> Option<Box<ExporterError>> {
282+
catch_panic!(
283+
if let Some(chunks) = handle {
284+
if let Some(chunk) = chunks.0.last_mut() {
285+
chunk.push(span.0);
286+
None
287+
} else {
288+
gen_error!(ErrorCode::InvalidArgument)
289+
}
290+
} else {
291+
gen_error!(ErrorCode::InvalidArgument)
292+
},
293+
gen_error!(ErrorCode::Panic)
294+
)
295+
}
296+
197297
#[cfg(test)]
198298
mod tests {
199299
use super::*;
@@ -356,4 +456,81 @@ mod tests {
356456
ddog_tracer_span_free(span);
357457
}
358458
}
459+
460+
// -- TracerTraceChunks tests --------------------------------------------
461+
462+
unsafe fn make_chunks(capacity: usize) -> Box<TracerTraceChunks> {
463+
let mut handle = MaybeUninit::<Box<TracerTraceChunks>>::uninit();
464+
let out = NonNull::new(handle.as_mut_ptr()).unwrap();
465+
let err = ddog_tracer_trace_chunks_new(capacity, out);
466+
assert!(err.is_none());
467+
handle.assume_init()
468+
}
469+
470+
#[test]
471+
fn trace_chunks_build_and_push() {
472+
unsafe {
473+
let mut chunks = make_chunks(2);
474+
475+
// Chunk 1: two spans
476+
ddog_tracer_trace_chunks_begin_chunk(Some(&mut *chunks));
477+
478+
let s1 = make_minimal_span();
479+
let err = ddog_tracer_trace_chunks_push_span(Some(&mut *chunks), s1);
480+
assert!(err.is_none());
481+
482+
let s2 = make_minimal_span();
483+
let err = ddog_tracer_trace_chunks_push_span(Some(&mut *chunks), s2);
484+
assert!(err.is_none());
485+
486+
// Chunk 2: one span
487+
ddog_tracer_trace_chunks_begin_chunk(Some(&mut *chunks));
488+
let s3 = make_minimal_span();
489+
let err = ddog_tracer_trace_chunks_push_span(Some(&mut *chunks), s3);
490+
assert!(err.is_none());
491+
492+
assert_eq!(chunks.0.len(), 2);
493+
assert_eq!(chunks.0[0].len(), 2);
494+
assert_eq!(chunks.0[1].len(), 1);
495+
496+
ddog_tracer_trace_chunks_free(chunks);
497+
}
498+
}
499+
500+
#[test]
501+
fn push_span_without_begin_chunk_returns_error() {
502+
unsafe {
503+
let mut chunks = make_chunks(0);
504+
505+
// No begin_chunk — push should fail
506+
let s = make_minimal_span();
507+
let err = ddog_tracer_trace_chunks_push_span(Some(&mut *chunks), s);
508+
assert!(err.is_some());
509+
ddog_trace_exporter_error_free(err);
510+
511+
ddog_tracer_trace_chunks_free(chunks);
512+
}
513+
}
514+
515+
#[test]
516+
fn trace_chunks_empty_is_valid() {
517+
unsafe {
518+
let chunks = make_chunks(0);
519+
assert_eq!(chunks.0.len(), 0);
520+
ddog_tracer_trace_chunks_free(chunks);
521+
}
522+
}
523+
524+
#[test]
525+
fn trace_chunks_empty_chunk_is_valid() {
526+
unsafe {
527+
let mut chunks = make_chunks(1);
528+
ddog_tracer_trace_chunks_begin_chunk(Some(&mut *chunks));
529+
530+
assert_eq!(chunks.0.len(), 1);
531+
assert_eq!(chunks.0[0].len(), 0);
532+
533+
ddog_tracer_trace_chunks_free(chunks);
534+
}
535+
}
359536
}

0 commit comments

Comments
 (0)