Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
cae62e4
WIP new "inverted" FFI
morrisonlevi Jul 3, 2025
a7d4ebc
use ArrayVec for sampes values, not just types
morrisonlevi Aug 29, 2025
761f76e
remove cursor rules from git
morrisonlevi Aug 29, 2025
ed14737
style: fix clippy::implicit_saturating_sub
morrisonlevi Aug 29, 2025
157f934
fix: don't use repr(u8) on enum
morrisonlevi Aug 29, 2025
147c00b
fix: examples, remove profile_intern
morrisonlevi Aug 29, 2025
46ec16b
WIP upscaling
morrisonlevi Sep 4, 2025
d126aa4
wip compile-able upscaling rules
morrisonlevi Sep 8, 2025
d6fbb55
fix compile issues in FFI
morrisonlevi Sep 8, 2025
76ef819
style: clang-format profiling examples
morrisonlevi Sep 8, 2025
108df8d
style: allow clippy::enum_variant_names
morrisonlevi Sep 8, 2025
49f550b
Merge remote-tracking branch 'origin/main' into levi/new-ffi
morrisonlevi Sep 8, 2025
6b5a210
reduce merge size
morrisonlevi Sep 8, 2025
c526724
wip ThinError
morrisonlevi Sep 9, 2025
3867ac9
shrink diff
morrisonlevi Sep 9, 2025
4cc42c9
prepare for merge
morrisonlevi Sep 9, 2025
0ae34c7
fix: license file and windows errors 🤞🏻
morrisonlevi Sep 9, 2025
4b986a3
fix: workaround Option<*Id> not lowering to `*mut void`
morrisonlevi Sep 9, 2025
08fb149
feat: FFI StringId constants
morrisonlevi Sep 9, 2025
7460b9a
fix: Default for Line/Location
morrisonlevi Sep 9, 2025
b224284
style: trim down PR diff
morrisonlevi Sep 9, 2025
7d59d5a
refactor: drop unused trait impls for StringOffset
morrisonlevi Sep 9, 2025
35f4bd5
refactor: drop unintentionally committed comment
morrisonlevi Sep 9, 2025
20af346
refactor: simplify
morrisonlevi Sep 10, 2025
de4b4d4
fix: no benches for datadog-profiling-validator
morrisonlevi Sep 10, 2025
6bec6bd
fix: upscaling based on Christophe's review
morrisonlevi Sep 11, 2025
9f341b2
style: fix clippy warnings
morrisonlevi Sep 11, 2025
d090095
fix: profiling FFI examples
morrisonlevi Sep 11, 2025
9d324a0
fix: docker bake for alpine
morrisonlevi Sep 11, 2025
5c3cbd4
Merge remote-tracking branch 'origin/main' into levi/new-ffi
morrisonlevi Sep 11, 2025
bd03ebb
refactor: remove unused StringOffset::checked_add
morrisonlevi Sep 11, 2025
0493491
doc: note rare CI failure for if it happens again
morrisonlevi Sep 11, 2025
3ca8f64
Merge remote-tracking branch 'origin/main' into levi/new-ffi
morrisonlevi Sep 11, 2025
2b23e0b
fix: exporter example user agent
morrisonlevi Sep 11, 2025
0e6947a
fix: prof examples including agentless
morrisonlevi Sep 11, 2025
0d1f917
fix: API key in exporter.cpp example
morrisonlevi Sep 12, 2025
5a61187
style: rename try_add_sample to try_add_sample_type to be more accurate
morrisonlevi Sep 17, 2025
c6aff25
feat: WIP ProfileAdapter FFI API
morrisonlevi Sep 18, 2025
41cbdb7
add dictionary to profile adapter
morrisonlevi Sep 18, 2025
26bab6a
wip FFI SampleBuilder tracks profile
morrisonlevi Sep 23, 2025
617091c
Merge remote-tracking branch 'origin/main' into levi/new-ffi
morrisonlevi Sep 23, 2025
cd33721
fix: SampleBuilder vs FFI SampleBuilder mismatch
morrisonlevi Sep 23, 2025
2988a7f
adjust profile adapter build_sample
morrisonlevi Sep 23, 2025
a5045aa
WIP upscaling through ProfileAdapter, fix leaks
morrisonlevi Sep 24, 2025
6d9a85a
wip
morrisonlevi Sep 24, 2025
4c5b99c
Merge remote-tracking branch 'origin/main' into levi/new-ffi
morrisonlevi Sep 29, 2025
75038aa
perf: add SampleBuilder::try_reserve_attributes
morrisonlevi Oct 3, 2025
c5d7b0d
style: fix clippy lints
morrisonlevi Oct 3, 2025
cb19cf4
fix: poisson upscaling happens before remap
morrisonlevi Oct 4, 2025
4bc8408
refactor: exporter.cpp to be shorter, more similar to original example
morrisonlevi Oct 7, 2025
3a1310c
wip: reworked profiles.c example
morrisonlevi Oct 8, 2025
0915322
fix: various issues with ProfileAdapter, enhance debugging
morrisonlevi Oct 8, 2025
a2ce313
fix: add back EncodedProfile_{bytes,drop} APIs
morrisonlevi Oct 17, 2025
1bff7e2
style: fix clippy lints
morrisonlevi Oct 17, 2025
9981642
style: fix clippy lint
morrisonlevi Oct 17, 2025
0208383
try to fix Windows
morrisonlevi Oct 17, 2025
83eeb7f
try 2 to fix Windows
morrisonlevi Oct 17, 2025
a977ceb
leave note about unintentional name change
morrisonlevi Oct 17, 2025
96caf65
Merge remote-tracking branch 'origin' into levi/new-ffi
morrisonlevi Oct 17, 2025
eee496c
fix: use Sleep API on Windows
morrisonlevi Oct 17, 2025
56ad0cb
add feedback from Ivo
morrisonlevi Oct 17, 2025
2f6eed7
feat: add ddog_prof_ProfileAdapter_clear_scratchpad_data
morrisonlevi Oct 17, 2025
5b8af04
Merge remote-tracking branch 'origin/levi/new-ffi' into levi/new-ffi
morrisonlevi Oct 18, 2025
ad92103
docs: document new function
morrisonlevi Oct 18, 2025
8f584b6
fix: leak and lifetime
morrisonlevi Oct 18, 2025
621ef70
docs: link to Raymond Chen's blog
morrisonlevi Oct 18, 2025
921eba4
fix: convert asserts to checks
morrisonlevi Oct 18, 2025
c54f87f
fix: panic
morrisonlevi Oct 19, 2025
9d12561
use more descriptive error message
morrisonlevi Oct 18, 2025
ffe715d
drop old-dated WIP todo
morrisonlevi Oct 19, 2025
3f4bcdb
fix: handle cleanups on failure
morrisonlevi Oct 20, 2025
9a97ed6
fix: SampleBuilder_finish leak
morrisonlevi Oct 20, 2025
06096de
Merge remote-tracking branch 'origin/main' into levi/new-ffi
morrisonlevi Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 102 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ members = [
"datadog-profiling",
"datadog-profiling-ffi",
"datadog-profiling-protobuf",
"datadog-profiling-replayer",
#"datadog-profiling-replayer", # todo: add back
"datadog-profiling-validator",
"datadog-remote-config",
"datadog-sidecar",
"datadog-sidecar-ffi",
Expand Down
296 changes: 250 additions & 46 deletions LICENSE-3rdparty.yml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions data-pipeline/src/trace_exporter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,9 @@ mod tests {
// This is expected - no metrics should be sent when disabled
// WouldBlock on Unix, TimedOut on Windows
}

// EINTR count in CI: 1; aka Interrupted system call (os error 4)
// If this happens again, we should attempt to handle interrupts.
Err(e) => panic!("Unexpected error reading from socket: {e}"),
}
}
Expand Down
17 changes: 7 additions & 10 deletions datadog-alloc/src/virtual_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use core::alloc::Layout;
/// intended for large allocations only, such as working with other allocators
/// to provide a large chunk for them.
#[derive(Clone, Copy, Debug)]
pub struct VirtualAllocator {}
pub struct VirtualAllocator;

