metrics/
key.rs

1use crate::{atomics::AtomicU64, cow::Cow, IntoLabels, KeyHasher, Label, SharedString};
2use std::{
3    borrow::Borrow,
4    cmp, fmt,
5    hash::{Hash, Hasher},
6    slice::Iter,
7    sync::atomic::{AtomicBool, Ordering},
8};
9
10const NO_LABELS: [Label; 0] = [];
11
12/// Name component of a key.
13#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
14pub struct KeyName(SharedString);
15
16impl KeyName {
17    /// Creates a `KeyName` from a static string.
18    pub const fn from_const_str(name: &'static str) -> Self {
19        KeyName(SharedString::const_str(name))
20    }
21
22    /// Gets a reference to the string used for this name.
23    pub fn as_str(&self) -> &str {
24        &self.0
25    }
26}
27
28impl<T> From<T> for KeyName
29where
30    T: Into<SharedString>,
31{
32    fn from(name: T) -> Self {
33        KeyName(name.into())
34    }
35}
36
37impl Borrow<str> for KeyName {
38    fn borrow(&self) -> &str {
39        self.0.borrow()
40    }
41}
42
43/// A metric identifier.
44///
45/// A key represents both the name and labels of a metric.
46///
47/// # Safety
48/// Clippy will report any usage of `Key` as the key of a map/set as "mutable key type", meaning
49/// that it believes that there is interior mutability present which could lead to a key being
50/// hashed different over time.  That behavior could lead to unexpected behavior, as standard
51/// maps/sets depend on keys having stable hashes over time, related to times when they must be
52/// recomputed as the internal storage is resized and items are moved around.
53///
54/// In this case, the `Hash` implementation of `Key` does _not_ depend on the fields which Clippy
55/// considers mutable (the atomics) and so it is actually safe against differing hashes being
56/// generated.  We personally allow this Clippy lint in places where we store the key, such as
57/// helper types in the `metrics-util` crate, and you may need to do the same if you're using it in
58/// such a way as well.
59#[derive(Debug)]
60pub struct Key {
61    name: KeyName,
62    labels: Cow<'static, [Label]>,
63    hashed: AtomicBool,
64    hash: AtomicU64,
65}
66
67impl Key {
68    /// Creates a [`Key`] from a name.
69    pub fn from_name<N>(name: N) -> Self
70    where
71        N: Into<KeyName>,
72    {
73        let name = name.into();
74        let labels = Cow::from_owned(Vec::new());
75
76        Self::builder(name, labels)
77    }
78
79    /// Creates a [`Key`] from a name and set of labels.
80    pub fn from_parts<N, L>(name: N, labels: L) -> Self
81    where
82        N: Into<KeyName>,
83        L: IntoLabels,
84    {
85        let name = name.into();
86        let labels = Cow::from_owned(labels.into_labels());
87
88        Self::builder(name, labels)
89    }
90
91    /// Creates a [`Key`] from a non-static name and a static set of labels.
92    pub fn from_static_labels<N>(name: N, labels: &'static [Label]) -> Self
93    where
94        N: Into<KeyName>,
95    {
96        Self {
97            name: name.into(),
98            labels: Cow::const_slice(labels),
99            hashed: AtomicBool::new(false),
100            hash: AtomicU64::new(0),
101        }
102    }
103
104    /// Creates a [`Key`] from a static name.
105    ///
106    /// This function is `const`, so it can be used in a static context.
107    pub const fn from_static_name(name: &'static str) -> Self {
108        Self::from_static_parts(name, &NO_LABELS)
109    }
110
111    /// Creates a [`Key`] from a static name and static set of labels.
112    ///
113    /// This function is `const`, so it can be used in a static context.
114    pub const fn from_static_parts(name: &'static str, labels: &'static [Label]) -> Self {
115        Self {
116            name: KeyName::from_const_str(name),
117            labels: Cow::const_slice(labels),
118            hashed: AtomicBool::new(false),
119            hash: AtomicU64::new(0),
120        }
121    }
122
123    fn builder(name: KeyName, labels: Cow<'static, [Label]>) -> Self {
124        let hash = generate_key_hash(&name, &labels);
125
126        Self { name, labels, hashed: AtomicBool::new(true), hash: AtomicU64::new(hash) }
127    }
128
129    /// Name of this key.
130    pub fn name(&self) -> &str {
131        self.name.0.as_ref()
132    }
133
134    /// Labels of this key, if they exist.
135    pub fn labels(&self) -> Iter<Label> {
136        self.labels.iter()
137    }
138
139    /// Consumes this [`Key`], returning the name parts and any labels.
140    pub fn into_parts(self) -> (KeyName, Vec<Label>) {
141        (self.name, self.labels.into_owned())
142    }
143
144    /// Clones this [`Key`], and expands the existing set of labels.
145    pub fn with_extra_labels(&self, extra_labels: Vec<Label>) -> Self {
146        if extra_labels.is_empty() {
147            return self.clone();
148        }
149
150        let name = self.name.clone();
151        let mut labels = self.labels.clone().into_owned();
152        labels.extend(extra_labels);
153
154        Self::builder(name, labels.into())
155    }
156
157    /// Gets the hash value for this key.
158    pub fn get_hash(&self) -> u64 {
159        if self.hashed.load(Ordering::Acquire) {
160            self.hash.load(Ordering::Acquire)
161        } else {
162            let hash = generate_key_hash(&self.name, &self.labels);
163            self.hash.store(hash, Ordering::Release);
164            self.hashed.store(true, Ordering::Release);
165            hash
166        }
167    }
168}
169
170fn generate_key_hash(name: &KeyName, labels: &Cow<'static, [Label]>) -> u64 {
171    let mut hasher = KeyHasher::default();
172    key_hasher_impl(&mut hasher, name, labels);
173    hasher.finish()
174}
175
176fn key_hasher_impl<H: Hasher>(state: &mut H, name: &KeyName, labels: &Cow<'static, [Label]>) {
177    name.0.hash(state);
178    labels.hash(state);
179}
180
181impl Clone for Key {
182    fn clone(&self) -> Self {
183        Self {
184            name: self.name.clone(),
185            labels: self.labels.clone(),
186            hashed: AtomicBool::new(self.hashed.load(Ordering::Acquire)),
187            hash: AtomicU64::new(self.hash.load(Ordering::Acquire)),
188        }
189    }
190}
191
192impl PartialEq for Key {
193    fn eq(&self, other: &Self) -> bool {
194        self.name == other.name && self.labels == other.labels
195    }
196}
197
198impl Eq for Key {}
199
200impl PartialOrd for Key {
201    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
202        Some(self.cmp(other))
203    }
204}
205
206impl Ord for Key {
207    fn cmp(&self, other: &Self) -> cmp::Ordering {
208        (&self.name, &self.labels).cmp(&(&other.name, &other.labels))
209    }
210}
211
212impl Hash for Key {
213    fn hash<H: Hasher>(&self, state: &mut H) {
214        key_hasher_impl(state, &self.name, &self.labels);
215    }
216}
217
218impl fmt::Display for Key {
219    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
220        if self.labels.is_empty() {
221            write!(f, "Key({})", self.name.as_str())
222        } else {
223            write!(f, "Key({}, [", self.name.as_str())?;
224            let mut first = true;
225            for label in self.labels.as_ref() {
226                if first {
227                    write!(f, "{} = {}", label.0, label.1)?;
228                    first = false;
229                } else {
230                    write!(f, ", {} = {}", label.0, label.1)?;
231                }
232            }
233            write!(f, "])")
234        }
235    }
236}
237
238impl<T> From<T> for Key
239where
240    T: Into<KeyName>,
241{
242    fn from(name: T) -> Self {
243        Self::from_name(name)
244    }
245}
246
247impl<N, L> From<(N, L)> for Key
248where
249    N: Into<KeyName>,
250    L: IntoLabels,
251{
252    fn from(parts: (N, L)) -> Self {
253        Self::from_parts(parts.0, parts.1)
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::Key;
260    use crate::{KeyName, Label};
261    use std::{collections::HashMap, ops::Deref, sync::Arc};
262
263    static BORROWED_NAME: &str = "name";
264    static FOOBAR_NAME: &str = "foobar";
265    static BORROWED_BASIC: Key = Key::from_static_name(BORROWED_NAME);
266    static LABELS: [Label; 1] = [Label::from_static_parts("key", "value")];
267    static BORROWED_LABELS: Key = Key::from_static_parts(BORROWED_NAME, &LABELS);
268
269    #[test]
270    fn test_key_ord_and_partialord() {
271        let keys_expected: Vec<Key> =
272            vec![Key::from_name("aaaa"), Key::from_name("bbbb"), Key::from_name("cccc")];
273
274        let keys_unsorted: Vec<Key> =
275            vec![Key::from_name("bbbb"), Key::from_name("cccc"), Key::from_name("aaaa")];
276
277        let keys = {
278            let mut keys = keys_unsorted.clone();
279            keys.sort();
280            keys
281        };
282        assert_eq!(keys, keys_expected);
283
284        let keys = {
285            let mut keys = keys_unsorted.clone();
286            keys.sort_by(|a, b| a.partial_cmp(b).unwrap());
287            keys
288        };
289        assert_eq!(keys, keys_expected);
290    }
291
292    #[test]
293    fn test_key_eq_and_hash() {
294        let mut keys = HashMap::new();
295
296        let owned_basic: Key = Key::from_name("name");
297        assert_eq!(&owned_basic, &BORROWED_BASIC);
298
299        let previous = keys.insert(owned_basic, 42);
300        assert!(previous.is_none());
301
302        let previous = keys.get(&BORROWED_BASIC);
303        assert_eq!(previous, Some(&42));
304
305        let labels = LABELS.to_vec();
306        let owned_labels = Key::from_parts(BORROWED_NAME, labels);
307        assert_eq!(&owned_labels, &BORROWED_LABELS);
308
309        let previous = keys.insert(owned_labels, 43);
310        assert!(previous.is_none());
311
312        let previous = keys.get(&BORROWED_LABELS);
313        assert_eq!(previous, Some(&43));
314
315        let basic: Key = "constant_key".into();
316        let cloned_basic = basic.clone();
317        assert_eq!(basic, cloned_basic);
318    }
319
320    #[test]
321    fn test_key_data_proper_display() {
322        let key1 = Key::from_name("foobar");
323        let result1 = key1.to_string();
324        assert_eq!(result1, "Key(foobar)");
325
326        let key2 = Key::from_parts(FOOBAR_NAME, vec![Label::new("system", "http")]);
327        let result2 = key2.to_string();
328        assert_eq!(result2, "Key(foobar, [system = http])");
329
330        let key3 = Key::from_parts(
331            FOOBAR_NAME,
332            vec![Label::new("system", "http"), Label::new("user", "joe")],
333        );
334        let result3 = key3.to_string();
335        assert_eq!(result3, "Key(foobar, [system = http, user = joe])");
336
337        let key4 = Key::from_parts(
338            FOOBAR_NAME,
339            vec![
340                Label::new("black", "black"),
341                Label::new("lives", "lives"),
342                Label::new("matter", "matter"),
343            ],
344        );
345        let result4 = key4.to_string();
346        assert_eq!(result4, "Key(foobar, [black = black, lives = lives, matter = matter])");
347    }
348
349    #[test]
350    fn test_key_name_equality() {
351        static KEY_NAME: &str = "key_name";
352
353        let borrowed_const = KeyName::from_const_str(KEY_NAME);
354        let borrowed_nonconst = KeyName::from(KEY_NAME);
355        let owned = KeyName::from(KEY_NAME.to_owned());
356
357        let shared_arc = Arc::from(KEY_NAME);
358        let shared = KeyName::from(Arc::clone(&shared_arc));
359
360        assert_eq!(borrowed_const, borrowed_nonconst);
361        assert_eq!(borrowed_const.as_str(), borrowed_nonconst.as_str());
362        assert_eq!(borrowed_const, owned);
363        assert_eq!(borrowed_const.as_str(), owned.as_str());
364        assert_eq!(borrowed_const, shared);
365        assert_eq!(borrowed_const.as_str(), shared.as_str());
366    }
367
368    #[test]
369    fn test_shared_key_name_drop_logic() {
370        let shared_arc = Arc::from("foo");
371        let shared = KeyName::from(Arc::clone(&shared_arc));
372
373        assert_eq!(shared_arc.deref(), shared.as_str());
374
375        assert_eq!(Arc::strong_count(&shared_arc), 2);
376        drop(shared);
377        assert_eq!(Arc::strong_count(&shared_arc), 1);
378
379        let shared_weak = Arc::downgrade(&shared_arc);
380        assert_eq!(Arc::strong_count(&shared_arc), 1);
381
382        let shared = KeyName::from(Arc::clone(&shared_arc));
383        assert_eq!(shared_arc.deref(), shared.as_str());
384        assert_eq!(Arc::strong_count(&shared_arc), 2);
385
386        drop(shared_arc);
387        assert_eq!(shared_weak.strong_count(), 1);
388
389        drop(shared);
390        assert_eq!(shared_weak.strong_count(), 0);
391    }
392}