Skip to content

Commit 54d8800

Browse files
committed
Implement Hasheable Float for use in metrics
1 parent 2863632 commit 54d8800

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed

opentelemetry/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ otel_unstable = []
4242
[dev-dependencies]
4343
opentelemetry_sdk = { path = "../opentelemetry-sdk", features = ["logs_level_enabled"]} # for documentation tests
4444
criterion = { version = "0.3" }
45+
rand = { workspace = true }
4546

4647
[[bench]]
4748
name = "metrics"

opentelemetry/src/common.rs

+122
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::borrow::Cow;
2+
use std::cmp::Ordering;
3+
use std::hash::{Hash, Hasher};
24
use std::sync::Arc;
35
use std::{fmt, hash};
46

@@ -422,6 +424,55 @@ impl KeyValue {
422424
}
423425
}
424426

427+
#[derive(Debug, Clone, Copy)]
428+
struct F64Hasheable(f64);
429+
430+
impl PartialEq for F64Hasheable {
431+
fn eq(&self, other: &Self) -> bool {
432+
self.0.to_bits() == other.0.to_bits()
433+
}
434+
}
435+
436+
impl Eq for F64Hasheable {}
437+
438+
impl Hash for F64Hasheable {
439+
fn hash<H: Hasher>(&self, state: &mut H) {
440+
self.0.to_bits().hash(state);
441+
}
442+
}
443+
444+
impl Hash for KeyValue {
445+
fn hash<H: Hasher>(&self, state: &mut H) {
446+
self.key.hash(state);
447+
match &self.value {
448+
Value::F64(f) => F64Hasheable(*f).hash(state),
449+
Value::Array(a) => match a {
450+
Array::Bool(b) => b.hash(state),
451+
Array::I64(i) => i.hash(state),
452+
Array::F64(f) => f.iter().for_each(|f| F64Hasheable(*f).hash(state)),
453+
Array::String(s) => s.hash(state),
454+
},
455+
Value::Bool(b) => b.hash(state),
456+
Value::I64(i) => i.hash(state),
457+
Value::String(s) => s.hash(state),
458+
};
459+
}
460+
}
461+
462+
impl PartialOrd for KeyValue {
463+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
464+
Some(self.cmp(other))
465+
}
466+
}
467+
468+
impl Ord for KeyValue {
469+
fn cmp(&self, other: &Self) -> Ordering {
470+
self.key.cmp(&other.key)
471+
}
472+
}
473+
474+
impl Eq for KeyValue {}
475+
425476
/// Marker trait for errors returned by exporters
426477
pub trait ExportError: std::error::Error + Send + Sync + 'static {
427478
/// The name of exporter that returned this error
@@ -594,3 +645,74 @@ impl InstrumentationLibraryBuilder {
594645
}
595646
}
596647
}
648+
649+
#[cfg(test)]
650+
mod tests {
651+
use rand::Rng;
652+
653+
use crate::KeyValue;
654+
use std::hash::DefaultHasher;
655+
use std::hash::{Hash, Hasher};
656+
657+
#[test]
658+
fn equality_kv_float() {
659+
let kv1 = KeyValue::new("key", 1.0);
660+
let kv2 = KeyValue::new("key", 1.0);
661+
assert_eq!(kv1, kv2);
662+
663+
let kv1 = KeyValue::new("key", 1.0);
664+
let kv2 = KeyValue::new("key", 1.01);
665+
assert_ne!(kv1, kv2);
666+
667+
let kv1 = KeyValue::new("key", std::f64::NAN);
668+
let kv2 = KeyValue::new("key", std::f64::NAN);
669+
assert_ne!(kv1, kv2, "NAN is not equal to itself");
670+
671+
let kv1 = KeyValue::new("key", std::f64::INFINITY);
672+
let kv2 = KeyValue::new("key", std::f64::INFINITY);
673+
assert_eq!(kv1, kv2);
674+
675+
let mut rng = rand::thread_rng();
676+
677+
for _ in 0..100 {
678+
let random_value = rng.gen::<f64>();
679+
let kv1 = KeyValue::new("key", random_value);
680+
let kv2 = KeyValue::new("key", random_value);
681+
assert_eq!(kv1, kv2);
682+
}
683+
}
684+
685+
#[test]
686+
fn hash_kv_float() {
687+
let kv1 = KeyValue::new("key", 1.0);
688+
let kv2 = KeyValue::new("key", 1.0);
689+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
690+
691+
let kv1 = KeyValue::new("key", 1.001);
692+
let kv2 = KeyValue::new("key", 1.001);
693+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
694+
695+
let kv1 = KeyValue::new("key", std::f64::NAN);
696+
let kv2 = KeyValue::new("key", std::f64::NAN);
697+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
698+
699+
let kv1 = KeyValue::new("key", std::f64::INFINITY);
700+
let kv2 = KeyValue::new("key", std::f64::INFINITY);
701+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
702+
703+
let mut rng = rand::thread_rng();
704+
705+
for _ in 0..100 {
706+
let random_value = rng.gen::<f64>();
707+
let kv1 = KeyValue::new("key", random_value);
708+
let kv2 = KeyValue::new("key", random_value);
709+
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
710+
}
711+
}
712+
713+
fn hash_helper<T: Hash>(item: &T) -> u64 {
714+
let mut hasher = DefaultHasher::new();
715+
item.hash(&mut hasher);
716+
hasher.finish()
717+
}
718+
}

0 commit comments

Comments
 (0)