1use thiserror::Error;
8
9#[derive(Debug)]
11pub struct CacheTopology {
12 pub caches: Vec<Cache>,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
18pub struct Cache {
19 pub level: u8,
21 pub cache_type: CacheType,
23 pub cpus: Vec<u32>,
25 pub size: u32,
27 pub associativity: Option<u32>,
29 pub line_size: u32,
31}
32
33#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
35pub enum CacheType {
36 Data,
38 Instruction,
40 Unified,
42}
43
44#[derive(Debug, Error)]
46pub enum HostTopologyError {
47 #[error("os error retrieving cache topology")]
49 Os(#[source] std::io::Error),
50}
51
52impl CacheTopology {
53 pub fn from_host() -> Result<Self, HostTopologyError> {
55 let mut caches = Self::host_caches().map_err(HostTopologyError::Os)?;
56 caches.sort();
57 caches.dedup();
58 Ok(Self { caches })
59 }
60}
61
62#[cfg(windows)]
63#[expect(unsafe_code)]
65mod windows {
66 use super::CacheTopology;
67 use crate::Cache;
68 use crate::CacheType;
69 use windows_sys::Win32::Foundation::ERROR_INSUFFICIENT_BUFFER;
70 use windows_sys::Win32::System::SystemInformation;
71
72 impl CacheTopology {
73 pub(crate) fn host_caches() -> std::io::Result<Vec<Cache>> {
74 let mut len = 0;
75 let r = unsafe {
77 SystemInformation::GetLogicalProcessorInformationEx(
78 SystemInformation::RelationCache,
79 std::ptr::null_mut(),
80 &mut len,
81 )
82 };
83 assert_eq!(r, 0);
84 let err = std::io::Error::last_os_error();
85 if err.raw_os_error() != Some(ERROR_INSUFFICIENT_BUFFER as i32) {
86 return Err(err);
87 }
88 let mut buf = vec![0u8; len as usize];
89 let r = unsafe {
92 SystemInformation::GetLogicalProcessorInformationEx(
93 SystemInformation::RelationCache,
94 buf.as_mut_ptr().cast(),
95 &mut len,
96 )
97 };
98 if r == 0 {
99 return Err(std::io::Error::last_os_error());
100 }
101
102 let mut caches = Vec::new();
103
104 let mut buf = buf.as_slice();
105 while !buf.is_empty() {
106 let info = unsafe {
109 &*buf
110 .as_ptr()
111 .cast::<SystemInformation::SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>()
112 };
113
114 assert_eq!(info.Relationship, SystemInformation::RelationCache);
115 buf = &buf[info.Size as usize..];
116
117 let cache = unsafe { &info.Anonymous.Cache };
120
121 let groups = unsafe {
124 std::slice::from_raw_parts(
125 cache.Anonymous.GroupMasks.as_ptr(),
126 cache.GroupCount as usize,
127 )
128 };
129
130 let mut cpus = Vec::new();
131 for group in groups {
132 for i in 0..usize::BITS {
133 if group.Mask & (1 << i) != 0 {
134 cpus.push(group.Group as u32 * usize::BITS + i);
135 }
136 }
137 }
138
139 caches.push(Cache {
140 cpus,
141 level: cache.Level,
142 cache_type: match cache.Type {
143 SystemInformation::CacheUnified => CacheType::Unified,
144 SystemInformation::CacheInstruction => CacheType::Instruction,
145 SystemInformation::CacheData => CacheType::Data,
146 _ => continue,
147 },
148 size: cache.CacheSize,
149 associativity: if cache.Associativity == !0 {
150 None
151 } else {
152 Some(cache.Associativity.into())
153 },
154 line_size: cache.LineSize.into(),
155 });
156 }
157
158 Ok(caches)
159 }
160 }
161}
162
163#[cfg(target_os = "linux")]
164mod linux {
165 use super::Cache;
166 use super::CacheTopology;
167
168 impl CacheTopology {
169 pub(crate) fn host_caches() -> std::io::Result<Vec<Cache>> {
170 let mut caches = Vec::new();
171 for cpu_entry in fs_err::read_dir("/sys/devices/system/cpu")? {
172 let cpu_path = cpu_entry?.path();
173 if cpu_path
174 .file_name()
175 .unwrap()
176 .to_str()
177 .unwrap()
178 .strip_prefix("cpu")
179 .and_then(|s| s.parse::<u32>().ok())
180 .is_none()
181 {
182 continue;
183 }
184 for entry in fs_err::read_dir(cpu_path.join("cache"))? {
185 let entry = entry?;
186 let path = entry.path();
187 if !path
188 .file_name()
189 .unwrap()
190 .to_str()
191 .is_some_and(|s| s.starts_with("index"))
192 {
193 continue;
194 }
195
196 let associativity = fs_err::read_to_string(path.join("ways_of_associativity"))?
197 .trim_end()
198 .parse()
199 .unwrap();
200
201 let mut cpus = Vec::new();
202 for range in fs_err::read_to_string(path.join("shared_cpu_list"))?
203 .trim_end()
204 .split(',')
205 {
206 if let Some((start, end)) = range.split_once('-') {
207 cpus.extend(
208 start.parse::<u32>().unwrap()..=end.parse::<u32>().unwrap(),
209 );
210 } else {
211 cpus.push(range.parse().unwrap());
212 }
213 }
214
215 let line_size_result = fs_err::read_to_string(path.join("coherency_line_size"));
216 let line_size = match line_size_result {
217 Ok(s) => s.trim_end().parse::<u32>().unwrap(),
218 Err(e) => match e.kind() {
219 std::io::ErrorKind::NotFound => 64,
220 _ => return std::io::Result::Err(e),
221 },
222 };
223 caches.push(Cache {
224 cpus,
225 level: fs_err::read_to_string(path.join("level"))?
226 .trim_end()
227 .parse()
228 .unwrap(),
229 cache_type: match fs_err::read_to_string(path.join("type"))?.trim_end() {
230 "Data" => super::CacheType::Data,
231 "Instruction" => super::CacheType::Instruction,
232 "Unified" => super::CacheType::Unified,
233 _ => continue,
234 },
235 size: fs_err::read_to_string(path.join("size"))?
236 .strip_suffix("K\n")
237 .unwrap()
238 .parse::<u32>()
239 .unwrap()
240 * 1024,
241 associativity: if associativity == 0 {
242 None
243 } else {
244 Some(associativity)
245 },
246 line_size,
247 });
248 }
249 }
250 Ok(caches)
251 }
252 }
253}
254
255#[cfg(target_os = "macos")]
256mod macos {
257 use super::Cache;
258 use super::CacheTopology;
259
260 impl CacheTopology {
261 pub(crate) fn host_caches() -> std::io::Result<Vec<Cache>> {
262 Ok(Vec::new())
264 }
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 #[test]
271 fn test_host_cache_topology() {
272 let topology = super::CacheTopology::from_host().unwrap();
273 assert!(!topology.caches.is_empty());
274 println!("{topology:?}");
275 }
276}