1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | #![doc = include_str!("../README.md" )] |
4 | #![cfg_attr (feature = "serde" , doc = include_str!("../md_doc/serde.md" ))] |
5 | #![allow (unknown_lints)] |
6 | #![deny (missing_docs)] |
7 | #![deny (rustdoc::broken_intra_doc_links)] |
8 | #![allow (clippy::upper_case_acronyms)] |
9 | #![allow (clippy::non_send_fields_in_send_ty)] |
10 | #![allow (renamed_and_removed_lints)] |
11 | #![allow (clippy::assertions_on_constants)] |
12 | #![allow (unknown_lints)] |
13 | |
14 | #[macro_use ] |
15 | mod macros; |
16 | |
17 | cfg_if::cfg_if! { |
18 | if #[cfg(feature = "unknown-ci" )] { |
19 | // This is used in CI to check that the build for unknown targets is compiling fine. |
20 | mod unknown; |
21 | use unknown as sys; |
22 | |
23 | #[cfg (test)] |
24 | pub(crate) const MIN_USERS: usize = 0; |
25 | } else if #[cfg(any(target_os = "macos" , target_os = "ios" ))] { |
26 | mod apple; |
27 | use apple as sys; |
28 | pub(crate) mod users; |
29 | mod network_helper_nix; |
30 | use network_helper_nix as network_helper; |
31 | mod network; |
32 | |
33 | // This is needed because macos uses `int*` for `getgrouplist`... |
34 | pub(crate) type GroupId = libc::c_int; |
35 | pub(crate) use libc::__error as libc_errno; |
36 | |
37 | #[cfg (test)] |
38 | pub(crate) const MIN_USERS: usize = 1; |
39 | } else if #[cfg(windows)] { |
40 | mod windows; |
41 | use windows as sys; |
42 | mod network_helper_win; |
43 | use network_helper_win as network_helper; |
44 | mod network; |
45 | |
46 | #[cfg (test)] |
47 | pub(crate) const MIN_USERS: usize = 1; |
48 | } else if #[cfg(any(target_os = "linux" , target_os = "android" ))] { |
49 | mod linux; |
50 | use linux as sys; |
51 | pub(crate) mod users; |
52 | mod network_helper_nix; |
53 | use network_helper_nix as network_helper; |
54 | mod network; |
55 | |
56 | // This is needed because macos uses `int*` for `getgrouplist`... |
57 | pub(crate) type GroupId = libc::gid_t; |
58 | #[cfg (target_os = "linux" )] |
59 | pub(crate) use libc::__errno_location as libc_errno; |
60 | #[cfg (target_os = "android" )] |
61 | pub(crate) use libc::__errno as libc_errno; |
62 | |
63 | #[cfg (test)] |
64 | pub(crate) const MIN_USERS: usize = 1; |
65 | } else if #[cfg(target_os = "freebsd" )] { |
66 | mod freebsd; |
67 | use freebsd as sys; |
68 | pub(crate) mod users; |
69 | mod network_helper_nix; |
70 | use network_helper_nix as network_helper; |
71 | mod network; |
72 | |
73 | // This is needed because macos uses `int*` for `getgrouplist`... |
74 | pub(crate) type GroupId = libc::gid_t; |
75 | pub(crate) use libc::__error as libc_errno; |
76 | |
77 | #[cfg (test)] |
78 | pub(crate) const MIN_USERS: usize = 1; |
79 | } else { |
80 | mod unknown; |
81 | use unknown as sys; |
82 | |
83 | #[cfg (test)] |
84 | pub(crate) const MIN_USERS: usize = 0; |
85 | } |
86 | } |
87 | |
88 | pub use common::{ |
89 | get_current_pid, CpuRefreshKind, DiskKind, DiskUsage, Gid, LoadAvg, MacAddr, NetworksIter, Pid, |
90 | PidExt, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, Uid, User, |
91 | }; |
92 | pub use sys::{Component, Cpu, Disk, NetworkData, Networks, Process, System}; |
93 | pub use traits::{ |
94 | ComponentExt, CpuExt, DiskExt, NetworkExt, NetworksExt, ProcessExt, SystemExt, UserExt, |
95 | }; |
96 | |
97 | #[cfg (feature = "c-interface" )] |
98 | pub use c_interface::*; |
99 | |
100 | #[cfg (feature = "c-interface" )] |
101 | mod c_interface; |
102 | mod common; |
103 | mod debug; |
104 | #[cfg (feature = "serde" )] |
105 | mod serde; |
106 | mod system; |
107 | mod traits; |
108 | mod utils; |
109 | |
110 | /// This function is only used on Linux targets, on the other platforms it does nothing and returns |
111 | /// `false`. |
112 | /// |
113 | /// On Linux, to improve performance, we keep a `/proc` file open for each process we index with |
114 | /// a maximum number of files open equivalent to half of the system limit. |
115 | /// |
116 | /// The problem is that some users might need all the available file descriptors so we need to |
117 | /// allow them to change this limit. |
118 | /// |
119 | /// Note that if you set a limit bigger than the system limit, the system limit will be set. |
120 | /// |
121 | /// Returns `true` if the new value has been set. |
122 | /// |
123 | /// ```no_run |
124 | /// use sysinfo::{System, SystemExt, set_open_files_limit}; |
125 | /// |
126 | /// // We call the function before any call to the processes update. |
127 | /// if !set_open_files_limit(10) { |
128 | /// // It'll always return false on non-linux targets. |
129 | /// eprintln!("failed to update the open files limit..." ); |
130 | /// } |
131 | /// let s = System::new_all(); |
132 | /// ``` |
133 | pub fn set_open_files_limit(mut _new_limit: isize) -> bool { |
134 | cfg_if::cfg_if! { |
135 | if #[cfg(all(not(feature = "unknown-ci" ), any(target_os = "linux" , target_os = "android" )))] |
136 | { |
137 | if _new_limit < 0 { |
138 | _new_limit = 0; |
139 | } |
140 | let max = sys::system::get_max_nb_fds(); |
141 | if _new_limit > max { |
142 | _new_limit = max; |
143 | } |
144 | unsafe { |
145 | if let Ok(ref mut x) = sys::system::REMAINING_FILES.lock() { |
146 | // If files are already open, to be sure that the number won't be bigger when those |
147 | // files are closed, we subtract the current number of opened files to the new |
148 | // limit. |
149 | let diff = max.saturating_sub(**x); |
150 | **x = _new_limit.saturating_sub(diff); |
151 | true |
152 | } else { |
153 | false |
154 | } |
155 | } |
156 | } else { |
157 | false |
158 | } |
159 | } |
160 | } |
161 | |
162 | // FIXME: Can be removed once negative trait bounds are supported. |
163 | #[cfg (doctest)] |
164 | mod doctest { |
165 | /// Check that `Process` doesn't implement `Clone`. |
166 | /// |
167 | /// First we check that the "basic" code works: |
168 | /// |
169 | /// ```no_run |
170 | /// use sysinfo::{Process, System, SystemExt}; |
171 | /// |
172 | /// let mut s = System::new_all(); |
173 | /// let p: &Process = s.processes().values().next().unwrap(); |
174 | /// ``` |
175 | /// |
176 | /// And now we check if it fails when we try to clone it: |
177 | /// |
178 | /// ```compile_fail |
179 | /// use sysinfo::{Process, System, SystemExt}; |
180 | /// |
181 | /// let mut s = System::new_all(); |
182 | /// let p: &Process = s.processes().values().next().unwrap(); |
183 | /// let p = (*p).clone(); |
184 | /// ``` |
185 | mod process_clone {} |
186 | |
187 | /// Check that `System` doesn't implement `Clone`. |
188 | /// |
189 | /// First we check that the "basic" code works: |
190 | /// |
191 | /// ```no_run |
192 | /// use sysinfo::{Process, System, SystemExt}; |
193 | /// |
194 | /// let s = System::new(); |
195 | /// ``` |
196 | /// |
197 | /// And now we check if it fails when we try to clone it: |
198 | /// |
199 | /// ```compile_fail |
200 | /// use sysinfo::{Process, System, SystemExt}; |
201 | /// |
202 | /// let s = System::new(); |
203 | /// let s = s.clone(); |
204 | /// ``` |
205 | mod system_clone {} |
206 | } |
207 | |
208 | #[cfg (test)] |
209 | mod test { |
210 | use crate::*; |
211 | |
212 | #[cfg (feature = "unknown-ci" )] |
213 | #[test ] |
214 | fn check_unknown_ci_feature() { |
215 | assert!(!System::IS_SUPPORTED); |
216 | } |
217 | |
218 | #[test ] |
219 | fn check_process_memory_usage() { |
220 | let mut s = System::new(); |
221 | s.refresh_all(); |
222 | |
223 | if System::IS_SUPPORTED { |
224 | // No process should have 0 as memory usage. |
225 | #[cfg (not(feature = "apple-sandbox" ))] |
226 | assert!(!s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); |
227 | } else { |
228 | // There should be no process, but if there is one, its memory usage should be 0. |
229 | assert!(s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); |
230 | } |
231 | } |
232 | |
233 | #[test ] |
234 | fn check_memory_usage() { |
235 | let mut s = System::new(); |
236 | |
237 | assert_eq!(s.total_memory(), 0); |
238 | assert_eq!(s.free_memory(), 0); |
239 | assert_eq!(s.available_memory(), 0); |
240 | assert_eq!(s.used_memory(), 0); |
241 | assert_eq!(s.total_swap(), 0); |
242 | assert_eq!(s.free_swap(), 0); |
243 | assert_eq!(s.used_swap(), 0); |
244 | |
245 | s.refresh_memory(); |
246 | if System::IS_SUPPORTED { |
247 | assert!(s.total_memory() > 0); |
248 | assert!(s.used_memory() > 0); |
249 | if s.total_swap() > 0 { |
250 | // I think it's pretty safe to assume that there is still some swap left... |
251 | assert!(s.free_swap() > 0); |
252 | } |
253 | } else { |
254 | assert_eq!(s.total_memory(), 0); |
255 | assert_eq!(s.used_memory(), 0); |
256 | assert_eq!(s.total_swap(), 0); |
257 | assert_eq!(s.free_swap(), 0); |
258 | } |
259 | } |
260 | |
261 | #[cfg (target_os = "linux" )] |
262 | #[test ] |
263 | fn check_processes_cpu_usage() { |
264 | if !System::IS_SUPPORTED { |
265 | return; |
266 | } |
267 | let mut s = System::new(); |
268 | |
269 | s.refresh_processes(); |
270 | // All CPU usage will start at zero until the second refresh |
271 | assert!(s |
272 | .processes() |
273 | .iter() |
274 | .all(|(_, proc_)| proc_.cpu_usage() == 0.0)); |
275 | |
276 | // Wait a bit to update CPU usage values |
277 | std::thread::sleep(System::MINIMUM_CPU_UPDATE_INTERVAL); |
278 | s.refresh_processes(); |
279 | assert!(s |
280 | .processes() |
281 | .iter() |
282 | .all(|(_, proc_)| proc_.cpu_usage() >= 0.0 |
283 | && proc_.cpu_usage() <= (s.cpus().len() as f32) * 100.0)); |
284 | assert!(s |
285 | .processes() |
286 | .iter() |
287 | .any(|(_, proc_)| proc_.cpu_usage() > 0.0)); |
288 | } |
289 | |
290 | #[test ] |
291 | fn check_cpu_usage() { |
292 | if !System::IS_SUPPORTED { |
293 | return; |
294 | } |
295 | let mut s = System::new(); |
296 | for _ in 0..10 { |
297 | s.refresh_cpu(); |
298 | // Wait a bit to update CPU usage values |
299 | std::thread::sleep(System::MINIMUM_CPU_UPDATE_INTERVAL); |
300 | if s.cpus().iter().any(|c| c.cpu_usage() > 0.0) { |
301 | // All good! |
302 | return; |
303 | } |
304 | } |
305 | panic!("CPU usage is always zero..." ); |
306 | } |
307 | |
308 | #[test ] |
309 | fn check_users() { |
310 | let mut s = System::new(); |
311 | assert!(s.users().is_empty()); |
312 | s.refresh_users_list(); |
313 | assert!(s.users().len() >= MIN_USERS); |
314 | |
315 | let mut s = System::new(); |
316 | assert!(s.users().is_empty()); |
317 | s.refresh_all(); |
318 | assert!(s.users().is_empty()); |
319 | |
320 | let s = System::new_all(); |
321 | assert!(s.users().len() >= MIN_USERS); |
322 | } |
323 | |
324 | #[test ] |
325 | fn check_uid_gid() { |
326 | let mut s = System::new(); |
327 | assert!(s.users().is_empty()); |
328 | s.refresh_users_list(); |
329 | let users = s.users(); |
330 | assert!(users.len() >= MIN_USERS); |
331 | |
332 | if System::IS_SUPPORTED { |
333 | #[cfg (not(target_os = "windows" ))] |
334 | { |
335 | let user = users |
336 | .iter() |
337 | .find(|u| u.name() == "root" ) |
338 | .expect("no root user" ); |
339 | assert_eq!(**user.id(), 0); |
340 | assert_eq!(*user.group_id(), 0); |
341 | if let Some(user) = users.iter().find(|u| *u.group_id() > 0) { |
342 | assert!(**user.id() > 0); |
343 | assert!(*user.group_id() > 0); |
344 | } |
345 | assert!(users.iter().filter(|u| **u.id() > 0).count() > 0); |
346 | } |
347 | |
348 | // And now check that our `get_user_by_id` method works. |
349 | s.refresh_processes(); |
350 | assert!(s |
351 | .processes() |
352 | .iter() |
353 | .filter_map(|(_, p)| p.user_id()) |
354 | .any(|uid| s.get_user_by_id(uid).is_some())); |
355 | } |
356 | } |
357 | |
358 | #[test ] |
359 | fn check_all_process_uids_resolvable() { |
360 | if System::IS_SUPPORTED { |
361 | let s = System::new_with_specifics( |
362 | RefreshKind::new() |
363 | .with_processes(ProcessRefreshKind::new().with_user()) |
364 | .with_users_list(), |
365 | ); |
366 | |
367 | // For every process where we can get a user ID, we should also be able |
368 | // to find that user ID in the global user list |
369 | for process in s.processes().values() { |
370 | if let Some(uid) = process.user_id() { |
371 | assert!(s.get_user_by_id(uid).is_some(), "No UID {:?} found" , uid); |
372 | } |
373 | } |
374 | } |
375 | } |
376 | |
377 | #[test ] |
378 | fn check_system_info() { |
379 | let s = System::new(); |
380 | |
381 | // We don't want to test on unsupported systems. |
382 | if System::IS_SUPPORTED { |
383 | assert!(!s.name().expect("Failed to get system name" ).is_empty()); |
384 | |
385 | assert!(!s |
386 | .kernel_version() |
387 | .expect("Failed to get kernel version" ) |
388 | .is_empty()); |
389 | |
390 | assert!(!s.os_version().expect("Failed to get os version" ).is_empty()); |
391 | |
392 | assert!(!s |
393 | .long_os_version() |
394 | .expect("Failed to get long OS version" ) |
395 | .is_empty()); |
396 | } |
397 | |
398 | assert!(!s.distribution_id().is_empty()); |
399 | } |
400 | |
401 | #[test ] |
402 | fn check_host_name() { |
403 | // We don't want to test on unsupported systems. |
404 | if System::IS_SUPPORTED { |
405 | let s = System::new(); |
406 | assert!(s.host_name().is_some()); |
407 | } |
408 | } |
409 | |
410 | #[test ] |
411 | fn check_refresh_process_return_value() { |
412 | // We don't want to test on unsupported systems. |
413 | if System::IS_SUPPORTED { |
414 | let _pid = get_current_pid().expect("Failed to get current PID" ); |
415 | |
416 | #[cfg (not(feature = "apple-sandbox" ))] |
417 | { |
418 | let mut s = System::new(); |
419 | // First check what happens in case the process isn't already in our process list. |
420 | assert!(s.refresh_process(_pid)); |
421 | // Then check that it still returns true if the process is already in our process list. |
422 | assert!(s.refresh_process(_pid)); |
423 | } |
424 | } |
425 | } |
426 | |
427 | #[test ] |
428 | fn ensure_is_supported_is_set_correctly() { |
429 | if MIN_USERS > 0 { |
430 | assert!(System::IS_SUPPORTED); |
431 | } else { |
432 | assert!(!System::IS_SUPPORTED); |
433 | } |
434 | } |
435 | |
436 | #[test ] |
437 | fn check_cpus_number() { |
438 | let mut s = System::new(); |
439 | |
440 | // This information isn't retrieved by default. |
441 | assert!(s.cpus().is_empty()); |
442 | if System::IS_SUPPORTED { |
443 | // The physical cores count is recomputed every time the function is called, so the |
444 | // information must be relevant even with nothing initialized. |
445 | let physical_cores_count = s |
446 | .physical_core_count() |
447 | .expect("failed to get number of physical cores" ); |
448 | |
449 | s.refresh_cpu(); |
450 | // The cpus shouldn't be empty anymore. |
451 | assert!(!s.cpus().is_empty()); |
452 | |
453 | // In case we are running inside a VM, it's possible to not have a physical core, only |
454 | // logical ones, which is why we don't test `physical_cores_count > 0`. |
455 | let physical_cores_count2 = s |
456 | .physical_core_count() |
457 | .expect("failed to get number of physical cores" ); |
458 | assert!(physical_cores_count2 <= s.cpus().len()); |
459 | assert_eq!(physical_cores_count, physical_cores_count2); |
460 | } else { |
461 | assert_eq!(s.physical_core_count(), None); |
462 | } |
463 | assert!(s.physical_core_count().unwrap_or(0) <= s.cpus().len()); |
464 | } |
465 | |
466 | #[test ] |
467 | fn check_nb_supported_signals() { |
468 | if System::IS_SUPPORTED { |
469 | assert!( |
470 | !System::SUPPORTED_SIGNALS.is_empty(), |
471 | "SUPPORTED_SIGNALS shoudn't be empty on supported systems!" |
472 | ); |
473 | } else { |
474 | assert!( |
475 | System::SUPPORTED_SIGNALS.is_empty(), |
476 | "SUPPORTED_SIGNALS should be empty on not support systems!" |
477 | ); |
478 | } |
479 | } |
480 | |
481 | // Ensure that the CPUs frequency isn't retrieved until we ask for it. |
482 | #[test ] |
483 | fn check_cpu_frequency() { |
484 | if !System::IS_SUPPORTED { |
485 | return; |
486 | } |
487 | let mut s = System::new(); |
488 | s.refresh_processes(); |
489 | for proc_ in s.cpus() { |
490 | assert_eq!(proc_.frequency(), 0); |
491 | } |
492 | s.refresh_cpu(); |
493 | for proc_ in s.cpus() { |
494 | assert_eq!(proc_.frequency(), 0); |
495 | } |
496 | // In a VM, it'll fail. |
497 | if std::env::var("APPLE_CI" ).is_err() && std::env::var("FREEBSD_CI" ).is_err() { |
498 | s.refresh_cpu_specifics(CpuRefreshKind::everything()); |
499 | for proc_ in s.cpus() { |
500 | assert_ne!(proc_.frequency(), 0); |
501 | } |
502 | } |
503 | } |
504 | |
505 | // In case `Process::updated` is misused, `System::refresh_processes` might remove them |
506 | // so this test ensures that it doesn't happen. |
507 | #[test ] |
508 | fn check_refresh_process_update() { |
509 | if !System::IS_SUPPORTED { |
510 | return; |
511 | } |
512 | let mut s = System::new_all(); |
513 | let total = s.processes().len() as isize; |
514 | s.refresh_processes(); |
515 | let new_total = s.processes().len() as isize; |
516 | // There should be almost no difference in the processes count. |
517 | assert!( |
518 | (new_total - total).abs() <= 5, |
519 | " {} <= 5" , |
520 | (new_total - total).abs() |
521 | ); |
522 | } |
523 | |
524 | // We ensure that the `Process` cmd information is retrieved as expected. |
525 | #[test ] |
526 | fn check_cmd_line() { |
527 | if !System::IS_SUPPORTED { |
528 | return; |
529 | } |
530 | let mut sys = System::new(); |
531 | sys.refresh_processes_specifics(ProcessRefreshKind::new()); |
532 | |
533 | assert!(sys |
534 | .processes() |
535 | .iter() |
536 | .any(|(_, process)| !process.cmd().is_empty())); |
537 | } |
538 | } |
539 | |