Skip to content

Commit 9f929cb

Browse files
feat(profiling): add heap-live allocation tracking to Profile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5b504e5 commit 9f929cb

File tree

13 files changed

+958
-49
lines changed

13 files changed

+958
-49
lines changed

datadog-profiling-replayer/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ fn main() -> anyhow::Result<()> {
209209

210210
let before = Instant::now();
211211
for (timestamp, sample) in samples {
212-
outprof.try_add_sample(sample, timestamp)?;
212+
outprof.try_add_sample(sample, timestamp, None)?;
213213
}
214214
let duration = before.elapsed();
215215

examples/ffi/exporter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ int main(int argc, char *argv[]) {
6666
.values = {&value, 1},
6767
.labels = {&label, 1},
6868
};
69-
auto add_result = ddog_prof_Profile_add(profile.get(), sample, 0);
69+
auto add_result = ddog_prof_Profile_add(profile.get(), sample, 0, 0);
7070
if (add_result.tag != DDOG_PROF_PROFILE_RESULT_OK) {
7171
print_error("Failed to add sample to profile: ", add_result.err);
7272
ddog_Error_drop(&add_result.err);

examples/ffi/exporter_manager.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ ddog_prof_Profile *create_profile_with_sample(void) {
6060
};
6161

6262
// Pass 0 as the timestamp parameter
63-
ddog_prof_Profile_Result add_result = ddog_prof_Profile_add(profile, sample, 0);
63+
ddog_prof_Profile_Result add_result = ddog_prof_Profile_add(profile, sample, 0, 0);
6464
if (add_result.tag != DDOG_PROF_PROFILE_RESULT_OK) {
6565
print_error("Failed to add sample to profile", &add_result.err);
6666
ddog_Error_drop(&add_result.err);

examples/ffi/profiles.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ int main(void) {
6161
for (int i = 0; i < NUM_SAMPLES; i++) {
6262
label.num = i;
6363

64-
ddog_prof_Profile_Result add_result = ddog_prof_Profile_add(&profile, sample, 0);
64+
ddog_prof_Profile_Result add_result = ddog_prof_Profile_add(&profile, sample, 0, 0);
6565
if (add_result.tag != DDOG_PROF_PROFILE_RESULT_OK) {
6666
ddog_CharSlice message = ddog_Error_message(&add_result.err);
6767
fprintf(stderr, "%.*s", (int)message.len, message.ptr);

libdd-profiling-ffi/src/profiles/datatypes.rs

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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]
588593
pub 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)]
9341015
mod 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

libdd-profiling/benches/add_samples.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub fn bench_add_sample_vs_add2(c: &mut Criterion) {
129129
values: &values,
130130
labels: vec![],
131131
};
132-
black_box(profile.try_add_sample(sample, None)).unwrap();
132+
black_box(profile.try_add_sample(sample, None, None)).unwrap();
133133
}
134134
black_box(profile.only_for_testing_num_aggregated_samples())
135135
})

libdd-profiling/examples/profiles.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fn main() {
4949
// Intentionally use the current time.
5050
let mut profile = Profile::try_new(&sample_types, Some(period)).unwrap();
5151

52-
match profile.try_add_sample(sample, None) {
52+
match profile.try_add_sample(sample, None, None) {
5353
Ok(_) => {}
5454
Err(_) => exit(1),
5555
}

libdd-profiling/src/cxx.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ impl Profile {
410410
};
411411

412412
// Profile interns the strings
413-
self.inner.try_add_sample(api_sample, None)?;
413+
self.inner.try_add_sample(api_sample, None, None)?;
414414
Ok(())
415415
}
416416

0 commit comments

Comments
 (0)