sysinfo/
lib.rs

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]
15mod macros;
16
17cfg_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
88pub use common::{
89    get_current_pid, CpuRefreshKind, DiskKind, DiskUsage, Gid, LoadAvg, MacAddr, NetworksIter, Pid,
90    PidExt, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, Uid, User,
91};
92pub use sys::{Component, Cpu, Disk, NetworkData, Networks, Process, System};
93pub use traits::{
94    ComponentExt, CpuExt, DiskExt, NetworkExt, NetworksExt, ProcessExt, SystemExt, UserExt,
95};
96
97#[cfg(feature = "c-interface")]
98pub use c_interface::*;
99
100#[cfg(feature = "c-interface")]
101mod c_interface;
102mod common;
103mod debug;
104#[cfg(feature = "serde")]
105mod serde;
106mod system;
107mod traits;
108mod 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/// ```
133pub 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)]
164mod 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)]
209mod 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}