Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Hasheable Float for use in metrics #1780

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions opentelemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ otel_unstable = []
[dev-dependencies]
opentelemetry_sdk = { path = "../opentelemetry-sdk", features = ["logs_level_enabled"]} # for documentation tests
criterion = { version = "0.3" }
rand = { workspace = true }

[[bench]]
name = "metrics"
Expand Down
122 changes: 122 additions & 0 deletions opentelemetry/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::{fmt, hash};

Expand Down Expand Up @@ -422,6 +424,55 @@
}
}

#[derive(Debug, Clone, Copy)]
struct F64Hasheable(f64);

impl PartialEq for F64Hasheable {
fn eq(&self, other: &Self) -> bool {
self.0.to_bits() == other.0.to_bits()
}

Check warning on line 433 in opentelemetry/src/common.rs

View check run for this annotation

Codecov / codecov/patch

opentelemetry/src/common.rs#L431-L433

Added lines #L431 - L433 were not covered by tests
}

impl Eq for F64Hasheable {}

impl Hash for F64Hasheable {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}

impl Hash for KeyValue {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key.hash(state);
match &self.value {
Value::F64(f) => F64Hasheable(*f).hash(state),
Value::Array(a) => match a {
Array::Bool(b) => b.hash(state),
Array::I64(i) => i.hash(state),
Array::F64(f) => f.iter().for_each(|f| F64Hasheable(*f).hash(state)),
Array::String(s) => s.hash(state),

Check warning on line 453 in opentelemetry/src/common.rs

View check run for this annotation

Codecov / codecov/patch

opentelemetry/src/common.rs#L449-L453

Added lines #L449 - L453 were not covered by tests
},
Value::Bool(b) => b.hash(state),
Value::I64(i) => i.hash(state),
Value::String(s) => s.hash(state),

Check warning on line 457 in opentelemetry/src/common.rs

View check run for this annotation

Codecov / codecov/patch

opentelemetry/src/common.rs#L455-L457

Added lines #L455 - L457 were not covered by tests
};
}
}

impl PartialOrd for KeyValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}

Check warning on line 465 in opentelemetry/src/common.rs

View check run for this annotation

Codecov / codecov/patch

opentelemetry/src/common.rs#L463-L465

Added lines #L463 - L465 were not covered by tests
}

impl Ord for KeyValue {
fn cmp(&self, other: &Self) -> Ordering {
self.key.cmp(&other.key)
}

Check warning on line 471 in opentelemetry/src/common.rs

View check run for this annotation

Codecov / codecov/patch

opentelemetry/src/common.rs#L469-L471

Added lines #L469 - L471 were not covered by tests
}

impl Eq for KeyValue {}

/// Marker trait for errors returned by exporters
pub trait ExportError: std::error::Error + Send + Sync + 'static {
/// The name of exporter that returned this error
Expand Down Expand Up @@ -594,3 +645,74 @@
}
}
}

#[cfg(test)]
mod tests {
use rand::Rng;

use crate::KeyValue;
use std::hash::DefaultHasher;
use std::hash::{Hash, Hasher};

#[test]
fn equality_kv_float() {
let kv1 = KeyValue::new("key", 1.0);
let kv2 = KeyValue::new("key", 1.0);
assert_eq!(kv1, kv2);

let kv1 = KeyValue::new("key", 1.0);
let kv2 = KeyValue::new("key", 1.01);
assert_ne!(kv1, kv2);

let kv1 = KeyValue::new("key", std::f64::NAN);
let kv2 = KeyValue::new("key", std::f64::NAN);
assert_ne!(kv1, kv2, "NAN is not equal to itself");

let kv1 = KeyValue::new("key", std::f64::INFINITY);
let kv2 = KeyValue::new("key", std::f64::INFINITY);
assert_eq!(kv1, kv2);

let mut rng = rand::thread_rng();

for _ in 0..100 {
let random_value = rng.gen::<f64>();
let kv1 = KeyValue::new("key", random_value);
let kv2 = KeyValue::new("key", random_value);
assert_eq!(kv1, kv2);
}
}

#[test]
fn hash_kv_float() {
let kv1 = KeyValue::new("key", 1.0);
let kv2 = KeyValue::new("key", 1.0);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));

let kv1 = KeyValue::new("key", 1.001);
let kv2 = KeyValue::new("key", 1.001);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));

let kv1 = KeyValue::new("key", std::f64::NAN);
let kv2 = KeyValue::new("key", std::f64::NAN);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));

let kv1 = KeyValue::new("key", std::f64::INFINITY);
let kv2 = KeyValue::new("key", std::f64::INFINITY);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));

let mut rng = rand::thread_rng();

for _ in 0..100 {
let random_value = rng.gen::<f64>();
let kv1 = KeyValue::new("key", random_value);
let kv2 = KeyValue::new("key", random_value);
assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
}
}

fn hash_helper<T: Hash>(item: &T) -> u64 {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
hasher.finish()
}
}
Loading