Skip to content

Commit 6bec6bd

Browse files
committed
fix: upscaling based on Christophe's review
1 parent de4b4d4 commit 6bec6bd

File tree

10 files changed

+574
-440
lines changed

10 files changed

+574
-440
lines changed

datadog-profiling-ffi/src/profiles/pprof_builder/mod.rs

Lines changed: 110 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33

44
mod upscaling;
55

6-
use function_name::named;
7-
pub use upscaling::UpscalingRule;
6+
pub use upscaling::ProportionalUpscalingRule;
87

98
use crate::profile_handle::ProfileHandle;
10-
use crate::profiles::{ensure_non_null_out_parameter, Utf8Option};
9+
use crate::profiles::pprof_builder::upscaling::PoissonUpscalingRule;
10+
use crate::profiles::{
11+
ensure_non_null_out_parameter, Utf8ConversionError, Utf8Option,
12+
};
1113
use crate::{ArcHandle, ProfileStatus};
1214
use datadog_profiling::exporter::EncodedProfile;
13-
use datadog_profiling::profiles;
1415
use datadog_profiling::profiles::datatypes::{
1516
Profile, ProfilesDictionary, ScratchPad,
1617
};
@@ -48,49 +49,127 @@ pub unsafe extern "C" fn ddog_prof_PprofBuilder_new(
4849
}())
4950
}
5051

51-
/// Adds a profile to the builder with the attached upscaling rules.
52+
/// Adds a profile to the builder without upscaling rules.
53+
///
54+
/// # Safety
55+
///
56+
/// - `handle` must refer to a live builder, and no other mutable
57+
/// references to that builder may be active for the duration of the call.
58+
/// - `profile` must be non-null and point to a valid `Profile` that
59+
/// remains alive until the pprof builder is done.
60+
/// TODO: finish safety
61+
#[must_use]
62+
#[no_mangle]
63+
pub unsafe extern "C" fn ddog_prof_PprofBuilder_add_profile(
64+
mut handle: ProfileHandle<PprofBuilder>,
65+
profile: *const Profile,
66+
) -> ProfileStatus {
67+
crate::profiles::ensure_non_null_insert!(profile);
68+
let result = || -> Result<(), ProfileStatus> {
69+
let builder = unsafe {
70+
handle
71+
.as_inner_mut()
72+
.map_err(|e| ProfileStatus::from_ffi_safe_error_message(e))?
73+
};
74+
let prof_ref = unsafe { &*profile };
75+
builder
76+
.try_add_profile(prof_ref)
77+
.map_err(ProfileStatus::from_ffi_safe_error_message)
78+
}();
79+
match result {
80+
Ok(_) => ProfileStatus::OK,
81+
Err(err) => err,
82+
}
83+
}
84+
85+
/// Adds a profile to the builder with the attached poisson upscaling rule.
86+
///
87+
/// # Safety
88+
///
89+
/// - `handle` must refer to a live builder, and no other mutable
90+
/// references to that builder may be active for the duration of the call.
91+
/// - `profile` must be non-null and point to a valid `Profile` that
92+
/// remains alive until the pprof builder is done.
93+
/// TODO: finish safety
94+
#[must_use]
95+
#[no_mangle]
96+
pub unsafe extern "C" fn ddog_prof_PprofBuilder_add_profile_with_poisson_upscaling(
97+
mut handle: ProfileHandle<PprofBuilder>,
98+
profile: *const Profile,
99+
upscaling_rule: PoissonUpscalingRule,
100+
) -> ProfileStatus {
101+
crate::profiles::ensure_non_null_insert!(profile);
102+
let result = || -> Result<(), ProfileStatus> {
103+
let builder = unsafe {
104+
handle
105+
.as_inner_mut()
106+
.map_err(|e| ProfileStatus::from_ffi_safe_error_message(e))?
107+
};
108+
let prof_ref = unsafe { &*profile };
109+
110+
let upscaling_rule = upscaling_rule
111+
.try_into()
112+
.map_err(ProfileStatus::from_ffi_safe_error_message)?;
113+
builder
114+
.try_add_profile_with_poisson_upscaling(prof_ref, upscaling_rule)
115+
.map_err(ProfileStatus::from_ffi_safe_error_message)
116+
}();
117+
match result {
118+
Ok(_) => ProfileStatus::OK,
119+
Err(status) => status,
120+
}
121+
}
122+
123+
/// Adds a profile to the builder with the attached proportional rule.
52124
///
53125
/// # Safety
54126
///
55127
/// - `handle` must refer to a live builder, and no other mutable
56128
/// references to that builder may be active for the duration of the call.
57129
/// - `profile` must be non-null and point to a valid `Profile` that
58-
/// remains alive for the duration of the call.
130+
/// remains alive until the pprof builder is done.
131+
/// TODO: finish safety
59132
#[must_use]
60-
#[named]
61133
#[no_mangle]
62-
pub unsafe extern "C" fn ddog_prof_PprofBuilder_add_profile<'a>(
134+
pub unsafe extern "C" fn ddog_prof_PprofBuilder_add_profile_with_proportional_upscaling<
135+
'a,
136+
>(
63137
mut handle: ProfileHandle<PprofBuilder<'a>>,
64138
profile: *const Profile,
65-
upscaling_rules: Slice<UpscalingRule<'a>>,
139+
upscaling_rules: Slice<ProportionalUpscalingRule<'a>>,
66140
utf8_option: Utf8Option,
67141
) -> ProfileStatus {
68142
crate::profiles::ensure_non_null_insert!(profile);
69-
ProfileStatus::from(|| -> Result<(), ProfileError> {
70-
let builder = unsafe { handle.as_inner_mut()? };
143+
let result = || -> Result<(), ProfileStatus> {
144+
let builder = unsafe { handle.as_inner_mut() }
145+
.map_err(ProfileStatus::from_ffi_safe_error_message)?;
71146
let prof_ref = unsafe { &*profile };
72147

73-
let upscaling_rules = upscaling_rules.try_as_slice().map_err(|e| {
74-
ProfileError::fmt(format_args!(
75-
"{} failed: upscaling rules couldn't be converted to a Rust slice: {e}",
76-
function_name!(),
77-
))
78-
})?;
148+
let upscaling_rules = upscaling_rules
149+
.try_as_slice()
150+
.map_err(ProfileStatus::from_ffi_safe_error_message)?;
79151

80-
builder.try_add_profile(
81-
prof_ref,
82-
upscaling_rules.iter().map(|rule| {
83-
let key = utf8_option
84-
.try_as_bytes_convert(rule.group_by_label_key)?;
85-
let value = utf8_option
86-
.try_as_bytes_convert(rule.group_by_label_value)?;
87-
let group_by_label = (key, value);
88-
let upscaling_info =
89-
profiles::UpscalingInfo::try_from(rule.upscaling_info)?;
90-
Ok(profiles::UpscalingRule { group_by_label, upscaling_info })
91-
}),
92-
)
93-
}())
152+
builder
153+
.try_add_profile_with_proportional_upscaling(
154+
prof_ref,
155+
upscaling_rules.iter().map(
156+
|rule| -> Result<_, Utf8ConversionError> {
157+
let key = rule.group_by_label.key;
158+
let value = utf8_option
159+
.try_as_bytes_convert(rule.group_by_label.value)?;
160+
Ok((
161+
(key, value),
162+
rule.sampled as f64 / rule.real as f64,
163+
))
164+
},
165+
),
166+
)
167+
.map_err(ProfileStatus::from_ffi_safe_error_message)
168+
}();
169+
match result {
170+
Ok(_) => ProfileStatus::OK,
171+
Err(status) => status,
172+
}
94173
}
95174

