Skip to content

Commit fff4bea

Browse files
authored
[Profiler] Implement interning API (#917)
1 parent c0f1e51 commit fff4bea

23 files changed

+1249
-37
lines changed

ddcommon-ffi/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod handle;
1515
pub mod option;
1616
pub mod result;
1717
pub mod slice;
18+
pub mod slice_mut;
1819
pub mod string;
1920
pub mod tags;
2021
pub mod timespec;
@@ -27,6 +28,7 @@ pub use handle::*;
2728
pub use option::*;
2829
pub use result::*;
2930
pub use slice::{CharSlice, Slice};
31+
pub use slice_mut::MutSlice;
3032
pub use string::*;
3133
pub use timespec::*;
3234
pub use vec::Vec;

ddcommon-ffi/src/slice_mut.rs

+277
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use core::slice;
5+
use serde::ser::Error;
6+
use serde::Serializer;
7+
use std::borrow::Cow;
8+
use std::fmt::{Debug, Display, Formatter};
9+
use std::hash::{Hash, Hasher};
10+
use std::marker::PhantomData;
11+
use std::os::raw::c_char;
12+
use std::ptr::NonNull;
13+
use std::str::Utf8Error;
14+
15+
#[repr(C)]
16+
#[derive(Copy, Clone)]
17+
pub struct MutSlice<'a, T: 'a> {
18+
/// Should be non-null and suitably aligned for the underlying type. It is
19+
/// allowed but not recommended for the pointer to be null when the len is
20+
/// zero.
21+
ptr: Option<NonNull<T>>,
22+
23+
/// The number of elements (not bytes) that `.ptr` points to. Must be less
24+
/// than or equal to [isize::MAX].
25+
len: usize,
26+
_marker: PhantomData<&'a mut [T]>,
27+
}
28+
29+
impl<'a, T: 'a> core::ops::Deref for MutSlice<'a, T> {
30+
type Target = [T];
31+
32+
fn deref(&self) -> &Self::Target {
33+
self.as_slice()
34+
}
35+
}
36+
37+
impl<T: Debug> Debug for MutSlice<'_, T> {
38+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
39+
self.as_slice().fmt(f)
40+
}
41+
}
42+
43+
/// Use to represent strings -- should be valid UTF-8.
44+
pub type CharMutSlice<'a> = MutSlice<'a, c_char>;
45+
46+
/// Use to represent bytes -- does not need to be valid UTF-8.
47+
pub type ByteMutSlice<'a> = MutSlice<'a, u8>;
48+
49+
#[inline]
50+
fn is_aligned<T>(ptr: NonNull<T>) -> bool {
51+
ptr.as_ptr() as usize % std::mem::align_of::<T>() == 0
52+
}
53+
54+
pub trait AsBytes<'a> {
55+
fn as_bytes(&self) -> &'a [u8];
56+
57+
#[inline]
58+
fn try_to_utf8(&self) -> Result<&'a str, Utf8Error> {
59+
std::str::from_utf8(self.as_bytes())
60+
}
61+
62+
fn try_to_string(&self) -> Result<String, Utf8Error> {
63+
Ok(self.try_to_utf8()?.to_string())
64+
}
65+
66+
#[inline]
67+
fn try_to_string_option(&self) -> Result<Option<String>, Utf8Error> {
68+
Ok(Some(self.try_to_string()?).filter(|x| !x.is_empty()))
69+
}
70+
71+
#[inline]
72+
fn to_utf8_lossy(&self) -> Cow<'a, str> {
73+
String::from_utf8_lossy(self.as_bytes())
74+
}
75+
76+
#[inline]
77+
/// # Safety
78+
/// Must only be used when the underlying data was already confirmed to be utf8.
79+
unsafe fn assume_utf8(&self) -> &'a str {
80+
std::str::from_utf8_unchecked(self.as_bytes())
81+
}
82+
}
83+
84+
impl<'a> AsBytes<'a> for MutSlice<'a, u8> {
85+
fn as_bytes(&self) -> &'a [u8] {
86+
self.as_slice()
87+
}
88+
}
89+
90+
impl<'a, T: 'a> MutSlice<'a, T> {
91+
/// Creates a valid empty slice (len=0, ptr is non-null).
92+
// TODO, this can be const once MSRV >= 1.85
93+
#[must_use]
94+
pub fn empty() -> Self {
95+
Self {
96+
ptr: Some(NonNull::dangling()),
97+
len: 0,
98+
_marker: PhantomData,
99+
}
100+
}
101+
102+
/// # Safety
103+
/// Uphold the same safety requirements as [std::str::from_raw_parts].
104+
/// However, it is allowed but not recommended to provide a null pointer
105+
/// when the len is 0.
106+
// TODO, this can be const once MSRV >= 1.85
107+
pub unsafe fn from_raw_parts(ptr: *mut T, len: usize) -> Self {
108+
Self {
109+
ptr: NonNull::new(ptr),
110+
len,
111+
_marker: PhantomData,
112+
}
113+
}
114+
115+
// TODO, this can be const once MSRV >= 1.85
116+
pub fn new(slice: &mut [T]) -> Self {
117+
Self {
118+
ptr: NonNull::new(slice.as_mut_ptr()),
119+
len: slice.len(),
120+
_marker: PhantomData,
121+
}
122+
}
123+
124+
pub fn as_mut_slice(&mut self) -> &'a mut [T] {
125+
if let Some(ptr) = self.ptr {
126+
// Crashing immediately is likely better than ignoring these.
127+
assert!(is_aligned(ptr));
128+
assert!(self.len <= isize::MAX as usize);
129+
unsafe { slice::from_raw_parts_mut(ptr.as_ptr(), self.len) }
130+
} else {
131+
// Crashing immediately is likely better than ignoring this.
132+
assert_eq!(self.len, 0);
133+
&mut []
134+
}
135+
}
136+
137+
pub fn as_slice(&self) -> &'a [T] {
138+
if let Some(ptr) = self.ptr {
139+
// Crashing immediately is likely better than ignoring these.
140+
assert!(is_aligned(ptr));
141+
assert!(self.len <= isize::MAX as usize);
142+
unsafe { slice::from_raw_parts(ptr.as_ptr(), self.len) }
143+
} else {
144+
// Crashing immediately is likely better than ignoring this.
145+
assert_eq!(self.len, 0);
146+
&[]
147+
}
148+
}
149+
150+
pub fn into_slice(self) -> &'a [T] {
151+
self.as_slice()
152+
}
153+
154+
pub fn into_mut_slice(mut self) -> &'a mut [T] {
155+
self.as_mut_slice()
156+
}
157+
}
158+
159+
impl<T> Default for MutSlice<'_, T> {
160+
fn default() -> Self {
161+
Self::empty()
162+
}
163+
}
164+
165+
impl<'a, T> Hash for MutSlice<'a, T>
166+
where
167+
MutSlice<'a, T>: AsBytes<'a>,
168+
{
169+
fn hash<H: Hasher>(&self, state: &mut H) {
170+
state.write(self.as_bytes())
171+
}
172+
}
173+
174+
impl<'a, T> serde::Serialize for MutSlice<'a, T>
175+
where
176+
MutSlice<'a, T>: AsBytes<'a>,
177+
{
178+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179+
where
180+
S: Serializer,
181+
{
182+
serializer.serialize_str(self.try_to_utf8().map_err(Error::custom)?)
183+
}
184+
}
185+
186+
impl<'a, T> Display for MutSlice<'a, T>
187+
where
188+
MutSlice<'a, T>: AsBytes<'a>,
189+
{
190+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
191+
write!(f, "{}", self.try_to_utf8().map_err(|_| std::fmt::Error)?)
192+
}
193+
}
194+
195+
impl<'a, T: 'a> From<&'a mut [T]> for MutSlice<'a, T> {
196+
fn from(s: &'a mut [T]) -> Self {
197+
MutSlice::new(s)
198+
}
199+
}
200+
201+
impl<'a, T> From<&'a mut Vec<T>> for MutSlice<'a, T> {
202+
fn from(value: &'a mut Vec<T>) -> Self {
203+
MutSlice::new(value)
204+
}
205+
}
206+
207+
impl<'a> From<&'a mut str> for MutSlice<'a, c_char> {
208+
fn from(s: &'a mut str) -> Self {
209+
// SAFETY: Rust strings meet all the invariants required.
210+
unsafe { MutSlice::from_raw_parts(s.as_mut_ptr().cast(), s.len()) }
211+
}
212+
}
213+
214+
#[cfg(test)]
215+
mod tests {
216+
use super::*;
217+
use std::ptr;
218+
219+
#[derive(Debug, Eq, PartialEq)]
220+
struct Foo(i64);
221+
222+
#[test]
223+
fn slice_from_foo() {
224+
let mut raw = Foo(42);
225+
let ptr = &mut raw as *mut _;
226+
let mut slice = unsafe { MutSlice::from_raw_parts(ptr, 1) };
227+
228+
let expected: &[Foo] = &[raw];
229+
let actual: &[Foo] = slice.as_mut_slice();
230+
231+
assert_eq!(expected, actual)
232+
}
233+
234+
#[test]
235+
fn test_iterator() {
236+
let slice: &mut [i32] = &mut [1, 2, 3];
237+
let slice = MutSlice::from(slice);
238+
239+
let mut iter = slice.iter();
240+
241+
assert_eq!(Some(&1), iter.next());
242+
assert_eq!(Some(&2), iter.next());
243+
assert_eq!(Some(&3), iter.next());
244+
}
245+
246+
#[test]
247+
fn test_null_len0() {
248+
let mut null_len0: MutSlice<u8> = MutSlice {
249+
ptr: None,
250+
len: 0,
251+
_marker: PhantomData,
252+
};
253+
assert_eq!(null_len0.as_mut_slice(), &[]);
254+
}
255+
256+
#[should_panic]
257+
#[test]
258+
fn test_null_panic() {
259+
let mut null_len0: MutSlice<u8> = MutSlice {
260+
ptr: None,
261+
len: 1,
262+
_marker: PhantomData,
263+
};
264+
_ = null_len0.as_mut_slice();
265+
}
266+
267+
#[should_panic]
268+
#[test]
269+
fn test_long_panic() {
270+
let mut dangerous: MutSlice<u8> = MutSlice {
271+
ptr: Some(ptr::NonNull::dangling()),
272+
len: isize::MAX as usize + 1,
273+
_marker: PhantomData,
274+
};
275+
_ = dangerous.as_mut_slice();
276+
}
277+
}

examples/ffi/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ add_executable(crashinfo crashinfo.cpp)
3030
target_compile_features(crashinfo PRIVATE cxx_std_20)
3131
target_link_libraries(crashinfo PRIVATE Datadog::Profiling)
3232

33+
add_executable(profile_intern profile_intern.cpp)
34+
# needed for designated initializers
35+
target_compile_features(profile_intern PRIVATE cxx_std_20)
36+
target_link_libraries(profile_intern PRIVATE Datadog::Profiling)
3337

3438
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
3539
target_compile_definitions(exporter PUBLIC _CRT_SECURE_NO_WARNINGS)

examples/ffi/exporter.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ int main(int argc, char *argv[]) {
133133
"\"platform\": {\"kernel\": \"Darwin Kernel 22.5.0\"}}");
134134

135135
auto res = ddog_prof_Exporter_set_timeout(exporter, 30000);
136-
if (res.tag == DDOG_PROF_VOID_RESULT_ERR) {
136+
if (res.tag == DDOG_VOID_RESULT_ERR) {
137137
print_error("Failed to set the timeout", res.err);
138138
ddog_Error_drop(&res.err);
139139
return 1;

0 commit comments

Comments
 (0)