metrale_bench/gate/
record_path.rs1use std::path::{Path, PathBuf};
10
11use sha2::{Digest, Sha256};
12
13use super::gate_dir;
14use super::record::{GateBaseline, GateRecord};
15
16pub fn date_of(unix_secs: u64) -> String {
18 let days = (unix_secs / 86_400) as i64 + 719_468;
19 let era = days.div_euclid(146_097);
20 let doe = days.rem_euclid(146_097) as u64;
21 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
22 let y = yoe as i64 + era * 400;
23 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
24 let mp = (5 * doy + 2) / 153;
25 let d = doy - (153 * mp + 2) / 5 + 1;
26 let m = if mp < 10 { mp + 3 } else { mp - 9 };
27 let y = if m <= 2 { y + 1 } else { y };
28 format!("{y:04}-{m:02}-{d:02}")
29}
30
31pub fn time_of_day(unix_secs: u64) -> String {
33 let secs = unix_secs % 86_400;
34 format!("{:02}{:02}{:02}", secs / 3_600, (secs / 60) % 60, secs % 60)
35}
36
37pub fn record_path(root: &Path, benchmark_id: &str, unix_secs: u64, sha: &str) -> PathBuf {
39 gate_dir(root, benchmark_id).join(format!("{}-{sha}.json", date_of(unix_secs)))
40}
41
42pub fn rerun_path(canonical: &Path, unix_secs: u64) -> PathBuf {
49 let name = canonical
50 .file_name()
51 .map(|n| n.to_string_lossy().into_owned())
52 .unwrap_or_default();
53 let day = date_of(unix_secs);
54 let tail = name
55 .strip_prefix(day.as_str())
56 .unwrap_or_else(|| panic!("record name {name:?} does not start with its day {day}"));
57 canonical.with_file_name(format!("{day}T{}Z{tail}", time_of_day(unix_secs)))
58}
59
60pub fn shard_suffix(shard: Option<(usize, usize)>) -> String {
64 shard.map_or(String::new(), |(i, n)| format!("-s{i}of{n}"))
65}
66
67pub fn variant_slug(model: &str) -> String {
71 let mut out = String::with_capacity(model.len());
72 for c in model.chars() {
73 let mapped = if c.is_ascii_alphanumeric() || c == '.' {
74 c.to_ascii_lowercase()
75 } else {
76 '-'
77 };
78 if mapped == '-' && out.ends_with('-') {
79 continue;
80 }
81 out.push(mapped);
82 }
83 out.trim_matches('-').to_string()
84}
85
86fn variant_file_slug(baseline: &GateBaseline, model: &str) -> String {
89 let slug = variant_slug(model);
90 let collides = baseline
91 .hardware
92 .values()
93 .flat_map(|hardware| hardware.models.keys())
94 .any(|other| other != model && variant_slug(other) == slug);
95 if !collides {
96 return slug;
97 }
98 let digest = metrale_closure::hex_lower(&Sha256::digest(model.as_bytes()));
99 format!("{slug}-{}", &digest[..16])
100}
101
102pub fn record_path_for(root: &Path, record: &GateRecord) -> PathBuf {
108 let shard = shard_suffix(record.shard());
109 let legacy = gate_dir(root, &record.benchmark_id).join(format!(
110 "{}-{}{shard}.json",
111 date_of(record.recorded_at),
112 record.git_sha
113 ));
114 let Ok(baseline) = super::bench::baseline_for(root, &record.benchmark_id) else {
115 return legacy;
116 };
117 if baseline.hardware.is_empty() {
118 return legacy;
119 }
120 let hardware = record.hardware.gate_key();
121 let is_default = match baseline.hardware.get(&hardware) {
122 Some(hw) => hw.default == record.target_model,
123 None => baseline
124 .hardware
125 .values()
126 .any(|hw| hw.default == record.target_model),
127 };
128 if is_default {
129 legacy
130 } else {
131 gate_dir(root, &record.benchmark_id).join(format!(
132 "{}-{}-{}{shard}.json",
133 date_of(record.recorded_at),
134 record.git_sha,
135 variant_file_slug(&baseline, &record.target_model)
136 ))
137 }
138}