use mira_proto::common::v1::{AnyValue, KeyValue, any_value::Value};
use prost::Message;
const IDENTITY: &[(&[&str], &[&str])] = &[
(
&["service.name", "service.instance.id"],
&["service.namespace"],
),
(&["k8s.pod.uid"], &["k8s.container.name"]),
(&["container.id"], &[]),
(&["host.id"], &["process.pid"]),
(&["host.name"], &["process.pid"]),
(&["service.name"], &["service.namespace"]),
];
pub fn hash64(bytes: &[u8]) -> u64 {
let mut h = Fnv::new();
h.write(bytes);
h.finish()
}
pub const NO_IDENTITY: u64 = 0;
pub fn resource_key(attrs: &[KeyValue]) -> u64 {
for (n, (required, optional)) in IDENTITY.iter().enumerate() {
if !required.iter().all(|k| find(attrs, k).is_some()) {
continue;
}
let mut h = Fnv::new();
h.write(&[n as u8]);
for k in required.iter().chain(optional.iter()) {
if let Some(v) = find(attrs, k) {
h.write(k.as_bytes());
h.write(&[0]);
h.value(v);
h.write(&[0]);
}
}
let key = h.finish();
return if key == NO_IDENTITY { 1 } else { key };
}
NO_IDENTITY
}
fn find<'a>(attrs: &'a [KeyValue], key: &str) -> Option<Option<&'a AnyValue>> {
attrs
.iter()
.find(|kv| kv.key == key)
.map(|kv| kv.value.as_ref())
}
struct Fnv(u64);
impl Fnv {
fn new() -> Self {
Self(0xcbf2_9ce4_8422_2325)
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 ^= b as u64;
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
}
}
fn value(&mut self, v: Option<&AnyValue>) {
match v.and_then(|v| v.value.as_ref()) {
Some(Value::StringValue(s)) => self.write(s.as_bytes()),
Some(other) => {
let owned = AnyValue {
value: Some(other.clone()),
};
self.write(&owned.encode_to_vec());
}
None => {}
}
}
fn finish(self) -> u64 {
mix(self.0)
}
}
fn mix(mut z: u64) -> u64 {
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
#[cfg(test)]
mod tests {
use super::*;
fn kv(k: &str, v: &str) -> KeyValue {
KeyValue {
key: k.into(),
value: Some(AnyValue {
value: Some(Value::StringValue(v.into())),
}),
}
}
#[test]
fn identity_survives_attribute_drift_and_reordering() {
let base = vec![
kv("service.name", "checkout"),
kv("service.instance.id", "7f3a"),
];
let drifted = vec![
kv("service.instance.id", "7f3a"),
kv("k8s.node.name", "node-4"),
kv("service.name", "checkout"),
];
assert_eq!(resource_key(&base), resource_key(&drifted));
let other = vec![
kv("service.name", "checkout"),
kv("service.instance.id", "91bd"),
];
assert_ne!(resource_key(&base), resource_key(&other));
assert_ne!(
resource_key(&[kv("host.id", "abc")]),
resource_key(&[kv("container.id", "abc")])
);
assert_eq!(resource_key(&[kv("a", "1"), kv("b", "2")]), NO_IDENTITY);
assert_eq!(resource_key(&[kv("a", "1")]), NO_IDENTITY);
assert_eq!(resource_key(&[]), NO_IDENTITY);
}
#[test]
fn a_non_string_identifying_value_is_still_an_identity_and_is_not_its_own_text() {
let int = |k: &str, v: i64| KeyValue {
key: k.into(),
value: Some(AnyValue {
value: Some(Value::IntValue(v)),
}),
};
let numeric = vec![
kv("service.name", "checkout"),
int("service.instance.id", 7),
];
assert_ne!(resource_key(&numeric), NO_IDENTITY);
assert_eq!(
resource_key(&numeric),
resource_key(&[
kv("service.name", "checkout"),
int("service.instance.id", 7)
])
);
assert_ne!(
resource_key(&numeric),
resource_key(&[
kv("service.name", "checkout"),
int("service.instance.id", 8)
])
);
assert_ne!(
resource_key(&numeric),
resource_key(&[
kv("service.name", "checkout"),
kv("service.instance.id", "7")
])
);
let valueless = vec![
kv("service.name", "checkout"),
KeyValue {
key: "service.instance.id".into(),
value: None,
},
];
assert_ne!(resource_key(&valueless), NO_IDENTITY);
assert_ne!(
resource_key(&valueless),
resource_key(&[kv("service.name", "checkout")])
);
}
}