#[cfg_attr(debug_assertions, track_caller)]
#[inline]
Expand Down Expand Up @@ -205,7 +205,7 @@ mod tests {
bolero::check!()
.with_generator(allocs)
.for_each(|size_align_vec| {
let allocator = VirtualAllocator {};
let allocator = VirtualAllocator;

for (size, align_bits, idx, val) in size_align_vec {
fuzzer_inner_loop(&allocator, *size, *align_bits, *idx, *val, MAX_SIZE)
Expand All @@ -215,10 +215,9 @@ mod tests {

#[test]
fn test_zero_sized() {
let alloc = VirtualAllocator {};
assert_eq!(0, core::mem::size_of::<VirtualAllocator>());
let zero_sized_layout = Layout::new::<VirtualAllocator>();
_ = alloc.allocate(zero_sized_layout).unwrap_err();
_ = VirtualAllocator.allocate(zero_sized_layout).unwrap_err();
}

#[test]
Expand All @@ -228,14 +227,13 @@ mod tests {
let too_large_layout = Layout::from_size_align(1, too_large)
.unwrap()
.pad_to_align();
let alloc = VirtualAllocator {};
_ = alloc.allocate(too_large_layout).unwrap_err();
_ = VirtualAllocator.allocate(too_large_layout).unwrap_err();
}

#[test]
fn test_small_cases() {
let page_size = os::page_size().unwrap();
let alloc = VirtualAllocator {};
let alloc = VirtualAllocator;

// Allocations get rounded up to page size.
let small_cases = [1, page_size - 1];
Expand Down Expand Up @@ -266,9 +264,8 @@ mod tests {
#[track_caller]
fn realistic_size(size: usize) {
let page_size = os::page_size().unwrap();
let alloc = VirtualAllocator {};
let layout = Layout::from_size_align(size, page_size).unwrap();
let wide_ptr = alloc.allocate(layout).unwrap();
let wide_ptr = VirtualAllocator.allocate(layout).unwrap();
let actual_size = wide_ptr.len();

// Should be a multiple of page size.
Expand All @@ -277,7 +274,7 @@ mod tests {
// Shouldn't ever be smaller than what was asked for.
assert!(actual_size >= size);

unsafe { alloc.deallocate(wide_ptr.cast(), layout) };
unsafe { VirtualAllocator.deallocate(wide_ptr.cast(), layout) };
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions datadog-profiling-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ ddsketch-ffi = ["dep:ddsketch-ffi"]
build_common = { path = "../build-common" }

[dependencies]
allocator-api2 = { version = "0.2", features = ["alloc"] }
anyhow = "1.0"
data-pipeline-ffi = { path = "../data-pipeline-ffi", default-features = false, optional = true }
datadog-crashtracker-ffi = { path = "../datadog-crashtracker-ffi", default-features = false, optional = true}
datadog-library-config-ffi = { path = "../datadog-library-config-ffi", default-features = false, optional = true }
datadog-profiling = { path = "../datadog-profiling" }
datadog-profiling-protobuf = { path = "../datadog-profiling-protobuf" }
ddcommon = { path = "../ddcommon" }
ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false, optional = true }
ddtelemetry-ffi = { path = "../ddtelemetry-ffi", default-features = false, optional = true, features = ["expanded_builder_macros"] }
Expand All @@ -50,6 +52,12 @@ futures = { version = "0.3", default-features = false }
http-body-util = "0.1"
hyper = { workspace = true}
libc = "0.2"
rustc-hash = { version = "1", default-features = false }
serde_json = { version = "1.0" }
symbolizer-ffi = { path = "../symbolizer-ffi", optional = true, default-features = false }
thiserror = "1"
tokio-util = "0.7.1"

[dev-dependencies]
datadog-profiling-protobuf = { path = "../datadog-profiling-protobuf", features = ["bolero", "prost_impls"] }
proptest = "1"
95 changes: 51 additions & 44 deletions datadog-profiling-ffi/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,72 +24,63 @@ prefix = "ddog_prof_"
renaming_overrides_prefixing = true

[export.rename]
# Common
"ByteSlice" = "ddog_ByteSlice"
"CharSlice" = "ddog_CharSlice"
"Endpoint" = "ddog_Endpoint"
"Error" = "ddog_Error"
"HttpStatus" = "ddog_HttpStatus"
"Option_Slice_Label" = "ddog_Option_Slice_Label"
"Slice_CChar" = "ddog_Slice_CChar"
"Slice_CharSlice" = "ddog_Slice_CharSlice"
"Slice_I64" = "ddog_Slice_I64"
"Slice_U64" = "ddog_Slice_U64"
"Slice_U8" = "ddog_Slice_U8"
"StringWrapper" = "ddog_StringWrapper"
"StringWrapperResult" = "ddog_StringWrapperResult"
"Tag" = "ddog_Tag"
"Timespec" = "ddog_Timespec"
"Vec_Tag" = "ddog_Vec_Tag"
"Vec_U8" = "ddog_Vec_U8"
"VoidResult" = "ddog_VoidResult"

"ProfilingEndpoint" = "ddog_prof_Endpoint"
# Profiles
"ArcHandle_ProfilesDictionary" = "ddog_prof_ProfilesDictionaryHandle"
"ArcHandle_ScratchPad" = "ddog_prof_ScratchPadHandle"
"ProfileHandle_PprofBuilder" = "ddog_prof_PprofBuilderHandle"
"ProfileHandle_Profile" = "ddog_prof_ProfileHandle"
"ProfileHandle_SampleBuilder" = "ddog_prof_SampleBuilderHandle"
"ProfileStatus" = "ddog_prof_Status"
"Vec_ProfileHandleProfile" = "ddog_prof_Vec_ProfileHandle"
"Vec_Usize" = "ddog_Vec_Usize"

# Exporter / Legacy
"CompressorFinishResult" = "ddog_prof_Compressor_FinishResult"
"ExporterNewResult" = "ddog_prof_Exporter_NewResult"
"File" = "ddog_prof_Exporter_File"
"LabelsSetLookupResult" = "ddog_prof_LabelsSet_LookupResult"
"ManagedStringId" = "ddog_prof_ManagedStringId"
"ManagedStringStorage" = "ddog_prof_ManagedStringStorage"
"ManagedStringStorageInternResult" = "ddog_prof_ManagedStringStorage_InternResult"
"ManagedStringStorageNewResult" = "ddog_prof_ManagedStringStorage_NewResult"
"ProfileBuilderBuildResult" = "ddog_prof_ProfileBuilder_BuildResult"
"ProfileBuilderNewResult" = "ddog_prof_ProfileBuilder_NewResult"
"ProfileError" = "ddog_prof_Profile_Error"
"ProfileExporter" = "ddog_prof_Exporter"
"ProfileNewResult" = "ddog_prof_Profile_NewResult"
"ProfileResult" = "ddog_prof_Profile_Result"
"ProfileVoidResult" = "ddog_prof_Profile_VoidResult"
"ProfilingEndpoint" = "ddog_prof_Endpoint"
"Request" = "ddog_prof_Exporter_Request"
"RequestBuildResult" = "ddog_prof_Exporter_Request_BuildResult"
"SendResult" = "ddog_prof_Exporter_SendResult"
"SerializeResult" = "ddog_prof_Profile_SerializeResult"
"Slice_File" = "ddog_prof_Exporter_Slice_File"
"ManagedStringStorage" = "ddog_prof_ManagedStringStorage"
"ManagedStringId" = "ddog_prof_ManagedStringId"
"StringWrapper" = "ddog_StringWrapper"
"StringWrapperResult" = "ddog_StringWrapperResult"
"VoidResult" = "ddog_VoidResult"

"CbindgenIsDumbStringId" = "ddog_prof_StringId"

"Slice_GenerationalIdLabelId" = "ddog_prof_Slice_LabelId"
"Slice_GenerationalIdLocationId" = "ddog_prof_Slice_LocationId"

"GenerationalId_FunctionId" = "ddog_prof_FunctionId"
"Result_GenerationalIdFunctionId" = "ddog_prof_FunctionId_Result"
"FunctionId" = "OpaqueFunctionId"

"GenerationalId_LabelId" = "ddog_prof_LabelId"
"Result_GenerationalIdLabelId" = "ddog_prof_LabelId_Result"
"LabelId" = "OpaqueLabelId"

"GenerationalId_LabelSetId" = "ddog_prof_LabelSetId"
"Result_GenerationalIdLabelSetId" = "ddog_prof_LabelSetId_Result"
"LabelSetId" = "OpaqueLabelSetId"

"GenerationalId_LocationId" = "ddog_prof_LocationId"
"Result_GenerationalIdLocationId" = "ddog_prof_LocationId_Result"
"LocationId" = "OpaqueLocationId"

"GenerationalId_MappingId" = "ddog_prof_MappingId"
"Result_GenerationalIdMappingId" = "ddog_prof_MappingId_Result"
"MappingId" = "OpaqueMappingId"

"GenerationalId_StackTraceId" = "ddog_prof_StackTraceId"
"Result_GenerationalIdStackTraceId" = "ddog_prof_StackTraceId_Result"
"StackTraceId" = "OpaqueStackTraceId"

"GenerationalId_StringId" = "ddog_prof_StringId"
"Result_GenerationalIdStringId" = "ddog_prof_StringId_Result"

# StringId is an alias of StringOffset, we need both to be `OpaqueStringId`
# for the current interning API.
"StringOffset" = "OpaqueStringId"
"StringId" = "OpaqueStringId"
"Slice_File" = "ddog_prof_Slice_Exporter_File" # todo: rename back to ddog_prof_Exporter_Slice_File ?
"SliceSetInsertResult" = "ddog_prof_LabelsSet_InsertResult"
"StoreInsertResult" = "ddog_prof_Store_InsertResult"
"StringTableInternResult" = "ddog_prof_StringTable_InternResult"
"StringTableLookupResult" = "ddog_prof_StringTable_LookupResult"
"StringTableNewResult" = "ddog_prof_StringTable_NewResult"

"HandleProfileExporter" = "ddog_prof_ProfileExporter"
"Handle_ProfileExporter" = "ddog_prof_ProfileExporter"
Expand All @@ -106,10 +97,26 @@ renaming_overrides_prefixing = true
"CancellationToken" = "struct ddog_OpaqueCancellationToken"
"Handle_TokioCancellationToken" = "ddog_CancellationToken"

# Horrible cbindgen output on these >.<
"Record_I64__2__OptZero" = "int64_t"
"Record_I64__3__OptZero" = "int64_t"
"Record_Line__4__OptZero" = "ddog_prof_Line"
"Record_StringOffset__2__OptZero" = "ddog_pprof_StringOffset"
"Record_StringOffset__3__OptZero" = "ddog_pprof_StringOffset"
"Record_StringOffset__4__OptZero" = "ddog_pprof_StringOffset"
"Record_StringOffset__5__OptZero" = "ddog_pprof_StringOffset"
"Record_StringOffset__6__OptZero" = "ddog_pprof_StringOffset"
"Record_U64__1__NoOptZero" = "uint64_t"
"Record_U64__1__OptZero" = "uint64_t"
"Record_U64__2__OptZero" = "uint64_t"
"Record_U64__3__OptZero" = "uint64_t"
"Record_U64__4__OptZero" = "uint64_t"

[export.mangle]
rename_types = "PascalCase"

[enum]
#merge_generic_tags = true
prefix_with_name = true
rename_variants = "ScreamingSnakeCase"

Expand Down
7 changes: 7 additions & 0 deletions datadog-profiling-ffi/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
# SPDX-License-Identifier: Apache-2.0

max_width = 80
doc_comment_code_block_width = 80
comment_width = 80
use_small_heuristics = "Max"
67 changes: 67 additions & 0 deletions datadog-profiling-ffi/src/arc_handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

use crate::EmptyHandleError;
use datadog_profiling::profiles::collections::Arc;
use datadog_profiling::profiles::ProfileError;
use std::ptr::{null_mut, NonNull};

/// Opaque FFI handle to an `Arc<T>`'s inner `T`.
///
/// Safety rules for implementors/callers:
/// - Do not create multiple owning `Arc<T>`s from the same raw pointer.
/// - Always restore the original `Arc` with `into_raw` after any `from_raw`.
/// - Use `as_inner()` to validate non-null before performing raw round-trips.
#[repr(transparent)]
#[derive(Debug)]
pub struct ArcHandle<T>(*mut T);

impl<T> Copy for ArcHandle<T> {}
impl<T> Clone for ArcHandle<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Default for ArcHandle<T> {
fn default() -> Self {
Self(null_mut())
}
}

impl<T> ArcHandle<T> {
/// Constructs a new handle by allocating an `Arc<T>` and returning its
/// inner pointer as a handle. Returns OutOfMemory on allocation failure.
pub fn new(value: T) -> Result<Self, ProfileError> {
let arc = Arc::try_new(value)?;
let ptr = Arc::into_raw(arc).as_ptr();
Ok(Self(ptr))
}

#[inline]
pub fn as_inner(&self) -> Result<&T, EmptyHandleError> {
unsafe { self.0.as_ref() }.ok_or(EmptyHandleError)
}

/// Tries to clone the resource this handle points to, and returns a new
/// handle to it.
pub fn try_clone(&self) -> Result<Self, ProfileError> {
let nn = NonNull::new(self.0).ok_or(EmptyHandleError)?;
// SAFETY: ArcHandle uses a pointer to T as its repr, and as long as
// callers have upheld safety requirements elsewhere, including the
// FFI, then there will be a valid object with refcount > 0.
unsafe { Arc::try_increment_count(nn.as_ptr())? };
Ok(Self(self.0))
}

/// Drops the resource that this handle refers to. It will remain alive if
/// there are other handles to the resource which were created by
/// successful calls to try_clone. This handle will now be empty and
/// operations on it will fail.
pub fn drop_resource(&mut self) {
// pointers aren't default until Rust 1.88.
let ptr = core::mem::replace(&mut self.0, null_mut());
if let Some(nn) = NonNull::new(ptr) {
drop(unsafe { Arc::from_raw(nn) });
}
}
}
Loading
Loading