Skip to content

Commit 7deb8a7

Browse files
[mq] working branch - merge 0618837 on top of main at 3757554
{"baseBranch":"main","baseCommit":"375755474367e7e7612e580922316dc3d66e4aff","createdAt":"2025-12-17T20:39:06.704256Z","headSha":"06188379a776165dea0921c93bb3b1311f434e32","id":"3bd6fa32-6aa7-4255-8428-5bd70d7b014c","priority":"200","pullRequestNumber":"1406","queuedAt":"2025-12-17T20:39:06.703085Z","status":"STATUS_QUEUED"}
2 parents e505661 + 0618837 commit 7deb8a7

File tree

2 files changed

+239
-12
lines changed

2 files changed

+239
-12
lines changed

examples/ffi/profiles.c

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#include <stdio.h>
77
#include <stdlib.h>
88

9+
// Number of samples to add with each API
10+
#define NUM_SAMPLES 5000000
11+
912
int main(void) {
1013
const ddog_prof_ValueType wall_time = {
1114
.type_ = DDOG_CHARSLICE_C("wall-time"),
@@ -14,16 +17,27 @@ int main(void) {
1417
const ddog_prof_Slice_ValueType sample_types = {&wall_time, 1};
1518
const ddog_prof_Period period = {wall_time, 60};
1619

17-
ddog_prof_Profile_NewResult new_result = ddog_prof_Profile_new(sample_types, &period);
18-
if (new_result.tag != DDOG_PROF_PROFILE_NEW_RESULT_OK) {
19-
ddog_CharSlice message = ddog_Error_message(&new_result.err);
20-
fprintf(stderr, "%.*s", (int)message.len, message.ptr);
21-
ddog_Error_drop(&new_result.err);
20+
// Create a ProfilesDictionary for the new API
21+
ddog_prof_ProfilesDictionaryHandle dict = {0};
22+
ddog_prof_Status dict_status = ddog_prof_ProfilesDictionary_new(&dict);
23+
if (dict_status.flags != 0) {
24+
fprintf(stderr, "Failed to create dictionary: %s\n", dict_status.err);
25+
ddog_prof_Status_drop(&dict_status);
2226
exit(EXIT_FAILURE);
2327
}
2428

25-
ddog_prof_Profile *profile = &new_result.ok;
29+
// Create profile using the dictionary
30+
ddog_prof_Profile profile = {0};
31+
ddog_prof_Status profile_status =
32+
ddog_prof_Profile_with_dictionary(&profile, &dict, sample_types, &period);
33+
if (profile_status.flags != 0) {
34+
fprintf(stderr, "Failed to create profile: %s\n", profile_status.err);
35+
ddog_prof_Status_drop(&profile_status);
36+
ddog_prof_ProfilesDictionary_drop(&dict);
37+
exit(EXIT_FAILURE);
38+
}
2639

40+
// Original API sample
2741
ddog_prof_Location root_location = {
2842
// yes, a zero-initialized mapping is valid
2943
.mapping = (ddog_prof_Mapping){0},
@@ -44,28 +58,107 @@ int main(void) {
4458
.labels = {&label, 1},
4559
};
4660

47-
for (int i = 0; i < 10000000; i++) {
61+
for (int i = 0; i < NUM_SAMPLES; i++) {
4862
label.num = i;
4963

50-
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);
5165
if (add_result.tag != DDOG_PROF_PROFILE_RESULT_OK) {
5266
ddog_CharSlice message = ddog_Error_message(&add_result.err);
5367
fprintf(stderr, "%.*s", (int)message.len, message.ptr);
5468
ddog_Error_drop(&add_result.err);
5569
}
5670
}
5771

72+
// New API sample using the dictionary
73+
// Insert strings into the dictionary
74+
ddog_prof_StringId2 function_name_id, filename_id, label_key_id;
75+
76+
dict_status = ddog_prof_ProfilesDictionary_insert_str(
77+
&function_name_id, dict, DDOG_CHARSLICE_C("{main}"), DDOG_PROF_UTF8_OPTION_ASSUME);
78+
if (dict_status.flags != 0) {
79+
fprintf(stderr, "Failed to insert function name: %s\n", dict_status.err);
80+
ddog_prof_Status_drop(&dict_status);
81+
goto cleanup;
82+
}
83+
84+
dict_status = ddog_prof_ProfilesDictionary_insert_str(&filename_id, dict,
85+
DDOG_CHARSLICE_C("/srv/example/index.php"),
86+
DDOG_PROF_UTF8_OPTION_ASSUME);
87+
if (dict_status.flags != 0) {
88+
fprintf(stderr, "Failed to insert filename: %s\n", dict_status.err);
89+
ddog_prof_Status_drop(&dict_status);
90+
goto cleanup;
91+
}
92+
93+
dict_status = ddog_prof_ProfilesDictionary_insert_str(
94+
&label_key_id, dict, DDOG_CHARSLICE_C("unique_counter"), DDOG_PROF_UTF8_OPTION_ASSUME);
95+
if (dict_status.flags != 0) {
96+
fprintf(stderr, "Failed to insert label key: %s\n", dict_status.err);
97+
ddog_prof_Status_drop(&dict_status);
98+
goto cleanup;
99+
}
100+
101+
// Create a function using the dictionary IDs
102+
ddog_prof_FunctionId2 function_id;
103+
ddog_prof_Function2 function2 = {
104+
.name = function_name_id,
105+
.system_name = DDOG_PROF_STRINGID2_EMPTY,
106+
.file_name = filename_id,
107+
};
108+
109+
dict_status = ddog_prof_ProfilesDictionary_insert_function(&function_id, dict, &function2);
110+
if (dict_status.flags != 0) {
111+
fprintf(stderr, "Failed to insert function: %s\n", dict_status.err);
112+
ddog_prof_Status_drop(&dict_status);
113+
goto cleanup;
114+
}
115+
116+
// Create a location using the dictionary IDs
117+
ddog_prof_Location2 location2 = {
118+
.mapping = (ddog_prof_MappingId2){0}, // null mapping is valid
119+
.function = function_id,
120+
.address = 0,
121+
.line = 0,
122+
};
123+
124+
// New API sample using dictionary IDs
125+
ddog_prof_Label2 label2 = {
126+
.key = label_key_id,
127+
.str = DDOG_CHARSLICE_C(""),
128+
.num = 0,
129+
.num_unit = DDOG_CHARSLICE_C(""),
130+
};
131+
const ddog_prof_Sample2 sample2 = {
132+
.locations = {&location2, 1},
133+
.values = {&value, 1},
134+
.labels = {&label2, 1},
135+
};
136+
137+
for (int i = 0; i < NUM_SAMPLES; i++) {
138+
label2.num = i;
139+
140+
ddog_prof_Status add2_status = ddog_prof_Profile_add2(&profile, sample2, 0);
141+
if (add2_status.flags != 0) {
142+
fprintf(stderr, "add2 error: %s\n", add2_status.err);
143+
ddog_prof_Status_drop(&add2_status);
144+
}
145+
}
146+
58147
// printf("Press any key to reset and drop...");
59148
// getchar();
60149

61-
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(profile);
150+
cleanup:
151+
; // Can't have a declaration after a label pre-C23, so use an empty statement.
152+
ddog_prof_Profile_Result reset_result = ddog_prof_Profile_reset(&profile);
62153
if (reset_result.tag != DDOG_PROF_PROFILE_RESULT_OK) {
63154
ddog_CharSlice message = ddog_Error_message(&reset_result.err);
64155
fprintf(stderr, "%.*s", (int)message.len, message.ptr);
65156
ddog_Error_drop(&reset_result.err);
66157
}
67-
ddog_prof_Profile_drop(profile);
158+
ddog_prof_Profile_drop(&profile);
68159

160+
// Drop the dictionary
161+
ddog_prof_ProfilesDictionary_drop(&dict);
69162

70163
return 0;
71-
}
164+
}

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

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use crate::string_storage::{get_inner_string_storage, ManagedStringStorage};
5+
use crate::{ensure_non_null_out_parameter, ArcHandle, ProfileError, ProfileStatus};
56
use anyhow::Context;
67
use function_name::named;
78
use libdd_common_ffi::slice::{AsBytes, ByteSlice, CharSlice, Slice};
89
use libdd_common_ffi::{wrap_with_ffi_result, Error, Handle, Timespec, ToInner};
910
use libdd_profiling::api::{self, ManagedStringId};
10-
use libdd_profiling::internal;
11+
use libdd_profiling::profiles::datatypes::{ProfilesDictionary, StringId2};
12+
use libdd_profiling::{api2, internal};
1113
use std::num::NonZeroI64;
1214
use std::str::Utf8Error;
1315
use std::time::SystemTime;
@@ -215,6 +217,44 @@ pub struct Sample<'a> {
215217
pub labels: Slice<'a, Label<'a>>,
216218
}
217219

220+
#[derive(Copy, Clone, Debug, Default)]
221+
#[repr(C)]
222+
pub struct Label2<'a> {
223+
pub key: StringId2,
224+
225+
/// At most one of `.str` and `.num` should not be empty.
226+
pub str: CharSlice<'a>,
227+
pub num: i64,
228+
229+
/// Should only be present when num is present.
230+
/// Specifies the units of num.
231+
/// Use arbitrary string (for example, "requests") as a custom count unit.
232+
/// If no unit is specified, consumer may apply heuristic to deduce the unit.
233+
/// Consumers may also interpret units like "bytes" and "kilobytes" as memory
234+
/// units and units like "seconds" and "nanoseconds" as time units,
235+
/// and apply appropriate unit conversions to these.
236+
pub num_unit: CharSlice<'a>,
237+
}
238+
239+
#[repr(C)]
240+
#[derive(Copy, Clone)]
241+
pub struct Sample2<'a> {
242+
/// The leaf is at locations[0].
243+
pub locations: Slice<'a, api2::Location2>,
244+
245+
/// The type and unit of each value is defined by the corresponding
246+
/// entry in Profile.sample_type. All samples must have the same
247+
/// number of values, the same as the length of Profile.sample_type.
248+
/// When aggregating multiple samples into a single sample, the
249+
/// result has a list of values that is the element-wise sum of the
250+
/// lists of the originals.
251+
pub values: Slice<'a, i64>,
252+
253+
/// label includes additional context for this sample. It can include
254+
/// things like a thread id, allocation size, etc
255+
pub labels: Slice<'a, Label2<'a>>,
256+
}
257+
218258
impl<'a> TryFrom<&'a Mapping<'a>> for api::Mapping<'a> {
219259
type Error = Utf8Error;
220260

@@ -400,6 +440,64 @@ pub unsafe extern "C" fn ddog_prof_Profile_new(
400440
profile_new(sample_types, period, None)
401441
}
402442

443+
/// Create a new profile with the given sample types. Must call
444+
/// `ddog_prof_Profile_drop` when you are done with the profile.
445+
///
446+
/// # Arguments
447+
/// * `out` - a non-null pointer to an uninitialized Profile.
448+
/// * `dict`: a valid reference to a ProfilesDictionary handle.
449+
/// * `sample_types`
450+
/// * `period` - Optional period of the profile. Passing None/null translates to zero values.
451+
///
452+
/// # Safety
453+
/// All slices must have pointers that are suitably aligned for their type
454+
/// and must have the correct number of elements for the slice.
455+
///
456+
/// The `dict` reference must be to a valid ProfilesDictionary handle. It may
457+
/// be an empty handle, but it must be a valid handle.
458+
///
459+
/// The `out` pointer must be non-null and suitable for pointer writes,
460+
/// including that it has the correct size and alignment.
461+
#[no_mangle]
462+
#[must_use]
463+
pub unsafe extern "C" fn ddog_prof_Profile_with_dictionary(
464+
out: *mut Profile,
465+
dict: &ArcHandle<ProfilesDictionary>,
466+
sample_types: Slice<ValueType>,
467+
period: Option<&Period>,
468+
) -> ProfileStatus {
469+
ensure_non_null_out_parameter!(out);
470+
match profile_with_dictionary(dict, sample_types, period) {
471+
// SAFETY: checked that it isn't null above, the rest comes from this
472+
// function's own safety conditions. Technically, our safety conditions
473+
// don't require a null check, but we're being safe there.
474+
Ok(profile) => unsafe {
475+
out.write(profile);
476+
ProfileStatus::OK
477+
},
478+
Err(e) => ProfileStatus::from(e),
479+
}
480+
}
481+
482+
unsafe fn profile_with_dictionary(
483+
dict: &ArcHandle<ProfilesDictionary>,
484+
sample_types: Slice<ValueType>,
485+
period: Option<&Period>,
486+
) -> Result<Profile, ProfileError> {
487+
let sample_types = sample_types.try_as_slice()?;
488+
let dict = dict.try_clone_into_arc()?;
489+
490+
let mut types = Vec::new();
491+
types.try_reserve_exact(sample_types.len())?;
492+
types.extend(sample_types.iter().map(api::ValueType::from));
493+
let period = period.map(Into::into);
494+
495+
match internal::Profile::try_new_with_dictionary(&types, period, dict) {
496+
Ok(ok) => Ok(Profile::new(ok)),
497+
Err(err) => Err(ProfileError::from(err)),
498+
}
499+
}
500+
403501
/// Same as `ddog_profile_new` but also configures a `string_storage` for the profile.
404502
#[no_mangle]
405503
#[must_use]
@@ -508,6 +606,42 @@ pub unsafe extern "C" fn ddog_prof_Profile_add(
508606
.context("ddog_prof_Profile_add failed")
509607
.into()
510608
}
609+
/// # Safety
610+
/// The `profile` ptr must point to a valid Profile object created by this
611+
/// module. All pointers inside the `sample` need to be valid for the duration
612+
/// of this call.
613+
///
614+
/// If successful, it returns the Ok variant.
615+
/// On error, it holds an error message in the error variant.
616+
///
617+
/// This call is _NOT_ thread-safe.
618+
#[must_use]
619+
#[no_mangle]
620+
pub unsafe extern "C" fn ddog_prof_Profile_add2(
621+
profile: *mut Profile,
622+
sample: Sample2,
623+
timestamp: Option<NonZeroI64>,
624+
) -> ProfileStatus {
625+
ProfileStatus::from((|| {
626+
let profile = profile_ptr_to_inner(profile)?;
627+
628+
let locations = sample.locations.try_as_slice()?;
629+
let values = sample.values.try_as_slice()?;
630+
let labels = sample.labels.try_as_slice()?;
631+
632+
let labels_iter = labels.iter().map(|label| -> anyhow::Result<api2::Label> {
633+
Ok(api2::Label {
634+
key: label.key,
635+
str: core::str::from_utf8(label.str.try_as_bytes()?)?,
636+
num: label.num,
637+
num_unit: core::str::from_utf8(label.num_unit.try_as_bytes()?)?,
638+
})
639+
});
640+
profile
641+
.try_add_sample2(locations, values, labels_iter, timestamp)
642+
.context("ddog_prof_Profile_add failed")
643+
})())
644+
}
511645

512646
pub(crate) unsafe fn profile_ptr_to_inner<'a>(
513647
profile_ptr: *mut Profile,

0 commit comments

Comments
 (0)