|
1 | 1 | // Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
4 | | -//! FFI functions for creating and manipulating individual tracer spans. |
| 4 | +//! FFI functions for creating and manipulating tracer spans and trace chunks. |
5 | 5 | //! |
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. |
8 | 12 |
|
9 | 13 | use crate::error::{ExporterError, ExporterErrorCode as ErrorCode}; |
10 | 14 | use crate::{catch_panic, gen_error}; |
@@ -194,6 +198,102 @@ pub unsafe extern "C" fn ddog_tracer_span_set_metric( |
194 | 198 | ) |
195 | 199 | } |
196 | 200 |
|
| 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 | + |
197 | 297 | #[cfg(test)] |
198 | 298 | mod tests { |
199 | 299 | use super::*; |
@@ -356,4 +456,81 @@ mod tests { |
356 | 456 | ddog_tracer_span_free(span); |
357 | 457 | } |
358 | 458 | } |
| 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 | + } |
359 | 536 | } |
0 commit comments