├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .gitignore ├── .cirrus.yml ├── src ├── lib.rs ├── platform │ ├── bsd.rs │ ├── illumos.rs │ ├── unix.rs │ ├── windows │ │ ├── socket.rs │ │ ├── disk.rs │ │ ├── mod.rs │ │ └── network_interfaces.rs │ ├── mod.rs │ ├── common.rs │ ├── netbsd.rs │ ├── macos.rs │ ├── freebsd.rs │ ├── openbsd.rs │ └── linux.rs └── data.rs ├── .woodpecker.yml ├── Cargo.toml ├── UNLICENSE ├── README.md ├── CODE_OF_CONDUCT.md └── examples └── info.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: valpackett 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | test_task: 2 | freebsd_instance: 3 | matrix: 4 | - image_family: freebsd-14-2 5 | cargo_cache: 6 | folder: $CARGO_HOME/registry 7 | fingerprint_script: cat Cargo.lock || echo 'nope' 8 | before_cache_script: rm -rf $CARGO_HOME/registry/index 9 | install_script: pkg install -y rust 10 | build_script: cargo build --verbose 11 | test_script: cargo run --verbose --example info 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library provides a way to access system information such as CPU load, mounted filesystems, 2 | //! network interfaces, etc. 3 | 4 | #[cfg_attr( 5 | any( 6 | target_os = "freebsd", 7 | target_os = "openbsd", 8 | target_os = "netbsd", 9 | target_vendor = "apple" 10 | ), 11 | macro_use 12 | )] 13 | extern crate lazy_static; 14 | 15 | #[cfg(feature = "serde")] 16 | extern crate the_serde as serde; 17 | 18 | pub mod data; 19 | pub mod platform; 20 | 21 | pub use self::data::*; 22 | pub use self::platform::Platform; 23 | pub use self::platform::PlatformImpl as System; 24 | -------------------------------------------------------------------------------- /src/platform/bsd.rs: -------------------------------------------------------------------------------- 1 | use libc::c_int; 2 | use crate::data::*; 3 | 4 | lazy_static! { 5 | pub static ref PAGESHIFT: c_int = { 6 | let mut pagesize = unsafe { getpagesize() }; 7 | let mut pageshift = 0; 8 | while pagesize > 1 { 9 | pageshift += 1; 10 | pagesize >>= 1; 11 | } 12 | pageshift - 10 // LOG1024 13 | }; 14 | } 15 | 16 | #[repr(C)] 17 | #[derive(Debug, Clone, Copy)] 18 | pub struct sysctl_cpu { 19 | user: usize, 20 | nice: usize, 21 | system: usize, 22 | interrupt: usize, 23 | idle: usize, 24 | } 25 | 26 | impl From for CpuTime { 27 | fn from(cpu: sysctl_cpu) -> CpuTime { 28 | CpuTime { 29 | user: cpu.user, 30 | nice: cpu.nice, 31 | system: cpu.system, 32 | interrupt: cpu.interrupt, 33 | idle: cpu.idle, 34 | other: 0, 35 | } 36 | } 37 | } 38 | 39 | #[link(name = "c")] 40 | extern "C" { 41 | fn getpagesize() -> c_int; 42 | } 43 | -------------------------------------------------------------------------------- /.woodpecker.yml: -------------------------------------------------------------------------------- 1 | pipeline: 2 | test: 3 | image: rust:alpine 4 | pull: true 5 | commands: 6 | - apk add musl-dev 7 | - RUST_BACKTRACE=1 cargo run --example info 8 | - RUST_BACKTRACE=1 cargo test -- --nocapture 9 | - RUST_BACKTRACE=1 cargo test -F serde -- --nocapture 10 | - rustup target add aarch64-apple-darwin aarch64-pc-windows-msvc aarch64-unknown-linux-gnu i686-pc-windows-gnu i686-unknown-freebsd i686-unknown-linux-gnu x86_64-pc-windows-gnu x86_64-unknown-freebsd x86_64-unknown-linux-musl x86_64-unknown-netbsd 11 | - cargo check --target aarch64-apple-darwin 12 | - cargo check --target aarch64-pc-windows-msvc 13 | - cargo check --target aarch64-unknown-linux-gnu 14 | - cargo check --target i686-pc-windows-gnu 15 | - cargo check --target i686-unknown-freebsd 16 | - cargo check --target i686-unknown-linux-gnu 17 | - cargo check --target x86_64-pc-windows-gnu 18 | - cargo check --target x86_64-unknown-freebsd 19 | - cargo check --target x86_64-unknown-linux-musl 20 | - cargo check --target x86_64-unknown-netbsd 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | linux: 7 | name: Linux ubuntu-latest 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Build 12 | run: cargo build --verbose 13 | - name: Run example 14 | run: cargo run --verbose --example info 15 | 16 | windows: 17 | name: Windows ${{ matrix.os }} 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | matrix: 21 | os: [windows-2019, windows-latest] 22 | steps: 23 | - uses: actions/checkout@v1 24 | - name: Build 25 | run: cargo build --verbose 26 | - name: Run example 27 | run: cargo run --verbose --example info 28 | 29 | macos: 30 | name: macOS-latest 31 | runs-on: macOS-latest 32 | steps: 33 | - uses: actions/checkout@v1 34 | - name: Get Rust 35 | run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup && sh ./rustup -y 36 | - name: Build 37 | run: source ~/.cargo/env; cargo build --verbose 38 | - name: Run example 39 | run: source ~/.cargo/env; cargo run --verbose --example info 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "systemstat" 3 | version = "0.2.5" 4 | edition = "2018" 5 | authors = [ "Val Packett " ] 6 | keywords = [ "System", "Info" ] 7 | description = "Get system information/statistics in a cross-platform way" 8 | license = "Unlicense" 9 | readme = "README.md" 10 | homepage = "https://github.com/valpackett/systemstat" 11 | repository = "https://github.com/valpackett/systemstat" 12 | 13 | [dependencies] 14 | time = "0.3.9" 15 | lazy_static = "1.0" 16 | bytesize = "1.1" 17 | libc = "0.2" 18 | the_serde = { package = "serde", version = "1.0", features = ["derive"], optional = true } 19 | 20 | [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] 21 | nom = "7.0" 22 | 23 | [target.'cfg(windows)'.dependencies.winapi] 24 | version = "0.3" 25 | features = ["fileapi", "sysinfoapi", "minwindef", "winbase", "winerror", "ws2def", "ws2ipdef", "pdh"] 26 | 27 | [package.metadata.docs.rs] 28 | targets = [ 29 | "x86_64-unknown-freebsd", 30 | "x86_64-unknown-openbsd", 31 | "x86_64-unknown-netbsd", 32 | "x86_64-unknown-linux-gnu", 33 | "x86_64-apple-darwin", 34 | "x86_64-pc-windows-msvc" 35 | ] 36 | 37 | [features] 38 | serde = ["the_serde", "bytesize/serde", "time/serde"] 39 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/systemstat?logo=rust) ![](https://img.shields.io/crates/d/systemstat)](https://crates.io/crates/systemstat) 2 | [![API Docs](https://docs.rs/systemstat/badge.svg)](https://docs.rs/systemstat/) 3 | [![CI status](https://ci.codeberg.org/api/badges/valpackett/systemstat/status.svg)](https://ci.codeberg.org/valpackett/systemstat) 4 | [![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](https://unlicense.org) 5 | [![Support me on Patreon](https://img.shields.io/badge/dynamic/json?logo=patreon&color=%23e85b46&label=support%20me%20on%20patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F9395291)](https://www.patreon.com/valpackett) 6 | 7 | # systemstat 8 | 9 | A Rust library for getting system information/statistics: 10 | 11 | - CPU load 12 | - load average 13 | - memory usage 14 | - uptime / boot time 15 | - battery life 16 | - filesystem mounts (and disk usage) 17 | - disk I/O statistics 18 | - network interfaces 19 | - network traffic statistics 20 | - CPU temperature 21 | 22 | Unlike [sys-info-rs](https://github.com/FillZpp/sys-info-rs), this one is written purely in Rust. 23 | 24 | Supported platforms (roughly ordered by completeness of support): 25 | 26 | - FreeBSD 27 | - Linux 28 | - OpenBSD 29 | - Windows 30 | - macOS 31 | - NetBSD 32 | - *more coming soon* 33 | 34 | ## Usage 35 | 36 | See [examples/info.rs](https://github.com/valpackett/systemstat/blob/master/examples/info.rs). 37 | 38 | ## Contributing 39 | 40 | Please feel free to submit pull requests! 41 | 42 | By participating in this project you agree to follow the [Contributor Code of Conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) and to release your contributions under the Unlicense. 43 | 44 | ## License 45 | 46 | This is free and unencumbered software released into the public domain. 47 | For more information, please refer to the `UNLICENSE` file or [unlicense.org](https://unlicense.org). 48 | -------------------------------------------------------------------------------- /src/platform/illumos.rs: -------------------------------------------------------------------------------- 1 | use super::common::*; 2 | use super::unix; 3 | use crate::data::*; 4 | use std::{io, path}; 5 | 6 | pub struct PlatformImpl; 7 | 8 | /// An implementation of `Platform` for illumos. 9 | /// See `Platform` for documentation. 10 | impl Platform for PlatformImpl { 11 | #[inline(always)] 12 | fn new() -> Self { 13 | PlatformImpl 14 | } 15 | 16 | fn cpu_load(&self) -> io::Result>> { 17 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 18 | } 19 | 20 | fn load_average(&self) -> io::Result { 21 | unix::load_average() 22 | } 23 | 24 | fn memory(&self) -> io::Result { 25 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 26 | } 27 | 28 | fn swap(&self) -> io::Result { 29 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 30 | } 31 | 32 | fn boot_time(&self) -> io::Result { 33 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 34 | } 35 | 36 | fn battery_life(&self) -> io::Result { 37 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 38 | } 39 | 40 | fn on_ac_power(&self) -> io::Result { 41 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 42 | } 43 | 44 | fn mounts(&self) -> io::Result> { 45 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 46 | } 47 | 48 | fn mount_at>(&self, _: P) -> io::Result { 49 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 50 | } 51 | 52 | fn block_device_statistics(&self) -> io::Result> { 53 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 54 | } 55 | 56 | fn networks(&self) -> io::Result> { 57 | unix::networks() 58 | } 59 | 60 | fn network_stats(&self, interface: &str) -> io::Result { 61 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 62 | } 63 | 64 | fn cpu_temp(&self) -> io::Result { 65 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 66 | } 67 | 68 | fn socket_stats(&self) -> io::Result { 69 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/platform/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{io, ffi, ptr, mem}; 2 | use libc::{c_int, getifaddrs, freeifaddrs, ifaddrs, sockaddr, sockaddr_in6, AF_INET, AF_INET6}; 3 | use crate::data::*; 4 | 5 | pub fn load_average() -> io::Result { 6 | let mut loads: [f64; 3] = [0.0, 0.0, 0.0]; 7 | if unsafe { getloadavg(&mut loads[0], 3) } != 3 { 8 | return Err(io::Error::new(io::ErrorKind::Other, "getloadavg() failed")) 9 | } 10 | Ok(LoadAverage { 11 | one: loads[0] as f32, 12 | five: loads[1] as f32, 13 | fifteen: loads[2] as f32 14 | }) 15 | } 16 | 17 | pub fn networks() -> io::Result> { 18 | let mut ifap: *mut ifaddrs = ptr::null_mut(); 19 | if unsafe { getifaddrs(&mut ifap) } != 0 { 20 | return Err(io::Error::new(io::ErrorKind::Other, "getifaddrs() failed")) 21 | } 22 | let ifirst = ifap; 23 | let mut result = BTreeMap::new(); 24 | while !ifap.is_null() { 25 | let ifa = unsafe { *ifap }; 26 | let name = unsafe { ffi::CStr::from_ptr(ifa.ifa_name).to_string_lossy().into_owned() }; 27 | let entry = result.entry(name.clone()).or_insert(Network { 28 | name, 29 | addrs: Vec::new(), 30 | }); 31 | let addr = parse_addr(ifa.ifa_addr); 32 | if addr != IpAddr::Unsupported { 33 | entry.addrs.push(NetworkAddrs { 34 | addr, 35 | netmask: parse_addr(ifa.ifa_netmask), 36 | }); 37 | } 38 | ifap = unsafe { (*ifap).ifa_next }; 39 | } 40 | unsafe { freeifaddrs(ifirst) }; 41 | Ok(result) 42 | } 43 | 44 | fn parse_addr(aptr: *const sockaddr) -> IpAddr { 45 | if aptr.is_null() { 46 | return IpAddr::Empty; 47 | } 48 | let addr = unsafe { *aptr }; 49 | match addr.sa_family as i32 { 50 | AF_INET => IpAddr::V4(Ipv4Addr::new(addr.sa_data[2] as u8, addr.sa_data[3] as u8, 51 | addr.sa_data[4] as u8, addr.sa_data[5] as u8)), 52 | AF_INET6 => { 53 | // This is horrible. 54 | let addr6: *const sockaddr_in6 = unsafe { mem::transmute(aptr) }; 55 | let mut a: [u8; 16] = unsafe { (*addr6).sin6_addr.s6_addr }; 56 | a[..].reverse(); 57 | let a: [u16; 8] = unsafe { mem::transmute(a) }; 58 | IpAddr::V6(Ipv6Addr::new(a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0])) 59 | }, 60 | _ => IpAddr::Unsupported, 61 | } 62 | } 63 | 64 | #[link(name = "c")] 65 | extern "C" { 66 | fn getloadavg(loadavg: *mut f64, nelem: c_int) -> c_int; 67 | } 68 | -------------------------------------------------------------------------------- /src/platform/windows/socket.rs: -------------------------------------------------------------------------------- 1 | use winapi::ctypes::c_ulong; 2 | use winapi::shared::winerror::ERROR_SUCCESS; 3 | use winapi::shared::ws2def::{AF_INET6, AF_INET}; 4 | 5 | use super::last_os_error; 6 | use crate::data::*; 7 | 8 | use std::io; 9 | 10 | #[derive(Debug, Default)] 11 | #[repr(C)] 12 | struct TcpStats { 13 | rto_algorithm: c_ulong, 14 | rto_min: c_ulong, 15 | rto_max: c_ulong, 16 | max_conn: c_ulong, 17 | active_opens: c_ulong, 18 | passive_opens: c_ulong, 19 | attemp_fails: c_ulong, 20 | estab_resets: c_ulong, 21 | curr_estab: c_ulong, 22 | in_segs: c_ulong, 23 | out_segs: c_ulong, 24 | retrans_segs: c_ulong, 25 | in_errs: c_ulong, 26 | out_rsts: c_ulong, 27 | num_conns: c_ulong, 28 | } 29 | 30 | #[derive(Debug, Default)] 31 | #[repr(C)] 32 | struct UdpStats { 33 | in_datagrams: c_ulong, 34 | no_ports: c_ulong, 35 | in_errors: c_ulong, 36 | out_datagrams: c_ulong, 37 | num_addrs: c_ulong, 38 | } 39 | 40 | #[link(name = "iphlpapi")] 41 | extern "system" { 42 | // https://msdn.microsoft.com/en-us/library/aa366023(v=vs.85).aspx 43 | fn GetTcpStatisticsEx(pStats: *mut TcpStats, dwFamily: c_ulong) -> c_ulong; 44 | // https://msdn.microsoft.com/en-us/library/aa366031(v=vs.85).aspx 45 | fn GetUdpStatisticsEx(pStats: *mut UdpStats, dwFamily: c_ulong) -> c_ulong; 46 | } 47 | 48 | pub fn get() -> io::Result { 49 | let mut tcp4 = TcpStats::default(); 50 | let mut tcp6 = TcpStats::default(); 51 | let mut udp4 = UdpStats::default(); 52 | let mut udp6 = UdpStats::default(); 53 | 54 | if ERROR_SUCCESS != unsafe { GetTcpStatisticsEx(&mut tcp4 as *mut _, AF_INET as c_ulong) } { 55 | last_os_error()?; 56 | } 57 | if ERROR_SUCCESS != unsafe { GetTcpStatisticsEx(&mut tcp6 as *mut _, AF_INET6 as c_ulong) } { 58 | last_os_error()?; 59 | } 60 | 61 | if ERROR_SUCCESS != unsafe { GetUdpStatisticsEx(&mut udp4 as *mut _, AF_INET as c_ulong) } { 62 | last_os_error()?; 63 | } 64 | if ERROR_SUCCESS != unsafe { GetUdpStatisticsEx(&mut udp6 as *mut _, AF_INET6 as c_ulong) } { 65 | last_os_error()?; 66 | } 67 | 68 | // println!("4: {:?}", tcp4 ); 69 | // println!("6: {:?}", tcp6 ); 70 | // println!("4: {:?}", udp4 ); 71 | // println!("6: {:?}", udp6 ); 72 | 73 | let stat = SocketStats { 74 | tcp_sockets_in_use: tcp4.num_conns as usize, 75 | tcp_sockets_orphaned: 0, // ? who or how to compute? 76 | tcp6_sockets_in_use: tcp6.num_conns as usize, 77 | udp_sockets_in_use: udp4.num_addrs as usize, 78 | udp6_sockets_in_use: udp6.num_addrs as usize, 79 | }; 80 | 81 | Ok(stat) 82 | } 83 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project owner at hello@unrelenting.technology. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project owner is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /examples/info.rs: -------------------------------------------------------------------------------- 1 | extern crate systemstat; 2 | 3 | use std::thread; 4 | use std::time::Duration; 5 | use systemstat::{System, Platform, saturating_sub_bytes}; 6 | 7 | fn main() { 8 | let sys = System::new(); 9 | 10 | match sys.mounts() { 11 | Ok(mounts) => { 12 | println!("\nMounts:"); 13 | for mount in mounts.iter() { 14 | println!("{} ---{}---> {} (available {} of {})", 15 | mount.fs_mounted_from, mount.fs_type, mount.fs_mounted_on, mount.avail, mount.total); 16 | } 17 | } 18 | Err(x) => println!("\nMounts: error: {}", x) 19 | } 20 | 21 | match sys.mount_at("/") { 22 | Ok(mount) => { 23 | println!("\nMount at /:"); 24 | println!("{} ---{}---> {} (available {} of {})", 25 | mount.fs_mounted_from, mount.fs_type, mount.fs_mounted_on, mount.avail, mount.total); 26 | } 27 | Err(x) => println!("\nMount at /: error: {}", x) 28 | } 29 | 30 | match sys.block_device_statistics() { 31 | Ok(stats) => { 32 | for blkstats in stats.values() { 33 | println!("{}: {:?}", blkstats.name, blkstats); 34 | } 35 | } 36 | Err(x) => println!("\nBlock statistics error: {}", x) 37 | } 38 | 39 | match sys.networks() { 40 | Ok(netifs) => { 41 | println!("\nNetworks:"); 42 | for netif in netifs.values() { 43 | println!("{} ({:?})", netif.name, netif.addrs); 44 | } 45 | } 46 | Err(x) => println!("\nNetworks: error: {}", x) 47 | } 48 | 49 | match sys.networks() { 50 | Ok(netifs) => { 51 | println!("\nNetwork interface statistics:"); 52 | for netif in netifs.values() { 53 | println!("{} statistics: ({:?})", netif.name, sys.network_stats(&netif.name)); 54 | } 55 | } 56 | Err(x) => println!("\nNetworks: error: {}", x) 57 | } 58 | 59 | match sys.battery_life() { 60 | Ok(battery) => 61 | print!("\nBattery: {}%, {}h{}m remaining", 62 | battery.remaining_capacity*100.0, 63 | battery.remaining_time.as_secs() / 3600, 64 | battery.remaining_time.as_secs() % 60), 65 | Err(x) => print!("\nBattery: error: {}", x) 66 | } 67 | 68 | match sys.on_ac_power() { 69 | Ok(power) => println!(", AC power: {}", power), 70 | Err(x) => println!(", AC power: error: {}", x) 71 | } 72 | 73 | match sys.memory() { 74 | Ok(mem) => println!("\nMemory: {} used / {} ({} bytes) total ({:?})", saturating_sub_bytes(mem.total, mem.free), mem.total, mem.total.as_u64(), mem.platform_memory), 75 | Err(x) => println!("\nMemory: error: {}", x) 76 | } 77 | 78 | match sys.swap() { 79 | Ok(swap) => println!("\nSwap: {} used / {} ({} bytes) total ({:?})", saturating_sub_bytes(swap.total, swap.free), swap.total, swap.total.as_u64(), swap.platform_swap), 80 | Err(x) => println!("\nSwap: error: {}", x) 81 | } 82 | 83 | match sys.load_average() { 84 | Ok(loadavg) => println!("\nLoad average: {} {} {}", loadavg.one, loadavg.five, loadavg.fifteen), 85 | Err(x) => println!("\nLoad average: error: {}", x) 86 | } 87 | 88 | match sys.uptime() { 89 | Ok(uptime) => println!("\nUptime: {:?}", uptime), 90 | Err(x) => println!("\nUptime: error: {}", x) 91 | } 92 | 93 | match sys.boot_time() { 94 | Ok(boot_time) => println!("\nBoot time: {}", boot_time), 95 | Err(x) => println!("\nBoot time: error: {}", x) 96 | } 97 | 98 | match sys.cpu_load_aggregate() { 99 | Ok(cpu)=> { 100 | println!("\nMeasuring CPU load..."); 101 | thread::sleep(Duration::from_secs(1)); 102 | let cpu = cpu.done().unwrap(); 103 | println!("CPU load: {}% user, {}% nice, {}% system, {}% intr, {}% idle ", 104 | cpu.user * 100.0, cpu.nice * 100.0, cpu.system * 100.0, cpu.interrupt * 100.0, cpu.idle * 100.0); 105 | }, 106 | Err(x) => println!("\nCPU load: error: {}", x) 107 | } 108 | 109 | match sys.cpu_temp() { 110 | Ok(cpu_temp) => println!("\nCPU temp: {}", cpu_temp), 111 | Err(x) => println!("\nCPU temp: {}", x) 112 | } 113 | 114 | match sys.socket_stats() { 115 | Ok(stats) => println!("\nSystem socket statistics: {:?}", stats), 116 | Err(x) => println!("\nSystem socket statistics: error: {}", x) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/platform/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module reexports the OS-specific module that actually implements Platform. 2 | pub mod common; 3 | pub use self::common::*; 4 | 5 | #[cfg(windows)] 6 | pub mod windows; 7 | #[cfg(windows)] 8 | pub use self::windows::PlatformImpl; 9 | 10 | #[cfg(unix)] 11 | pub mod unix; 12 | 13 | #[cfg(any( 14 | target_os = "freebsd", 15 | target_os = "openbsd", 16 | target_os = "netbsd", 17 | target_vendor = "apple" 18 | ))] 19 | pub mod bsd; 20 | 21 | #[cfg(target_os = "freebsd")] 22 | pub mod freebsd; 23 | #[cfg(target_os = "freebsd")] 24 | pub use self::freebsd::PlatformImpl; 25 | 26 | #[cfg(target_os = "openbsd")] 27 | pub mod openbsd; 28 | #[cfg(target_os = "openbsd")] 29 | pub use self::openbsd::PlatformImpl; 30 | 31 | #[cfg(target_os = "netbsd")] 32 | pub mod netbsd; 33 | #[cfg(target_os = "netbsd")] 34 | pub use self::netbsd::PlatformImpl; 35 | 36 | #[cfg(target_vendor = "apple")] 37 | pub mod macos; 38 | #[cfg(target_vendor = "apple")] 39 | pub use self::macos::PlatformImpl; 40 | 41 | #[cfg(any(target_os = "linux", target_os = "android"))] 42 | pub mod linux; 43 | #[cfg(any(target_os = "linux", target_os = "android"))] 44 | pub use self::linux::PlatformImpl; 45 | 46 | #[cfg(any(target_os = "illumos", target_os = "solaris"))] 47 | pub mod illumos; 48 | #[cfg(any(target_os = "illumos", target_os = "solaris"))] 49 | pub use self::illumos::PlatformImpl; 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | use std::thread; 55 | use std::time::Duration; 56 | 57 | #[test] 58 | fn test_cpu_load() { 59 | let load = PlatformImpl::new().cpu_load().unwrap(); 60 | thread::sleep(Duration::from_millis(300)); 61 | let load = load.done().unwrap(); 62 | assert!(!load.is_empty()); 63 | for cpu in load.iter() { 64 | let sum = cpu.user + cpu.nice + cpu.system + cpu.interrupt + cpu.idle + cpu.platform.sum(); 65 | assert!(sum > 0.95 && sum < 1.05); 66 | } 67 | } 68 | 69 | #[test] 70 | fn test_cpu_load_aggregate() { 71 | let cpu = PlatformImpl::new().cpu_load_aggregate().unwrap(); 72 | thread::sleep(Duration::from_millis(300)); 73 | let cpu = cpu.done().unwrap(); 74 | let sum = cpu.user + cpu.nice + cpu.system + cpu.interrupt + cpu.idle + cpu.platform.sum(); 75 | assert!(sum > 0.95 && sum < 1.05); 76 | } 77 | 78 | #[test] 79 | fn test_load_average() { 80 | let load = PlatformImpl::new().load_average().unwrap(); 81 | assert!(load.one > 0.00001 && load.five > 0.00001 && load.fifteen > 0.00001); 82 | } 83 | 84 | #[test] 85 | fn test_memory() { 86 | let mem = PlatformImpl::new().memory().unwrap(); 87 | assert!(mem.free.as_u64() > 1024 && mem.total.as_u64() > 1024); 88 | } 89 | 90 | #[test] 91 | fn test_swap() { 92 | let swap = PlatformImpl::new().swap().unwrap(); 93 | assert!(swap.free <= swap.total); 94 | } 95 | 96 | #[test] 97 | fn test_mem_and_swap() { 98 | let (mem, swap) = PlatformImpl::new().memory_and_swap().unwrap(); 99 | assert!(mem.free.as_u64() > 1024 && mem.total.as_u64() > 1024); 100 | assert!(swap.free <= swap.total); 101 | } 102 | 103 | #[test] 104 | fn test_battery_life() { 105 | if let Ok(bat) = PlatformImpl::new().battery_life() { 106 | assert!(bat.remaining_capacity <= 100.0 && bat.remaining_capacity >= 0.0); 107 | } 108 | } 109 | 110 | #[test] 111 | fn test_on_ac_power() { 112 | PlatformImpl::new().on_ac_power().unwrap(); 113 | } 114 | 115 | #[test] 116 | fn test_mounts() { 117 | let mounts = PlatformImpl::new().mounts().unwrap(); 118 | assert!(!mounts.is_empty()); 119 | assert!(mounts.iter().find(|m| m.fs_mounted_on == "/").unwrap().fs_mounted_on == "/"); 120 | } 121 | 122 | #[test] 123 | fn test_mount_at() { 124 | // XXX: PathBuf required instead of constant string at least on FreeBSD?? 125 | let mount = PlatformImpl::new().mount_at(std::path::PathBuf::from("/")).unwrap(); 126 | assert!(mount.fs_mounted_on == "/"); 127 | } 128 | 129 | #[test] 130 | fn test_networks() { 131 | let networks = PlatformImpl::new().networks().unwrap(); 132 | assert!(!networks.values().find(|n| n.name == "lo" || n.name == "lo0").unwrap().addrs.is_empty()); 133 | } 134 | 135 | #[test] 136 | fn test_cpu_measurement_is_send() { 137 | use crate::{DelayedMeasurement, CPULoad}; 138 | #[allow(dead_code)] 139 | fn take_delayed(dm: DelayedMeasurement>) { 140 | use std::thread; 141 | thread::spawn(move || dm); 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/platform/common.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path, convert::{TryFrom, TryInto}}; 2 | use crate::data::*; 3 | 4 | /// The Platform trait declares all the functions for getting system information. 5 | /// 6 | /// NOTE: any impl MUST override one of `uptime` or `boot_time`. 7 | pub trait Platform { 8 | fn new() -> Self; 9 | 10 | /// Returns a delayed vector of CPU load statistics, one object per CPU (core). 11 | /// 12 | /// You need to wait some time (about a second is good) before unwrapping the 13 | /// `DelayedMeasurement` with `.done()`. 14 | fn cpu_load(&self) -> io::Result>>; 15 | 16 | /// Returns a delayed CPU load statistics object, average over all CPUs (cores). 17 | /// 18 | /// You need to wait some time (about a second is good) before unwrapping the 19 | /// `DelayedMeasurement` with `.done()`. 20 | fn cpu_load_aggregate(&self) -> io::Result> { 21 | let measurement = self.cpu_load()?; 22 | Ok(DelayedMeasurement::new( 23 | Box::new(move || measurement.done().map(|ls| { 24 | let mut it = ls.iter(); 25 | let first = it.next().unwrap().clone(); // has to be a variable, rust moves the iterator otherwise 26 | it.fold(first, |acc, l| acc.avg_add(l)) 27 | })))) 28 | } 29 | 30 | /// Returns a load average object. 31 | fn load_average(&self) -> io::Result; 32 | 33 | /// Returns a memory information object. 34 | fn memory(&self) -> io::Result; 35 | 36 | /// Returns a swap memory information object. 37 | fn swap(&self) -> io::Result; 38 | 39 | /// Returns a swap and a memory information object. 40 | /// On some platforms this is more efficient than calling memory() and swap() separately 41 | /// If memory() or swap() are not implemented for a platform, this function will fail. 42 | fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { 43 | // Do swap first, in order to fail fast if it's not implemented 44 | let swap = self.swap()?; 45 | let memory = self.memory()?; 46 | Ok((memory, swap)) 47 | } 48 | 49 | /// Returns the system uptime. 50 | fn uptime(&self) -> io::Result { 51 | self.boot_time().and_then(|bt| { 52 | (OffsetDateTime::now_utc() - bt) 53 | .try_into() 54 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "Could not process time")) 55 | }) 56 | } 57 | 58 | /// Returns the system boot time. 59 | fn boot_time(&self) -> io::Result { 60 | self.uptime().and_then(|ut| { 61 | Ok(OffsetDateTime::now_utc() 62 | - time::Duration::try_from(ut) 63 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "Could not process time"))?) 64 | }) 65 | } 66 | 67 | /// Returns a battery life information object. 68 | fn battery_life(&self) -> io::Result; 69 | 70 | /// Returns whether AC power is plugged in. 71 | fn on_ac_power(&self) -> io::Result; 72 | 73 | /// Returns a vector of filesystem mount information objects. 74 | fn mounts(&self) -> io::Result>; 75 | 76 | /// Returns a filesystem mount information object for the filesystem at a given path. 77 | fn mount_at>(&self, path: P) -> io::Result { 78 | self.mounts() 79 | .and_then(|mounts| { 80 | mounts 81 | .into_iter() 82 | .find(|mount| path::Path::new(&mount.fs_mounted_on) == path.as_ref()) 83 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No such mount")) 84 | }) 85 | } 86 | 87 | /// Returns a map of block device statistics objects 88 | fn block_device_statistics(&self) -> io::Result>; 89 | 90 | /// Returns a map of network intefrace information objects. 91 | /// 92 | /// It's a map because most operating systems return an object per IP address, not per 93 | /// interface, and we're doing deduplication and packing everything into one object per 94 | /// interface. You can use the .values() iterator if you need to iterate over all of them. 95 | fn networks(&self) -> io::Result>; 96 | 97 | /// Returns statistics for a given interface (bytes/packets sent/received) 98 | fn network_stats(&self, interface: &str) -> io::Result; 99 | 100 | /// Returns the current CPU temperature in degrees Celsius. 101 | /// 102 | /// Depending on the platform, this might be core 0, package, etc. 103 | fn cpu_temp(&self) -> io::Result; 104 | 105 | /// Returns information about the number of sockets in use 106 | fn socket_stats(&self) -> io::Result; 107 | } 108 | -------------------------------------------------------------------------------- /src/platform/windows/disk.rs: -------------------------------------------------------------------------------- 1 | use winapi::ctypes::c_ulong; 2 | use winapi::shared::minwindef::FALSE; 3 | use winapi::um::fileapi::{GetDiskFreeSpaceExW, GetLogicalDriveStringsW, GetVolumeInformationW, GetDriveTypeW}; 4 | use winapi::um::winnt::ULARGE_INTEGER; 5 | 6 | use super::{last_os_error, u16_array_to_string}; 7 | use crate::data::*; 8 | 9 | use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; 10 | use std::{io, ptr}; 11 | use std::mem::MaybeUninit; 12 | 13 | pub fn drives() -> io::Result> { 14 | let logical_drives = unsafe { GetLogicalDriveStringsW(0, ptr::null_mut()) }; 15 | 16 | let mut u16s = Vec::with_capacity(logical_drives as usize); 17 | let p_u16s = u16s.as_mut_ptr(); 18 | 19 | let get_logical_drives = unsafe { GetLogicalDriveStringsW(logical_drives, p_u16s) }; 20 | 21 | // (X://\0)*\0 22 | if get_logical_drives + 1 != logical_drives { 23 | last_os_error()?; 24 | } 25 | 26 | unsafe { u16s.set_len(logical_drives as usize) }; 27 | 28 | // (X://\0)*\0 29 | let drives = u16s.split(|c| *c == 0).filter(|iter| !iter.is_empty()); 30 | 31 | let mut vec: Vec = Vec::new(); 32 | 33 | for us in drives { 34 | let name = decode_utf16(us.iter().cloned()) 35 | .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) 36 | .collect::(); 37 | 38 | let (max, fs, tag) = get_volume_information(us)?; 39 | 40 | let tmp = if max == 0 { 41 | Filesystem { 42 | name_max: max as _, 43 | fs_type: fs, 44 | fs_mounted_from: tag, 45 | fs_mounted_on: name, 46 | total: ByteSize::b(0), 47 | avail: ByteSize::b(0), 48 | free: ByteSize::b(0), 49 | files: 0, 50 | files_total: 0, 51 | files_avail: 0 52 | } 53 | } else { 54 | let (total, avail, free) = get_disk_space_ext(us)?; 55 | 56 | Filesystem { 57 | name_max: max as _, 58 | fs_type: fs, 59 | fs_mounted_from: tag, 60 | fs_mounted_on: name, 61 | total: ByteSize::b(total), 62 | avail: ByteSize::b(avail), 63 | free: ByteSize::b(free), 64 | files: 0, // don't find.. 65 | files_total: 0, 66 | files_avail: 0, 67 | } 68 | }; 69 | 70 | vec.push(tmp); 71 | } 72 | 73 | Ok(vec) 74 | } 75 | 76 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx 77 | fn get_volume_information(name: &[u16]) -> io::Result<(c_ulong, String, String)> { 78 | let p_name = name.as_ptr(); 79 | 80 | let mut volume_name = Vec::with_capacity(255); 81 | let p_volume_name = volume_name.as_mut_ptr(); 82 | 83 | let mut fs_name = Vec::with_capacity(255); 84 | let p_fs_name = fs_name.as_mut_ptr(); 85 | 86 | let mut volume_serial = Vec::with_capacity(255); 87 | let p_volume_serial = volume_serial.as_mut_ptr(); 88 | 89 | let mut max_component_length: c_ulong = 0; 90 | let mut fs_flags: c_ulong = 0; 91 | 92 | if FALSE == unsafe { 93 | GetVolumeInformationW( 94 | p_name, 95 | p_volume_name, 96 | 255, 97 | p_volume_serial, 98 | &mut max_component_length as *mut _, 99 | &mut fs_flags as *mut _, 100 | p_fs_name, 101 | 255, 102 | ) 103 | } { 104 | match unsafe { GetDriveTypeW(p_name) } { 105 | 2 => { // REMOVABLE DRIVE (Floppy, USB, etc) 106 | return Ok(( 107 | max_component_length, 108 | String::from("REM"), 109 | u16_array_to_string(p_volume_name) 110 | )) 111 | }, 112 | 5 => { // DRIVE_CDROM 113 | return Ok(( 114 | max_component_length, 115 | String::from("CDROM"), 116 | u16_array_to_string(p_volume_name) 117 | )) 118 | }, 119 | _ => last_os_error()? 120 | }; 121 | } 122 | 123 | Ok(( 124 | max_component_length, 125 | u16_array_to_string(p_fs_name), 126 | u16_array_to_string(p_volume_name), 127 | )) 128 | } 129 | 130 | fn get_disk_space_ext(name: &[u16]) -> io::Result<(u64, u64, u64)> { 131 | let p_name = name.as_ptr(); 132 | 133 | let mut avail: MaybeUninit = MaybeUninit::uninit(); 134 | let mut total: MaybeUninit = MaybeUninit::uninit(); 135 | let mut free: MaybeUninit = MaybeUninit::uninit(); 136 | 137 | if FALSE == unsafe { 138 | GetDiskFreeSpaceExW( 139 | p_name, 140 | avail.as_mut_ptr(), 141 | total.as_mut_ptr(), 142 | free.as_mut_ptr(), 143 | ) 144 | } { 145 | last_os_error()?; 146 | } 147 | 148 | let avail = unsafe { avail.assume_init() }; 149 | let total = unsafe { total.assume_init() }; 150 | let free = unsafe { free.assume_init() }; 151 | 152 | unsafe { Ok((*total.QuadPart(), *avail.QuadPart(), *free.QuadPart())) } 153 | } 154 | -------------------------------------------------------------------------------- /src/platform/netbsd.rs: -------------------------------------------------------------------------------- 1 | // use super::bsd; 2 | use super::common::*; 3 | use super::unix; 4 | use crate::data::*; 5 | use libc::{c_int, c_void, sysctl, CTL_VM}; 6 | use std::{io, mem, path, ptr}; 7 | 8 | pub struct PlatformImpl; 9 | 10 | // https://github.com/NetBSD/src/blob/8e2e7cb174ca27b848b18119f33cf4c212fe22ee/sys/uvm/uvm_param.h#L169 11 | static VM_UVMEXP2: c_int = 5; 12 | 13 | macro_rules! sysctl { 14 | ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => {{ 15 | let mib = &$mib; 16 | let mut size = $size; 17 | if unsafe { 18 | sysctl( 19 | &mib[0] as *const _ as *mut _, 20 | mib.len() as u32, 21 | $dataptr as *mut _ as *mut c_void, 22 | &mut size, 23 | ptr::null_mut(), 24 | 0, 25 | ) 26 | } != 0 27 | && $shouldcheck 28 | { 29 | return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")); 30 | } 31 | size 32 | }}; 33 | ($mib:expr, $dataptr:expr, $size:expr) => { 34 | sysctl!($mib, $dataptr, $size, true) 35 | }; 36 | } 37 | 38 | /// An implementation of `Platform` for NetBSD. 39 | /// See `Platform` for documentation. 40 | impl Platform for PlatformImpl { 41 | #[inline(always)] 42 | fn new() -> Self { 43 | PlatformImpl 44 | } 45 | 46 | fn cpu_load(&self) -> io::Result>> { 47 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 48 | } 49 | 50 | fn load_average(&self) -> io::Result { 51 | unix::load_average() 52 | } 53 | 54 | fn memory(&self) -> io::Result { 55 | PlatformMemory::new().map(|pm| pm.to_memory()) 56 | } 57 | 58 | fn swap(&self) -> io::Result { 59 | PlatformMemory::new().map(|pm| pm.to_swap()) 60 | } 61 | 62 | fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { 63 | let pm = PlatformMemory::new()?; 64 | Ok((pm.clone().to_memory(), pm.to_swap())) 65 | } 66 | 67 | fn boot_time(&self) -> io::Result { 68 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 69 | } 70 | 71 | fn battery_life(&self) -> io::Result { 72 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 73 | } 74 | 75 | fn on_ac_power(&self) -> io::Result { 76 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 77 | } 78 | 79 | fn mounts(&self) -> io::Result> { 80 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 81 | } 82 | 83 | fn mount_at>(&self, _: P) -> io::Result { 84 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 85 | } 86 | 87 | fn block_device_statistics(&self) -> io::Result> { 88 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 89 | } 90 | 91 | fn networks(&self) -> io::Result> { 92 | unix::networks() 93 | } 94 | 95 | fn network_stats(&self, _interface: &str) -> io::Result { 96 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 97 | } 98 | 99 | fn cpu_temp(&self) -> io::Result { 100 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 101 | } 102 | 103 | fn socket_stats(&self) -> io::Result { 104 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 105 | } 106 | } 107 | 108 | impl PlatformMemory { 109 | // Retrieve platform memory information 110 | fn new() -> io::Result { 111 | let mut uvm_info = uvmexp_sysctl::default(); 112 | sysctl!( 113 | &[CTL_VM, VM_UVMEXP2], 114 | &mut uvm_info, 115 | mem::size_of::() 116 | ); 117 | 118 | Ok(Self { 119 | pageshift: uvm_info.pageshift, 120 | total: ByteSize::b((uvm_info.npages << uvm_info.pageshift) as u64), 121 | active: ByteSize::b((uvm_info.active << uvm_info.pageshift) as u64), 122 | inactive: ByteSize::b((uvm_info.inactive << uvm_info.pageshift) as u64), 123 | wired: ByteSize::b((uvm_info.wired << uvm_info.pageshift) as u64), 124 | anon: ByteSize::b((uvm_info.anonpages << uvm_info.pageshift) as u64), 125 | files: ByteSize::b((uvm_info.filepages << uvm_info.pageshift) as u64), 126 | exec: ByteSize::b((uvm_info.execpages << uvm_info.pageshift) as u64), 127 | free: ByteSize::b((uvm_info.free << uvm_info.pageshift) as u64), 128 | paging: ByteSize::b((uvm_info.paging << uvm_info.pageshift) as u64), 129 | sw: ByteSize::b((uvm_info.swpages << uvm_info.pageshift) as u64), 130 | swinuse: ByteSize::b((uvm_info.swpginuse << uvm_info.pageshift) as u64), 131 | swonly: ByteSize::b((uvm_info.swpgonly << uvm_info.pageshift) as u64), 132 | }) 133 | } 134 | fn to_memory(self) -> Memory { 135 | Memory { 136 | total: self.total, 137 | free: self.free, 138 | platform_memory: self, 139 | } 140 | } 141 | fn to_swap(self) -> Swap { 142 | Swap { 143 | total: self.sw, 144 | free: saturating_sub_bytes(self.sw, self.swinuse), 145 | platform_swap: self, 146 | } 147 | } 148 | } 149 | 150 | // https://github.com/NetBSD/src/blob/038135cba4b80f5c8d1e32fbc5b73c91c2f276d9/sys/uvm/uvm_extern.h#L420-L515 151 | #[repr(C)] 152 | #[derive(Debug, Default)] 153 | struct uvmexp_sysctl { 154 | pagesize: i64, 155 | pagemask: i64, 156 | pageshift: i64, 157 | npages: i64, 158 | free: i64, 159 | active: i64, 160 | inactive: i64, 161 | paging: i64, 162 | wired: i64, 163 | zeropages: i64, 164 | reserve_pagedaemon: i64, 165 | reserve_kernel: i64, 166 | freemin: i64, 167 | freetarg: i64, 168 | inactarg: i64, // unused 169 | wiredmax: i64, 170 | nswapdev: i64, 171 | swpages: i64, 172 | swpginuse: i64, 173 | swpgonly: i64, 174 | nswget: i64, 175 | unused1: i64, // unused; was nanon 176 | cpuhit: i64, 177 | cpumiss: i64, 178 | faults: i64, 179 | traps: i64, 180 | intrs: i64, 181 | swtch: i64, 182 | softs: i64, 183 | syscalls: i64, 184 | pageins: i64, 185 | swapins: i64, // unused 186 | swapouts: i64, // unused 187 | pgswapin: i64, // unused 188 | pgswapout: i64, 189 | forks: i64, 190 | forks_ppwait: i64, 191 | forks_sharevm: i64, 192 | pga_zerohit: i64, 193 | pga_zeromiss: i64, 194 | zeroaborts: i64, 195 | fltnoram: i64, 196 | fltnoanon: i64, 197 | fltpgwait: i64, 198 | fltpgrele: i64, 199 | fltrelck: i64, 200 | fltrelckok: i64, 201 | fltanget: i64, 202 | fltanretry: i64, 203 | fltamcopy: i64, 204 | fltnamap: i64, 205 | fltnomap: i64, 206 | fltlget: i64, 207 | fltget: i64, 208 | flt_anon: i64, 209 | flt_acow: i64, 210 | flt_obj: i64, 211 | flt_prcopy: i64, 212 | flt_przero: i64, 213 | pdwoke: i64, 214 | pdrevs: i64, 215 | unused4: i64, 216 | pdfreed: i64, 217 | pdscans: i64, 218 | pdanscan: i64, 219 | pdobscan: i64, 220 | pdreact: i64, 221 | pdbusy: i64, 222 | pdpageouts: i64, 223 | pdpending: i64, 224 | pddeact: i64, 225 | anonpages: i64, 226 | filepages: i64, 227 | execpages: i64, 228 | colorhit: i64, 229 | colormiss: i64, 230 | ncolors: i64, 231 | bootpages: i64, 232 | poolpages: i64, 233 | countsyncone: i64, 234 | countsyncall: i64, 235 | anonunknown: i64, 236 | anonclean: i64, 237 | anondirty: i64, 238 | fileunknown: i64, 239 | fileclean: i64, 240 | filedirty: i64, 241 | fltup: i64, 242 | fltnoup: i64, 243 | } 244 | -------------------------------------------------------------------------------- /src/platform/macos.rs: -------------------------------------------------------------------------------- 1 | use std::{io, ptr, mem::{self, MaybeUninit}, ffi, slice}; 2 | use libc::{ 3 | c_int, c_void, host_statistics64, mach_host_self, size_t, statfs, sysconf, sysctl, 4 | sysctlnametomib, timeval, vm_statistics64, xsw_usage, CTL_VM, HOST_VM_INFO64, 5 | HOST_VM_INFO64_COUNT, KERN_SUCCESS, VM_SWAPUSAGE, _SC_PHYS_PAGES, 6 | }; 7 | use crate::data::*; 8 | use super::common::*; 9 | use super::unix; 10 | use super::bsd; 11 | 12 | pub struct PlatformImpl; 13 | 14 | macro_rules! sysctl_mib { 15 | ($len:expr, $name:expr) => { 16 | { 17 | let mut mib: [c_int; $len] = [0; $len]; 18 | let mut sz: size_t = mib.len(); 19 | let s = ffi::CString::new($name).unwrap(); 20 | unsafe { sysctlnametomib(s.as_ptr(), &mut mib[0], &mut sz) }; 21 | mib 22 | } 23 | } 24 | } 25 | 26 | macro_rules! sysctl { 27 | ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => { 28 | { 29 | let mib = &$mib; 30 | let mut size = $size; 31 | if unsafe { sysctl(&mib[0] as *const _ as *mut _, mib.len() as u32, 32 | $dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck { 33 | return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) 34 | } 35 | size 36 | } 37 | }; 38 | ($mib:expr, $dataptr:expr, $size:expr) => { 39 | sysctl!($mib, $dataptr, $size, true) 40 | } 41 | } 42 | 43 | lazy_static! { 44 | static ref KERN_BOOTTIME: [c_int; 2] = sysctl_mib!(2, "kern.boottime"); 45 | } 46 | 47 | /// An implementation of `Platform` for macOS. 48 | /// See `Platform` for documentation. 49 | impl Platform for PlatformImpl { 50 | #[inline(always)] 51 | fn new() -> Self { 52 | PlatformImpl 53 | } 54 | 55 | fn cpu_load(&self) -> io::Result>> { 56 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 57 | } 58 | 59 | fn load_average(&self) -> io::Result { 60 | unix::load_average() 61 | } 62 | 63 | fn memory(&self) -> io::Result { 64 | // Get Total Memory 65 | let total = match unsafe { sysconf(_SC_PHYS_PAGES) } { 66 | -1 => { 67 | return Err(io::Error::new( 68 | io::ErrorKind::Other, 69 | "sysconf(_SC_PHYS_PAGES) failed", 70 | )) 71 | } 72 | n => n as u64, 73 | }; 74 | 75 | // Get Usage Info 76 | let host_port = unsafe { mach_host_self() }; 77 | let mut stat = MaybeUninit::::zeroed(); 78 | let mut stat_count = HOST_VM_INFO64_COUNT; 79 | 80 | let ret = unsafe { 81 | host_statistics64( 82 | host_port, 83 | HOST_VM_INFO64, 84 | stat.as_mut_ptr() as *mut i32, 85 | &mut stat_count, 86 | ) 87 | }; 88 | 89 | if ret != KERN_SUCCESS { 90 | return Err(io::Error::new( 91 | io::ErrorKind::Other, 92 | "host_statistics64() failed", 93 | )); 94 | } 95 | let stat = unsafe { stat.assume_init() }; 96 | 97 | let pmem = PlatformMemory { 98 | total: ByteSize::kib(total << *bsd::PAGESHIFT), 99 | active: ByteSize::kib((stat.active_count as u64) << *bsd::PAGESHIFT), 100 | inactive: ByteSize::kib((stat.inactive_count as u64) << *bsd::PAGESHIFT), 101 | wired: ByteSize::kib((stat.wire_count as u64) << *bsd::PAGESHIFT), 102 | free: ByteSize::kib((stat.free_count as u64) << *bsd::PAGESHIFT), 103 | purgeable: ByteSize::kib((stat.purgeable_count as u64) << *bsd::PAGESHIFT), 104 | speculative: ByteSize::kib((stat.speculative_count as u64) << *bsd::PAGESHIFT), 105 | compressor: ByteSize::kib((stat.compressor_page_count as u64) << *bsd::PAGESHIFT), 106 | throttled: ByteSize::kib((stat.throttled_count as u64) << *bsd::PAGESHIFT), 107 | external: ByteSize::kib((stat.external_page_count as u64) << *bsd::PAGESHIFT), 108 | internal: ByteSize::kib((stat.internal_page_count as u64) << *bsd::PAGESHIFT), 109 | uncompressed_in_compressor: ByteSize::kib( 110 | (stat.total_uncompressed_pages_in_compressor as u64) << *bsd::PAGESHIFT, 111 | ), 112 | }; 113 | 114 | Ok(Memory { 115 | total: pmem.total, 116 | // This is the available memory, but free is more akin to: 117 | // pmem.free - pmem.speculative 118 | free: pmem.free + pmem.inactive, 119 | platform_memory: pmem, 120 | }) 121 | } 122 | 123 | fn swap(&self) -> io::Result { 124 | let mut xsw_usage = MaybeUninit::::zeroed(); 125 | sysctl!([CTL_VM, VM_SWAPUSAGE], &mut xsw_usage, mem::size_of::()); 126 | let xsw_usage = unsafe { xsw_usage.assume_init() }; 127 | 128 | let ps = PlatformSwap { 129 | total: ByteSize::b(xsw_usage.xsu_total), 130 | used: ByteSize::b(xsw_usage.xsu_used), 131 | avail: ByteSize::b(xsw_usage.xsu_avail), 132 | pagesize: ByteSize::b(xsw_usage.xsu_pagesize as u64), 133 | encrypted: xsw_usage.xsu_encrypted != 0, 134 | }; 135 | 136 | Ok(Swap { 137 | total: ps.total, 138 | free: ps.avail, 139 | platform_swap: ps 140 | }) 141 | } 142 | 143 | fn boot_time(&self) -> io::Result { 144 | let mut data: timeval = unsafe { mem::zeroed() }; 145 | sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); 146 | let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); 147 | Ok(ts) 148 | } 149 | 150 | fn battery_life(&self) -> io::Result { 151 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 152 | } 153 | 154 | fn on_ac_power(&self) -> io::Result { 155 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 156 | } 157 | 158 | fn mounts(&self) -> io::Result> { 159 | let mut mptr: *mut statfs = ptr::null_mut(); 160 | let len = unsafe { getmntinfo(&mut mptr, 2_i32) }; 161 | if len < 1 { 162 | return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) 163 | } 164 | let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; 165 | Ok(mounts.iter().map(statfs_to_fs).collect::>()) 166 | } 167 | 168 | fn block_device_statistics(&self) -> io::Result> { 169 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 170 | } 171 | 172 | fn networks(&self) -> io::Result> { 173 | unix::networks() 174 | } 175 | 176 | fn network_stats(&self, _interface: &str) -> io::Result { 177 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 178 | } 179 | 180 | fn cpu_temp(&self) -> io::Result { 181 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 182 | } 183 | 184 | fn socket_stats(&self) -> io::Result { 185 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 186 | } 187 | } 188 | 189 | fn statfs_to_fs(x: &statfs) -> Filesystem { 190 | Filesystem { 191 | files: (x.f_files as usize).saturating_sub(x.f_ffree as usize), 192 | files_total: x.f_files as usize, 193 | files_avail: x.f_ffree as usize, 194 | free: ByteSize::b(x.f_bfree * x.f_bsize as u64), 195 | avail: ByteSize::b(x.f_bavail * x.f_bsize as u64), 196 | total: ByteSize::b(x.f_blocks * x.f_bsize as u64), 197 | name_max: 256, 198 | fs_type: unsafe { ffi::CStr::from_ptr(&x.f_fstypename[0]).to_string_lossy().into_owned() }, 199 | fs_mounted_from: unsafe { ffi::CStr::from_ptr(&x.f_mntfromname[0]).to_string_lossy().into_owned() }, 200 | fs_mounted_on: unsafe { ffi::CStr::from_ptr(&x.f_mntonname[0]).to_string_lossy().into_owned() }, 201 | } 202 | } 203 | 204 | #[link(name = "c")] 205 | extern "C" { 206 | #[cfg_attr(not(target_arch = "aarch64"), link_name = "getmntinfo$INODE64")] 207 | fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; 208 | } 209 | -------------------------------------------------------------------------------- /src/platform/freebsd.rs: -------------------------------------------------------------------------------- 1 | // You are likely to be eaten by a grue. 2 | 3 | use std::{io, path, ptr, mem, ffi, slice, time}; 4 | use std::os::unix::ffi::OsStrExt; 5 | use libc::{c_void, c_int, size_t, sysctl, sysctlnametomib, timeval, statfs}; 6 | use crate::data::*; 7 | use super::common::*; 8 | use super::unix; 9 | use super::bsd; 10 | 11 | pub struct PlatformImpl; 12 | 13 | macro_rules! sysctl_mib { 14 | ($len:expr, $name:expr) => { 15 | { 16 | let mut mib: [c_int; $len] = [0; $len]; 17 | let mut sz: size_t = mib.len(); 18 | let s = ffi::CString::new($name).unwrap(); 19 | unsafe { sysctlnametomib(s.as_ptr(), &mut mib[0], &mut sz) }; 20 | mib 21 | } 22 | } 23 | } 24 | 25 | macro_rules! sysctl { 26 | ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => { 27 | { 28 | let mib = &$mib; 29 | let mut size = $size; 30 | if unsafe { sysctl(&mib[0], mib.len() as u32, 31 | $dataptr as *mut _ as *mut c_void, &mut size, ptr::null(), 0) } != 0 && $shouldcheck { 32 | return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) 33 | } 34 | size 35 | } 36 | }; 37 | ($mib:expr, $dataptr:expr, $size:expr) => { 38 | sysctl!($mib, $dataptr, $size, true) 39 | } 40 | } 41 | 42 | lazy_static! { 43 | static ref KERN_CP_TIMES: [c_int; 2] = sysctl_mib!(2, "kern.cp_times"); 44 | static ref KERN_BOOTTIME: [c_int; 2] = sysctl_mib!(2, "kern.boottime"); 45 | static ref V_ACTIVE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_active_count"); 46 | static ref V_INACTIVE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_inactive_count"); 47 | static ref V_WIRE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_wire_count"); 48 | static ref V_CACHE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_cache_count"); 49 | static ref V_FREE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_free_count"); 50 | static ref ZFS_ARC_SIZE: [c_int; 5] = sysctl_mib!(5, "kstat.zfs.misc.arcstats.size"); 51 | static ref BATTERY_LIFE: [c_int; 4] = sysctl_mib!(4, "hw.acpi.battery.life"); 52 | static ref BATTERY_TIME: [c_int; 4] = sysctl_mib!(4, "hw.acpi.battery.time"); 53 | static ref ACLINE: [c_int; 3] = sysctl_mib!(3, "hw.acpi.acline"); 54 | static ref CPU0TEMP: [c_int; 4] = sysctl_mib!(4, "dev.cpu.0.temperature"); 55 | 56 | static ref CP_TIMES_SIZE: usize = { 57 | let mut size: usize = 0; 58 | unsafe { sysctl(&KERN_CP_TIMES[0], KERN_CP_TIMES.len() as u32, 59 | ptr::null_mut(), &mut size, ptr::null(), 0) }; 60 | size 61 | }; 62 | } 63 | 64 | /// An implementation of `Platform` for FreeBSD. 65 | /// See `Platform` for documentation. 66 | impl Platform for PlatformImpl { 67 | #[inline(always)] 68 | fn new() -> Self { 69 | PlatformImpl 70 | } 71 | 72 | fn cpu_load(&self) -> io::Result>> { 73 | let loads = measure_cpu()?; 74 | Ok(DelayedMeasurement::new( 75 | Box::new(move || Ok(loads.iter() 76 | .zip(measure_cpu()?.iter()) 77 | .map(|(prev, now)| (*now - prev).to_cpuload()) 78 | .collect::>())))) 79 | } 80 | 81 | fn load_average(&self) -> io::Result { 82 | unix::load_average() 83 | } 84 | 85 | fn memory(&self) -> io::Result { 86 | let mut active: usize = 0; sysctl!(V_ACTIVE_COUNT, &mut active, mem::size_of::()); 87 | let mut inactive: usize = 0; sysctl!(V_INACTIVE_COUNT, &mut inactive, mem::size_of::()); 88 | let mut wired: usize = 0; sysctl!(V_WIRE_COUNT, &mut wired, mem::size_of::()); 89 | let mut cache: usize = 0; sysctl!(V_CACHE_COUNT, &mut cache, mem::size_of::(), false); 90 | let mut free: usize = 0; sysctl!(V_FREE_COUNT, &mut free, mem::size_of::()); 91 | let arc = ByteSize::b(zfs_arc_size().unwrap_or(0)); 92 | let pmem = PlatformMemory { 93 | active: ByteSize::kib((active as u64) << *bsd::PAGESHIFT), 94 | inactive: ByteSize::kib((inactive as u64) << *bsd::PAGESHIFT), 95 | wired: saturating_sub_bytes(ByteSize::kib((wired as u64) << *bsd::PAGESHIFT), arc), 96 | cache: ByteSize::kib((cache as u64) << *bsd::PAGESHIFT), 97 | zfs_arc: arc, 98 | free: ByteSize::kib((free as u64) << *bsd::PAGESHIFT), 99 | }; 100 | Ok(Memory { 101 | total: pmem.active + pmem.inactive + pmem.wired + pmem.cache + arc + pmem.free, 102 | free: pmem.inactive + pmem.cache + arc + pmem.free, 103 | platform_memory: pmem, 104 | }) 105 | } 106 | 107 | fn swap(&self) -> io::Result { 108 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 109 | } 110 | 111 | fn boot_time(&self) -> io::Result { 112 | let mut data: timeval = unsafe { mem::zeroed() }; 113 | sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); 114 | let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); 115 | Ok(ts) 116 | } 117 | 118 | fn battery_life(&self) -> io::Result { 119 | let mut life: usize = 0; sysctl!(BATTERY_LIFE, &mut life, mem::size_of::()); 120 | let mut time: i32 = 0; sysctl!(BATTERY_TIME, &mut time, mem::size_of::()); 121 | Ok(BatteryLife { 122 | remaining_capacity: life as f32 / 100.0, 123 | remaining_time: time::Duration::from_secs(if time < 0 { 0 } else { time as u64 }), 124 | }) 125 | } 126 | 127 | fn on_ac_power(&self) -> io::Result { 128 | let mut on: usize = 0; sysctl!(ACLINE, &mut on, mem::size_of::()); 129 | Ok(on == 1) 130 | } 131 | 132 | fn mounts(&self) -> io::Result> { 133 | let mut mptr: *mut statfs = ptr::null_mut(); 134 | let len = unsafe { getmntinfo(&mut mptr, 1_i32) }; 135 | if len < 1 { 136 | return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) 137 | } 138 | let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; 139 | Ok(mounts.iter().map(|m| statfs_to_fs(&m)).collect::>()) 140 | } 141 | 142 | fn mount_at>(&self, path: P) -> io::Result { 143 | let path = ffi::CString::new(path.as_ref().as_os_str().as_bytes())?; 144 | let mut sfs: statfs = unsafe { mem::zeroed() }; 145 | if unsafe { statfs(path.as_ptr() as *const _, &mut sfs) } != 0 { 146 | return Err(io::Error::new(io::ErrorKind::Other, "statfs() failed")); 147 | } 148 | Ok(statfs_to_fs(&sfs)) 149 | } 150 | 151 | fn block_device_statistics(&self) -> io::Result> { 152 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 153 | } 154 | 155 | fn networks(&self) -> io::Result> { 156 | unix::networks() 157 | } 158 | 159 | fn network_stats(&self, _interface: &str) -> io::Result { 160 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 161 | } 162 | 163 | fn cpu_temp(&self) -> io::Result { 164 | let mut temp: i32 = 0; sysctl!(CPU0TEMP, &mut temp, mem::size_of::()); 165 | // The sysctl interface supports more units, but both amdtemp and coretemp always 166 | // use IK (deciKelvin) 167 | Ok((temp as f32 - 2731.5) / 10.0) 168 | } 169 | 170 | fn socket_stats(&self) -> io::Result { 171 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 172 | } 173 | } 174 | 175 | 176 | fn measure_cpu() -> io::Result> { 177 | let cpus = *CP_TIMES_SIZE / mem::size_of::(); 178 | let mut data: Vec = Vec::with_capacity(cpus); 179 | unsafe { data.set_len(cpus) }; 180 | 181 | sysctl!(KERN_CP_TIMES, &mut data[0], *CP_TIMES_SIZE); 182 | Ok(data.into_iter().map(|cpu| cpu.into()).collect()) 183 | } 184 | 185 | fn zfs_arc_size() -> io::Result { 186 | let mut zfs_arc: usize = 0; 187 | sysctl!(ZFS_ARC_SIZE, &mut zfs_arc, mem::size_of::()); 188 | Ok(zfs_arc as u64) 189 | } 190 | 191 | fn statfs_to_fs(fs: &statfs) -> Filesystem { 192 | Filesystem { 193 | files: (fs.f_files as usize).saturating_sub(fs.f_ffree as usize), 194 | files_total: fs.f_files as usize, 195 | files_avail: fs.f_ffree as usize, 196 | free: ByteSize::b(fs.f_bfree * fs.f_bsize), 197 | avail: ByteSize::b(fs.f_bavail as u64 * fs.f_bsize), 198 | total: ByteSize::b(fs.f_blocks * fs.f_bsize), 199 | name_max: fs.f_namemax as usize, 200 | fs_type: unsafe { ffi::CStr::from_ptr(&fs.f_fstypename[0]).to_string_lossy().into_owned() }, 201 | fs_mounted_from: unsafe { ffi::CStr::from_ptr(&fs.f_mntfromname[0]).to_string_lossy().into_owned() }, 202 | fs_mounted_on: unsafe { ffi::CStr::from_ptr(&fs.f_mntonname[0]).to_string_lossy().into_owned() }, 203 | } 204 | } 205 | 206 | #[link(name = "c")] 207 | extern "C" { 208 | #[link_name = "getmntinfo@FBSD_1.0"] 209 | fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; 210 | } 211 | -------------------------------------------------------------------------------- /src/platform/windows/mod.rs: -------------------------------------------------------------------------------- 1 | use winapi::ctypes::c_char; 2 | use winapi::shared::minwindef::*; 3 | use winapi::shared::winerror::ERROR_SUCCESS; 4 | use winapi::um::{sysinfoapi, winbase}; 5 | use winapi::um::pdh::{ 6 | PDH_FMT_COUNTERVALUE_ITEM_A, 7 | PDH_FMT_DOUBLE, 8 | PDH_HCOUNTER, 9 | PDH_HQUERY, 10 | PDH_FMT_NOCAP100, 11 | PdhAddEnglishCounterA, 12 | PdhCloseQuery, 13 | PdhCollectQueryData, 14 | PdhGetFormattedCounterArrayA, 15 | PdhOpenQueryA, 16 | }; 17 | 18 | mod disk; 19 | mod network_interfaces; 20 | mod socket; 21 | 22 | use super::common::*; 23 | use crate::data::*; 24 | 25 | use std::ffi::CStr; 26 | use std::slice::from_raw_parts; 27 | use std::cmp; 28 | use std::{io, mem, path}; 29 | 30 | fn u16_array_to_string(p: *const u16) -> String { 31 | use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; 32 | unsafe { 33 | if p.is_null() { 34 | return String::new(); 35 | } 36 | let mut amt = 0usize; 37 | while !p.add(amt).is_null() && *p.add(amt) != 0u16 { 38 | amt += 1; 39 | } 40 | let u16s = from_raw_parts(p, amt); 41 | decode_utf16(u16s.iter().cloned()) 42 | .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) 43 | .collect::() 44 | } 45 | } 46 | 47 | fn c_char_array_to_string(p: *const c_char) -> String { 48 | unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() } 49 | } 50 | 51 | fn last_os_error() -> io::Result<()> { 52 | Err(io::Error::last_os_error()) 53 | } 54 | 55 | pub struct PlatformImpl; 56 | 57 | /// An implementation of `Platform` for Windows. 58 | /// See `Platform` for documentation. 59 | impl Platform for PlatformImpl { 60 | #[inline(always)] 61 | fn new() -> Self { 62 | PlatformImpl 63 | } 64 | 65 | fn cpu_load(&self) -> io::Result>> { 66 | const PDH_MORE_DATA: u32 = 0x8000_07D2; 67 | 68 | struct QueryHandle(PDH_HQUERY); 69 | 70 | // Pdh is supposedly synchronized internally with a mutex 71 | unsafe impl Send for QueryHandle {} 72 | unsafe impl Sync for QueryHandle {} 73 | 74 | impl Drop for QueryHandle { 75 | fn drop(&mut self){ 76 | unsafe { 77 | PdhCloseQuery(self.0); 78 | } 79 | } 80 | } 81 | 82 | struct CounterHandle(PDH_HCOUNTER); 83 | 84 | unsafe impl Send for CounterHandle {} 85 | unsafe impl Sync for CounterHandle {} 86 | 87 | struct PerformanceCounter { 88 | query: QueryHandle, 89 | counter: CounterHandle, 90 | } 91 | 92 | impl PerformanceCounter { 93 | pub fn new(key: &CStr) -> io::Result { 94 | let mut query = std::ptr::null_mut(); 95 | let status = unsafe { 96 | PdhOpenQueryA(std::ptr::null(), 0, &mut query) 97 | }; 98 | 99 | if status as u32 != ERROR_SUCCESS { 100 | return Err(io::Error::from_raw_os_error(status)); 101 | } 102 | 103 | let query = QueryHandle(query); 104 | 105 | let mut counter = std::ptr::null_mut(); 106 | let status = unsafe { 107 | PdhAddEnglishCounterA(query.0, key.as_ptr(), 0, &mut counter) 108 | }; 109 | 110 | if status as u32 != ERROR_SUCCESS { 111 | return Err(io::Error::from_raw_os_error(status)); 112 | } 113 | 114 | let counter = CounterHandle(counter); 115 | 116 | let status = unsafe { 117 | PdhCollectQueryData(query.0) 118 | }; 119 | 120 | if status as u32 != ERROR_SUCCESS { 121 | return Err(io::Error::from_raw_os_error(status)); 122 | } 123 | 124 | Ok(Self { 125 | query, 126 | counter, 127 | }) 128 | } 129 | 130 | fn next_value(&self) -> io::Result> { 131 | let status = unsafe { 132 | PdhCollectQueryData(self.query.0) 133 | }; 134 | 135 | if status as u32 != ERROR_SUCCESS { 136 | return Err(io::Error::from_raw_os_error(status)); 137 | } 138 | 139 | let mut buffer_size = 0; 140 | let mut item_count = 0; 141 | let status = unsafe { 142 | PdhGetFormattedCounterArrayA(self.counter.0, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &mut buffer_size, &mut item_count, std::ptr::null_mut()) 143 | }; 144 | 145 | match status as u32 { 146 | PDH_MORE_DATA => {}, 147 | ERROR_SUCCESS => { 148 | return Ok(Vec::new()); 149 | } 150 | _ => { 151 | return Err(io::Error::from_raw_os_error(status)); 152 | } 153 | } 154 | 155 | let mut items = Vec::new(); 156 | items.reserve(item_count as usize * std::mem::size_of::()); 157 | let status = unsafe { 158 | PdhGetFormattedCounterArrayA(self.counter.0, PDH_FMT_DOUBLE, &mut buffer_size, &mut item_count, items.as_mut_ptr()) 159 | }; 160 | 161 | if status as u32 != ERROR_SUCCESS { 162 | return Err(io::Error::from_raw_os_error(status)); 163 | } 164 | 165 | unsafe { 166 | items.set_len(item_count as usize); 167 | } 168 | 169 | Ok(items) 170 | } 171 | } 172 | 173 | let user_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% User Time\0").unwrap())?; 174 | let idle_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% Idle Time\0").unwrap())?; 175 | let system_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% Privileged Time\0").unwrap())?; 176 | let interrupt_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% Interrupt Time\0").unwrap())?; 177 | 178 | Ok(DelayedMeasurement::new(Box::new(move || { 179 | let user = user_counter.next_value()?; 180 | let idle = idle_counter.next_value()?; 181 | let system = system_counter.next_value()?; 182 | let interrupt = interrupt_counter.next_value()?; 183 | 184 | let count = user.iter().filter(|item| unsafe { CStr::from_ptr(item.szName).to_string_lossy() } != "_Total").count(); 185 | 186 | let mut ret = vec![ 187 | CPULoad { 188 | user: 0.0, 189 | nice: 0.0, 190 | system: 0.0, 191 | interrupt: 0.0, 192 | idle: 0.0, 193 | platform: PlatformCpuLoad {}, 194 | }; 195 | count 196 | ]; 197 | 198 | for item in user { 199 | let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; 200 | if let Ok(n) = name.parse::(){ 201 | ret[n].user = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; 202 | } 203 | } 204 | 205 | for item in idle { 206 | let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; 207 | if let Ok(n) = name.parse::(){ 208 | ret[n].idle = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; 209 | } 210 | } 211 | 212 | for item in system { 213 | let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; 214 | if let Ok(n) = name.parse::(){ 215 | ret[n].system = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; 216 | } 217 | } 218 | 219 | for item in interrupt { 220 | let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; 221 | if let Ok(n) = name.parse::(){ 222 | ret[n].interrupt = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; 223 | } 224 | } 225 | 226 | Ok(ret) 227 | }))) 228 | } 229 | 230 | fn load_average(&self) -> io::Result { 231 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 232 | } 233 | 234 | fn memory(&self) -> io::Result { 235 | PlatformMemory::new().map(|pm| pm.to_memory()) 236 | } 237 | 238 | fn swap(&self) -> io::Result { 239 | PlatformMemory::new().map(|pm| pm.to_swap()) 240 | } 241 | 242 | fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { 243 | let pm = PlatformMemory::new()?; 244 | Ok((pm.clone().to_memory(), pm.to_swap())) 245 | } 246 | 247 | fn uptime(&self) -> io::Result { 248 | let since_boot: u64 = unsafe { sysinfoapi::GetTickCount64() }; 249 | Ok(Duration::from_millis(since_boot)) 250 | } 251 | 252 | fn battery_life(&self) -> io::Result { 253 | let status = power_status(); 254 | if status.BatteryFlag == 128 { 255 | return Err(io::Error::new(io::ErrorKind::Other, "Battery absent")); 256 | } 257 | if status.BatteryFlag == 255 { 258 | return Err(io::Error::new( 259 | io::ErrorKind::Other, 260 | "Battery status unknown", 261 | )); 262 | } 263 | Ok(BatteryLife { 264 | remaining_capacity: status.BatteryLifePercent as f32 / 100.0, 265 | remaining_time: Duration::from_secs(status.BatteryLifeTime as u64), 266 | }) 267 | } 268 | 269 | fn on_ac_power(&self) -> io::Result { 270 | Ok(power_status().ACLineStatus == 1) 271 | } 272 | 273 | fn mounts(&self) -> io::Result> { 274 | disk::drives() 275 | } 276 | 277 | fn block_device_statistics(&self) -> io::Result> { 278 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 279 | } 280 | 281 | fn networks(&self) -> io::Result> { 282 | network_interfaces::get() 283 | } 284 | 285 | fn network_stats(&self, _interface: &str) -> io::Result { 286 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 287 | } 288 | 289 | fn cpu_temp(&self) -> io::Result { 290 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 291 | } 292 | 293 | fn socket_stats(&self) -> io::Result { 294 | socket::get() 295 | } 296 | } 297 | 298 | fn power_status() -> winbase::SYSTEM_POWER_STATUS { 299 | let mut status = winbase::SYSTEM_POWER_STATUS { 300 | ACLineStatus: 0, 301 | BatteryFlag: 0, 302 | BatteryLifePercent: 0, 303 | Reserved1: 0, 304 | BatteryLifeTime: 0, 305 | BatteryFullLifeTime: 0, 306 | }; 307 | unsafe { 308 | winbase::GetSystemPowerStatus(&mut status); 309 | } 310 | status 311 | } 312 | 313 | impl PlatformMemory { 314 | // Retrieve platform memory information 315 | fn new() -> io::Result { 316 | let mut status = sysinfoapi::MEMORYSTATUSEX { 317 | dwLength: mem::size_of::() as DWORD, 318 | dwMemoryLoad: 0, 319 | ullTotalPhys: 0, 320 | ullAvailPhys: 0, 321 | ullTotalPageFile: 0, 322 | ullAvailPageFile: 0, 323 | ullTotalVirtual: 0, 324 | ullAvailVirtual: 0, 325 | ullAvailExtendedVirtual: 0, 326 | }; 327 | let ret = unsafe { 328 | sysinfoapi::GlobalMemoryStatusEx(&mut status) 329 | }; 330 | if ret == 0 { 331 | return Err(io::Error::last_os_error()) 332 | } 333 | 334 | Ok(Self { 335 | load: status.dwMemoryLoad, 336 | total_phys: ByteSize::b(status.ullTotalPhys), 337 | avail_phys: ByteSize::b(status.ullAvailPhys), 338 | total_pagefile: ByteSize::b(status.ullTotalPageFile), 339 | avail_pagefile: ByteSize::b(status.ullAvailPageFile), 340 | total_virt: ByteSize::b(status.ullTotalVirtual), 341 | avail_virt: ByteSize::b(status.ullAvailVirtual), 342 | avail_ext: ByteSize::b(status.ullAvailExtendedVirtual), 343 | }) 344 | } 345 | 346 | // Convert the platform memory information to Memory 347 | fn to_memory(self) -> Memory { 348 | Memory { 349 | total: self.total_phys, 350 | free: self.avail_phys, 351 | platform_memory: self, 352 | } 353 | } 354 | 355 | // Convert the platform memory information to Swap 356 | fn to_swap(self) -> Swap { 357 | // Be catious because pagefile and phys don't always sync up 358 | // Despite the name, pagefile includes both physical and swap memory 359 | let total = saturating_sub_bytes(self.total_pagefile, self.total_phys); 360 | let free = saturating_sub_bytes(self.avail_pagefile, self.avail_phys); 361 | Swap { 362 | total, 363 | // Sometimes, especially when swap total is 0, free can exceed total 364 | free: cmp::min(total, free), 365 | platform_swap: self, 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | //! This module provides the data structures that represent system information. 2 | //! 3 | //! They're always the same across all platforms. 4 | 5 | pub use bytesize::ByteSize; 6 | pub use std::collections::BTreeMap; 7 | use std::io; 8 | pub use std::net::{Ipv4Addr, Ipv6Addr}; 9 | use std::ops::Sub; 10 | pub use std::time::Duration; 11 | pub use time::OffsetDateTime; 12 | 13 | #[cfg(feature = "serde")] 14 | use serde::{Deserialize, Serialize}; 15 | 16 | #[inline(always)] 17 | pub fn saturating_sub_bytes(l: ByteSize, r: ByteSize) -> ByteSize { 18 | ByteSize::b(l.as_u64().saturating_sub(r.as_u64())) 19 | } 20 | 21 | /// A wrapper for a measurement that takes time. 22 | /// 23 | /// Time should pass between getting the object and calling .done() on it. 24 | pub struct DelayedMeasurement { 25 | res: Box io::Result + Send>, 26 | } 27 | 28 | impl DelayedMeasurement { 29 | #[inline(always)] 30 | pub fn new(f: Box io::Result + Send>) -> DelayedMeasurement { 31 | DelayedMeasurement { res: f } 32 | } 33 | 34 | #[inline(always)] 35 | pub fn done(&self) -> io::Result { 36 | (self.res)() 37 | } 38 | } 39 | 40 | #[cfg(not(target_os = "linux"))] 41 | #[cfg_attr( 42 | feature = "serde", 43 | derive(Serialize, Deserialize), 44 | serde(crate = "the_serde") 45 | )] 46 | #[derive(Debug, Clone)] 47 | pub struct PlatformCpuLoad {} 48 | 49 | #[cfg(target_os = "linux")] 50 | #[cfg_attr( 51 | feature = "serde", 52 | derive(Serialize, Deserialize), 53 | serde(crate = "the_serde") 54 | )] 55 | #[derive(Debug, Clone)] 56 | pub struct PlatformCpuLoad { 57 | pub iowait: f32, 58 | } 59 | 60 | impl PlatformCpuLoad { 61 | #[cfg(target_os = "linux")] 62 | #[inline(always)] 63 | pub fn avg_add(self, rhs: &Self) -> Self { 64 | PlatformCpuLoad { 65 | iowait: (self.iowait + rhs.iowait) / 2.0, 66 | } 67 | } 68 | 69 | #[cfg(not(target_os = "linux"))] 70 | #[inline(always)] 71 | pub fn avg_add(self, _rhs: &Self) -> Self { 72 | PlatformCpuLoad {} 73 | } 74 | 75 | #[cfg(target_os = "linux")] 76 | #[inline(always)] 77 | pub fn zero() -> Self { 78 | PlatformCpuLoad { iowait: 0.0 } 79 | } 80 | 81 | #[cfg(not(target_os = "linux"))] 82 | #[inline(always)] 83 | pub fn zero() -> Self { 84 | PlatformCpuLoad {} 85 | } 86 | 87 | #[cfg(target_os = "linux")] 88 | #[inline(always)] 89 | pub fn from(input: f32) -> Self { 90 | PlatformCpuLoad { iowait: input } 91 | } 92 | 93 | #[cfg(not(target_os = "linux"))] 94 | #[inline(always)] 95 | pub fn from(_input: f32) -> Self { 96 | PlatformCpuLoad {} 97 | } 98 | 99 | #[cfg(target_os = "linux")] 100 | #[inline(always)] 101 | pub fn sum(&self) -> f32 { 102 | self.iowait 103 | } 104 | 105 | #[cfg(not(target_os = "linux"))] 106 | #[inline(always)] 107 | pub fn sum(&self) -> f32 { 108 | 0.0 109 | } 110 | } 111 | 112 | #[cfg_attr( 113 | feature = "serde", 114 | derive(Serialize, Deserialize), 115 | serde(crate = "the_serde") 116 | )] 117 | #[derive(Debug, Clone)] 118 | pub struct CPULoad { 119 | pub user: f32, 120 | pub nice: f32, 121 | pub system: f32, 122 | pub interrupt: f32, 123 | pub idle: f32, 124 | pub platform: PlatformCpuLoad, 125 | } 126 | 127 | impl CPULoad { 128 | #[inline(always)] 129 | pub fn avg_add(self, rhs: &Self) -> Self { 130 | CPULoad { 131 | user: (self.user + rhs.user) / 2.0, 132 | nice: (self.nice + rhs.nice) / 2.0, 133 | system: (self.system + rhs.system) / 2.0, 134 | interrupt: (self.interrupt + rhs.interrupt) / 2.0, 135 | idle: (self.idle + rhs.idle) / 2.0, 136 | platform: self.platform.avg_add(&rhs.platform), 137 | } 138 | } 139 | } 140 | 141 | #[cfg_attr( 142 | feature = "serde", 143 | derive(Serialize, Deserialize), 144 | serde(crate = "the_serde") 145 | )] 146 | #[derive(Debug, Clone, Copy)] 147 | pub struct CpuTime { 148 | pub user: usize, 149 | pub nice: usize, 150 | pub system: usize, 151 | pub interrupt: usize, 152 | pub idle: usize, 153 | pub other: usize, 154 | } 155 | 156 | impl<'a> Sub<&'a CpuTime> for CpuTime { 157 | type Output = CpuTime; 158 | 159 | #[inline(always)] 160 | fn sub(self, rhs: &CpuTime) -> CpuTime { 161 | CpuTime { 162 | user: self.user.saturating_sub(rhs.user), 163 | nice: self.nice.saturating_sub(rhs.nice), 164 | system: self.system.saturating_sub(rhs.system), 165 | interrupt: self.interrupt.saturating_sub(rhs.interrupt), 166 | idle: self.idle.saturating_sub(rhs.idle), 167 | other: self.other.saturating_sub(rhs.other), 168 | } 169 | } 170 | } 171 | 172 | impl CpuTime { 173 | pub fn to_cpuload(&self) -> CPULoad { 174 | let total = self.user + self.nice + self.system + self.interrupt + self.idle + self.other; 175 | if total == 0 { 176 | CPULoad { 177 | user: 0.0, 178 | nice: 0.0, 179 | system: 0.0, 180 | interrupt: 0.0, 181 | idle: 0.0, 182 | platform: PlatformCpuLoad::zero(), 183 | } 184 | } else { 185 | CPULoad { 186 | user: self.user as f32 / total as f32, 187 | nice: self.nice as f32 / total as f32, 188 | system: self.system as f32 / total as f32, 189 | interrupt: self.interrupt as f32 / total as f32, 190 | idle: self.idle as f32 / total as f32, 191 | platform: PlatformCpuLoad::from(self.other as f32 / total as f32), 192 | } 193 | } 194 | } 195 | } 196 | 197 | #[cfg_attr( 198 | feature = "serde", 199 | derive(Serialize, Deserialize), 200 | serde(crate = "the_serde") 201 | )] 202 | #[derive(Debug, Clone)] 203 | pub struct LoadAverage { 204 | pub one: f32, 205 | pub five: f32, 206 | pub fifteen: f32, 207 | } 208 | 209 | #[cfg(target_os = "windows")] 210 | #[cfg_attr( 211 | feature = "serde", 212 | derive(Serialize, Deserialize), 213 | serde(crate = "the_serde") 214 | )] 215 | #[derive(Debug, Clone)] 216 | pub struct PlatformMemory { 217 | pub load: u32, 218 | pub total_phys: ByteSize, 219 | pub avail_phys: ByteSize, 220 | pub total_pagefile: ByteSize, 221 | pub avail_pagefile: ByteSize, 222 | pub total_virt: ByteSize, 223 | pub avail_virt: ByteSize, 224 | pub avail_ext: ByteSize, 225 | } 226 | 227 | #[cfg(target_os = "freebsd")] 228 | #[cfg_attr( 229 | feature = "serde", 230 | derive(Serialize, Deserialize), 231 | serde(crate = "the_serde") 232 | )] 233 | #[derive(Debug, Clone)] 234 | pub struct PlatformMemory { 235 | pub active: ByteSize, 236 | pub inactive: ByteSize, 237 | pub wired: ByteSize, 238 | pub cache: ByteSize, 239 | pub zfs_arc: ByteSize, 240 | pub free: ByteSize, 241 | } 242 | 243 | #[cfg(any(target_os = "openbsd", target_os = "illumos", target_os = "solaris"))] 244 | #[cfg_attr( 245 | feature = "serde", 246 | derive(Serialize, Deserialize), 247 | serde(crate = "the_serde") 248 | )] 249 | #[derive(Debug, Clone)] 250 | pub struct PlatformMemory { 251 | pub total: ByteSize, 252 | pub active: ByteSize, 253 | pub inactive: ByteSize, 254 | pub wired: ByteSize, 255 | pub cache: ByteSize, 256 | pub free: ByteSize, 257 | pub paging: ByteSize, 258 | pub sw: ByteSize, 259 | pub swinuse: ByteSize, 260 | pub swonly: ByteSize, 261 | } 262 | 263 | #[cfg(target_os = "netbsd")] 264 | #[cfg_attr( 265 | feature = "serde", 266 | derive(Serialize, Deserialize), 267 | serde(crate = "the_serde") 268 | )] 269 | #[derive(Debug, Clone)] 270 | pub struct PlatformMemory { 271 | pub pageshift: i64, 272 | pub total: ByteSize, 273 | pub active: ByteSize, 274 | pub inactive: ByteSize, 275 | pub wired: ByteSize, 276 | pub free: ByteSize, 277 | pub paging: ByteSize, 278 | pub anon: ByteSize, 279 | pub files: ByteSize, 280 | pub exec: ByteSize, 281 | pub sw: ByteSize, 282 | pub swinuse: ByteSize, 283 | pub swonly: ByteSize, 284 | } 285 | 286 | #[cfg(target_vendor = "apple")] 287 | #[cfg_attr( 288 | feature = "serde", 289 | derive(Serialize, Deserialize), 290 | serde(crate = "the_serde") 291 | )] 292 | #[derive(Debug, Clone)] 293 | pub struct PlatformMemory { 294 | pub total: ByteSize, 295 | pub active: ByteSize, 296 | pub inactive: ByteSize, 297 | pub wired: ByteSize, 298 | pub free: ByteSize, 299 | pub purgeable: ByteSize, 300 | pub speculative: ByteSize, 301 | pub compressor: ByteSize, 302 | pub throttled: ByteSize, 303 | pub external: ByteSize, 304 | pub internal: ByteSize, 305 | pub uncompressed_in_compressor: ByteSize, 306 | } 307 | 308 | #[cfg(any(target_os = "linux", target_os = "android"))] 309 | #[cfg_attr( 310 | feature = "serde", 311 | derive(Serialize, Deserialize), 312 | serde(crate = "the_serde") 313 | )] 314 | #[derive(Debug, Clone)] 315 | pub struct PlatformMemory { 316 | pub meminfo: BTreeMap, 317 | } 318 | 319 | #[cfg_attr( 320 | feature = "serde", 321 | derive(Serialize, Deserialize), 322 | serde(crate = "the_serde") 323 | )] 324 | #[derive(Debug, Clone)] 325 | pub struct Memory { 326 | pub total: ByteSize, 327 | pub free: ByteSize, 328 | pub platform_memory: PlatformMemory, 329 | } 330 | 331 | #[cfg(any( 332 | target_os = "windows", 333 | target_os = "linux", 334 | target_os = "android", 335 | target_os = "openbsd", 336 | target_os = "netbsd", 337 | target_os = "illumos", 338 | target_os = "solaris" 339 | ))] 340 | pub type PlatformSwap = PlatformMemory; 341 | 342 | #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] 343 | #[cfg_attr( 344 | feature = "serde", 345 | derive(Serialize, Deserialize), 346 | serde(crate = "the_serde") 347 | )] 348 | #[derive(Debug, Clone)] 349 | pub struct PlatformSwap { 350 | pub total: ByteSize, 351 | pub avail: ByteSize, 352 | pub used: ByteSize, 353 | pub pagesize: ByteSize, 354 | pub encrypted: bool, 355 | } 356 | 357 | #[cfg_attr( 358 | feature = "serde", 359 | derive(Serialize, Deserialize), 360 | serde(crate = "the_serde") 361 | )] 362 | #[derive(Debug, Clone)] 363 | pub struct Swap { 364 | pub total: ByteSize, 365 | pub free: ByteSize, 366 | pub platform_swap: PlatformSwap, 367 | } 368 | 369 | #[cfg_attr( 370 | feature = "serde", 371 | derive(Serialize, Deserialize), 372 | serde(crate = "the_serde") 373 | )] 374 | #[derive(Debug, Clone)] 375 | pub struct BatteryLife { 376 | pub remaining_capacity: f32, 377 | pub remaining_time: Duration, 378 | } 379 | 380 | #[cfg_attr( 381 | feature = "serde", 382 | derive(Serialize, Deserialize), 383 | serde(crate = "the_serde") 384 | )] 385 | #[derive(Debug, Clone)] 386 | pub struct Filesystem { 387 | /// Used file nodes in filesystem 388 | pub files: usize, 389 | /// Total file nodes in filesystem 390 | pub files_total: usize, 391 | /// Free nodes available to non-superuser 392 | pub files_avail: usize, 393 | /// Free bytes in filesystem 394 | pub free: ByteSize, 395 | /// Free bytes available to non-superuser 396 | pub avail: ByteSize, 397 | /// Total bytes in filesystem 398 | pub total: ByteSize, 399 | /// Maximum filename length 400 | pub name_max: usize, 401 | pub fs_type: String, 402 | pub fs_mounted_from: String, 403 | pub fs_mounted_on: String, 404 | } 405 | 406 | #[cfg_attr( 407 | feature = "serde", 408 | derive(Serialize, Deserialize), 409 | serde(crate = "the_serde") 410 | )] 411 | #[derive(Debug, Clone)] 412 | pub struct BlockDeviceStats { 413 | pub name: String, 414 | pub read_ios: usize, 415 | pub read_merges: usize, 416 | pub read_sectors: usize, 417 | pub read_ticks: usize, 418 | pub write_ios: usize, 419 | pub write_merges: usize, 420 | pub write_sectors: usize, 421 | pub write_ticks: usize, 422 | pub in_flight: usize, 423 | pub io_ticks: usize, 424 | pub time_in_queue: usize, 425 | } 426 | 427 | #[cfg_attr( 428 | feature = "serde", 429 | derive(Serialize, Deserialize), 430 | serde(crate = "the_serde") 431 | )] 432 | #[derive(Debug, Clone, PartialEq)] 433 | pub enum IpAddr { 434 | Empty, 435 | Unsupported, 436 | V4(Ipv4Addr), 437 | V6(Ipv6Addr), 438 | } 439 | 440 | #[cfg_attr( 441 | feature = "serde", 442 | derive(Serialize, Deserialize), 443 | serde(crate = "the_serde") 444 | )] 445 | #[derive(Debug, Clone)] 446 | pub struct NetworkAddrs { 447 | pub addr: IpAddr, 448 | pub netmask: IpAddr, 449 | } 450 | 451 | #[cfg_attr( 452 | feature = "serde", 453 | derive(Serialize, Deserialize), 454 | serde(crate = "the_serde") 455 | )] 456 | #[derive(Debug, Clone)] 457 | pub struct Network { 458 | pub name: String, 459 | pub addrs: Vec, 460 | } 461 | 462 | #[cfg_attr( 463 | feature = "serde", 464 | derive(Serialize, Deserialize), 465 | serde(crate = "the_serde") 466 | )] 467 | #[derive(Debug, Clone)] 468 | pub struct NetworkStats { 469 | pub rx_bytes: ByteSize, 470 | pub tx_bytes: ByteSize, 471 | pub rx_packets: u64, 472 | pub tx_packets: u64, 473 | pub rx_errors: u64, 474 | pub tx_errors: u64, 475 | } 476 | 477 | #[cfg_attr( 478 | feature = "serde", 479 | derive(Serialize, Deserialize), 480 | serde(crate = "the_serde") 481 | )] 482 | #[derive(Debug, Clone)] 483 | pub struct SocketStats { 484 | pub tcp_sockets_in_use: usize, 485 | pub tcp_sockets_orphaned: usize, 486 | pub udp_sockets_in_use: usize, 487 | pub tcp6_sockets_in_use: usize, 488 | pub udp6_sockets_in_use: usize, 489 | } 490 | -------------------------------------------------------------------------------- /src/platform/windows/network_interfaces.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_void, free, malloc, size_t}; 2 | use winapi::ctypes::*; 3 | use winapi::shared::minwindef::*; 4 | use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS}; 5 | use winapi::shared::ws2def::{AF_INET6, AF_INET, AF_UNSPEC, SOCKADDR}; 6 | use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH; 7 | 8 | use super::{c_char_array_to_string, last_os_error, u16_array_to_string}; 9 | use crate::data::*; 10 | 11 | use std::collections::BTreeMap; 12 | use std::io::Write; 13 | use std::{io, mem, ptr}; 14 | 15 | // unions with non-`Copy` fields are unstable (see issue #32836) 16 | // #[repr(C)] 17 | // union AlimentOrLengthIfIndex { 18 | // aliment: c_ulonglong, 19 | // length_ifindex: LengthIfIndex, 20 | // } 21 | 22 | #[repr(C)] 23 | struct LengthIfIndex { 24 | length: ULONG, 25 | ifindex: DWORD, 26 | } 27 | 28 | #[repr(C)] 29 | struct LengthFlags { 30 | length: ULONG, 31 | flags: DWORD, 32 | } 33 | 34 | #[repr(C)] 35 | struct SoketAddress { 36 | lp_sockaddr: *mut SOCKADDR, 37 | i_sockaddr_length: c_int, 38 | } 39 | 40 | #[repr(C)] 41 | struct IpAdapterPrefix { 42 | aol: LengthIfIndex, 43 | next: *mut IpAdapterPrefix, 44 | address: SoketAddress, 45 | prefix_length: ULONG, 46 | } 47 | 48 | #[repr(C)] 49 | struct IpAdapterUnicastAddress { 50 | aol: LengthFlags, 51 | next: *mut IpAdapterUnicastAddress, 52 | address: SoketAddress, 53 | prefix_origin: c_int, 54 | suffix_origin: c_int, 55 | dad_state: c_int, 56 | valid_lifetime: ULONG, 57 | preferred_lifetime: ULONG, 58 | lease_lifetime: ULONG, 59 | on_link_prefix_length: u8, 60 | } 61 | 62 | const MAX_ADAPTER_ADDRESS_LENGTH: usize = 8; 63 | 64 | #[repr(C)] 65 | struct IpAdapterAddresses { 66 | aol: LengthIfIndex, 67 | next: *mut IpAdapterAddresses, 68 | adapter_name: *mut c_char, 69 | first_unicass_address: *mut IpAdapterUnicastAddress, 70 | first_anycass_address: *const c_void, 71 | first_multicass_address: *const c_void, 72 | first_dns_server_address: *const c_void, 73 | dns_suffix: *mut wchar_t, 74 | description: *mut wchar_t, 75 | friendly_name: *mut wchar_t, 76 | physical_address: [u8; MAX_ADAPTER_ADDRESS_LENGTH], 77 | physical_address_length: DWORD, 78 | flags: DWORD, 79 | mtu: DWORD, 80 | if_type: DWORD, 81 | oper_status: c_int, 82 | ipv6_if_index: DWORD, 83 | zone_indices: [DWORD; 16], 84 | first_prefix: *mut IpAdapterPrefix, 85 | } 86 | 87 | // https://msdn.microsoft.com/en-us/library/aa365915(v=vs.85).aspx 88 | // https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366066(d=printer,v=vs.85).aspx 89 | // C:\Program Files (x86)\Windows Kits\8.1\Include\um\IPHlpApi.h 90 | #[link(name = "iphlpapi")] 91 | extern "system" { 92 | fn GetAdaptersAddresses( 93 | family: ULONG, 94 | flags: ULONG, 95 | reserved: *const c_void, 96 | addresses: *mut IpAdapterAddresses, 97 | size: *mut ULONG, 98 | ) -> ULONG; 99 | } 100 | 101 | const WORKING_BUFFER_SIZEL: size_t = 15000; 102 | 103 | pub fn get() -> io::Result> { 104 | let mut new_size: ULONG = WORKING_BUFFER_SIZEL as ULONG; 105 | let mut p_adapter: *mut IpAdapterAddresses; 106 | loop { 107 | unsafe { 108 | p_adapter = malloc(new_size as size_t) as *mut IpAdapterAddresses; 109 | if p_adapter.is_null() { 110 | panic!("Failed: malloc!"); 111 | } 112 | let res_code = GetAdaptersAddresses( 113 | 0, 114 | AF_UNSPEC as ULONG, // ipv4 & ipv6 115 | ptr::null(), 116 | p_adapter, 117 | &mut new_size as *mut ULONG, 118 | ); 119 | match res_code { 120 | // 0 121 | ERROR_SUCCESS => break, 122 | // 111, retry 123 | ERROR_BUFFER_OVERFLOW => { 124 | new_size *= 2; 125 | free(p_adapter as *mut c_void); 126 | continue; 127 | } 128 | _ => { 129 | free(p_adapter as *mut c_void); 130 | last_os_error()?; 131 | } 132 | } 133 | } 134 | } 135 | 136 | let mut map = BTreeMap::new(); 137 | // key->adapter_name, name-> friendly_name, maybe should use the adapter_name all. 138 | unsafe { 139 | let mut cur_p_adapter = p_adapter; 140 | while !cur_p_adapter.is_null() { 141 | // name, mac, etc 142 | let adapter_name = c_char_array_to_string((*cur_p_adapter).adapter_name); 143 | // println!("adapter_name : {}", adapter_name); 144 | 145 | // let dns_suffix = u16_array_to_string((*cur_p_adapter).dns_suffix); 146 | // println!("dns_suffix : {}", dns_suffix); 147 | 148 | let friendly_name = u16_array_to_string((*cur_p_adapter).friendly_name); 149 | // println!("friendly_name: {}", friendly_name); 150 | 151 | // let description = u16_array_to_string((*cur_p_adapter).description); 152 | // println!("description : {}", description); 153 | 154 | // let mac = physical_address_to_string(&(*cur_p_adapter).physical_address, (*cur_p_adapter).physical_address_length); 155 | // println!("mac : {}", mac); 156 | 157 | let mut addrs = Vec::new(); 158 | // ip 159 | let mut cur_p_addr = (*cur_p_adapter).first_unicass_address; 160 | while !cur_p_addr.is_null() { 161 | let addr = parse_addr_and_netmask( 162 | (*cur_p_addr).address.lp_sockaddr, 163 | (*cur_p_addr).on_link_prefix_length, 164 | ); 165 | addrs.push(addr); 166 | // println!("{:?}", addr); 167 | // next addr 168 | cur_p_addr = (*cur_p_addr).next; 169 | } 170 | let network = Network { 171 | name: friendly_name, 172 | addrs, 173 | }; 174 | map.insert(adapter_name, network); 175 | 176 | // next adapter 177 | cur_p_adapter = (*cur_p_adapter).next; 178 | } 179 | } 180 | 181 | unsafe { 182 | free(p_adapter as *mut c_void); 183 | } 184 | Ok(map) 185 | } 186 | 187 | fn _physical_address_to_string(array: [u8; 8], length: DWORD) -> String { 188 | let mut bytes = Vec::with_capacity(length as usize); 189 | for (idx, b) in array.iter().enumerate().take(length as usize) { 190 | if idx == 0 { 191 | write!(&mut bytes, "{:02X}", b).unwrap(); 192 | } else { 193 | write!(&mut bytes, "-{:02X}", b).unwrap(); 194 | } 195 | } 196 | String::from_utf8_lossy(&bytes[..]).into_owned() 197 | } 198 | 199 | // Thanks , copy from unix.rs and some modify 200 | fn parse_addr_and_netmask(aptr: *const SOCKADDR, net_bits: u8) -> NetworkAddrs { 201 | if aptr.is_null() { 202 | return NetworkAddrs { 203 | addr: IpAddr::Empty, 204 | netmask: IpAddr::Empty, 205 | }; 206 | } 207 | let addr = unsafe { *aptr }; 208 | match addr.sa_family as i32 { 209 | AF_INET => { 210 | let addr = IpAddr::V4(Ipv4Addr::new( 211 | addr.sa_data[2] as u8, 212 | addr.sa_data[3] as u8, 213 | addr.sa_data[4] as u8, 214 | addr.sa_data[5] as u8, 215 | )); 216 | let netmask = if net_bits <= 32 { 217 | IpAddr::V4(netmask_v4(net_bits)) 218 | } else { 219 | IpAddr::Empty 220 | }; 221 | NetworkAddrs { addr, netmask } 222 | } 223 | AF_INET6 => { 224 | // This is horrible. 225 | #[allow(clippy::cast_ptr_alignment)] 226 | let addr6: *const SOCKADDR_IN6_LH = aptr as *const SOCKADDR_IN6_LH; 227 | let mut a: [u8; 16] = unsafe { *std::ptr::read_unaligned(addr6).sin6_addr.u.Byte() }; 228 | a[..].reverse(); 229 | let a: [u16; 8] = unsafe { mem::transmute(a) }; 230 | let addr = IpAddr::V6(Ipv6Addr::new( 231 | a[7], 232 | a[6], 233 | a[5], 234 | a[4], 235 | a[3], 236 | a[2], 237 | a[1], 238 | a[0], 239 | )); 240 | let netmask = if net_bits <= 128 { 241 | IpAddr::V6(netmask_v6(net_bits)) 242 | } else { 243 | IpAddr::Empty 244 | }; 245 | NetworkAddrs { addr, netmask } 246 | } 247 | _ => NetworkAddrs { 248 | addr: IpAddr::Empty, 249 | netmask: IpAddr::Empty, 250 | }, 251 | } 252 | } 253 | 254 | // This faster than [u8;4], but v6 is slower if use this.. 255 | // And the scan() method is slower also. 256 | fn netmask_v4(bits: u8) -> Ipv4Addr { 257 | let mut i = (0..4).map(|idx| { 258 | let idx8 = idx << 3; 259 | match (bits as usize > idx8, bits as usize > idx8 + 8) { 260 | (true, true) => 255, 261 | (true, false) => 255u8.wrapping_shl((8 - bits % 8) as u32), 262 | _ => 0, 263 | } 264 | }); 265 | Ipv4Addr::new( 266 | i.next().unwrap(), 267 | i.next().unwrap(), 268 | i.next().unwrap(), 269 | i.next().unwrap(), 270 | ) 271 | } 272 | 273 | fn netmask_v6(bits: u8) -> Ipv6Addr { 274 | let mut tmp = [0u16; 8]; 275 | (0..8).for_each(|idx| { 276 | let idx16 = idx << 4; 277 | match (bits as usize > idx16, bits as usize > idx16 + 16) { 278 | (true, true) => { 279 | tmp[idx] = 0xffff; 280 | } 281 | (true, false) => { 282 | tmp[idx] = 0xffffu16.wrapping_shl((16 - bits % 16) as u32); 283 | } 284 | _ => {} 285 | } 286 | }); 287 | Ipv6Addr::new( 288 | tmp[0], 289 | tmp[1], 290 | tmp[2], 291 | tmp[3], 292 | tmp[4], 293 | tmp[5], 294 | tmp[6], 295 | tmp[7], 296 | ) 297 | } 298 | 299 | #[test] 300 | fn netmask_v4_test() { 301 | vec![ 302 | (0, "0.0.0.0"), 303 | (1, "128.0.0.0"), 304 | (2, "192.0.0.0"), 305 | (3, "224.0.0.0"), 306 | (4, "240.0.0.0"), 307 | (5, "248.0.0.0"), 308 | (6, "252.0.0.0"), 309 | (7, "254.0.0.0"), 310 | (8, "255.0.0.0"), 311 | (9, "255.128.0.0"), 312 | (10, "255.192.0.0"), 313 | (11, "255.224.0.0"), 314 | (12, "255.240.0.0"), 315 | (13, "255.248.0.0"), 316 | (14, "255.252.0.0"), 317 | (15, "255.254.0.0"), 318 | (16, "255.255.0.0"), 319 | (17, "255.255.128.0"), 320 | (18, "255.255.192.0"), 321 | (19, "255.255.224.0"), 322 | (20, "255.255.240.0"), 323 | (21, "255.255.248.0"), 324 | (22, "255.255.252.0"), 325 | (23, "255.255.254.0"), 326 | (24, "255.255.255.0"), 327 | (25, "255.255.255.128"), 328 | (26, "255.255.255.192"), 329 | (27, "255.255.255.224"), 330 | (28, "255.255.255.240"), 331 | (29, "255.255.255.248"), 332 | (30, "255.255.255.252"), 333 | (31, "255.255.255.254"), 334 | (32, "255.255.255.255"), 335 | ].into_iter() 336 | .for_each(|(i, addr)| assert_eq!(netmask_v4(i), addr.parse::().unwrap())) 337 | } 338 | 339 | #[test] 340 | fn netmask_v6_test() { 341 | vec![ 342 | (0, "::"), 343 | (1, "8000::"), 344 | (2, "c000::"), 345 | (3, "e000::"), 346 | (4, "f000::"), 347 | (5, "f800::"), 348 | (6, "fc00::"), 349 | (7, "fe00::"), 350 | (8, "ff00::"), 351 | (9, "ff80::"), 352 | (10, "ffc0::"), 353 | (11, "ffe0::"), 354 | (12, "fff0::"), 355 | (13, "fff8::"), 356 | (14, "fffc::"), 357 | (15, "fffe::"), 358 | (16, "ffff::"), 359 | (17, "ffff:8000::"), 360 | (18, "ffff:c000::"), 361 | (19, "ffff:e000::"), 362 | (20, "ffff:f000::"), 363 | (21, "ffff:f800::"), 364 | (22, "ffff:fc00::"), 365 | (23, "ffff:fe00::"), 366 | (24, "ffff:ff00::"), 367 | (25, "ffff:ff80::"), 368 | (26, "ffff:ffc0::"), 369 | (27, "ffff:ffe0::"), 370 | (28, "ffff:fff0::"), 371 | (29, "ffff:fff8::"), 372 | (30, "ffff:fffc::"), 373 | (31, "ffff:fffe::"), 374 | (32, "ffff:ffff::"), 375 | (33, "ffff:ffff:8000::"), 376 | (34, "ffff:ffff:c000::"), 377 | (35, "ffff:ffff:e000::"), 378 | (36, "ffff:ffff:f000::"), 379 | (37, "ffff:ffff:f800::"), 380 | (38, "ffff:ffff:fc00::"), 381 | (39, "ffff:ffff:fe00::"), 382 | (40, "ffff:ffff:ff00::"), 383 | (41, "ffff:ffff:ff80::"), 384 | (42, "ffff:ffff:ffc0::"), 385 | (43, "ffff:ffff:ffe0::"), 386 | (44, "ffff:ffff:fff0::"), 387 | (45, "ffff:ffff:fff8::"), 388 | (46, "ffff:ffff:fffc::"), 389 | (47, "ffff:ffff:fffe::"), 390 | (48, "ffff:ffff:ffff::"), 391 | (49, "ffff:ffff:ffff:8000::"), 392 | (50, "ffff:ffff:ffff:c000::"), 393 | (51, "ffff:ffff:ffff:e000::"), 394 | (52, "ffff:ffff:ffff:f000::"), 395 | (53, "ffff:ffff:ffff:f800::"), 396 | (54, "ffff:ffff:ffff:fc00::"), 397 | (55, "ffff:ffff:ffff:fe00::"), 398 | (56, "ffff:ffff:ffff:ff00::"), 399 | (57, "ffff:ffff:ffff:ff80::"), 400 | (58, "ffff:ffff:ffff:ffc0::"), 401 | (59, "ffff:ffff:ffff:ffe0::"), 402 | (60, "ffff:ffff:ffff:fff0::"), 403 | (61, "ffff:ffff:ffff:fff8::"), 404 | (62, "ffff:ffff:ffff:fffc::"), 405 | (63, "ffff:ffff:ffff:fffe::"), 406 | (64, "ffff:ffff:ffff:ffff::"), 407 | (65, "ffff:ffff:ffff:ffff:8000::"), 408 | (66, "ffff:ffff:ffff:ffff:c000::"), 409 | (67, "ffff:ffff:ffff:ffff:e000::"), 410 | (68, "ffff:ffff:ffff:ffff:f000::"), 411 | (69, "ffff:ffff:ffff:ffff:f800::"), 412 | (70, "ffff:ffff:ffff:ffff:fc00::"), 413 | (71, "ffff:ffff:ffff:ffff:fe00::"), 414 | (72, "ffff:ffff:ffff:ffff:ff00::"), 415 | (73, "ffff:ffff:ffff:ffff:ff80::"), 416 | (74, "ffff:ffff:ffff:ffff:ffc0::"), 417 | (75, "ffff:ffff:ffff:ffff:ffe0::"), 418 | (76, "ffff:ffff:ffff:ffff:fff0::"), 419 | (77, "ffff:ffff:ffff:ffff:fff8::"), 420 | (78, "ffff:ffff:ffff:ffff:fffc::"), 421 | (79, "ffff:ffff:ffff:ffff:fffe::"), 422 | (80, "ffff:ffff:ffff:ffff:ffff::"), 423 | (81, "ffff:ffff:ffff:ffff:ffff:8000::"), 424 | (82, "ffff:ffff:ffff:ffff:ffff:c000::"), 425 | (83, "ffff:ffff:ffff:ffff:ffff:e000::"), 426 | (84, "ffff:ffff:ffff:ffff:ffff:f000::"), 427 | (85, "ffff:ffff:ffff:ffff:ffff:f800::"), 428 | (86, "ffff:ffff:ffff:ffff:ffff:fc00::"), 429 | (87, "ffff:ffff:ffff:ffff:ffff:fe00::"), 430 | (88, "ffff:ffff:ffff:ffff:ffff:ff00::"), 431 | (89, "ffff:ffff:ffff:ffff:ffff:ff80::"), 432 | (90, "ffff:ffff:ffff:ffff:ffff:ffc0::"), 433 | (91, "ffff:ffff:ffff:ffff:ffff:ffe0::"), 434 | (92, "ffff:ffff:ffff:ffff:ffff:fff0::"), 435 | (93, "ffff:ffff:ffff:ffff:ffff:fff8::"), 436 | (94, "ffff:ffff:ffff:ffff:ffff:fffc::"), 437 | (95, "ffff:ffff:ffff:ffff:ffff:fffe::"), 438 | (96, "ffff:ffff:ffff:ffff:ffff:ffff::"), 439 | (97, "ffff:ffff:ffff:ffff:ffff:ffff:8000:0"), 440 | (98, "ffff:ffff:ffff:ffff:ffff:ffff:c000:0"), 441 | (99, "ffff:ffff:ffff:ffff:ffff:ffff:e000:0"), 442 | (100, "ffff:ffff:ffff:ffff:ffff:ffff:f000:0"), 443 | (101, "ffff:ffff:ffff:ffff:ffff:ffff:f800:0"), 444 | (102, "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0"), 445 | (103, "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0"), 446 | (104, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0"), 447 | (105, "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0"), 448 | (106, "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0"), 449 | (107, "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0"), 450 | (108, "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0"), 451 | (109, "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0"), 452 | (110, "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0"), 453 | (111, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0"), 454 | (112, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0"), 455 | (113, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000"), 456 | (114, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000"), 457 | (115, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000"), 458 | (116, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000"), 459 | (117, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800"), 460 | (118, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00"), 461 | (119, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00"), 462 | (120, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00"), 463 | (121, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80"), 464 | (122, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0"), 465 | (123, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0"), 466 | (124, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0"), 467 | (125, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8"), 468 | (126, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc"), 469 | (127, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"), 470 | (128, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 471 | ].into_iter() 472 | .for_each(|(i, addr)| assert_eq!(netmask_v6(i), addr.parse::().unwrap())) 473 | } 474 | -------------------------------------------------------------------------------- /src/platform/openbsd.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path, ptr, time, fs, mem, ffi, slice}; 2 | use std::os::unix::io::AsRawFd; 3 | use std::os::unix::ffi::OsStrExt; 4 | use std::mem::size_of; 5 | use libc::{c_void, c_int, c_uint, c_ulong, c_uchar, ioctl, sysctl, timeval, statfs, ifaddrs, getifaddrs, if_data, freeifaddrs}; 6 | use crate::data::*; 7 | use super::common::*; 8 | use super::unix; 9 | use super::bsd; 10 | 11 | pub struct PlatformImpl; 12 | 13 | macro_rules! sysctl { 14 | ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => { 15 | { 16 | let mib = &$mib; 17 | let mut size = $size; 18 | if unsafe { sysctl(&mib[0], mib.len() as u32, 19 | $dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck { 20 | return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) 21 | } 22 | size 23 | } 24 | }; 25 | ($mib:expr, $dataptr:expr, $size:expr) => { 26 | sysctl!($mib, $dataptr, $size, true) 27 | } 28 | } 29 | 30 | lazy_static! { 31 | static ref APM_IOC_GETPOWER: c_ulong = 0x40000000u64 | ((size_of::() & 0x1fff) << 16) as u64 | (0x41 << 8) | 3; 32 | // OpenBSD does not have sysctlnametomib, so more copy-pasting of magic numbers from C headers :( 33 | static ref HW_NCPU: [c_int; 2] = [6, 3]; 34 | static ref KERN_CPTIME2: [c_int; 3] = [1, 71, 0]; 35 | static ref KERN_BOOTTIME: [c_int; 2] = [1, 21]; 36 | static ref VM_UVMEXP: [c_int; 2] = [2, 4]; 37 | static ref VFS_BCACHESTAT: [c_int; 3] = [10, 0, 3]; 38 | } 39 | 40 | #[link(name = "c")] 41 | extern "C" { 42 | fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; 43 | } 44 | 45 | /// An implementation of `Platform` for OpenBSD. 46 | /// See `Platform` for documentation. 47 | impl Platform for PlatformImpl { 48 | #[inline(always)] 49 | fn new() -> Self { 50 | PlatformImpl 51 | } 52 | 53 | fn cpu_load(&self) -> io::Result>> { 54 | let loads = measure_cpu()?; 55 | Ok(DelayedMeasurement::new( 56 | Box::new(move || Ok(loads.iter() 57 | .zip(measure_cpu()?.iter()) 58 | .map(|(prev, now)| (*now - prev).to_cpuload()) 59 | .collect::>())))) 60 | } 61 | 62 | fn load_average(&self) -> io::Result { 63 | unix::load_average() 64 | } 65 | 66 | fn memory(&self) -> io::Result { 67 | PlatformMemory::new().map(|pm| pm.to_memory()) 68 | } 69 | 70 | fn swap(&self) -> io::Result { 71 | PlatformMemory::new().map(|pm| pm.to_swap()) 72 | } 73 | 74 | fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { 75 | let pm = PlatformMemory::new()?; 76 | Ok((pm.clone().to_memory(), pm.to_swap())) 77 | } 78 | 79 | fn boot_time(&self) -> io::Result { 80 | let mut data: timeval = unsafe { mem::zeroed() }; 81 | sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); 82 | let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); 83 | Ok(ts) 84 | } 85 | 86 | // /dev/apm is probably the nicest interface I've seen :) 87 | fn battery_life(&self) -> io::Result { 88 | let f = fs::File::open("/dev/apm")?; 89 | let mut info = apm_power_info::default(); 90 | if unsafe { ioctl(f.as_raw_fd(), *APM_IOC_GETPOWER, &mut info) } == -1 { 91 | return Err(io::Error::new(io::ErrorKind::Other, "ioctl() failed")) 92 | } 93 | if info.battery_state == 0xff { // APM_BATT_UNKNOWN 94 | return Err(io::Error::new(io::ErrorKind::Other, "Battery state unknown")) 95 | } 96 | if info.battery_state == 4 { // APM_BATTERY_ABSENT 97 | return Err(io::Error::new(io::ErrorKind::Other, "Battery absent")) 98 | } 99 | Ok(BatteryLife { 100 | remaining_capacity: info.battery_life as f32, 101 | remaining_time: time::Duration::from_secs(info.minutes_left as u64), 102 | }) 103 | } 104 | 105 | fn on_ac_power(&self) -> io::Result { 106 | let f = fs::File::open("/dev/apm")?; 107 | let mut info = apm_power_info::default(); 108 | if unsafe { ioctl(f.as_raw_fd(), *APM_IOC_GETPOWER, &mut info) } == -1 { 109 | return Err(io::Error::new(io::ErrorKind::Other, "ioctl() failed")) 110 | } 111 | Ok(info.ac_state == 0x01) // APM_AC_ON 112 | } 113 | 114 | fn mounts(&self) -> io::Result> { 115 | let mut mptr: *mut statfs = ptr::null_mut(); 116 | let len = unsafe { getmntinfo(&mut mptr, 1 as i32) }; 117 | if len < 1 { 118 | return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) 119 | } 120 | let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; 121 | Ok(mounts.iter().map(|m| statfs_to_fs(&m)).collect::>()) 122 | } 123 | 124 | fn mount_at>(&self, path: P) -> io::Result { 125 | let path = ffi::CString::new(path.as_ref().as_os_str().as_bytes())?; 126 | let mut sfs: statfs = unsafe { mem::zeroed() }; 127 | if unsafe { statfs(path.as_ptr() as *const _, &mut sfs) } != 0 { 128 | return Err(io::Error::new(io::ErrorKind::Other, "statfs() failed")); 129 | } 130 | Ok(statfs_to_fs(&sfs)) 131 | } 132 | 133 | fn block_device_statistics(&self) -> io::Result> { 134 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 135 | } 136 | 137 | fn networks(&self) -> io::Result> { 138 | unix::networks() 139 | } 140 | 141 | fn network_stats(&self, interface: &str) -> io::Result { 142 | let mut rx_bytes: u64 = 0; 143 | let mut tx_bytes: u64 = 0; 144 | let mut rx_packets: u64 = 0; 145 | let mut tx_packets: u64 = 0; 146 | let mut rx_errors: u64 = 0; 147 | let mut tx_errors: u64 = 0; 148 | let mut ifap: *mut ifaddrs = std::ptr::null_mut(); 149 | let mut ifa: *mut ifaddrs; 150 | let mut data: *mut if_data; 151 | unsafe { 152 | getifaddrs(&mut ifap); 153 | ifa = ifap; 154 | // Multiple entries may be same network but for different addresses (ipv4, ipv6, link 155 | // layer) 156 | while !ifa.is_null() { 157 | let c_str: &std::ffi::CStr = std::ffi::CStr::from_ptr((*ifa).ifa_name); 158 | let str_net: &str = match c_str.to_str() { 159 | Ok(v) => v, 160 | Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "C string cannot be converted")) 161 | }; 162 | if interface == str_net { 163 | data = (*ifa).ifa_data as *mut if_data; 164 | // if_data may not be present in every table 165 | if !data.is_null() { 166 | rx_bytes += (*data).ifi_ibytes; 167 | tx_bytes += (*data).ifi_obytes; 168 | rx_packets += (*data).ifi_ipackets; 169 | tx_packets += (*data).ifi_opackets; 170 | rx_errors += (*data).ifi_ierrors; 171 | tx_errors += (*data).ifi_oerrors; 172 | } 173 | } 174 | ifa = (*ifa).ifa_next; 175 | } 176 | freeifaddrs(ifap); 177 | } 178 | Ok(NetworkStats { 179 | rx_bytes: ByteSize::b(rx_bytes), 180 | tx_bytes: ByteSize::b(tx_bytes), 181 | rx_packets, 182 | tx_packets, 183 | rx_errors, 184 | tx_errors, 185 | }) 186 | } 187 | 188 | fn cpu_temp(&self) -> io::Result { 189 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 190 | } 191 | 192 | fn socket_stats(&self) -> io::Result { 193 | Err(io::Error::new(io::ErrorKind::Other, "Not supported")) 194 | } 195 | } 196 | 197 | fn measure_cpu() -> io::Result> { 198 | let mut cpus: usize = 0; 199 | sysctl!(HW_NCPU, &mut cpus, mem::size_of::()); 200 | let mut data: Vec = Vec::with_capacity(cpus); 201 | unsafe { data.set_len(cpus) }; 202 | for i in 0..cpus { 203 | let mut mib = KERN_CPTIME2.clone(); 204 | mib[2] = i as i32; 205 | sysctl!(mib, &mut data[i], mem::size_of::()); 206 | } 207 | Ok(data.into_iter().map(|cpu| cpu.into()).collect()) 208 | } 209 | 210 | impl PlatformMemory { 211 | // Retrieve platform memory information 212 | fn new() -> io::Result { 213 | let mut uvm_info = uvmexp::default(); 214 | sysctl!(VM_UVMEXP, &mut uvm_info, mem::size_of::()); 215 | let mut bcache_info = bcachestats::default(); 216 | sysctl!( 217 | VFS_BCACHESTAT, 218 | &mut bcache_info, 219 | mem::size_of::() 220 | ); 221 | 222 | Ok(Self { 223 | total: ByteSize::kib((uvm_info.npages << *bsd::PAGESHIFT) as u64), 224 | active: ByteSize::kib((uvm_info.active << *bsd::PAGESHIFT) as u64), 225 | inactive: ByteSize::kib((uvm_info.inactive << *bsd::PAGESHIFT) as u64), 226 | wired: ByteSize::kib((uvm_info.wired << *bsd::PAGESHIFT) as u64), 227 | cache: ByteSize::kib((bcache_info.numbufpages << *bsd::PAGESHIFT) as u64), 228 | free: ByteSize::kib((uvm_info.free << *bsd::PAGESHIFT) as u64), 229 | paging: ByteSize::kib((uvm_info.paging << *bsd::PAGESHIFT) as u64), 230 | sw: ByteSize::kib((uvm_info.swpages << *bsd::PAGESHIFT) as u64), 231 | swinuse: ByteSize::kib((uvm_info.swpginuse << *bsd::PAGESHIFT) as u64), 232 | swonly: ByteSize::kib((uvm_info.swpgonly << *bsd::PAGESHIFT) as u64), 233 | }) 234 | } 235 | fn to_memory(self) -> Memory { 236 | Memory { 237 | total: self.total, 238 | free: self.inactive + self.cache + self.free + self.paging, 239 | platform_memory: self, 240 | } 241 | } 242 | fn to_swap(self) -> Swap { 243 | Swap { 244 | total: self.sw, 245 | free: saturating_sub_bytes(self.sw, self.swinuse), 246 | platform_swap: self, 247 | } 248 | } 249 | } 250 | 251 | fn statfs_to_fs(fs: &statfs) -> Filesystem { 252 | Filesystem { 253 | files: (fs.f_files as usize).saturating_sub(fs.f_ffree as usize), 254 | files_total: fs.f_files as usize, 255 | files_avail: fs.f_ffree as usize, 256 | free: ByteSize::b(fs.f_bfree * fs.f_bsize as u64), 257 | avail: ByteSize::b(fs.f_bavail as u64 * fs.f_bsize as u64), 258 | total: ByteSize::b(fs.f_blocks * fs.f_bsize as u64), 259 | name_max: fs.f_namemax as usize, 260 | fs_type: unsafe { ffi::CStr::from_ptr(&fs.f_fstypename[0]).to_string_lossy().into_owned() }, 261 | fs_mounted_from: unsafe { ffi::CStr::from_ptr(&fs.f_mntfromname[0]).to_string_lossy().into_owned() }, 262 | fs_mounted_on: unsafe { ffi::CStr::from_ptr(&fs.f_mntonname[0]).to_string_lossy().into_owned() }, 263 | } 264 | } 265 | 266 | #[repr(C)] 267 | #[derive(Debug, Clone, Copy)] 268 | /// Fields of KERN_CPTIME2: https://github.com/openbsd/src/blob/0403d5bcc6af6e3b8d03ad1c6de319d5acc58295/sys/sys/sched.h#L83 269 | pub struct sysctl_cpu { 270 | user: usize, 271 | nice: usize, 272 | system: usize, 273 | spin: usize, 274 | interrupt: usize, 275 | idle: usize, 276 | } 277 | 278 | impl From for CpuTime { 279 | fn from(cpu: sysctl_cpu) -> CpuTime { 280 | CpuTime { 281 | user: cpu.user, 282 | nice: cpu.nice, 283 | system: cpu.system, 284 | interrupt: cpu.interrupt, 285 | idle: cpu.idle, 286 | other: 0, 287 | } 288 | } 289 | } 290 | 291 | #[derive(Default, Debug)] 292 | #[repr(C)] 293 | struct apm_power_info { 294 | battery_state: c_uchar, 295 | ac_state: c_uchar, 296 | battery_life: c_uchar, 297 | spare1: c_uchar, 298 | minutes_left: c_uint, 299 | spare2: [c_uint; 6], 300 | } 301 | 302 | #[derive(Default, Debug)] 303 | #[repr(C)] 304 | struct bcachestats { 305 | numbufs: i64, /* number of buffers allocated */ 306 | numbufpages: i64, /* number of pages in buffer cache */ 307 | numdirtypages: i64, /* number of dirty free pages */ 308 | numcleanpages: i64, /* number of clean free pages */ 309 | pendingwrites: i64, /* number of pending writes */ 310 | pendingreads: i64, /* number of pending reads */ 311 | numwrites: i64, /* total writes started */ 312 | numreads: i64, /* total reads started */ 313 | cachehits: i64, /* total reads found in cache */ 314 | busymapped: i64, /* number of busy and mapped buffers */ 315 | dmapages: i64, /* dma reachable pages in buffer cache */ 316 | highpages: i64, /* pages above dma region */ 317 | delwribufs: i64, /* delayed write buffers */ 318 | kvaslots: i64, /* kva slots total */ 319 | kvaslots_avail: i64, /* available kva slots */ 320 | highflips: i64, /* total flips to above DMA */ 321 | highflops: i64, /* total failed flips to above DMA */ 322 | dmaflips: i64, /* total flips from high to DMA */ 323 | } 324 | 325 | #[derive(Default, Debug)] 326 | #[repr(C)] 327 | struct uvmexp { 328 | /* vm_page constants */ 329 | pagesize: c_int, /* size of a page (PAGE_SIZE): must be power of 2 */ 330 | pagemask: c_int, /* page mask */ 331 | pageshift: c_int, /* page shift */ 332 | 333 | /* vm_page counters */ 334 | npages: c_int, /* number of pages we manage */ 335 | free: c_int, /* number of free pages */ 336 | active: c_int, /* number of active pages */ 337 | inactive: c_int, /* number of pages that we free'd but may want back */ 338 | paging: c_int, /* number of pages in the process of being paged out */ 339 | wired: c_int, /* number of wired pages */ 340 | 341 | zeropages: c_int, /* number of zero'd pages */ 342 | reserve_pagedaemon: c_int, /* number of pages reserved for pagedaemon */ 343 | reserve_kernel: c_int, /* number of pages reserved for kernel */ 344 | anonpages: c_int, /* number of pages used by anon pagers */ 345 | vnodepages: c_int, /* number of pages used by vnode page cache */ 346 | vtextpages: c_int, /* number of pages used by vtext vnodes */ 347 | 348 | /* pageout params */ 349 | freemin: c_int, /* min number of free pages */ 350 | freetarg: c_int, /* target number of free pages */ 351 | inactarg: c_int, /* target number of inactive pages */ 352 | wiredmax: c_int, /* max number of wired pages */ 353 | anonmin: c_int, /* min threshold for anon pages */ 354 | vtextmin: c_int, /* min threshold for vtext pages */ 355 | vnodemin: c_int, /* min threshold for vnode pages */ 356 | anonminpct: c_int, /* min percent anon pages */ 357 | vtextminpct: c_int,/* min percent vtext pages */ 358 | vnodeminpct: c_int,/* min percent vnode pages */ 359 | 360 | /* swap */ 361 | nswapdev: c_int, /* number of configured swap devices in system */ 362 | swpages: c_int, /* number of PAGE_SIZE'ed swap pages */ 363 | swpginuse: c_int, /* number of swap pages in use */ 364 | swpgonly: c_int, /* number of swap pages in use, not also in RAM */ 365 | nswget: c_int, /* number of times fault calls uvm_swap_get() */ 366 | nanon: c_int, /* number total of anon's in system */ 367 | nanonneeded: c_int,/* number of anons currently needed */ 368 | nfreeanon: c_int, /* number of free anon's */ 369 | 370 | /* stat counters */ 371 | faults: c_int, /* page fault count */ 372 | traps: c_int, /* trap count */ 373 | intrs: c_int, /* interrupt count */ 374 | swtch: c_int, /* context switch count */ 375 | softs: c_int, /* software interrupt count */ 376 | syscalls: c_int, /* system calls */ 377 | pageins: c_int, /* pagein operation count */ 378 | /* pageouts are in pdpageouts below */ 379 | obsolete_swapins: c_int, /* swapins */ 380 | obsolete_swapouts: c_int, /* swapouts */ 381 | pgswapin: c_int, /* pages swapped in */ 382 | pgswapout: c_int, /* pages swapped out */ 383 | forks: c_int, /* forks */ 384 | forks_ppwait: c_int, /* forks where parent waits */ 385 | forks_sharevm: c_int, /* forks where vmspace is shared */ 386 | pga_zerohit: c_int, /* pagealloc where zero wanted and zero 387 | was available */ 388 | pga_zeromiss: c_int, /* pagealloc where zero wanted and zero 389 | not available */ 390 | zeroaborts: c_int, /* number of times page zeroing was 391 | aborted */ 392 | 393 | /* fault subcounters */ 394 | fltnoram: c_int, /* number of times fault was out of ram */ 395 | fltnoanon: c_int, /* number of times fault was out of anons */ 396 | fltnoamap: c_int, /* number of times fault was out of amap chunks */ 397 | fltpgwait: c_int, /* number of times fault had to wait on a page */ 398 | fltpgrele: c_int, /* number of times fault found a released page */ 399 | fltrelck: c_int, /* number of times fault relock called */ 400 | fltrelckok: c_int, /* number of times fault relock is a success */ 401 | fltanget: c_int, /* number of times fault gets anon page */ 402 | fltanretry: c_int, /* number of times fault retrys an anon get */ 403 | fltamcopy: c_int, /* number of times fault clears "needs copy" */ 404 | fltnamap: c_int, /* number of times fault maps a neighbor anon page */ 405 | fltnomap: c_int, /* number of times fault maps a neighbor obj page */ 406 | fltlget: c_int, /* number of times fault does a locked pgo_get */ 407 | fltget: c_int, /* number of times fault does an unlocked get */ 408 | flt_anon: c_int, /* number of times fault anon (case 1a) */ 409 | flt_acow: c_int, /* number of times fault anon cow (case 1b) */ 410 | flt_obj: c_int, /* number of times fault is on object page (2a) */ 411 | flt_prcopy: c_int, /* number of times fault promotes with copy (2b) */ 412 | flt_przero: c_int, /* number of times fault promotes with zerofill (2b) */ 413 | 414 | /* daemon counters */ 415 | pdwoke: c_int, /* number of times daemon woke up */ 416 | pdrevs: c_int, /* number of times daemon rev'd clock hand */ 417 | pdswout: c_int, /* number of times daemon called for swapout */ 418 | pdfreed: c_int, /* number of pages daemon freed since boot */ 419 | pdscans: c_int, /* number of pages daemon scanned since boot */ 420 | pdanscan: c_int, /* number of anonymous pages scanned by daemon */ 421 | pdobscan: c_int, /* number of object pages scanned by daemon */ 422 | pdreact: c_int, /* number of pages daemon reactivated since boot */ 423 | pdbusy: c_int, /* number of times daemon found a busy page */ 424 | pdpageouts: c_int, /* number of times daemon started a pageout */ 425 | pdpending: c_int, /* number of times daemon got a pending pagout */ 426 | pddeact: c_int, /* number of pages daemon deactivates */ 427 | pdreanon: c_int, /* anon pages reactivated due to min threshold */ 428 | pdrevnode: c_int, /* vnode pages reactivated due to min threshold */ 429 | pdrevtext: c_int, /* vtext pages reactivated due to min threshold */ 430 | 431 | fpswtch: c_int, /* FPU context switches */ 432 | kmapent: c_int, /* number of kernel map entries */ 433 | } 434 | 435 | -------------------------------------------------------------------------------- /src/platform/linux.rs: -------------------------------------------------------------------------------- 1 | use super::common::*; 2 | use super::unix; 3 | use crate::data::*; 4 | use libc::statvfs; 5 | use libc::{c_char, c_long, c_schar, c_uint, c_ulong, c_ushort}; 6 | use nom::bytes::complete::{tag, take_till, take_until}; 7 | use nom::character::complete::{digit1, multispace0, not_line_ending, space1}; 8 | use nom::character::is_space; 9 | use nom::combinator::{complete, map, map_res, opt, verify}; 10 | use nom::error::ParseError; 11 | use nom::multi::{fold_many0, many0, many1}; 12 | use nom::sequence::{delimited, preceded, tuple}; 13 | use nom::{IResult, Parser}; 14 | use std::io::Read; 15 | use std::path::Path; 16 | use std::str; 17 | use std::time::Duration; 18 | use std::{fs, io, mem, path}; 19 | 20 | fn read_file(path: &str) -> io::Result { 21 | let mut s = String::new(); 22 | fs::File::open(path) 23 | .and_then(|mut f| f.read_to_string(&mut s)) 24 | .map(|_| s) 25 | } 26 | 27 | fn value_from_file(path: &str) -> io::Result { 28 | read_file(path)? 29 | .trim_end_matches('\n') 30 | .parse() 31 | .map_err(|_| { 32 | io::Error::new( 33 | io::ErrorKind::Other, 34 | format!("File: \"{}\" doesn't contain an int value", &path), 35 | ) 36 | }) 37 | } 38 | 39 | fn capacity(charge_full: i32, charge_now: i32) -> f32 { 40 | charge_now as f32 / charge_full as f32 41 | } 42 | 43 | fn time(on_ac: bool, charge_full: i32, charge_now: i32, current_now: i32) -> Duration { 44 | if current_now != 0 { 45 | if on_ac { 46 | // Charge time 47 | Duration::from_secs( 48 | charge_full.saturating_sub(charge_now).abs() as u64 * 3600u64 / current_now as u64, 49 | ) 50 | } else { 51 | // Discharge time 52 | Duration::from_secs(charge_now as u64 * 3600u64 / current_now as u64) 53 | } 54 | } else { 55 | Duration::new(0, 0) 56 | } 57 | } 58 | 59 | /// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and 60 | /// trailing whitespace, returning the output of `inner`. 61 | fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( 62 | inner: F, 63 | ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> 64 | where 65 | F: Parser<&'a str, O, E>, 66 | { 67 | delimited(multispace0, inner, multispace0) 68 | } 69 | 70 | /// Parse a number out of a string, surrounded by whitespace 71 | fn num(input: &str) -> IResult<&str, T> { 72 | map_res( 73 | map_res(map(ws(digit1), str::as_bytes), str::from_utf8), 74 | str::FromStr::from_str, 75 | )(input) 76 | } 77 | 78 | // Parse `cpuX`, where X is a number 79 | fn proc_stat_cpu_prefix(input: &str) -> IResult<&str, ()> { 80 | map(tuple((tag("cpu"), digit1)), |_| ())(input) 81 | } 82 | 83 | // Parse a `/proc/stat` CPU line into a `CpuTime` struct 84 | fn proc_stat_cpu_time(input: &str) -> IResult<&str, CpuTime> { 85 | map( 86 | preceded( 87 | ws(proc_stat_cpu_prefix), 88 | tuple((num, num, num, num, num, num)), 89 | ), 90 | |(user, nice, system, idle, iowait, irq)| CpuTime { 91 | user, 92 | nice, 93 | system, 94 | idle, 95 | interrupt: irq, 96 | other: iowait, 97 | }, 98 | )(input) 99 | } 100 | 101 | // Parse the top CPU load aggregate line of `/proc/stat` 102 | fn proc_stat_cpu_aggregate(input: &str) -> IResult<&str, ()> { 103 | map(tuple((tag("cpu"), space1)), |_| ())(input) 104 | } 105 | 106 | // Parse `/proc/stat` to extract per-CPU loads 107 | fn proc_stat_cpu_times(input: &str) -> IResult<&str, Vec> { 108 | preceded( 109 | map(ws(not_line_ending), proc_stat_cpu_aggregate), 110 | many1(map_res(ws(not_line_ending), |input| { 111 | proc_stat_cpu_time(input) 112 | .map(|(_, res)| res) 113 | .map_err(|_| ()) 114 | })), 115 | )(input) 116 | } 117 | 118 | #[test] 119 | fn test_proc_stat_cpu_times() { 120 | let input = "cpu 5972658 30964 2383250 392840200 70075 0 43945 0 0 0 121 | cpu0 444919 3155 198700 24405593 4622 0 36738 0 0 0 122 | cpu1 296558 428 76249 24715635 1426 0 1280 0 0 0 123 | cpu2 402963 949 231689 24417433 6386 0 1780 0 0 0 124 | cpu3 301571 2452 88088 24698799 1906 0 177 0 0 0 125 | cpu4 427192 2896 200043 24427598 4640 0 519 0 0 0 126 | cpu5 301433 515 86228 24695368 3925 0 107 0 0 0 127 | cpu6 432794 2884 202838 24426726 4213 0 380 0 0 0 128 | cpu7 304364 337 89802 24709831 2965 0 78 0 0 0 129 | cpu8 475829 3608 211253 24379789 5645 0 438 0 0 0 130 | cpu9 306784 880 86744 24704036 4669 0 81 0 0 0 131 | cpu10 444170 3768 212504 24415053 5346 0 331 0 0 0 132 | cpu11 300957 519 87052 24712048 4294 0 77 0 0 0 133 | cpu12 445953 3608 209153 24415924 5458 0 288 0 0 0 134 | cpu13 318262 752 89195 24681010 4133 0 1254 0 0 0 135 | cpu14 451390 3802 216997 24404205 4852 0 283 0 0 0 136 | cpu15 317509 401 96705 24631145 5588 0 124 0 0 0 137 | intr 313606509 40 27 0 0 0 0 0 58 1 94578 0 2120 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 31 170440 151744 109054 197097 174402 169253 171292 0 0 0 1251812 0 0 0 0 0 0 0 0 6302 0 0 0 0 0 0 0 58 0 0 0 0 0 916279 10132 140390 8096 69021 79664 26669 79961 34865 33195 102807 124189 76108 69587 7073 3 9710 116522 10436256 0 2079496 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 138 | ctxt 535905166 139 | btime 1605203377 140 | processes 1360293 141 | procs_running 1 142 | procs_blocked 0 143 | softirq 81473629 1251347 8827732 10 325789 37 0 177903 43807896 2777 27080138 144 | "; 145 | let result = proc_stat_cpu_times(input).unwrap().1; 146 | assert_eq!(result.len(), 16); 147 | assert_eq!(result[0].user, 444919); 148 | assert_eq!(result[0].nice, 3155); 149 | assert_eq!(result[0].system, 198700); 150 | assert_eq!(result[0].idle, 24405593); 151 | assert_eq!(result[0].other, 4622); 152 | assert_eq!(result[0].interrupt, 0); 153 | } 154 | 155 | /// Get the current per-CPU `CpuTime` statistics 156 | fn cpu_time() -> io::Result> { 157 | read_file("/proc/stat").and_then(|data| { 158 | proc_stat_cpu_times(&data) 159 | .map(|(_, res)| res) 160 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 161 | }) 162 | } 163 | 164 | // Parse a `/proc/meminfo` line into (key, ByteSize) 165 | fn proc_meminfo_line(input: &str) -> IResult<&str, (&str, ByteSize)> { 166 | complete(map( 167 | tuple((take_until(":"), delimited(tag(":"), num, ws(tag("kB"))))), 168 | |(key, value)| (key, ByteSize::kib(value)), 169 | ))(input) 170 | } 171 | 172 | // Optionally parse a `/proc/meminfo` line` 173 | fn proc_meminfo_line_opt(input: &str) -> IResult<&str, Option<(&str, ByteSize)>> { 174 | opt(proc_meminfo_line)(input) 175 | } 176 | 177 | // Parse `/proc/meminfo` into a hashmap 178 | fn proc_meminfo(input: &str) -> IResult<&str, BTreeMap> { 179 | fold_many0( 180 | map_res( 181 | verify(ws(not_line_ending), |item: &str| !item.is_empty()), 182 | |input| { 183 | proc_meminfo_line_opt(input) 184 | .map(|(_, res)| res) 185 | .map_err(|_| ()) 186 | }, 187 | ), 188 | BTreeMap::new, 189 | |mut map: BTreeMap, opt| { 190 | if let Some((key, val)) = opt { 191 | map.insert(key.to_string(), val); 192 | } 193 | map 194 | }, 195 | )(input) 196 | } 197 | 198 | #[test] 199 | fn test_proc_meminfo() { 200 | let input = "MemTotal: 32345596 kB 201 | MemFree: 13160208 kB 202 | MemAvailable: 27792164 kB 203 | Buffers: 4724 kB 204 | Cached: 14776312 kB 205 | SwapCached: 0 kB 206 | Active: 8530160 kB 207 | Inactive: 9572028 kB 208 | Active(anon): 18960 kB 209 | Inactive(anon): 3415400 kB 210 | Active(file): 8511200 kB 211 | Inactive(file): 6156628 kB 212 | Unevictable: 0 kB 213 | Mlocked: 0 kB 214 | SwapTotal: 6143996 kB 215 | SwapFree: 6143996 kB 216 | Dirty: 66124 kB 217 | Writeback: 0 kB 218 | AnonPages: 3313376 kB 219 | Mapped: 931060 kB 220 | Shmem: 134716 kB 221 | KReclaimable: 427080 kB 222 | Slab: 648316 kB 223 | SReclaimable: 427080 kB 224 | SUnreclaim: 221236 kB 225 | KernelStack: 18752 kB 226 | PageTables: 30576 kB 227 | NFS_Unstable: 0 kB 228 | Bounce: 0 kB 229 | WritebackTmp: 0 kB 230 | CommitLimit: 22316792 kB 231 | Committed_AS: 7944504 kB 232 | VmallocTotal: 34359738367 kB 233 | VmallocUsed: 78600 kB 234 | VmallocChunk: 0 kB 235 | Percpu: 10496 kB 236 | HardwareCorrupted: 0 kB 237 | AnonHugePages: 0 kB 238 | ShmemHugePages: 0 kB 239 | ShmemPmdMapped: 0 kB 240 | FileHugePages: 0 kB 241 | FilePmdMapped: 0 kB 242 | HugePages_Total: 0 243 | HugePages_Free: 0 244 | HugePages_Rsvd: 0 245 | HugePages_Surp: 0 246 | Hugepagesize: 2048 kB 247 | Hugetlb: 0 kB 248 | DirectMap4k: 1696884 kB 249 | DirectMap2M: 17616896 kB 250 | DirectMap1G: 13631488 kB 251 | "; 252 | let result = proc_meminfo(input).unwrap().1; 253 | assert_eq!(result.len(), 47); 254 | assert_eq!( 255 | result.get(&"Buffers".to_string()), 256 | Some(&ByteSize::kib(4724)) 257 | ); 258 | assert_eq!( 259 | result.get(&"KReclaimable".to_string()), 260 | Some(&ByteSize::kib(427080)) 261 | ); 262 | } 263 | 264 | /// Get memory statistics 265 | fn memory_stats() -> io::Result> { 266 | read_file("/proc/meminfo").and_then(|data| { 267 | proc_meminfo(&data) 268 | .map(|(_, res)| res) 269 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 270 | }) 271 | } 272 | 273 | // Parse a single word 274 | fn word_s(input: &str) -> IResult<&str, &str> { 275 | take_till(|c| is_space(c as u8))(input) 276 | } 277 | 278 | /// `/proc/mounts` data 279 | struct ProcMountsData { 280 | source: String, 281 | target: String, 282 | fstype: String, 283 | } 284 | 285 | // Parse a `/proc/mounts` line to get a mountpoint 286 | fn proc_mounts_line(input: &str) -> IResult<&str, ProcMountsData> { 287 | map( 288 | tuple((ws(word_s), ws(word_s), ws(word_s))), 289 | |(source, target, fstype)| ProcMountsData { 290 | source: source.to_string(), 291 | target: target.to_string(), 292 | fstype: fstype.to_string(), 293 | }, 294 | )(input) 295 | } 296 | 297 | // Parse `/proc/mounts` to get a list of mountpoints 298 | fn proc_mounts(input: &str) -> IResult<&str, Vec> { 299 | many1(map_res(ws(not_line_ending), |input| { 300 | if input.is_empty() { 301 | Err(()) 302 | } else { 303 | proc_mounts_line(input).map(|(_, res)| res).map_err(|_| ()) 304 | } 305 | }))(input) 306 | } 307 | 308 | #[test] 309 | fn test_proc_mounts() { 310 | let test_input_1 = r#"/dev/md0 / btrfs rw,noatime,space_cache,subvolid=15192,subvol=/var/lib/docker/btrfs/subvolumes/df6eb8d3ce1a295bcc252e51ba086cb7705a046a79a342b74729f3f738129f04 0 0 311 | proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 312 | tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755,inode64 0 0 313 | devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0 314 | sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0 315 | tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755,inode64 0 0"#; 316 | let mounts = proc_mounts(test_input_1).unwrap().1; 317 | assert!(mounts.len() == 6); 318 | let root = mounts.iter().find(|m| m.target == "/").unwrap(); 319 | assert!(root.source == "/dev/md0"); 320 | assert!(root.target == "/"); 321 | assert!(root.fstype == "btrfs"); 322 | 323 | let test_input_2 = r#"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 324 | tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755,inode64 0 0 325 | devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0 326 | sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0 327 | tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755,inode64 0 0 328 | /dev/md0 / btrfs rw,noatime,space_cache,subvolid=15192,subvol=/var/lib/docker/btrfs/subvolumes/df6eb8d3ce1a295bcc252e51ba086cb7705a046a79a342b74729f3f738129f04 0 0"#; 329 | let mounts = proc_mounts(test_input_2).unwrap().1; 330 | assert!(mounts.len() == 6); 331 | let root = mounts.iter().find(|m| m.target == "/").unwrap(); 332 | assert!(root.source == "/dev/md0"); 333 | assert!(root.target == "/"); 334 | assert!(root.fstype == "btrfs"); 335 | 336 | // On some distros, there is a blank line at the end of `/proc/mounts`, 337 | // so we test here to make sure we do not crash on that 338 | let test_input_3 = r#"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 339 | sys /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 340 | dev /dev devtmpfs rw,nosuid,relatime,size=16131864k,nr_inodes=4032966,mode=755,inode64 0 0 341 | run /run tmpfs rw,nosuid,nodev,relatime,mode=755,inode64 0 0 342 | efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0 343 | /dev/nvme0n1p3 / btrfs rw,noatime,ssd,space_cache,subvolid=5,subvol=/ 0 0 344 | securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 345 | tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0 346 | "#; 347 | let mounts = proc_mounts(test_input_3).unwrap().1; 348 | assert!(mounts.len() == 8); 349 | let root = mounts.iter().find(|m| m.target == "/").unwrap(); 350 | assert!(root.source == "/dev/nvme0n1p3"); 351 | assert!(root.target == "/"); 352 | assert!(root.fstype == "btrfs"); 353 | } 354 | 355 | /// `/proc/net/sockstat` data 356 | struct ProcNetSockStat { 357 | tcp_in_use: usize, 358 | tcp_orphaned: usize, 359 | udp_in_use: usize, 360 | } 361 | 362 | // Parse `/proc/net/sockstat` to get socket statistics 363 | fn proc_net_sockstat(input: &str) -> IResult<&str, ProcNetSockStat> { 364 | map( 365 | preceded( 366 | not_line_ending, 367 | tuple(( 368 | preceded(ws(tag("TCP: inuse")), num), 369 | delimited(ws(tag("orphan")), num, not_line_ending), 370 | preceded(ws(tag("UDP: inuse")), num), 371 | )), 372 | ), 373 | |(tcp_in_use, tcp_orphaned, udp_in_use)| ProcNetSockStat { 374 | tcp_in_use, 375 | tcp_orphaned, 376 | udp_in_use, 377 | }, 378 | )(input) 379 | } 380 | 381 | #[test] 382 | fn test_proc_net_sockstat() { 383 | let input = "sockets: used 925 384 | TCP: inuse 20 orphan 0 tw 12 alloc 23 mem 2 385 | UDP: inuse 1 mem 2 386 | UDPLITE: inuse 0 387 | RAW: inuse 0 388 | FRAG: inuse 0 memory 0 389 | "; 390 | let result = proc_net_sockstat(input).unwrap().1; 391 | assert_eq!(result.tcp_in_use, 20); 392 | assert_eq!(result.tcp_orphaned, 0); 393 | assert_eq!(result.udp_in_use, 1); 394 | } 395 | 396 | /// `/proc/net/sockstat6` data 397 | struct ProcNetSockStat6 { 398 | tcp_in_use: usize, 399 | udp_in_use: usize, 400 | } 401 | 402 | // Parse `/proc/net/sockstat6` to get socket statistics 403 | fn proc_net_sockstat6(input: &str) -> IResult<&str, ProcNetSockStat6> { 404 | map( 405 | ws(tuple(( 406 | preceded(tag("TCP6: inuse"), num), 407 | preceded(tag("UDP6: inuse"), num), 408 | ))), 409 | |(tcp_in_use, udp_in_use)| ProcNetSockStat6 { 410 | tcp_in_use, 411 | udp_in_use, 412 | }, 413 | )(input) 414 | } 415 | 416 | #[test] 417 | fn test_proc_net_sockstat6() { 418 | let input = "TCP6: inuse 3 419 | UDP6: inuse 1 420 | UDPLITE6: inuse 0 421 | RAW6: inuse 1 422 | FRAG6: inuse 0 memory 0 423 | "; 424 | let result = proc_net_sockstat6(input).unwrap().1; 425 | assert_eq!(result.tcp_in_use, 3); 426 | assert_eq!(result.udp_in_use, 1); 427 | } 428 | 429 | /// Stat a mountpoint to gather filesystem statistics 430 | fn stat_mount(mount: ProcMountsData) -> io::Result { 431 | let mut info: statvfs = unsafe { mem::zeroed() }; 432 | let target = format!("{}\0", mount.target); 433 | let result = unsafe { statvfs(target.as_ptr() as *const c_char, &mut info) }; 434 | match result { 435 | 0 => Ok(Filesystem { 436 | files: (info.f_files as usize).saturating_sub(info.f_ffree as usize), 437 | files_total: info.f_files as usize, 438 | files_avail: info.f_favail as usize, 439 | free: ByteSize::b(info.f_bfree as u64 * info.f_bsize as u64), 440 | avail: ByteSize::b(info.f_bavail as u64 * info.f_bsize as u64), 441 | total: ByteSize::b(info.f_blocks as u64 * info.f_bsize as u64), 442 | name_max: info.f_namemax as usize, 443 | fs_type: mount.fstype, 444 | fs_mounted_from: mount.source, 445 | fs_mounted_on: mount.target, 446 | }), 447 | _ => Err(io::Error::last_os_error()), 448 | } 449 | } 450 | 451 | // Parse a line of `/proc/diskstats` 452 | fn proc_diskstats_line(input: &str) -> IResult<&str, BlockDeviceStats> { 453 | map( 454 | ws(tuple(( 455 | num::, 456 | num::, 457 | word_s, 458 | num, 459 | num, 460 | num, 461 | num, 462 | num, 463 | num, 464 | num, 465 | num, 466 | num, 467 | num, 468 | num, 469 | ))), 470 | |( 471 | _major_number, 472 | _minor_number, 473 | name, 474 | read_ios, 475 | read_merges, 476 | read_sectors, 477 | read_ticks, 478 | write_ios, 479 | write_merges, 480 | write_sectors, 481 | write_ticks, 482 | in_flight, 483 | io_ticks, 484 | time_in_queue, 485 | )| BlockDeviceStats { 486 | name: name.to_string(), 487 | read_ios, 488 | read_merges, 489 | read_sectors, 490 | read_ticks, 491 | write_ios, 492 | write_merges, 493 | write_sectors, 494 | write_ticks, 495 | in_flight, 496 | io_ticks, 497 | time_in_queue, 498 | }, 499 | )(input) 500 | } 501 | 502 | // Parse `/proc/diskstats` to get a Vec 503 | fn proc_diskstats(input: &str) -> IResult<&str, Vec> { 504 | many0(ws(map_res(not_line_ending, |input| { 505 | proc_diskstats_line(input) 506 | .map(|(_, res)| res) 507 | .map_err(|_| ()) 508 | })))(input) 509 | } 510 | 511 | #[test] 512 | fn test_proc_diskstats() { 513 | let input = " 259 0 nvme0n1 142537 3139 15957288 470540 1235382 57191 140728002 5369037 0 1801270 5898257 0 0 0 0 102387 58679 514 | 259 1 nvme0n1p1 767 2505 20416 1330 2 0 2 38 0 200 1369 0 0 0 0 0 0 515 | 259 2 nvme0n1p2 65 0 4680 37 0 0 0 0 0 44 37 0 0 0 0 0 0 516 | 259 3 nvme0n1p3 141532 634 15927512 469040 1132993 57191 140728000 5308878 0 1801104 5777919 0 0 0 0 0 0 517 | "; 518 | let result = proc_diskstats(input).unwrap().1; 519 | assert_eq!(result.len(), 4); 520 | assert_eq!(&result[3].name, "nvme0n1p3"); 521 | assert_eq!(result[3].read_ios, 141532); 522 | assert_eq!(result[3].write_ios, 1132993); 523 | } 524 | 525 | pub struct PlatformImpl; 526 | 527 | /// An implementation of `Platform` for Linux. 528 | /// See `Platform` for documentation. 529 | impl Platform for PlatformImpl { 530 | #[inline(always)] 531 | fn new() -> Self { 532 | PlatformImpl 533 | } 534 | 535 | fn cpu_load(&self) -> io::Result>> { 536 | cpu_time().map(|times| { 537 | DelayedMeasurement::new(Box::new(move || { 538 | cpu_time().map(|delay_times| { 539 | delay_times 540 | .iter() 541 | .zip(times.iter()) 542 | .map(|(now, prev)| (*now - prev).to_cpuload()) 543 | .collect::>() 544 | }) 545 | })) 546 | }) 547 | } 548 | 549 | fn load_average(&self) -> io::Result { 550 | unix::load_average() 551 | } 552 | 553 | fn memory(&self) -> io::Result { 554 | PlatformMemory::new().map(PlatformMemory::to_memory) 555 | } 556 | 557 | fn swap(&self) -> io::Result { 558 | PlatformMemory::new().map(PlatformMemory::to_swap) 559 | } 560 | 561 | fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { 562 | let pm = PlatformMemory::new()?; 563 | Ok((pm.clone().to_memory(), pm.to_swap())) 564 | } 565 | 566 | fn uptime(&self) -> io::Result { 567 | let mut info: sysinfo = unsafe { mem::zeroed() }; 568 | unsafe { sysinfo(&mut info) }; 569 | Ok(Duration::from_secs(info.uptime as u64)) 570 | } 571 | 572 | fn boot_time(&self) -> io::Result { 573 | read_file("/proc/stat").and_then(|data| { 574 | data.lines() 575 | .find(|line| line.starts_with("btime ")) 576 | .ok_or(io::Error::new( 577 | io::ErrorKind::InvalidData, 578 | "Could not find btime in /proc/stat", 579 | )) 580 | .and_then(|line| { 581 | let timestamp_str = line 582 | .strip_prefix("btime ") 583 | .expect("line starts with 'btime '"); 584 | timestamp_str 585 | .parse::() 586 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 587 | .and_then(|timestamp| { 588 | OffsetDateTime::from_unix_timestamp(timestamp).map_err(|err| { 589 | io::Error::new(io::ErrorKind::InvalidData, err.to_string()) 590 | }) 591 | }) 592 | }) 593 | }) 594 | } 595 | 596 | fn battery_life(&self) -> io::Result { 597 | let dir = "/sys/class/power_supply"; 598 | let entries = fs::read_dir(&dir)?; 599 | let mut full = 0; 600 | let mut now = 0; 601 | let mut current = 0; 602 | for e in entries { 603 | let p = e.unwrap().path(); 604 | let s = p.to_str().unwrap(); 605 | if value_from_file::(&(s.to_string() + "/type")) 606 | .map(|t| t == "Battery") 607 | .unwrap_or(false) 608 | { 609 | let f = value_from_file::(&(s.to_string() + "/energy_full")) 610 | .or_else(|_| value_from_file::(&(s.to_string() + "/charge_full"))); 611 | let n = value_from_file::(&(s.to_string() + "/energy_now")) 612 | .or_else(|_| value_from_file::(&(s.to_string() + "/charge_now"))); 613 | let c = value_from_file::(&(s.to_string() + "/power_now")) 614 | .or_else(|_| value_from_file::(&(s.to_string() + "/current_now"))); 615 | if let (Ok(f), Ok(n), Ok(c)) = (f, n, c) { 616 | full += f; 617 | now += n; 618 | current += c; 619 | } 620 | } 621 | } 622 | if full != 0 { 623 | let on_ac = matches!(self.on_ac_power(), Ok(true)); 624 | Ok(BatteryLife { 625 | remaining_capacity: capacity(full, now), 626 | remaining_time: time(on_ac, full, now, current), 627 | }) 628 | } else { 629 | Err(io::Error::new( 630 | io::ErrorKind::Other, 631 | "Missing battery information", 632 | )) 633 | } 634 | } 635 | 636 | fn on_ac_power(&self) -> io::Result { 637 | let dir = "/sys/class/power_supply"; 638 | let entries = fs::read_dir(&dir)?; 639 | let mut on_ac = false; 640 | for e in entries { 641 | let p = e.unwrap().path(); 642 | let s = p.to_str().unwrap(); 643 | if value_from_file::(&(s.to_string() + "/type")) 644 | .map(|t| t == "Mains") 645 | .unwrap_or(false) 646 | { 647 | on_ac |= value_from_file::(&(s.to_string() + "/online")).map(|v| v == 1)? 648 | } 649 | } 650 | Ok(on_ac) 651 | } 652 | 653 | fn mounts(&self) -> io::Result> { 654 | read_file("/proc/mounts") 655 | .and_then(|data| { 656 | proc_mounts(&data) 657 | .map(|(_, res)| res) 658 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 659 | }) 660 | .map(|mounts| { 661 | mounts 662 | .into_iter() 663 | .filter_map(|mount| stat_mount(mount).ok()) 664 | .collect() 665 | }) 666 | } 667 | 668 | fn mount_at>(&self, path: P) -> io::Result { 669 | read_file("/proc/mounts") 670 | .and_then(|data| { 671 | proc_mounts(&data) 672 | .map(|(_, res)| res) 673 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 674 | }) 675 | .and_then(|mounts| { 676 | mounts 677 | .into_iter() 678 | .find(|mount| Path::new(&mount.target) == path.as_ref()) 679 | .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No such mount")) 680 | }) 681 | .and_then(stat_mount) 682 | } 683 | 684 | fn block_device_statistics(&self) -> io::Result> { 685 | let mut result: BTreeMap = BTreeMap::new(); 686 | let stats: Vec = read_file("/proc/diskstats").and_then(|data| { 687 | proc_diskstats(&data) 688 | .map(|(_, res)| res) 689 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 690 | })?; 691 | 692 | for blkstats in stats { 693 | result.entry(blkstats.name.clone()).or_insert(blkstats); 694 | } 695 | Ok(result) 696 | } 697 | 698 | fn networks(&self) -> io::Result> { 699 | unix::networks() 700 | } 701 | 702 | fn network_stats(&self, interface: &str) -> io::Result { 703 | let path_root: String = ("/sys/class/net/".to_string() + interface) + "/statistics/"; 704 | let stats_file = |file: &str| (&path_root).to_string() + file; 705 | 706 | let rx_bytes: u64 = value_from_file::(&stats_file("rx_bytes"))?; 707 | let tx_bytes: u64 = value_from_file::(&stats_file("tx_bytes"))?; 708 | let rx_packets: u64 = value_from_file::(&stats_file("rx_packets"))?; 709 | let tx_packets: u64 = value_from_file::(&stats_file("tx_packets"))?; 710 | let rx_errors: u64 = value_from_file::(&stats_file("rx_errors"))?; 711 | let tx_errors: u64 = value_from_file::(&stats_file("tx_errors"))?; 712 | 713 | Ok(NetworkStats { 714 | rx_bytes: ByteSize::b(rx_bytes), 715 | tx_bytes: ByteSize::b(tx_bytes), 716 | rx_packets, 717 | tx_packets, 718 | rx_errors, 719 | tx_errors, 720 | }) 721 | } 722 | 723 | fn cpu_temp(&self) -> io::Result { 724 | read_file("/sys/class/thermal/thermal_zone0/temp") 725 | .or(read_file("/sys/class/hwmon/hwmon0/temp1_input")) 726 | .and_then(|data| match data.trim().parse::() { 727 | Ok(x) => Ok(x), 728 | Err(_) => Err(io::Error::new( 729 | io::ErrorKind::Other, 730 | "Could not parse float", 731 | )), 732 | }) 733 | .map(|num| num / 1000.0) 734 | } 735 | 736 | fn socket_stats(&self) -> io::Result { 737 | let sockstats: ProcNetSockStat = read_file("/proc/net/sockstat").and_then(|data| { 738 | proc_net_sockstat(&data) 739 | .map(|(_, res)| res) 740 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 741 | })?; 742 | let sockstats6: ProcNetSockStat6 = read_file("/proc/net/sockstat6").and_then(|data| { 743 | proc_net_sockstat6(&data) 744 | .map(|(_, res)| res) 745 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 746 | })?; 747 | let result: SocketStats = SocketStats { 748 | tcp_sockets_in_use: sockstats.tcp_in_use, 749 | tcp_sockets_orphaned: sockstats.tcp_orphaned, 750 | udp_sockets_in_use: sockstats.udp_in_use, 751 | tcp6_sockets_in_use: sockstats6.tcp_in_use, 752 | udp6_sockets_in_use: sockstats6.udp_in_use, 753 | }; 754 | Ok(result) 755 | } 756 | } 757 | 758 | impl PlatformMemory { 759 | // Retrieve platform memory information 760 | fn new() -> io::Result { 761 | memory_stats() 762 | .or_else(|_| { 763 | // If there's no procfs, e.g. in a chroot without mounting it or something 764 | let mut meminfo = BTreeMap::new(); 765 | let mut info: sysinfo = unsafe { mem::zeroed() }; 766 | unsafe { sysinfo(&mut info) }; 767 | let unit = info.mem_unit as u64; 768 | meminfo.insert( 769 | "MemTotal".to_owned(), 770 | ByteSize::b(info.totalram as u64 * unit), 771 | ); 772 | meminfo.insert( 773 | "MemFree".to_owned(), 774 | ByteSize::b(info.freeram as u64 * unit), 775 | ); 776 | meminfo.insert( 777 | "Shmem".to_owned(), 778 | ByteSize::b(info.sharedram as u64 * unit), 779 | ); 780 | meminfo.insert( 781 | "Buffers".to_owned(), 782 | ByteSize::b(info.bufferram as u64 * unit), 783 | ); 784 | meminfo.insert( 785 | "SwapTotal".to_owned(), 786 | ByteSize::b(info.totalswap as u64 * unit), 787 | ); 788 | meminfo.insert( 789 | "SwapFree".to_owned(), 790 | ByteSize::b(info.freeswap as u64 * unit), 791 | ); 792 | Ok(meminfo) 793 | }) 794 | .map(|meminfo| PlatformMemory { meminfo }) 795 | } 796 | 797 | // Convert the platform memory information to Memory 798 | fn to_memory(self) -> Memory { 799 | let meminfo = &self.meminfo; 800 | Memory { 801 | total: meminfo.get("MemTotal").copied().unwrap_or(ByteSize::b(0)), 802 | free: saturating_sub_bytes( 803 | meminfo.get("MemFree").copied().unwrap_or(ByteSize::b(0)) 804 | + meminfo.get("Buffers").copied().unwrap_or(ByteSize::b(0)) 805 | + meminfo.get("Cached").copied().unwrap_or(ByteSize::b(0)) 806 | + meminfo 807 | .get("SReclaimable") 808 | .copied() 809 | .unwrap_or(ByteSize::b(0)), 810 | meminfo.get("Shmem").copied().unwrap_or(ByteSize::b(0)), 811 | ), 812 | platform_memory: self, 813 | } 814 | } 815 | 816 | // Convert the platform memory information to Swap 817 | fn to_swap(self) -> Swap { 818 | let meminfo = &self.meminfo; 819 | Swap { 820 | total: meminfo.get("SwapTotal").copied().unwrap_or(ByteSize::b(0)), 821 | free: meminfo.get("SwapFree").copied().unwrap_or(ByteSize::b(0)), 822 | platform_swap: self, 823 | } 824 | } 825 | } 826 | 827 | #[repr(C)] 828 | #[derive(Debug)] 829 | struct sysinfo { 830 | uptime: c_long, 831 | loads: [c_ulong; 3], 832 | totalram: c_ulong, 833 | freeram: c_ulong, 834 | sharedram: c_ulong, 835 | bufferram: c_ulong, 836 | totalswap: c_ulong, 837 | freeswap: c_ulong, 838 | procs: c_ushort, 839 | totalhigh: c_ulong, 840 | freehigh: c_ulong, 841 | mem_unit: c_uint, 842 | padding: [c_schar; 8], 843 | } 844 | 845 | #[link(name = "c")] 846 | extern "C" { 847 | fn sysinfo(info: *mut sysinfo); 848 | } 849 | --------------------------------------------------------------------------------