Skip to content

Commit 0bd90fd

Browse files
authored
feat(profiling): make dictionary ids comparable (#1570)
# What does this PR do? Adds comparability and hashability for dictionary-backed `api2` identity types. Specifically, this PR adds `Eq`/`PartialEq`/`Hash` for: - `FunctionId2` - `MappingId2` - `api2::Location2` Documents that these comparisons are intended for values produced by the same `ProfilesDictionary`. # Motivation I intend to use this in a future PR to optimize the internals of the Profile. I'm opening a separate PR to keep PRs smaller and more reviewable. # Additional Notes None. # How to test the change? 1. Check out branch `levi/api2-eq-hash`. 2. Run compile checks: - `cargo check -p libdd-profiling` - `cargo check -p libdd-profiling --benches` 3. Confirm: - `api2::Location2`/`FunctionId2`/`MappingId2` can be used in `Eq`/`Hash` contexts for same-dictionary IDs Co-authored-by: levi.morrison <levi.morrison@datadoghq.com>
1 parent 0e7299f commit 0bd90fd

3 files changed

Lines changed: 132 additions & 3 deletions

File tree

libdd-profiling/src/api2.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
use crate::profiles::datatypes::{FunctionId2, MappingId2, StringId2};
55

6-
#[derive(Copy, Clone, Debug, Default)]
6+
/// A location keyed by `MappingId2`/`FunctionId2` handles.
7+
///
8+
/// `Eq`/`Hash` comparisons use those handle values, so they are intended for
9+
/// data that comes from the same `ProfilesDictionary`.
10+
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
711
#[repr(C)]
812
pub struct Location2 {
913
pub mapping: MappingId2,
@@ -55,3 +59,51 @@ impl<'a> Label<'a> {
5559
}
5660
}
5761
}
62+
63+
#[cfg(test)]
64+
mod tests {
65+
use super::*;
66+
use crate::profiles::datatypes::{Function2, Mapping2, ProfilesDictionary};
67+
use std::collections::hash_map::DefaultHasher;
68+
use std::hash::{Hash, Hasher};
69+
70+
fn hash_of<T: Hash>(value: &T) -> u64 {
71+
let mut hasher = DefaultHasher::new();
72+
value.hash(&mut hasher);
73+
hasher.finish()
74+
}
75+
76+
#[test]
77+
fn location2_equality_and_hash_follow_dictionary_handle_identity() {
78+
let dict = ProfilesDictionary::try_new().unwrap();
79+
let shared = dict.try_insert_str2("shared").unwrap();
80+
81+
let function = Function2 {
82+
name: shared,
83+
system_name: shared,
84+
file_name: shared,
85+
};
86+
let mapping = Mapping2 {
87+
memory_start: 1,
88+
memory_limit: 2,
89+
file_offset: 3,
90+
filename: shared,
91+
build_id: shared,
92+
};
93+
94+
let location_a = Location2 {
95+
mapping: dict.try_insert_mapping2(mapping).unwrap(),
96+
function: dict.try_insert_function2(function).unwrap(),
97+
address: 42,
98+
line: 7,
99+
};
100+
let location_b = Location2 {
101+
mapping: dict.try_insert_mapping2(mapping).unwrap(),
102+
function: dict.try_insert_function2(function).unwrap(),
103+
address: 42,
104+
line: 7,
105+
};
106+
assert_eq!(location_a, location_b);
107+
assert_eq!(hash_of(&location_a), hash_of(&location_b));
108+
}
109+
}

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ impl From<Function2> for Function {
5959
/// performed on; it is not generally guaranteed that ids from one dictionary
6060
/// can be used in another dictionary, even if it happens to work by
6161
/// implementation detail.
62-
#[derive(Clone, Copy, Debug)]
62+
///
63+
/// `Eq`/`Hash` and other comparisons are based on this handle value, and are
64+
/// only intended for ids produced by the same `ProfilesDictionary`.
65+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
6366
#[repr(transparent)]
6467
pub struct FunctionId2(pub(crate) *mut Function2);
6568

@@ -102,6 +105,9 @@ impl From<SetId<Function>> for FunctionId2 {
102105
#[cfg(test)]
103106
mod tests {
104107
use super::*;
108+
use crate::profiles::datatypes::ProfilesDictionary;
109+
use std::collections::hash_map::DefaultHasher;
110+
use std::hash::{Hash, Hasher};
105111
use std::mem::offset_of;
106112

107113
#[test]
@@ -121,4 +127,36 @@ mod tests {
121127
offset_of!(Function2, file_name)
122128
);
123129
}
130+
131+
fn hash_of<T: Hash>(value: &T) -> u64 {
132+
let mut hasher = DefaultHasher::new();
133+
value.hash(&mut hasher);
134+
hasher.finish()
135+
}
136+
137+
#[test]
138+
fn function_id2_equality_and_hash_are_pointer_identity_based() {
139+
let dict = ProfilesDictionary::try_new().unwrap();
140+
let name = dict.try_insert_str2("name").unwrap();
141+
let system_name = dict.try_insert_str2("system").unwrap();
142+
let file_name = dict.try_insert_str2("file").unwrap();
143+
144+
let function = Function2 {
145+
name,
146+
system_name,
147+
file_name,
148+
};
149+
let same_a = dict.try_insert_function2(function).unwrap();
150+
let same_b = dict.try_insert_function2(function).unwrap();
151+
assert_eq!(same_a, same_b);
152+
assert_eq!(hash_of(&same_a), hash_of(&same_b));
153+
154+
let other = Function2 {
155+
name: dict.try_insert_str2("other_name").unwrap(),
156+
system_name,
157+
file_name,
158+
};
159+
let different = dict.try_insert_function2(other).unwrap();
160+
assert_ne!(same_a, different);
161+
}
124162
}

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ impl From<Mapping> for Mapping2 {
6767
/// performed on; it is not generally guaranteed that ids from one dictionary
6868
/// can be used in another dictionary, even if it happens to work by
6969
/// implementation detail.
70-
#[derive(Clone, Copy, Debug)]
70+
///
71+
/// `Eq`/`Hash` and other comparisons are based on this handle value, and are
72+
/// only intended for ids produced by the same `ProfilesDictionary`.
73+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
7174
#[repr(transparent)]
7275
pub struct MappingId2(pub(crate) *mut Mapping2);
7376

@@ -110,6 +113,9 @@ impl From<SetId<Mapping>> for MappingId2 {
110113
#[cfg(test)]
111114
mod tests {
112115
use super::*;
116+
use crate::profiles::datatypes::ProfilesDictionary;
117+
use std::collections::hash_map::DefaultHasher;
118+
use std::hash::{Hash, Hasher};
113119
use std::mem::offset_of;
114120

115121
#[test]
@@ -140,4 +146,37 @@ mod tests {
140146
offset_of!(Mapping2, build_id)
141147
);
142148
}
149+
150+
fn hash_of<T: Hash>(value: &T) -> u64 {
151+
let mut hasher = DefaultHasher::new();
152+
value.hash(&mut hasher);
153+
hasher.finish()
154+
}
155+
156+
#[test]
157+
fn mapping_id2_equality_and_hash_are_pointer_identity_based() {
158+
let dict = ProfilesDictionary::try_new().unwrap();
159+
let filename = dict.try_insert_str2("filename").unwrap();
160+
let build_id = dict.try_insert_str2("build").unwrap();
161+
162+
let mapping = Mapping2 {
163+
memory_start: 100,
164+
memory_limit: 200,
165+
file_offset: 10,
166+
filename,
167+
build_id,
168+
};
169+
let same_a = dict.try_insert_mapping2(mapping).unwrap();
170+
let same_b = dict.try_insert_mapping2(mapping).unwrap();
171+
assert_eq!(same_a, same_b);
172+
assert_eq!(hash_of(&same_a), hash_of(&same_b));
173+
174+
let different = dict
175+
.try_insert_mapping2(Mapping2 {
176+
memory_start: 101,
177+
..mapping
178+
})
179+
.unwrap();
180+
assert_ne!(same_a, different);
181+
}
143182
}

0 commit comments

Comments
 (0)