cu_profiler_core/baseline/
fingerprint.rs1use serde::{Deserialize, Serialize};
8
9const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
10const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
11
12#[must_use]
14pub fn hash_bytes(bytes: &[u8]) -> String {
15 let mut h = FNV_OFFSET;
16 for &b in bytes {
17 h ^= u64::from(b);
18 h = h.wrapping_mul(FNV_PRIME);
19 }
20 format!("{h:016x}")
21}
22
23#[must_use]
25pub fn hash_str(s: &str) -> String {
26 hash_bytes(s.as_bytes())
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct Fingerprint {
33 pub scenario_hash: String,
35 pub fixture_hash: String,
37 pub config_hash: String,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub program_binary_hash: Option<String>,
42}
43
44impl Fingerprint {
45 #[must_use]
47 pub fn new(scenario: &str, fixture: &str, config: &str, program_binary: Option<&[u8]>) -> Self {
48 Self {
49 scenario_hash: hash_str(scenario),
50 fixture_hash: hash_str(fixture),
51 config_hash: hash_str(config),
52 program_binary_hash: program_binary.map(hash_bytes),
53 }
54 }
55
56 #[must_use]
58 pub fn staleness_reasons(&self, other: &Fingerprint) -> Vec<String> {
59 let mut reasons = Vec::new();
60 if self.scenario_hash != other.scenario_hash {
61 reasons.push("scenario definition changed".to_string());
62 }
63 if self.fixture_hash != other.fixture_hash {
64 reasons.push("fixture hash changed".to_string());
65 }
66 if self.config_hash != other.config_hash {
67 reasons.push("config hash changed".to_string());
68 }
69 if self.program_binary_hash != other.program_binary_hash {
70 reasons.push("program binary hash changed".to_string());
71 }
72 reasons
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn hash_is_stable_and_distinct() {
82 assert_eq!(hash_str("abc"), hash_str("abc"));
83 assert_ne!(hash_str("abc"), hash_str("abd"));
84 }
85
86 #[test]
87 fn detects_changed_fixture() {
88 let a = Fingerprint::new("s", "fix1", "cfg", None);
89 let b = Fingerprint::new("s", "fix2", "cfg", None);
90 let reasons = a.staleness_reasons(&b);
91 assert_eq!(reasons, vec!["fixture hash changed".to_string()]);
92 }
93}