@@ -583,24 +583,35 @@ impl From<ProfileNewResult> for Result<Profile, Error> {
583583/// The `profile` ptr must point to a valid Profile object created by this
584584/// module.
585585/// This call is _NOT_ thread-safe.
586+ ///
587+ /// # Arguments
588+ /// * `track_ptr` - If non-zero, also track this allocation for heap-live profiling. The sample data
589+ /// is copied into owned storage and will be automatically injected during profile reset. Use 0 to
590+ /// skip tracking.
586591#[ must_use]
587592#[ no_mangle]
588593pub unsafe extern "C" fn ddog_prof_Profile_add (
589594 profile : * mut Profile ,
590595 sample : Sample ,
591596 timestamp : Option < NonZeroI64 > ,
597+ track_ptr : usize ,
592598) -> ProfileResult {
593599 ( || {
594600 let profile = profile_ptr_to_inner ( profile) ?;
601+ let track = if track_ptr != 0 {
602+ Some ( track_ptr as u64 )
603+ } else {
604+ None
605+ } ;
595606 let uses_string_ids = sample
596607 . labels
597608 . first ( )
598609 . is_some_and ( |label| label. key . is_empty ( ) && label. key_id . value > 0 ) ;
599610
600611 if uses_string_ids {
601- profile. add_string_id_sample ( sample. into ( ) , timestamp)
612+ profile. add_string_id_sample ( sample. into ( ) , timestamp, track )
602613 } else {
603- profile. try_add_sample ( sample. try_into ( ) ?, timestamp)
614+ profile. try_add_sample ( sample. try_into ( ) ?, timestamp, track )
604615 }
605616 } ) ( )
606617 . context ( "ddog_prof_Profile_add failed" )
@@ -930,6 +941,76 @@ pub unsafe extern "C" fn ddog_prof_Profile_reset(profile: *mut Profile) -> Profi
930941 . into ( )
931942}
932943
944+ /// Enable heap-live allocation tracking on this profile. When enabled,
945+ /// calls to `ddog_prof_Profile_add` with a non-zero `track_ptr` will copy
946+ /// the sample data into owned storage. Tracked allocations are automatically
947+ /// injected during profile reset and survive across resets.
948+ ///
949+ /// # Arguments
950+ /// * `profile` - A mutable reference to the profile.
951+ /// * `max_tracked` - Maximum number of allocations to track simultaneously.
952+ /// * `excluded_labels` - Label keys to strip from tracked allocations (e.g., high-cardinality
953+ /// labels like "span id").
954+ /// * `excluded_labels_len` - Number of elements in `excluded_labels`.
955+ /// * `alloc_size_idx` - Index of alloc-size in the sample values array.
956+ /// * `heap_live_samples_idx` - Index of heap-live-samples in the sample values array.
957+ /// * `heap_live_size_idx` - Index of heap-live-size in the sample values array.
958+ ///
959+ /// # Safety
960+ /// The `profile` ptr must point to a valid Profile object created by this
961+ /// module. `excluded_labels` must be valid for `excluded_labels_len` elements.
962+ /// This call is _NOT_ thread-safe.
963+ #[ no_mangle]
964+ pub unsafe extern "C" fn ddog_prof_Profile_enable_heap_live_tracking (
965+ profile : * mut Profile ,
966+ max_tracked : usize ,
967+ excluded_labels : * const CharSlice < ' _ > ,
968+ excluded_labels_len : usize ,
969+ alloc_size_idx : usize ,
970+ heap_live_samples_idx : usize ,
971+ heap_live_size_idx : usize ,
972+ ) -> ProfileResult {
973+ ( || {
974+ let profile = profile_ptr_to_inner ( profile) ?;
975+ let labels: Vec < & str > = if excluded_labels. is_null ( ) || excluded_labels_len == 0 {
976+ Vec :: new ( )
977+ } else {
978+ let slice = std:: slice:: from_raw_parts ( excluded_labels, excluded_labels_len) ;
979+ slice
980+ . iter ( )
981+ . map ( |cs| cs. try_to_utf8 ( ) )
982+ . collect :: < Result < Vec < _ > , _ > > ( ) ?
983+ } ;
984+ profile. enable_heap_live_tracking (
985+ max_tracked,
986+ & labels,
987+ alloc_size_idx,
988+ heap_live_samples_idx,
989+ heap_live_size_idx,
990+ ) ;
991+ anyhow:: Ok ( ( ) )
992+ } ) ( )
993+ . context ( "ddog_prof_Profile_enable_heap_live_tracking failed" )
994+ . into ( )
995+ }
996+
997+ /// Remove a tracked heap-live allocation by pointer. No-op if heap-live
998+ /// tracking is disabled or the pointer is not tracked.
999+ ///
1000+ /// # Arguments
1001+ /// * `profile` - A mutable reference to the profile.
1002+ /// * `ptr` - The pointer value of the allocation to untrack.
1003+ ///
1004+ /// # Safety
1005+ /// The `profile` ptr must point to a valid Profile object created by this
1006+ /// module. This call is _NOT_ thread-safe.
1007+ #[ no_mangle]
1008+ pub unsafe extern "C" fn ddog_prof_Profile_untrack_allocation ( profile : * mut Profile , ptr : usize ) {
1009+ if let Ok ( profile) = profile_ptr_to_inner ( profile) {
1010+ profile. untrack_allocation ( ptr as u64 ) ;
1011+ }
1012+ }
1013+
9331014#[ cfg( test) ]
9341015mod tests {
9351016 use super :: * ;
@@ -965,7 +1046,7 @@ mod tests {
9651046 labels : Slice :: empty ( ) ,
9661047 } ;
9671048
968- let result = Result :: from ( ddog_prof_Profile_add ( & mut profile, sample, None ) ) ;
1049+ let result = Result :: from ( ddog_prof_Profile_add ( & mut profile, sample, None , 0 ) ) ;
9691050 result. unwrap_err ( ) ;
9701051 ddog_prof_Profile_drop ( & mut profile) ;
9711052 Ok ( ( ) )
@@ -1013,7 +1094,7 @@ mod tests {
10131094 labels : Slice :: from ( & labels) ,
10141095 } ;
10151096
1016- Result :: from ( ddog_prof_Profile_add ( & mut profile, sample, None ) ) ?;
1097+ Result :: from ( ddog_prof_Profile_add ( & mut profile, sample, None , 0 ) ) ?;
10171098 assert_eq ! (
10181099 profile
10191100 . inner
@@ -1023,7 +1104,7 @@ mod tests {
10231104 1
10241105 ) ;
10251106
1026- Result :: from ( ddog_prof_Profile_add ( & mut profile, sample, None ) ) ?;
1107+ Result :: from ( ddog_prof_Profile_add ( & mut profile, sample, None , 0 ) ) ?;
10271108 assert_eq ! (
10281109 profile
10291110 . inner
@@ -1099,7 +1180,7 @@ mod tests {
10991180 labels : Slice :: from ( labels. as_slice ( ) ) ,
11001181 } ;
11011182
1102- Result :: from ( ddog_prof_Profile_add ( & mut profile, main_sample, None ) ) . unwrap ( ) ;
1183+ Result :: from ( ddog_prof_Profile_add ( & mut profile, main_sample, None , 0 ) ) . unwrap ( ) ;
11031184 assert_eq ! (
11041185 profile
11051186 . inner
@@ -1109,7 +1190,7 @@ mod tests {
11091190 1
11101191 ) ;
11111192
1112- Result :: from ( ddog_prof_Profile_add ( & mut profile, test_sample, None ) ) . unwrap ( ) ;
1193+ Result :: from ( ddog_prof_Profile_add ( & mut profile, test_sample, None , 0 ) ) . unwrap ( ) ;
11131194 assert_eq ! (
11141195 profile
11151196 . inner
0 commit comments