metrale_bench/benchmarks/default_tier_boot/
memory.rs1use std::collections::BTreeMap;
14
15use crate::result::Verdict;
16
17pub const MIB: f64 = 1024.0 * 1024.0;
19
20#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
22pub struct MemoryReport {
23 pub kv_blocks: usize,
25 pub kv_block_bytes: u64,
27 pub max_batch_size: usize,
29 pub budget_bytes: u64,
31 pub ledger_live_bytes: Option<u64>,
34 pub mem_available_at_start_bytes: Option<u64>,
36 pub mem_available_now_bytes: Option<u64>,
38 pub rss_anon_bytes: Option<u64>,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct Footprint {
45 pub kv_blocks: usize,
46 pub max_batch_size: usize,
47 pub budget_mib: f64,
48 pub device_footprint_mib: f64,
51 pub footprint_over_budget_mib: f64,
53 pub ledger_live_mib: Option<f64>,
54}
55
56pub fn footprint(report: &MemoryReport) -> Result<Footprint, String> {
60 let (Some(start), Some(now), Some(rss)) = (
61 report.mem_available_at_start_bytes,
62 report.mem_available_now_bytes,
63 report.rss_anon_bytes,
64 ) else {
65 let missing: Vec<&str> = [
66 (
67 "mem_available_at_start_bytes",
68 report.mem_available_at_start_bytes,
69 ),
70 ("mem_available_now_bytes", report.mem_available_now_bytes),
71 ("rss_anon_bytes", report.rss_anon_bytes),
72 ]
73 .into_iter()
74 .filter(|(_, v)| v.is_none())
75 .map(|(k, _)| k)
76 .collect();
77 return Err(format!(
78 "/memory reported null for {} — the server could not read the host's memory \
79 counters, so there is no device footprint to measure",
80 missing.join(", ")
81 ));
82 };
83 let device = i128::from(start) - i128::from(now) - i128::from(rss);
84 let over = device - i128::from(report.budget_bytes);
85 Ok(Footprint {
86 kv_blocks: report.kv_blocks,
87 max_batch_size: report.max_batch_size,
88 budget_mib: report.budget_bytes as f64 / MIB,
89 device_footprint_mib: device as f64 / MIB,
90 footprint_over_budget_mib: over as f64 / MIB,
91 ledger_live_mib: report.ledger_live_bytes.map(|b| b as f64 / MIB),
92 })
93}
94
95impl Footprint {
96 pub fn metrics(&self, m: &mut BTreeMap<String, f64>) {
99 m.insert("kv_blocks".to_string(), self.kv_blocks as f64);
100 m.insert("max_batch_size".to_string(), self.max_batch_size as f64);
101 m.insert("budget_mib".to_string(), self.budget_mib);
102 m.insert(
103 "device_footprint_mib".to_string(),
104 self.device_footprint_mib,
105 );
106 m.insert(
107 "footprint_over_budget_mib".to_string(),
108 self.footprint_over_budget_mib,
109 );
110 if let Some(ledger) = self.ledger_live_mib {
111 m.insert("ledger_live_mib".to_string(), ledger);
112 }
113 }
114}
115
116pub fn verdict_for(result: &Result<Footprint, String>) -> Verdict {
119 match result {
120 Err(why) => Verdict::fail(format!("INCONCLUSIVE: {why}")),
121 Ok(f) => Verdict::info(format!(
122 "{} KV blocks at {} slots; device footprint {:.0} MiB against a {:.0} MiB budget \
123 ({:+.0} MiB) — informational, no bounds are declared yet",
124 f.kv_blocks,
125 f.max_batch_size,
126 f.device_footprint_mib,
127 f.budget_mib,
128 f.footprint_over_budget_mib,
129 )),
130 }
131}