1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Profile {
14 Low,
17 Balanced,
19 High,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct TuneParams {
26 pub threads: usize,
28 pub copy_buffer: usize,
30 pub uring_queue_depth: usize,
32 pub zstd_level: i32,
34}
35
36impl Profile {
37 #[must_use]
39 pub fn params(self, cores: usize) -> TuneParams {
40 let cores = cores.max(1);
41 match self {
42 Profile::Low => TuneParams {
43 threads: cores.min(2),
44 copy_buffer: 256 * 1024,
45 uring_queue_depth: 32,
46 zstd_level: 1,
47 },
48 Profile::Balanced => TuneParams {
49 threads: cores,
50 copy_buffer: 1024 * 1024,
51 uring_queue_depth: 128,
52 zstd_level: 3,
53 },
54 Profile::High => TuneParams {
55 threads: cores,
56 copy_buffer: 8 * 1024 * 1024,
57 uring_queue_depth: 512,
58 zstd_level: 6,
59 },
60 }
61 }
62
63 #[must_use]
65 pub fn name(self) -> &'static str {
66 match self {
67 Profile::Low => "low",
68 Profile::Balanced => "balanced",
69 Profile::High => "high",
70 }
71 }
72}
73
74#[must_use]
79pub fn resolve(cores: usize, ram_gib: Option<f64>) -> Profile {
80 let low = cores <= 2 || ram_gib.is_some_and(|r| r <= 2.0);
81 if low {
82 return Profile::Low;
83 }
84 let high = cores >= 8 && ram_gib.is_none_or(|r| r >= 16.0);
85 if high {
86 Profile::High
87 } else {
88 Profile::Balanced
89 }
90}
91
92#[must_use]
94pub fn detect() -> Profile {
95 resolve(num_cpus::get(), total_ram_gib())
96}
97
98#[must_use]
101pub fn total_ram_gib() -> Option<f64> {
102 #[cfg(target_os = "linux")]
103 {
104 let text = std::fs::read_to_string("/proc/meminfo").ok()?;
105 for line in text.lines() {
106 if let Some(rest) = line.strip_prefix("MemTotal:") {
107 let kib: f64 = rest.trim().trim_end_matches("kB").trim().parse().ok()?;
108 return Some(kib / (1024.0 * 1024.0));
109 }
110 }
111 None
112 }
113 #[cfg(not(target_os = "linux"))]
114 {
115 None
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn classifies_tiers() {
125 assert_eq!(resolve(1, Some(16.0)), Profile::Low); assert_eq!(resolve(16, Some(1.0)), Profile::Low); assert_eq!(resolve(4, Some(8.0)), Profile::Balanced);
128 assert_eq!(resolve(8, Some(16.0)), Profile::High);
129 assert_eq!(resolve(12, None), Profile::High); assert_eq!(resolve(4, None), Profile::Balanced);
131 }
132
133 #[test]
134 fn params_scale_with_tier() {
135 let low = Profile::Low.params(16);
136 let high = Profile::High.params(16);
137 assert_eq!(low.threads, 2);
138 assert_eq!(high.threads, 16);
139 assert!(high.copy_buffer > low.copy_buffer);
140 assert!(high.uring_queue_depth > low.uring_queue_depth);
141 }
142
143 #[test]
144 fn params_never_zero_threads() {
145 assert_eq!(Profile::Balanced.params(0).threads, 1);
146 }
147}