cranpose_services/
device_info.rs1use std::{cell::RefCell, rc::Rc, time::Duration};
20
21pub trait DeviceInfo {
23 fn total_memory_bytes(&self) -> Option<u64>;
25
26 fn resident_memory_bytes(&self) -> Option<u64> {
29 None
30 }
31
32 fn available_memory_bytes(&self) -> Option<u64> {
39 None
40 }
41
42 fn process_cpu_time(&self) -> Option<Duration> {
48 None
49 }
50
51 fn release_free_memory(&self) -> bool {
58 false
59 }
60}
61
62pub type DeviceInfoRef = Rc<dyn DeviceInfo>;
63
64struct DefaultDeviceInfo;
65
66impl DeviceInfo for DefaultDeviceInfo {
67 fn total_memory_bytes(&self) -> Option<u64> {
68 #[cfg(any(target_os = "linux", target_os = "android"))]
69 {
70 let text = std::fs::read_to_string("/proc/meminfo").ok()?;
71 for line in text.lines() {
72 if let Some(rest) = line.strip_prefix("MemTotal:") {
73 let kb: u64 = rest.split_whitespace().next()?.parse().ok()?;
74 return Some(kb * 1024);
75 }
76 }
77 None
78 }
79 #[cfg(not(any(target_os = "linux", target_os = "android")))]
80 {
81 None
82 }
83 }
84
85 fn resident_memory_bytes(&self) -> Option<u64> {
86 #[cfg(any(target_os = "linux", target_os = "android"))]
87 {
88 let text = std::fs::read_to_string("/proc/self/statm").ok()?;
89 resident_bytes_from_statm(&text, page_size_bytes())
90 }
91 #[cfg(not(any(target_os = "linux", target_os = "android")))]
92 {
93 None
94 }
95 }
96}
97
98#[cfg(any(target_os = "linux", target_os = "android", test))]
99fn resident_bytes_from_statm(text: &str, page_size: u64) -> Option<u64> {
100 text.split_whitespace()
101 .nth(1)?
102 .parse::<u64>()
103 .ok()?
104 .checked_mul(page_size)
105}
106
107#[cfg(any(target_os = "linux", target_os = "android", test))]
108const fn page_size_bytes() -> u64 {
109 4096
110}
111
112thread_local! {
113 static PLATFORM_DEVICE_INFO: RefCell<Option<DeviceInfoRef>> = const { RefCell::new(None) };
114}
115
116pub fn set_platform_device_info(info: DeviceInfoRef) {
118 PLATFORM_DEVICE_INFO.with(|cell| *cell.borrow_mut() = Some(info));
119}
120
121pub fn clear_platform_device_info() {
123 PLATFORM_DEVICE_INFO.with(|cell| *cell.borrow_mut() = None);
124}
125
126pub fn device_info() -> DeviceInfoRef {
129 PLATFORM_DEVICE_INFO
130 .with(|cell| cell.borrow().clone())
131 .unwrap_or_else(|| Rc::new(DefaultDeviceInfo))
132}
133
134pub fn release_free_memory() -> bool {
141 device_info().release_free_memory()
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn registered_device_info_takes_precedence() {
150 clear_platform_device_info();
151 struct Fake;
152 impl DeviceInfo for Fake {
153 fn total_memory_bytes(&self) -> Option<u64> {
154 Some(8 * 1024 * 1024 * 1024)
155 }
156 }
157 set_platform_device_info(Rc::new(Fake));
158 assert_eq!(device_info().total_memory_bytes(), Some(8 << 30));
159 clear_platform_device_info();
160 }
161
162 #[test]
163 fn a_platform_that_will_not_say_reports_nothing_rather_than_zero() {
164 clear_platform_device_info();
165 struct Silent;
166 impl DeviceInfo for Silent {
167 fn total_memory_bytes(&self) -> Option<u64> {
168 None
169 }
170 }
171 set_platform_device_info(Rc::new(Silent));
172
173 let info = device_info();
174 assert_eq!(info.total_memory_bytes(), None);
175 assert_eq!(info.resident_memory_bytes(), None);
176 assert_eq!(info.available_memory_bytes(), None);
177 assert_eq!(info.process_cpu_time(), None);
178 assert!(!info.release_free_memory());
179 assert!(!release_free_memory());
180 clear_platform_device_info();
181 }
182
183 #[test]
184 fn a_platform_that_can_answer_is_asked_through_the_free_function() {
185 clear_platform_device_info();
186 struct Rich;
187 impl DeviceInfo for Rich {
188 fn total_memory_bytes(&self) -> Option<u64> {
189 Some(4 << 30)
190 }
191 fn resident_memory_bytes(&self) -> Option<u64> {
192 Some(256 << 20)
193 }
194 fn available_memory_bytes(&self) -> Option<u64> {
195 Some(512 << 20)
196 }
197 fn process_cpu_time(&self) -> Option<Duration> {
198 Some(Duration::from_millis(1_250))
199 }
200 fn release_free_memory(&self) -> bool {
201 true
202 }
203 }
204 set_platform_device_info(Rc::new(Rich));
205
206 let info = device_info();
207 assert_eq!(info.resident_memory_bytes(), Some(256 << 20));
208 assert_eq!(info.available_memory_bytes(), Some(512 << 20));
209 assert_eq!(info.process_cpu_time(), Some(Duration::from_millis(1_250)));
210 assert!(release_free_memory());
211 clear_platform_device_info();
212 }
213
214 #[test]
215 fn the_resident_set_is_the_second_field_of_statm_in_pages() {
216 let statm = "123456 2048 512 64 0 1024 0\n";
217 assert_eq!(resident_bytes_from_statm(statm, 4096), Some(2048 * 4096));
218 assert_eq!(resident_bytes_from_statm(statm, 16384), Some(2048 * 16384));
219 }
220
221 #[test]
222 fn an_unreadable_statm_line_is_unknown_rather_than_no_memory() {
223 for broken in ["", "123456", "123456 notanumber 512", " "] {
224 assert_eq!(
225 resident_bytes_from_statm(broken, page_size_bytes()),
226 None,
227 "{broken:?} should read as unknown"
228 );
229 }
230 }
231}