96175
/// Builds and returns a compressed `EncodedProfile` via `out_profile`.
Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,87 @@
11
// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use datadog_profiling::profiles;
5-
use datadog_profiling::profiles::ProfileError;
4+
use datadog_profiling::profiles::collections::StringId;
5+
use datadog_profiling::profiles::datatypes::MAX_SAMPLE_TYPES;
6+
use ddcommon::error::FfiSafeErrorMessage;
67
use ddcommon_ffi::slice::CharSlice;
8+
use std::ffi::CStr;
9+
use std::num::NonZeroU64;
710

811
#[repr(C)]
912
#[derive(Clone, Copy, Debug)]
10-
pub struct UpscalingProportional {
11-
scale: f64,
13+
pub struct GroupByLabel<'a> {
14+
pub key: StringId,
15+
pub value: CharSlice<'a>,
1216
}
1317

1418
#[repr(C)]
1519
#[derive(Clone, Copy, Debug)]
16-
pub struct UpscalingPoisson {
17-
sampling_distance: u64,
20+
pub struct ProportionalUpscalingRule<'a> {
21+
/// The labels to group the sample values by. If it should apply to all
22+
/// samples and not group by label, then use the empty StringId and empty
23+
/// CharSlice.
24+
pub group_by_label: GroupByLabel<'a>,
25+
pub sampled: u64,
26+
pub real: u64,
1827
}
1928

