Skip to content

Commit 7ce9a60

Browse files
authored
feat(profiling): add ProfilesDictionary (#1349)
feat(profiling): parallel set and string set test: exercise thread safety feat(profiling): add ProfilesDictionary Merge branch 'main' into levi/profiles_dictionary docs: improve ProfilesDictionary and related types Merge branch 'main' into levi/profiles_dictionary test: v1 and v2 have compat reprs Merge branch 'main' into levi/profiles_dictionary refactor: move transmute into From style: remove unused import Co-authored-by: levi.morrison <[email protected]>
1 parent aeae62b commit 7ce9a60

File tree

7 files changed

+528
-1
lines changed

7 files changed

+528
-1
lines changed

libdd-profiling/src/profiles/collections/parallel/string_set.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl ParallelStringSet {
8787
}
8888

8989
/// # Safety
90-
/// The caller must ensure that the StringId is valid for this set.
90+
/// The caller must ensure that the StringRef is valid for this set.
9191
pub unsafe fn get(&self, id: StringRef) -> &str {
9292
// SAFETY: safe as long as caller respects this function's safety.
9393
unsafe { core::mem::transmute::<&str, &str>(id.0.deref()) }
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::profiles::collections::{SetId, StringRef};
5+
use crate::profiles::datatypes::StringId2;
6+
7+
/// A representation of a function that is an intersection of the Otel and
8+
/// Pprof representations. Omits the start line to save space because Datadog
9+
/// doesn't use this in any way.
10+
///
11+
/// This representation is used internally by the `ProfilesDictionary`, and
12+
/// utilizes the fact that `StringRef`s don't have null values.
13+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
14+
#[repr(C)]
15+
pub struct Function {
16+
pub name: StringRef,
17+
pub system_name: StringRef,
18+
pub file_name: StringRef,
19+
}
20+
21+
/// An FFI-safe version of the Function which allows null.
22+
#[repr(C)]
23+
#[derive(Copy, Clone, Debug, Default)]
24+
pub struct Function2 {
25+
pub name: StringId2,
26+
pub system_name: StringId2,
27+
pub file_name: StringId2,
28+
}
29+
30+
impl From<Function> for Function2 {
31+
fn from(f: Function) -> Function2 {
32+
Function2 {
33+
name: f.name.into(),
34+
system_name: f.system_name.into(),
35+
file_name: f.file_name.into(),
36+
}
37+
}
38+
}
39+
40+
impl From<Function2> for Function {
41+
fn from(f2: Function2) -> Function {
42+
Function {
43+
name: f2.name.into(),
44+
system_name: f2.system_name.into(),
45+
file_name: f2.file_name.into(),
46+
}
47+
}
48+
}
49+
50+
/// An FFI-safe representation of a "handle" to a function which has been
51+
/// stored in the `ProfilesDictionary`. The representation is ensured to be a
52+
/// pointer for ABI stability, but callers should not generally dereference
53+
/// this pointer. When using the id, the caller needs to be sure that the
54+
/// `ProfilesDictionary` it refers to is the same one that the operations are
55+
/// performed on; it is not generally guaranteed that ids from one dictionary
56+
/// can be used in another dictionary, even if it happens to work by
57+
/// implementation detail.
58+
#[derive(Clone, Copy, Debug)]
59+
#[repr(transparent)]
60+
pub struct FunctionId2(pub(crate) *mut Function2);
61+
62+
// todo: when MSRV is 1.88.0+, derive Default
63+
impl Default for FunctionId2 {
64+
fn default() -> Self {
65+
Self(core::ptr::null_mut())
66+
}
67+
}
68+
69+
impl FunctionId2 {
70+
pub fn is_empty(self) -> bool {
71+
self.0.is_null()
72+
}
73+
74+
/// Converts the `FunctionId2` into an `Option<Function2>` where an empty
75+
/// `FunctionId2` converts to a `None`.
76+
///
77+
/// # Safety
78+
/// The pointer object must still be alive. In general this means the
79+
/// profiles dictionary it came from must be alive.
80+
pub unsafe fn read(self) -> Option<Function2> {
81+
if self.is_empty() {
82+
None
83+
} else {
84+
Some(self.0.read())
85+
}
86+
}
87+
}
88+
89+
impl From<SetId<Function>> for FunctionId2 {
90+
fn from(id: SetId<Function>) -> FunctionId2 {
91+
// SAFETY: the function that SetId points to is layout compatible with
92+
// the one that FunctionId2 points to. The reverse is not true for the
93+
// null StringId cases.
94+
unsafe { core::mem::transmute::<SetId<Function>, FunctionId2>(id) }
95+
}
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use super::*;
101+
use std::mem::offset_of;
102+
103+
#[test]
104+
fn v1_and_v2_have_compatible_representations() {
105+
// Begin with basic size and alignment.
106+
assert_eq!(size_of::<Function>(), size_of::<Function2>());
107+
assert_eq!(align_of::<Function>(), align_of::<Function2>());
108+
109+
// Then check members.
110+
assert_eq!(offset_of!(Function, name), offset_of!(Function2, name));
111+
assert_eq!(
112+
offset_of!(Function, system_name),
113+
offset_of!(Function2, system_name)
114+
);
115+
assert_eq!(
116+
offset_of!(Function, file_name),
117+
offset_of!(Function2, file_name)
118+
);
119+
}
120+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use crate::profiles::collections::{SetId, StringRef};
5+
use crate::profiles::datatypes::StringId2;
6+
7+
/// A representation of a mapping that is an intersection of the Otel and Pprof
8+
/// representations. Omits boolean attributes because Datadog doesn't use them
9+
/// in any way.
10+
///
11+
/// This representation is used internally by the `ProfilesDictionary`, and
12+
/// utilizes the fact that `StringRef`s don't have null values.
13+
#[repr(C)]
14+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
15+
pub struct Mapping {
16+
pub memory_start: u64,
17+
pub memory_limit: u64,
18+
pub file_offset: u64,
19+
pub filename: StringRef,
20+
pub build_id: StringRef, // missing in Otel, is it made into an attribute?
21+
}
22+
23+
/// An FFI-safe version of the Mapping which allows null.
24+
#[repr(C)]
25+
#[derive(Copy, Clone, Debug, Default)]
26+
pub struct Mapping2 {
27+
pub memory_start: u64,
28+
pub memory_limit: u64,
29+
pub file_offset: u64,
30+
pub filename: StringId2,
31+
pub build_id: StringId2, // missing in Otel, is it made into an attribute?
32+
}
33+
34+
impl From<Mapping2> for Mapping {
35+
fn from(m2: Mapping2) -> Self {
36+
Self {
37+
memory_start: m2.memory_start,
38+
memory_limit: m2.memory_limit,
39+
file_offset: m2.file_offset,
40+
filename: m2.filename.into(),
41+
build_id: m2.build_id.into(),
42+
}
43+
}
44+
}
45+
46+
impl From<Mapping> for Mapping2 {
47+
fn from(m: Mapping) -> Self {
48+
Self {
49+
memory_start: m.memory_start,
50+
memory_limit: m.memory_limit,
51+
file_offset: m.file_offset,
52+
filename: m.filename.into(),
53+
build_id: m.build_id.into(),
54+
}
55+
}
56+
}
57+
58+
/// An FFI-safe representation of a "handle" to a mapping which has been
59+
/// stored in the `ProfilesDictionary`. The representation is ensured to be a
60+
/// pointer for ABI stability, but callers should not generally dereference
61+
/// this pointer. When using the id, the caller needs to be sure that the
62+
/// `ProfilesDictionary` it refers to is the same one that the operations are
63+
/// performed on; it is not generally guaranteed that ids from one dictionary
64+
/// can be used in another dictionary, even if it happens to work by
65+
/// implementation detail.
66+
#[derive(Clone, Copy, Debug)]
67+
#[repr(transparent)]
68+
pub struct MappingId2(pub(crate) *mut Mapping2);
69+
70+
// todo: when MSRV is 1.88.0+, derive Default
71+
impl Default for MappingId2 {
72+
fn default() -> Self {
73+
Self(core::ptr::null_mut())
74+
}
75+
}
76+
77+
impl MappingId2 {
78+
pub fn is_empty(self) -> bool {
79+
self.0.is_null()
80+
}
81+
82+
/// Converts the `MappingId2` into an `Option<Mapping2>` where an empty
83+
/// `MappingId2` converts to a `None`.
84+
///
85+
/// # Safety
86+
/// The pointer object must still be alive. In general this means the
87+
/// profiles dictionary it came from must be alive.
88+
pub unsafe fn read(self) -> Option<Mapping2> {
89+
if self.is_empty() {
90+
None
91+
} else {
92+
Some(self.0.read())
93+
}
94+
}
95+
}
96+
97+
impl From<SetId<Mapping>> for MappingId2 {
98+
fn from(id: SetId<Mapping>) -> MappingId2 {
99+
// SAFETY: the mapping that SetId points to is layout compatible with
100+
// the one that MappingId2 points to. The reverse is not true for the
101+
// null StringId cases.
102+
unsafe { core::mem::transmute::<SetId<Mapping>, MappingId2>(id) }
103+
}
104+
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::*;
109+
use std::mem::offset_of;
110+
#[test]
111+
fn v1_and_v2_have_compatible_representations() {
112+
// Begin with basic size and alignment.
113+
assert_eq!(size_of::<Mapping>(), size_of::<Mapping2>());
114+
assert_eq!(align_of::<Mapping>(), align_of::<Mapping2>());
115+
116+
// Then check members.
117+
assert_eq!(
118+
offset_of!(Mapping, memory_start),
119+
offset_of!(Mapping2, memory_start)
120+
);
121+
assert_eq!(
122+
offset_of!(Mapping, memory_limit),
123+
offset_of!(Mapping2, memory_limit)
124+
);
125+
assert_eq!(
126+
offset_of!(Mapping, file_offset),
127+
offset_of!(Mapping2, file_offset)
128+
);
129+
assert_eq!(
130+
offset_of!(Mapping, filename),
131+
offset_of!(Mapping2, filename)
132+
);
133+
assert_eq!(
134+
offset_of!(Mapping, build_id),
135+
offset_of!(Mapping2, build_id)
136+
);
137+
}
138+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
mod function;
5+
mod mapping;
6+
mod profiles_dictionary;
7+
mod string;
8+
9+
pub use function::*;
10+
pub use mapping::*;
11+
pub use profiles_dictionary::*;
12+
pub use string::*;

0 commit comments

Comments
 (0)