2029
#[repr(C)]
2130
#[derive(Clone, Copy, Debug)]
22-
pub struct UpscalingPoissonNonSampleTypeCount {
23-
count_value: u64,
31+
pub struct PoissonUpscalingRule {
32+
/// Which offset in the profile's sample is the sum. Must be disjoint from
33+
/// `count_offset`.
34+
sum_offset: u32,
35+
/// Which offset in the profile's sample is the count. Must be disjoint
36+
/// from `sum_offset`.
37+
count_offset: u32,
2438
sampling_distance: u64,
2539
}
2640

27-
/// cbindgen:prefix-with-name=false
28-
/// cbindgen:rename-all=ScreamingSnakeCase
29-
#[repr(C)]
30-
#[derive(Clone, Copy, Debug)]
31-
#[allow(clippy::enum_variant_names)] // doing this for FFI purposes
32-
pub enum UpscalingInfo {
33-
DdogPprofUpscalingProportional(UpscalingProportional),
34-
DdogPprofUpscalingPoisson(UpscalingPoisson),
35-
DdogPprofUpscalingPoissonNonSampleTypeCount(
36-
UpscalingPoissonNonSampleTypeCount,
37-
),
41+
#[derive(Debug)]
42+
pub enum PoissonUpscalingConversionError {
43+
SamplingDistance,
44+
SumOffset,
45+
CountOffset,
3846
}
3947

40-
impl TryFrom<UpscalingInfo> for profiles::UpscalingInfo {
41-
type Error = ProfileError;
48+
// SAFETY: all cases use Rust c-str literals.
49+
unsafe impl FfiSafeErrorMessage for PoissonUpscalingConversionError {
50+
fn as_ffi_str(&self) -> &'static CStr {
51+
match self {
52+
PoissonUpscalingConversionError::SamplingDistance => c"PoissonUpscalingRule.sampling_distance cannot be zero",
53+
PoissonUpscalingConversionError::SumOffset => c"PoissonUpscalingRule.sum_offset must be less than MAX_SAMPLE_TYPES",
54+
PoissonUpscalingConversionError::CountOffset => c"PoissonUpscalingRule.count_offset must be less than MAX_SAMPLE_TYPES",
55+
}
56+
}
57+
}
4258

43-
fn try_from(info: UpscalingInfo) -> Result<Self, Self::Error> {
44-
Ok(match info {
45-
UpscalingInfo::DdogPprofUpscalingProportional(UpscalingProportional { scale }) => {
46-
profiles::UpscalingInfo::Proportional { scale }
47-
}
48-
UpscalingInfo::DdogPprofUpscalingPoisson(UpscalingPoisson { sampling_distance }) => {
49-
profiles::UpscalingInfo::Poisson {
50-
sampling_distance: sampling_distance
51-
.try_into()
52-
.map_err(|_| ProfileError::other("invalid input: upscaling Poisson sampling distance was zero"))?,
53-
}
54-
}
55-
UpscalingInfo::DdogPprofUpscalingPoissonNonSampleTypeCount(UpscalingPoissonNonSampleTypeCount{
56-
count_value,
57-
sampling_distance,
58-
}) => profiles::UpscalingInfo::PoissonNonSampleTypeCount {
59-
count_value: count_value.try_into()
60-
.map_err(|_| ProfileError::other("invalid input: upscaling PoissonNonSampleTypeCount count value was zero"))?,
61-
sampling_distance: sampling_distance.try_into()
62-
.map_err(|_| ProfileError::other("invalid input: upscaling PoissonNonSampleTypeCount sampling distance was zero"))?,
63-
},
64-
})
59+
impl core::fmt::Display for PoissonUpscalingConversionError {
60+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
61+
self.as_rust_str().fmt(f)
6562
}
6663
}
6764

68-
#[repr(C)]
69-
pub struct UpscalingRule<'a> {
70-
pub group_by_label_key: CharSlice<'a>, // todo: this one is interned already
71-
pub group_by_label_value: CharSlice<'a>, // "OutOfBoundsException", "0 - 9ms"
72-
pub upscaling_info: UpscalingInfo,
65+
impl core::error::Error for PoissonUpscalingConversionError {}
66+
67+
impl TryFrom<PoissonUpscalingRule>
68+
for datadog_profiling::profiles::PoissonUpscalingRule
69+
{
70+
type Error = PoissonUpscalingConversionError;
71+
72+
fn try_from(value: PoissonUpscalingRule) -> Result<Self, Self::Error> {
73+
let Some(sampling_distance) = NonZeroU64::new(value.sampling_distance)
74+
else {
75+
return Err(PoissonUpscalingConversionError::SamplingDistance);
76+
};
77+
let sum_offset = value.count_offset as usize;
78+
let count_offset = value.count_offset as usize;
79+
if sum_offset >= MAX_SAMPLE_TYPES {
80+
return Err(PoissonUpscalingConversionError::SumOffset);
81+
}
82+
if count_offset >= MAX_SAMPLE_TYPES {
83+
return Err(PoissonUpscalingConversionError::CountOffset);
84+
}
85+
Ok(Self { sum_offset, count_offset, sampling_distance })
86+
}
7387
}

datadog-profiling-ffi/src/profiles/scratchpad.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ pub unsafe extern "C" fn ddog_prof_ScratchPad_get_trace_endpoint_str(
336336
) -> ProfileStatus {
337337
ensure_non_null_out_parameter!(result);
338338
let Ok(pad) = handle.as_inner() else {
339-
return ProfileStatus::from_thin_error(EmptyHandleError);
339+
return ProfileStatus::from_ffi_safe_error_message(EmptyHandleError);
340340
};
341341
if let Some(s) =
342342
pad.endpoint_tracker().get_trace_endpoint_str(local_root_span_id)

0 commit comments

Comments
 (0)