├── .gitignore ├── .rustfmt.toml ├── src ├── os │ ├── windows │ │ ├── os_impl_ext.rs │ │ ├── mod.rs │ │ ├── public.rs │ │ └── internal.rs │ ├── macos │ │ ├── os_impl_ext.rs │ │ ├── system.rs │ │ ├── mod.rs │ │ ├── cpu.rs │ │ ├── mem.rs │ │ └── misc.rs │ ├── mod.rs │ ├── os_impl.rs │ ├── linux │ │ ├── ps │ │ │ ├── state.rs │ │ │ ├── process.rs │ │ │ ├── mod.rs │ │ │ └── stat.rs │ │ ├── mem │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── os_impl_ext.rs │ │ ├── cpu │ │ │ ├── processor.rs │ │ │ ├── time.rs │ │ │ ├── mod.rs │ │ │ └── cores.rs │ │ ├── sysinfo.rs │ │ ├── sysproc.rs │ │ ├── mocks.rs │ │ └── mounts.rs │ └── unix │ │ └── mod.rs ├── util.rs ├── bin │ └── test.rs ├── error.rs ├── lib.rs └── api.rs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── master.yml ├── CHANGELOG.md ├── LICENSE ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | .idea 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | edition = "2018" 3 | -------------------------------------------------------------------------------- /src/os/windows/os_impl_ext.rs: -------------------------------------------------------------------------------- 1 | pub trait OsImplExt {} 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/os/macos/os_impl_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::macos::MacOS; 2 | use crate::Result; 3 | pub trait OsImplExt { 4 | fn model(&self) -> Result; 5 | } 6 | 7 | impl OsImplExt for MacOS { 8 | fn model(&self) -> Result { 9 | crate::macos::model() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.6.0 2 | - rename linux `kernel_version` to `kernel_release` 3 | - add `Error::NixSyscallError` 4 | - functions that returned `Error::FfiError` now return unified `Error::NixSyscallError` 5 | - remove `Memory` struct from linux impl and all associated functions 6 | - add `SysInfo` struct that replaces `Memory` 7 | -------------------------------------------------------------------------------- /src/os/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | pub mod linux; 3 | #[cfg(target_os = "macos")] 4 | pub mod macos; 5 | #[cfg(unix)] 6 | pub(crate) mod unix; 7 | #[cfg(windows)] 8 | pub mod windows; 9 | 10 | #[cfg(target_os = "linux")] 11 | pub(crate) use linux::OsImplExt; 12 | #[cfg(target_os = "macos")] 13 | pub(crate) use macos::OsImplExt; 14 | #[cfg(target_os = "windows")] 15 | pub(crate) use windows::OsImplExt; 16 | 17 | pub(crate) mod os_impl; 18 | pub(crate) use os_impl::OsImpl; 19 | -------------------------------------------------------------------------------- /src/os/os_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | /// Common api 4 | pub(crate) trait OsImpl { 5 | fn hostname(&self) -> Result; 6 | fn domain_name(&self) -> Result; 7 | fn uptime(&self) -> Result; 8 | fn arch(&self) -> Result; 9 | fn cpu(&self) -> Result; 10 | fn cpu_clock(&self) -> Result; 11 | fn cpu_cores(&self) -> Result; 12 | fn logical_cores(&self) -> Result; 13 | fn memory_total(&self) -> Result; 14 | fn memory_free(&self) -> Result; 15 | fn swap_total(&self) -> Result; 16 | fn swap_free(&self) -> Result; 17 | } 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## What did you implement: 8 | 9 | 12 | 13 | Closes: #xxx 14 | 15 | ## How did you verify your change: 16 | 17 | ## What (if anything) would need to be called out in the CHANGELOG for the next release: -------------------------------------------------------------------------------- /src/os/macos/system.rs: -------------------------------------------------------------------------------- 1 | use crate::Result; 2 | 3 | use sysctl::Sysctl; 4 | 5 | pub(crate) const SYSCTL_CPU: &str = "machdep.cpu.brand_string"; 6 | pub(crate) const SYSCTL_HOSTNAME: &str = "kern.hostname"; 7 | pub(crate) const SYSCTL_DOMAINNAME: &str = "kern.nisdomainname"; 8 | pub(crate) const SYSCTL_BOOTTIME: &str = "kern.boottime"; 9 | pub(crate) const SYSCTL_MODEL: &str = "hw.model"; 10 | pub(crate) const SYSCTL_MEMSIZE: &str = "hw.memsize"; 11 | pub(crate) const SYSCTL_USERMEM: &str = "hw.usermem"; 12 | pub(crate) const SYSCTL_CPU_FREQUENCY: &str = "hw.cpufrequency"; 13 | pub(crate) const SYSCTL_CPU_CORES: &str = "hw.physicalcpu"; 14 | pub(crate) const SYSCTL_CPU_TYPE: &str = "hw.cputype"; 15 | pub(crate) const SYSCTL_LOGICAL_CORES: &str = "hw.logicalcpu"; 16 | pub(crate) const SYSCTL_VM_SWAPUSAGE: &str = "vm.swapusage"; 17 | 18 | pub(crate) fn sysctl(property: &str) -> Result { 19 | Ok(sysctl::Ctl::new(property).unwrap().value().unwrap()) // # TODO: proper error handling... 20 | } 21 | -------------------------------------------------------------------------------- /src/os/linux/ps/state.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "serialize")] 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Eq, PartialEq)] 5 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 6 | pub enum ProcessState { 7 | Running, 8 | Sleeping, 9 | Waiting, 10 | Zombie, 11 | Stopped, 12 | TracingStop, 13 | Dead, 14 | Wakekill, 15 | Waking, 16 | Parked, 17 | Idle, 18 | Unknown, 19 | } 20 | 21 | impl From<&str> for ProcessState { 22 | fn from(s: &str) -> Self { 23 | use self::ProcessState::*; 24 | match s.chars().next() { 25 | Some('R') => Running, 26 | Some('S') => Sleeping, 27 | Some('D') => Waiting, 28 | Some('Z') => Zombie, 29 | Some('T') => Stopped, 30 | Some('t') => TracingStop, 31 | Some('X') | Some('x') => Dead, 32 | Some('K') => Wakekill, 33 | Some('W') => Waking, 34 | Some('P') => Parked, 35 | Some('I') => Idle, 36 | _ => Unknown, 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(target_os = "macos", allow(dead_code))] 2 | use super::Error; 3 | use std::any::type_name; 4 | 5 | pub fn trim_parse_map(inp: &str) -> Result 6 | where 7 | T: std::str::FromStr, 8 | T::Err: std::fmt::Display, 9 | { 10 | inp.trim().parse::().map_err(|e| { 11 | Error::InvalidInputError( 12 | inp.to_string(), 13 | format!("cannot parse as '{}' - '{}'", type_name::(), e), 14 | ) 15 | }) 16 | } 17 | 18 | pub fn next<'l, T, I>(iter: &mut I, src: &str) -> Result 19 | where 20 | T: std::str::FromStr, 21 | T::Err: std::fmt::Display, 22 | I: Iterator, 23 | { 24 | if let Some(s) = iter.next() { 25 | return trim_parse_map(s); 26 | } 27 | 28 | Err(Error::InvalidInputError( 29 | src.to_string(), 30 | format!("there was no element of type {}", type_name::()), 31 | )) 32 | } 33 | pub fn skip(n: usize, iter: &mut I) -> &mut I 34 | where 35 | I: Iterator, 36 | { 37 | for _ in 0..n { 38 | iter.next(); 39 | } 40 | iter 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2020 Wojciech Kępka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsys" 3 | version = "0.5.5" 4 | authors = ["Wojciech Kępka Result<()> { 5 | // You can either use api through Rsys object 6 | // for os-agnostic experience 7 | let rsys = Rsys::new(); 8 | println!("HOSTNAME - {}", rsys.hostname()?); 9 | println!("CPU - {}", rsys.cpu()?); 10 | println!("CPU CORES - {}", rsys.cpu_cores()?); 11 | println!("CPU CLOCK - {}MHz", rsys.cpu_clock()?); 12 | println!("UPTIME - {}s", rsys.uptime()?); 13 | println!("ARCH - {}", rsys.arch()?); 14 | println!("SWAP TOTAL - {}b", rsys.swap_total()?); 15 | println!("SWAP FREE - {}b", rsys.swap_free()?); 16 | println!("MEMORY TOTAL - {}b", rsys.memory_total()?); 17 | println!("MEMORY FREE - {}b", rsys.memory_free()?); 18 | 19 | #[cfg(target_os = "linux")] 20 | { 21 | // Or use functions in each module 22 | println!("KERNEL VERSION - {}", rsys::linux::kernel_release()?); 23 | 24 | // Os-specific functions are also available as methods 25 | println!("KERNEL_VERSION - {}", rsys.kernel_release()?); 26 | println!("{:#?}", rsys.pids()?); 27 | println!("MOUNTS - {:#?}", rsys::linux::mounts::mounts()?); 28 | } 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | /// Error type used by this crate 5 | pub enum RsysError { 6 | #[error("Failed to parse command output - `{0}`")] 7 | CommandParseError(String), 8 | #[error("Failed to read a file at `{0}` - `{1}`")] 9 | FileReadError(String, String), 10 | #[error("Failed to acquire local time - `{0}`")] 11 | TimeError(String), 12 | #[error("Failed to parse value from input `{0}` - `{1}`")] 13 | InvalidInputError(String, String), 14 | #[error("Failed to serialize `{0}` - `{1}`")] 15 | SerializeError(String, String), 16 | #[cfg(unix)] 17 | #[error("Syscall failed - `{0}`")] 18 | NixSyscallError(#[from] nix::Error), 19 | #[cfg(unix)] 20 | #[error("Sysctl failed - `{0}`")] 21 | SysctlError(#[from] sysctl::SysctlError), 22 | #[error("Unexpected sysctl value `{0:?}`")] 23 | UnexpectedSysctlValue(sysctl::CtlValue), 24 | #[cfg(target_os = "macos")] 25 | #[error("Invalid sysctl property `{0}`")] 26 | InvalidSysctlProp(String), 27 | 28 | // Windows 29 | #[cfg(target_os = "windows")] 30 | #[error("Failed to communicate with Win32 api. Error code: `{0}`, Reason: `{1}`")] 31 | WinApiError(u32, String), 32 | } 33 | 34 | /// Helper result definition for less repetition in type signatures 35 | /// used throughout this crate 36 | pub type RsysResult = std::result::Result; 37 | -------------------------------------------------------------------------------- /src/os/linux/ps/process.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::ps::{cmdline, ProcessStat}; 2 | use crate::linux::{SysFs, SysPath}; 3 | use crate::Result; 4 | 5 | #[cfg(feature = "serialize")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | pub type Processes = Vec; 9 | 10 | #[derive(Debug, Eq, PartialEq)] 11 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 12 | pub struct Task { 13 | pub cmdline: String, 14 | pub stat: ProcessStat, 15 | } 16 | 17 | impl Task { 18 | pub(crate) fn from_sys_path(path: &SysPath) -> Result { 19 | Ok(Task { 20 | cmdline: cmdline(path)?, 21 | stat: ProcessStat::from_sys_path(path)?, 22 | }) 23 | } 24 | } 25 | 26 | #[derive(Debug, Eq, PartialEq)] 27 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 28 | pub struct Process { 29 | pub cmdline: String, 30 | pub stat: ProcessStat, 31 | } 32 | 33 | impl Process { 34 | pub fn new(pid: i32) -> Result { 35 | let p = SysFs::Proc.join(pid.to_string()); 36 | Ok(Process { 37 | cmdline: cmdline(&p)?, 38 | stat: ProcessStat::from_sys_path(&p)?, 39 | }) 40 | } 41 | 42 | pub fn tasks(&self) -> Result> { 43 | let mut tasks = Vec::new(); 44 | for entry in SysFs::Proc 45 | .join(self.stat.pid.to_string()) 46 | .join("task") 47 | .read_dir()? 48 | .flatten() 49 | { 50 | tasks.push(Task::from_sys_path(&SysFs::Custom(entry.path()).into_syspath())?); 51 | } 52 | Ok(tasks) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/os/linux/mem/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::sysinfo; 2 | use crate::Result; 3 | 4 | /// Returns the total amount of installed RAM in Bytes. 5 | pub fn memory_total() -> Result { 6 | sysinfo().map(|s| s.memory_total() as usize) 7 | } 8 | 9 | /// Returns the amount of completely unused RAM in Bytes. 10 | /// 11 | /// "Unused" in this context means that the RAM in neither actively used by 12 | /// programs, nor by the operating system as disk cache or buffer. It is 13 | /// "wasted" RAM since it currently serves no purpose. 14 | pub fn memory_free() -> Result { 15 | sysinfo().map(|s| s.memory_free() as usize) 16 | } 17 | 18 | /// Returns the amount of swap memory in Bytes. 19 | pub fn swap_total() -> Result { 20 | sysinfo().map(|s| s.swap_total() as usize) 21 | } 22 | 23 | /// Returns the amount of unused swap memory in Bytes. 24 | pub fn swap_free() -> Result { 25 | sysinfo().map(|s| s.swap_free() as usize) 26 | } 27 | 28 | /// Returns the total amount of shared RAM in Bytes. 29 | pub fn memory_shared() -> Result { 30 | sysinfo().map(|s| s.memory_shared() as usize) 31 | } 32 | 33 | /// Returns the total amount of memory used by buffers in Bytes. 34 | pub fn memory_buffered() -> Result { 35 | sysinfo().map(|s| s.memory_buffered() as usize) 36 | } 37 | 38 | /// Returns the total high memory size in Bytes. 39 | pub fn memory_high_total() -> Result { 40 | sysinfo().map(|s| s.memory_high_total() as usize) 41 | } 42 | 43 | /// Returns the total amount of unused high memory size in Bytes. 44 | pub fn memory_high_free() -> Result { 45 | sysinfo().map(|s| s.memory_high_free() as usize) 46 | } 47 | -------------------------------------------------------------------------------- /src/os/macos/mod.rs: -------------------------------------------------------------------------------- 1 | //! MacOS specific api 2 | mod cpu; 3 | mod mem; 4 | mod misc; 5 | mod os_impl_ext; 6 | mod system; 7 | 8 | pub use crate::os::unix::arch; 9 | pub use cpu::{cpu, cpu_clock, cpu_cores, logical_cores}; 10 | pub use mem::{memory_free, memory_total, swap_free, swap_total}; 11 | pub use misc::{domain_name, hostname, model, uptime}; 12 | pub use os_impl_ext::OsImplExt; 13 | 14 | use crate::os::OsImpl; 15 | use crate::Result; 16 | 17 | #[derive(Default)] 18 | pub(crate) struct MacOS {} 19 | impl MacOS { 20 | pub fn new() -> Self { 21 | Self::default() 22 | } 23 | } 24 | 25 | impl OsImpl for MacOS { 26 | fn hostname(&self) -> Result { 27 | hostname() 28 | } 29 | 30 | fn domain_name(&self) -> Result { 31 | domain_name() 32 | } 33 | 34 | fn uptime(&self) -> Result { 35 | uptime() 36 | } 37 | 38 | fn arch(&self) -> Result { 39 | arch() 40 | } 41 | 42 | fn cpu(&self) -> Result { 43 | cpu() 44 | } 45 | 46 | fn cpu_clock(&self) -> Result { 47 | cpu_clock() 48 | } 49 | 50 | fn cpu_cores(&self) -> Result { 51 | cpu_cores() 52 | } 53 | 54 | fn logical_cores(&self) -> Result { 55 | logical_cores() 56 | } 57 | 58 | fn memory_total(&self) -> Result { 59 | memory_total() 60 | } 61 | 62 | fn memory_free(&self) -> Result { 63 | memory_free() 64 | } 65 | 66 | fn swap_total(&self) -> Result { 67 | swap_total() 68 | } 69 | 70 | fn swap_free(&self) -> Result { 71 | swap_free() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: master 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths-ignore: 7 | - "*.md" 8 | - "LICENSE" 9 | branches: 10 | - master 11 | pull_request: 12 | paths-ignore: 13 | - "*.md" 14 | - "LICENSE" 15 | branches: 16 | - master 17 | 18 | env: 19 | CARGO_TERM_COLOR: always 20 | 21 | jobs: 22 | codestyle: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Set up Rust 26 | uses: hecrj/setup-rust-action@v1 27 | with: 28 | components: rustfmt 29 | rust-version: stable 30 | - uses: actions/checkout@v1 31 | - run: cargo fmt --all -- --check 32 | 33 | lint: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Set up Rust 37 | uses: hecrj/setup-rust-action@v1 38 | with: 39 | components: clippy 40 | - uses: actions/checkout@v1 41 | - run: cargo clippy --all-targets -- -D clippy::all 42 | 43 | compile: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Set up Rust 47 | uses: hecrj/setup-rust-action@v1 48 | - uses: actions/checkout@master 49 | - run: cargo check --all 50 | 51 | test: 52 | needs: [codestyle, lint, compile] 53 | strategy: 54 | matrix: 55 | os: 56 | - ubuntu-latest 57 | - macos-latest 58 | runs-on: ${{ matrix.os }} 59 | 60 | steps: 61 | - name: Setup Rust 62 | uses: hecrj/setup-rust-action@v1 63 | with: 64 | rust-version: ${{ matrix.rust }} 65 | - name: Checkout 66 | uses: actions/checkout@v1 67 | - name: Test 68 | run: cargo test 69 | -------------------------------------------------------------------------------- /src/os/unix/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(target_os = "macos", allow(dead_code))] 2 | use crate::{Error, Result}; 3 | 4 | #[cfg(not(target_os = "macos"))] 5 | use libc::size_t; 6 | #[cfg(target_os = "macos")] 7 | use std::os::raw::c_int; 8 | 9 | use libc::c_char; 10 | use nix::{errno::Errno, sys::utsname, unistd}; 11 | use std::ffi::CStr; 12 | 13 | /// Returns a hostname. 14 | pub fn hostname() -> Result { 15 | let mut buf = [0u8; 64]; 16 | Ok(unistd::gethostname(&mut buf)?.to_string_lossy().to_string()) 17 | } 18 | 19 | /// Returns the processor architecture 20 | pub fn arch() -> Result { 21 | Ok(utsname::uname().machine().to_string()) 22 | } 23 | 24 | /// Returns a domainname by calling getdomainname syscall 25 | pub fn domain_name() -> Result { 26 | const BUF_LEN: usize = 64; // Acording to manual entry of getdomainname this is the limit 27 | // of length for the domain name 28 | let mut buf = [0u8; BUF_LEN]; 29 | let ptr = buf.as_mut_ptr() as *mut c_char; 30 | 31 | #[cfg(target_os = "macos")] 32 | let len = BUF_LEN as c_int; 33 | #[cfg(not(target_os = "macos"))] 34 | let len = BUF_LEN as size_t; 35 | 36 | let res = unsafe { libc::getdomainname(ptr, len) }; 37 | Errno::result(res) 38 | .map(|_| { 39 | unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) } 40 | .to_string_lossy() 41 | .to_string() 42 | }) 43 | .map_err(Error::from) 44 | } 45 | 46 | /// Returns a kernel release of host os. 47 | pub fn kernel_release() -> Result { 48 | Ok(utsname::uname().release().to_string()) 49 | } 50 | 51 | /// The number of clock ticks per second. 52 | pub fn clock_tick() -> Result> { 53 | unistd::sysconf(nix::unistd::SysconfVar::CLK_TCK).map_err(Error::from) 54 | } 55 | -------------------------------------------------------------------------------- /src/os/macos/cpu.rs: -------------------------------------------------------------------------------- 1 | use crate::macos::system::*; 2 | use crate::{Error, Result}; 3 | 4 | use goblin::mach::cputype::{ 5 | CPU_TYPE_ALPHA, CPU_TYPE_ARM, CPU_TYPE_ARM64, CPU_TYPE_I860, CPU_TYPE_MIPS, CPU_TYPE_POWERPC, CPU_TYPE_POWERPC64, 6 | CPU_TYPE_SPARC, CPU_TYPE_X86, CPU_TYPE_X86_64, 7 | }; 8 | use sysctl::CtlValue; 9 | 10 | #[allow(dead_code)] 11 | fn _arch() -> Result { 12 | let cpu_type = match sysctl(SYSCTL_CPU_TYPE)? { 13 | CtlValue::Int(ty) => Ok(ty), 14 | val => Err(Error::UnexpectedSysctlValue(val)), 15 | }? as u32; 16 | 17 | Ok(match cpu_type { 18 | CPU_TYPE_ALPHA => "alpha", 19 | CPU_TYPE_ARM => "arm32", 20 | CPU_TYPE_ARM64 => "arm64", 21 | CPU_TYPE_X86_64 => "x86_64", 22 | CPU_TYPE_X86 => "x86", 23 | CPU_TYPE_I860 => "i860", 24 | CPU_TYPE_MIPS => "mips", 25 | CPU_TYPE_POWERPC => "ppc", 26 | CPU_TYPE_POWERPC64 => "ppc_64", 27 | CPU_TYPE_SPARC => "sparc", 28 | _ => "unknown", 29 | } 30 | .to_string()) 31 | } 32 | 33 | pub fn cpu() -> Result { 34 | match sysctl(SYSCTL_CPU)? { 35 | CtlValue::String(cpu) => Ok(cpu), 36 | val => Err(Error::UnexpectedSysctlValue(val)), 37 | } 38 | } 39 | 40 | pub fn cpu_clock() -> Result { 41 | match sysctl(SYSCTL_CPU_FREQUENCY)? { 42 | CtlValue::S64(clock) => Ok((clock / 1_000_000) as f32), 43 | val => Err(Error::UnexpectedSysctlValue(val)), 44 | } 45 | } 46 | 47 | pub fn cpu_cores() -> Result { 48 | match sysctl(SYSCTL_CPU_CORES)? { 49 | CtlValue::Int(cores) => Ok(cores as u16), 50 | val => Err(Error::UnexpectedSysctlValue(val)), 51 | } 52 | } 53 | 54 | pub fn logical_cores() -> Result { 55 | match sysctl(SYSCTL_LOGICAL_CORES)? { 56 | CtlValue::S64(num) => Ok(num as u16), 57 | val => Err(Error::UnexpectedSysctlValue(val)), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/os/macos/mem.rs: -------------------------------------------------------------------------------- 1 | use crate::macos::system::*; 2 | use crate::{Error, Result}; 3 | 4 | use std::mem; 5 | use sysctl::CtlValue; 6 | 7 | //################################################################################ 8 | // Public 9 | //################################################################################ 10 | 11 | pub fn memory_total() -> Result { 12 | match sysctl(SYSCTL_MEMSIZE)? { 13 | CtlValue::S64(num) => Ok(num as usize), 14 | val => Err(Error::UnexpectedSysctlValue(val)), 15 | } 16 | } 17 | 18 | pub fn memory_free() -> Result { 19 | // this returns a 32bit value so on systems with > 2GB of memory it will 20 | // show incorrect values. More on this here https://discussions.apple.com/thread/1775836 21 | match sysctl(SYSCTL_USERMEM)? { 22 | CtlValue::Int(num) => Ok(num as usize), 23 | val => Err(Error::UnexpectedSysctlValue(val)), 24 | } 25 | } 26 | 27 | pub fn swap_total() -> Result { 28 | get_swap().map(|swap| swap.total as usize) 29 | } 30 | 31 | pub fn swap_free() -> Result { 32 | get_swap().map(|swap| swap.free as usize) 33 | } 34 | 35 | //################################################################################ 36 | // Internal 37 | //################################################################################ 38 | 39 | #[derive(Clone, Default)] 40 | #[repr(C)] 41 | struct SwapUsage { 42 | total: libc::c_long, 43 | used: libc::c_long, 44 | free: libc::c_long, 45 | } 46 | 47 | fn get_swap() -> Result { 48 | match sysctl(SYSCTL_VM_SWAPUSAGE)? { 49 | CtlValue::Struct(mut val) => { 50 | let val_ptr = val.as_mut_ptr(); 51 | let swapusage_ptr = val_ptr as *mut SwapUsage; 52 | let swapusage_ref = unsafe { &mut *swapusage_ptr }; 53 | Ok(mem::take(swapusage_ref)) 54 | } 55 | val => Err(Error::UnexpectedSysctlValue(val)), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/os/macos/misc.rs: -------------------------------------------------------------------------------- 1 | use crate::macos::system::*; 2 | use crate::{Error, Result}; 3 | 4 | use std::time::{SystemTime, UNIX_EPOCH}; 5 | use sysctl::CtlValue; 6 | 7 | //################################################################################ 8 | // Public 9 | //################################################################################ 10 | 11 | pub fn hostname() -> Result { 12 | match sysctl(SYSCTL_HOSTNAME)? { 13 | CtlValue::String(hostname) => Ok(hostname), 14 | val => Err(Error::UnexpectedSysctlValue(val)), 15 | } 16 | } 17 | 18 | pub fn uptime() -> Result { 19 | let boot = match sysctl(SYSCTL_BOOTTIME)? { 20 | CtlValue::Struct(time) => { 21 | let time_ptr = time.as_ptr(); 22 | let uptime_ptr = time_ptr as *const Uptime; 23 | let uptime_ref = unsafe { &*uptime_ptr }; 24 | Ok(uptime_ref.sec as u64) 25 | } 26 | val => Err(Error::UnexpectedSysctlValue(val)), 27 | }?; 28 | 29 | let now = SystemTime::now() 30 | .duration_since(UNIX_EPOCH) 31 | .map_err(|e| Error::TimeError(e.to_string()))? 32 | .as_secs(); 33 | 34 | Ok(now - boot) 35 | } 36 | 37 | pub fn domain_name() -> Result { 38 | match sysctl(SYSCTL_DOMAINNAME)? { 39 | CtlValue::String(cpu) => Ok(cpu), 40 | val => Err(Error::UnexpectedSysctlValue(val)), 41 | } 42 | } 43 | 44 | //################################################################################ 45 | // UNIQUE 46 | //################################################################################ 47 | 48 | /// Returns a model of host machine. 49 | pub fn model() -> Result { 50 | match sysctl(SYSCTL_MODEL)? { 51 | CtlValue::String(cpu) => Ok(cpu), 52 | val => Err(Error::UnexpectedSysctlValue(val)), 53 | } 54 | } 55 | 56 | //################################################################################ 57 | // Internal 58 | //################################################################################ 59 | 60 | #[repr(C)] 61 | struct Uptime { 62 | sec: libc::time_t, 63 | usec: libc::time_t, 64 | } 65 | -------------------------------------------------------------------------------- /src/os/linux/mod.rs: -------------------------------------------------------------------------------- 1 | //! Linux specific api 2 | #![cfg(target_os = "linux")] 3 | 4 | #[cfg(test)] 5 | pub(crate) mod mocks; 6 | 7 | pub mod cpu; 8 | pub mod mem; 9 | pub mod mounts; 10 | mod os_impl_ext; 11 | pub mod ps; 12 | mod sysinfo; 13 | mod sysproc; 14 | 15 | pub use crate::os::unix::{arch, clock_tick, domain_name, hostname, kernel_release}; 16 | pub use sysinfo::{sysinfo, SysInfo}; 17 | pub(crate) use sysproc::{SysFs, SysPath}; 18 | pub(crate) use { 19 | mem::{memory_free, memory_total, swap_free, swap_total}, 20 | os_impl_ext::OsImplExt, 21 | }; 22 | 23 | use crate::os::OsImpl; 24 | use crate::Result; 25 | 26 | /// Returns uptime of host machine in seconds 27 | pub fn uptime() -> Result { 28 | Ok(sysinfo()?.uptime().as_secs()) 29 | } 30 | 31 | #[derive(Default)] 32 | pub(crate) struct Linux {} 33 | 34 | impl Linux { 35 | pub fn new() -> Self { 36 | Self::default() 37 | } 38 | } 39 | 40 | impl OsImpl for Linux { 41 | fn hostname(&self) -> Result { 42 | hostname() 43 | } 44 | 45 | fn domain_name(&self) -> Result { 46 | domain_name() 47 | } 48 | 49 | fn uptime(&self) -> Result { 50 | uptime() 51 | } 52 | 53 | fn arch(&self) -> Result { 54 | arch() 55 | } 56 | 57 | fn cpu(&self) -> Result { 58 | cpu::model() 59 | } 60 | 61 | fn cpu_clock(&self) -> Result { 62 | clock_tick().and_then(|clock| { 63 | if let Some(clock) = clock { 64 | Ok(clock as f32) 65 | } else { 66 | cpu::clock() 67 | } 68 | }) 69 | } 70 | 71 | fn cpu_cores(&self) -> Result { 72 | cpu::core_count() 73 | } 74 | 75 | fn logical_cores(&self) -> Result { 76 | cpu::logical_cores() 77 | } 78 | 79 | fn memory_total(&self) -> Result { 80 | memory_total() 81 | } 82 | 83 | fn memory_free(&self) -> Result { 84 | memory_free() 85 | } 86 | 87 | fn swap_total(&self) -> Result { 88 | swap_total() 89 | } 90 | 91 | fn swap_free(&self) -> Result { 92 | swap_free() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # rsys 2 | //! Crate for aquiring information about host machine and operating system 3 | //! in a os-agnostic fashion. 4 | //! 5 | //! The common api is available through Rsys struct which compiles conditionally with 6 | //! required methods. The error and result type is available at the root of this crate for convienience 7 | //! while all the methods exposed by Rsys struct are also available in each os module. 8 | //! 9 | //! Main goals are clear and easy api with as much of the api being os-agnostic. 10 | //! 11 | //! ## Example usage: 12 | //! ``` 13 | //! use rsys::{Result, Rsys}; 14 | //! 15 | //! fn main() -> Result<()> { 16 | //! // You can either use api through Rsys object 17 | //! // for os-agnostic experience 18 | //! let rsys = Rsys::new(); 19 | //! println!("HOSTNAME - {}", rsys.hostname()?); 20 | //! println!("CPU - {}", rsys.cpu()?); 21 | //! println!("CPU CORES - {}", rsys.cpu_cores()?); 22 | //! println!("CPU CLOCK - {}MHz", rsys.cpu_clock()?); 23 | //! println!("UPTIME - {}s", rsys.uptime()?); 24 | //! println!("ARCH - {}", rsys.arch()?); 25 | //! println!("SWAP TOTAL - {}b", rsys.swap_total()?); 26 | //! println!("SWAP FREE - {}b", rsys.swap_free()?); 27 | //! println!("MEMORY TOTAL - {}b", rsys.memory_total()?); 28 | //! println!("MEMORY FREE - {}b", rsys.memory_free()?); 29 | //! 30 | //! #[cfg(target_os = "linux")] 31 | //! { 32 | //! // Or use functions in each module 33 | //! println!("KERNEL VERSION - {}", rsys::linux::kernel_release()?); 34 | //! 35 | //! // Os-specific functions are also available as methods 36 | //! println!("KERNEL_VERSION - {}", rsys.kernel_release()?); 37 | //! println!("{:#?}", rsys.pids()?); 38 | //! println!("MOUNTS - {:#?}", rsys::linux::mounts::mounts()?); 39 | //! } 40 | //! Ok(()) 41 | //! } 42 | //! ``` 43 | 44 | #[cfg(target_os = "windows")] 45 | extern crate winapi; 46 | 47 | mod api; 48 | mod error; 49 | mod os; 50 | pub(crate) mod util; 51 | pub use api::Rsys; 52 | pub use error::{RsysError as Error, RsysResult as Result}; 53 | 54 | #[cfg(target_os = "linux")] 55 | pub use os::linux; 56 | #[cfg(target_os = "macos")] 57 | pub use os::macos; 58 | #[cfg(target_os = "windows")] 59 | pub use os::windows; 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsys 2 | [![master](https://github.com/wojciechkepka/rsys/actions/workflows/master.yml/badge.svg)](https://github.com/wojciechkepka/rsys/actions/workflows/master.yml) 3 | [![crates.io](https://img.shields.io/crates/v/rsys)](https://crates.io/crates/rsys) 4 | [![crates.io](https://img.shields.io/crates/l/rsys)](https://github.com/wojciechkepka/rsys/blob/master/LICENSE) 5 | [![Docs](https://img.shields.io/badge/docs-master-brightgreen)](https://docs.rs/rsys) 6 | Crate for aquiring information about host machine and operating system 7 | in a os-agnostic fashion. 8 | 9 | The common api is available through Rsys struct which compiles conditionally with 10 | required methods. The error and result type is available at the root of this crate for convienience 11 | while all the methods exposed by Rsys struct are also available in each os module. 12 | 13 | Main goals are clear and easy api with as much of the api being os-agnostic. 14 | 15 | ## Example usage: 16 | - `Cargo.toml` 17 | 18 | ```toml 19 | [dependencies] 20 | rsys = "0.5" 21 | ``` 22 | 23 | - `main.rs` 24 | ```rust 25 | use rsys::{Result, Rsys}; 26 | 27 | fn main() -> Result<()> { 28 | // You can either use api through Rsys object 29 | // for os-agnostic experience 30 | let rsys = Rsys::new(); 31 | println!("HOSTNAME - {}", rsys.hostname()?); 32 | println!("CPU - {}", rsys.cpu()?); 33 | println!("CPU CORES - {}", rsys.cpu_cores()?); 34 | println!("CPU CLOCK - {}MHz", rsys.cpu_clock()?); 35 | println!("UPTIME - {}s", rsys.uptime()?); 36 | println!("ARCH - {}", rsys.arch()?); 37 | println!("SWAP TOTAL - {}b", rsys.swap_total()?); 38 | println!("SWAP FREE - {}b", rsys.swap_free()?); 39 | println!("MEMORY TOTAL - {}b", rsys.memory_total()?); 40 | println!("MEMORY FREE - {}b", rsys.memory_free()?); 41 | 42 | #[cfg(target_os = "linux")] 43 | { 44 | // Or use functions in each module 45 | println!("KERNEL VERSION - {}", rsys::linux::kernel_release()?); 46 | 47 | // Os-specific functions are also available as methods 48 | println!("KERNEL_VERSION - {}", rsys.kernel_release()?); 49 | println!("{:#?}", rsys.pids()?); 50 | println!("MOUNTS - {:#?}", rsys::linux::mounts::mounts()?); 51 | } 52 | Ok(()) 53 | } 54 | ``` 55 | 56 | ## TODO 57 | - [ ] Finish macos common api 58 | - [ ] Finish windows common api 59 | - [ ] Add async feature for async file reads and commands etc... 60 | 61 | ## License 62 | [**MIT**](https://gitlab.com/vvvxxx/rsys/-/blob/master/LICENSE) 63 | -------------------------------------------------------------------------------- /src/os/windows/mod.rs: -------------------------------------------------------------------------------- 1 | //! Windows specific api 2 | mod internal; 3 | mod os_impl_ext; 4 | mod public; 5 | 6 | use super::OsImpl; 7 | use crate::{Error, Result}; 8 | use std::{ 9 | ffi::OsString, 10 | mem, 11 | os::windows::ffi::OsStringExt, 12 | ptr::{null, null_mut}, 13 | }; 14 | use winapi::{ 15 | shared::{minwindef::HKEY, winerror::ERROR_SUCCESS}, 16 | um::{ 17 | errhandlingapi::GetLastError, 18 | //lmwksta::{NetWkstaGetInfo, WKSTA_INFO_100}, 19 | sysinfoapi::{ 20 | GetLogicalProcessorInformation, GetSystemInfo, GetTickCount64, GlobalMemoryStatusEx, MEMORYSTATUSEX, 21 | SYSTEM_INFO, 22 | }, 23 | winbase::{FormatMessageW, GetComputerNameW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS}, 24 | winnt::{ 25 | KEY_READ, 26 | LANG_NEUTRAL, 27 | MAKELANGID, 28 | SUBLANG_DEFAULT, 29 | SYSTEM_LOGICAL_PROCESSOR_INFORMATION, 30 | //SYSTEM_PROCESSOR_CYCLE_TIME_INFORMATION, 31 | }, 32 | winreg::{RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE}, 33 | }, 34 | }; 35 | 36 | pub(crate) use internal::*; 37 | pub use os_impl_ext::OsImplExt; 38 | pub use public::*; 39 | 40 | const BUF_SIZE: usize = 4096; 41 | const NUL: char = '\0'; 42 | const CARIAGE: char = '\r'; 43 | const NL: char = '\n'; 44 | 45 | //https://github.com/retep998/winapi-rs/issues/780 46 | const MAX_COMPUTERNAME_LENGTH: u32 = 31; 47 | 48 | #[derive(Default)] 49 | pub(crate) struct Windows {} 50 | 51 | impl OsImplExt for Windows {} 52 | 53 | impl Windows { 54 | pub fn new() -> Self { 55 | Self::default() 56 | } 57 | } 58 | 59 | impl OsImpl for Windows { 60 | fn hostname(&self) -> Result { 61 | hostname() 62 | } 63 | 64 | fn domain_name(&self) -> Result { 65 | domain_name() 66 | } 67 | 68 | fn uptime(&self) -> Result { 69 | uptime() 70 | } 71 | 72 | fn arch(&self) -> Result { 73 | arch() 74 | } 75 | 76 | fn cpu(&self) -> Result { 77 | cpu() 78 | } 79 | 80 | fn cpu_clock(&self) -> Result { 81 | cpu_clock() 82 | } 83 | 84 | fn cpu_cores(&self) -> Result { 85 | cpu_cores() 86 | } 87 | 88 | fn logical_cores(&self) -> Result { 89 | logical_cores() 90 | } 91 | 92 | fn memory_total(&self) -> Result { 93 | memory_total() 94 | } 95 | 96 | fn memory_free(&self) -> Result { 97 | memory_free() 98 | } 99 | 100 | fn swap_total(&self) -> Result { 101 | swap_total() 102 | } 103 | 104 | fn swap_free(&self) -> Result { 105 | swap_free() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/os/linux/ps/mod.rs: -------------------------------------------------------------------------------- 1 | //! All about processes 2 | 3 | mod process; 4 | mod stat; 5 | mod state; 6 | 7 | pub use process::*; 8 | pub use stat::*; 9 | pub use state::*; 10 | 11 | use crate::linux::{SysFs, SysPath}; 12 | use crate::{Error, Result}; 13 | 14 | use std::fs; 15 | 16 | //################################################################################ 17 | // Public 18 | //################################################################################ 19 | 20 | /// Returns detailed Process information parsed from /proc/[pid]/stat 21 | pub fn stat_process(pid: i32) -> Result { 22 | ProcessStat::from_stat(&SysFs::Proc.join(pid.to_string()).join("stat").read()?) 23 | } 24 | 25 | /// Returns a list of pids read from /proc 26 | pub fn pids() -> Result> { 27 | let path = SysFs::Proc.into_syspath().into_pathbuf(); 28 | let mut pids = Vec::new(); 29 | for entry in fs::read_dir(&path) 30 | .map_err(|e| Error::FileReadError(path.to_string_lossy().to_string(), e.to_string()))? 31 | .flatten() 32 | { 33 | let filename = entry.file_name(); 34 | let sfilename = filename.as_os_str().to_string_lossy(); 35 | if sfilename.chars().all(|c| c.is_digit(10)) { 36 | pids.push( 37 | sfilename 38 | .parse::() 39 | .map_err(|e| Error::InvalidInputError(sfilename.to_string(), e.to_string()))?, 40 | ); 41 | } 42 | } 43 | Ok(pids) 44 | } 45 | 46 | /// Returns all processes currently seen in /proc parsed as Processes 47 | pub fn processes() -> Result { 48 | let mut ps = Vec::new(); 49 | for pid in pids()? { 50 | ps.push(Process::new(pid)?); 51 | } 52 | 53 | Ok(ps) 54 | } 55 | 56 | //################################################################################ 57 | // Internal 58 | //################################################################################ 59 | 60 | fn cmdline(path: &SysPath) -> Result { 61 | path.extend("cmdline") 62 | .read() 63 | .map(|s| s.trim_end_matches('\x00').replace('\x00', " ")) 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use std::{fs, io}; 70 | #[test] 71 | fn parses_cmdline() -> io::Result<()> { 72 | let line = "/usr/lib/firefox/firefox\x00-contentproc\x00-childID\x001\x00-isForBrowser\x00-prefsLen\x001\x00-prefMapSize\x00234803\x00-parentBuildID\x0020201001181215\x00-appdir\x00/usr/lib/firefox/browser\x006732\x00true\x00tab\x00"; 73 | 74 | let dir = tempfile::tempdir()?; 75 | fs::write(dir.path().join("cmdline"), line)?; 76 | 77 | let after = "/usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -prefsLen 1 -prefMapSize 234803 -parentBuildID 20201001181215 -appdir /usr/lib/firefox/browser 6732 true tab".to_string(); 78 | 79 | assert_eq!( 80 | after, 81 | cmdline(&SysFs::Custom(dir.path().to_owned()).into_syspath()).unwrap() 82 | ); 83 | 84 | dir.close() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/os/linux/os_impl_ext.rs: -------------------------------------------------------------------------------- 1 | use super::{cpu::*, kernel_release, mem::*, mounts::*, ps::*, Linux}; 2 | use crate::Result; 3 | 4 | /// Trait extending Rsys functionality with linux specific api 5 | pub trait OsImplExt { 6 | // 7 | // mem 8 | // 9 | 10 | /// Returns the total amount of shared RAM in Bytes. 11 | fn memory_shared(&self) -> Result; 12 | 13 | /// Returns the total amount of memory used by buffers in Bytes. 14 | fn memory_buffered(&self) -> Result; 15 | 16 | /// Returns the total high memory size in Bytes. 17 | fn memory_high_total(&self) -> Result; 18 | 19 | /// Returns the total amount of unused high memory size in Bytes. 20 | fn memory_high_free(&self) -> Result; 21 | 22 | // 23 | // ps 24 | // 25 | 26 | /// Returns detailed Process information parsed from /proc/[pid]/stat 27 | fn stat_process(&self, pid: i32) -> Result; 28 | 29 | /// Returns a list of pids read from /proc 30 | fn pids(&self) -> Result>; 31 | 32 | /// Returns all processes currently seen in /proc parsed as Processes 33 | fn processes(&self) -> Result; 34 | 35 | // 36 | // other 37 | // 38 | 39 | /// Returns kernel version of host os. 40 | fn kernel_release(&self) -> Result; 41 | 42 | /// Returns MountPoints read from /proc/mounts 43 | fn mounts(&self) -> Result; 44 | 45 | // 46 | // cpu 47 | // 48 | 49 | /// Returns virtual Cores of host cpu 50 | fn cores(&self) -> Result; 51 | 52 | /// Returns a Processor object containing gathered information about host cpu 53 | fn processor(&self) -> Result; 54 | } 55 | 56 | impl OsImplExt for Linux { 57 | // 58 | // mem 59 | // 60 | 61 | fn memory_shared(&self) -> Result { 62 | memory_shared() 63 | } 64 | 65 | fn memory_buffered(&self) -> Result { 66 | memory_buffered() 67 | } 68 | 69 | fn memory_high_total(&self) -> Result { 70 | memory_high_total() 71 | } 72 | 73 | fn memory_high_free(&self) -> Result { 74 | memory_high_free() 75 | } 76 | 77 | // 78 | // ps 79 | // 80 | 81 | fn stat_process(&self, pid: i32) -> Result { 82 | stat_process(pid) 83 | } 84 | 85 | fn pids(&self) -> Result> { 86 | pids() 87 | } 88 | 89 | fn processes(&self) -> Result { 90 | processes() 91 | } 92 | 93 | // 94 | // other 95 | // 96 | 97 | fn kernel_release(&self) -> Result { 98 | kernel_release() 99 | } 100 | 101 | fn mounts(&self) -> Result { 102 | mounts() 103 | } 104 | 105 | // 106 | // cpu 107 | // 108 | 109 | fn cores(&self) -> Result { 110 | cores() 111 | } 112 | 113 | fn processor(&self) -> Result { 114 | processor() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/os/windows/public.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub fn hostname() -> Result { 4 | let mut out_buf: Vec = vec![0; BUF_SIZE]; 5 | let mut out_size: u32 = MAX_COMPUTERNAME_LENGTH; 6 | unsafe { 7 | let ret = GetComputerNameW(out_buf.as_mut_ptr(), &mut out_size); 8 | if ret == 0 { 9 | let (id, msg) = last_error_msg()?; 10 | return Err(Error::WinApiError(id, msg)); 11 | } 12 | } 13 | utf16_buf_to_string(&out_buf) 14 | } 15 | 16 | pub fn uptime() -> Result { 17 | unsafe { Ok((GetTickCount64() as u64) * 1000) } 18 | } 19 | 20 | pub fn arch() -> Result { 21 | unsafe { 22 | let arch = match system_info().u.s().wProcessorArchitecture { 23 | 9 => "x64", 24 | 5 => "ARM", 25 | 12 => "ARM64", 26 | 6 => "Intel Itanium-based", 27 | 0 => "x86", 28 | _ => "", 29 | }; 30 | Ok(arch.to_string()) 31 | } 32 | } 33 | 34 | pub fn cpu() -> Result { 35 | Ok("".to_string()) 36 | } 37 | 38 | // # TODO 39 | // Figure out why the registry is returning an empty buffer (probably not finding the right hkey?) 40 | pub fn cpu_clock() -> Result { 41 | reg_val::( 42 | HKEY_LOCAL_MACHINE, 43 | "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 44 | "~MHz", 45 | ) 46 | .map(|v| v as f32) 47 | } 48 | 49 | pub fn cpu_cores() -> Result { 50 | if is_cpu_hyperthreaded()? { 51 | Ok(logical_cores()? / 2) 52 | } else { 53 | Ok(logical_cores()?) 54 | } 55 | } 56 | 57 | pub fn logical_cores() -> Result { 58 | Ok(system_info().dwNumberOfProcessors as u16) 59 | } 60 | 61 | pub fn memory_total() -> Result { 62 | Ok(memory_status()?.ullTotalPhys as usize) 63 | } 64 | 65 | pub fn memory_free() -> Result { 66 | Ok(memory_status()?.ullAvailPhys as usize) 67 | } 68 | 69 | pub fn swap_total() -> Result { 70 | Ok(memory_status()?.ullTotalVirtual as usize) 71 | } 72 | 73 | pub fn swap_free() -> Result { 74 | Ok(memory_status()?.ullAvailVirtual as usize) 75 | } 76 | 77 | pub fn default_iface() -> Result { 78 | Ok("".to_string()) 79 | } 80 | 81 | pub fn ipv4(iface: &str) -> Result { 82 | Ok("".to_string()) 83 | } 84 | 85 | pub fn ipv6(_iface: &str) -> Result { 86 | Ok("".to_string()) 87 | } 88 | 89 | pub fn mac(iface: &str) -> Result { 90 | Ok("".to_string()) 91 | } 92 | 93 | pub fn interfaces() -> Result> { 94 | Ok(vec![]) 95 | } 96 | 97 | pub fn domainname() -> Result { 98 | // Ok(net_wksta().wki100_langroup) 99 | Ok("".to_string()) 100 | } 101 | 102 | //################################################################################ 103 | // UNIQUE 104 | 105 | /// Returns a number between 0 and 100 that specifies the approximate percentage of physical memory that is in use 106 | /// (0 indicates no memory use and 100 indicates full memory use). 107 | pub fn memory_load() -> Result { 108 | Ok(memory_status()?.dwMemoryLoad as u32) 109 | } 110 | -------------------------------------------------------------------------------- /src/os/linux/cpu/processor.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::cpu::{cores, Cores, CpuTime, BOGOMIPS, CACHE_SIZE, MODEL_NAME}; 2 | use crate::linux::{SysFs, SysPath}; 3 | use crate::{Error, Result}; 4 | 5 | #[cfg(feature = "serialize")] 6 | use serde::{Deserialize, Serialize}; 7 | use std::str::FromStr; 8 | 9 | #[derive(Clone, Debug, Default, PartialEq)] 10 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 11 | /// A structure representing host machine cpu 12 | pub struct Processor { 13 | pub cores: Cores, 14 | pub model: String, 15 | /// Cache size in Bytes 16 | pub cache_size: u64, // bytes 17 | /// Relative measurement of how fast a computer is. 18 | pub bogomips: f32, 19 | } 20 | 21 | impl Processor { 22 | /// Returns core count of this processor 23 | pub fn core_count(&self) -> usize { 24 | self.cores.len() 25 | } 26 | 27 | /// Returns cpu time spent by this processor 28 | pub fn cpu_time(&self) -> Result> { 29 | CpuTime::from_stat("") 30 | } 31 | 32 | pub(crate) fn from_sys() -> Result { 33 | let mut proc = Self::from_sys_path(&SysFs::Proc.join("cpuinfo"))?; 34 | proc.cores = cores()?; 35 | Ok(proc) 36 | } 37 | 38 | pub(crate) fn from_sys_path(path: &SysPath) -> Result { 39 | let cpuinfo = path.read()?; 40 | let mut proc = Processor::default(); 41 | for line in cpuinfo.lines() { 42 | if line.starts_with(MODEL_NAME) { 43 | proc.model = Self::last_line_elem(line).to_string(); 44 | } else if line.starts_with(BOGOMIPS) { 45 | proc.bogomips = Self::bogomips_from(line)?; 46 | } else if line.starts_with(CACHE_SIZE) { 47 | proc.cache_size = Self::cache_size_from(line)?; 48 | } 49 | } 50 | Ok(proc) 51 | } 52 | 53 | fn last_line_elem(line: &str) -> &str { 54 | line.split(':').last().unwrap_or_default().trim() 55 | } 56 | 57 | fn bogomips_from(line: &str) -> Result { 58 | f32::from_str(Self::last_line_elem(line)).map_err(|e| Error::InvalidInputError(line.to_string(), e.to_string())) 59 | } 60 | 61 | fn cache_size_from(line: &str) -> Result { 62 | u64::from_str(Self::last_line_elem(line).split_whitespace().next().unwrap_or_default()) 63 | .map_err(|e| Error::InvalidInputError(line.to_string(), e.to_string())) 64 | .map(|v| v * 1024) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::*; 71 | use crate::linux::mocks::CPUINFO; 72 | use std::{fs, io}; 73 | 74 | #[test] 75 | fn creates_processor_from_cpuinfo() -> io::Result<()> { 76 | let dir = tempfile::tempdir()?; 77 | fs::write(dir.path().join("cpuinfo"), CPUINFO)?; 78 | 79 | let cpu = Processor { 80 | bogomips: 7_189.98, 81 | cache_size: 524_288, 82 | model: "AMD Ryzen 5 3600 6-Core Processor".to_string(), 83 | cores: Vec::new(), 84 | }; 85 | 86 | assert_eq!( 87 | cpu, 88 | Processor::from_sys_path(&SysFs::Custom(dir.path().to_owned()).join("cpuinfo")).unwrap() 89 | ); 90 | 91 | dir.close() 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/os/linux/sysinfo.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, Result}; 2 | 3 | use libc::SI_LOAD_SHIFT; 4 | use nix::errno::Errno; 5 | use std::{cmp, mem, time::Duration}; 6 | 7 | /// A structure containing certain statistics on memory and swap usage, as 8 | /// well as the load average and uptime. 9 | pub struct SysInfo(libc::sysinfo); 10 | 11 | // Shamelessly borrowed from nix crate to add missing fields 12 | impl SysInfo { 13 | /// Returns the load average tuple. 14 | /// 15 | /// The returned values represent the load average over time intervals of 16 | /// 1, 5, and 15 minutes, respectively. 17 | pub fn load_average(&self) -> (f64, f64, f64) { 18 | ( 19 | self.0.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, 20 | self.0.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, 21 | self.0.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, 22 | ) 23 | } 24 | 25 | /// Returns the time since system boot. 26 | pub fn uptime(&self) -> Duration { 27 | // Truncate negative values to 0 28 | Duration::from_secs(cmp::max(self.0.uptime, 0) as u64) 29 | } 30 | 31 | /// Current number of processes. 32 | pub fn process_count(&self) -> u16 { 33 | self.0.procs 34 | } 35 | 36 | /// Returns the amount of swap memory in Bytes. 37 | pub fn swap_total(&self) -> u64 { 38 | self.scale_mem(self.0.totalswap) 39 | } 40 | 41 | /// Returns the amount of unused swap memory in Bytes. 42 | pub fn swap_free(&self) -> u64 { 43 | self.scale_mem(self.0.freeswap) 44 | } 45 | 46 | /// Returns the total amount of installed RAM in Bytes. 47 | pub fn memory_total(&self) -> u64 { 48 | self.scale_mem(self.0.totalram) 49 | } 50 | 51 | /// Returns the amount of completely unused RAM in Bytes. 52 | /// 53 | /// "Unused" in this context means that the RAM in neither actively used by 54 | /// programs, nor by the operating system as disk cache or buffer. It is 55 | /// "wasted" RAM since it currently serves no purpose. 56 | pub fn memory_free(&self) -> u64 { 57 | self.scale_mem(self.0.freeram) 58 | } 59 | 60 | /// Returns the total amount of shared RAM in Bytes. 61 | pub fn memory_shared(&self) -> u64 { 62 | self.scale_mem(self.0.sharedram) 63 | } 64 | 65 | /// Returns the total amount of memory used by buffers in Bytes. 66 | pub fn memory_buffered(&self) -> u64 { 67 | self.scale_mem(self.0.bufferram) 68 | } 69 | 70 | /// Returns the total high memory size in Bytes. 71 | pub fn memory_high_total(&self) -> u64 { 72 | self.scale_mem(self.0.totalhigh) 73 | } 74 | 75 | /// Returns the total amount of unused high memory size in Bytes. 76 | pub fn memory_high_free(&self) -> u64 { 77 | self.scale_mem(self.0.totalhigh) 78 | } 79 | 80 | fn scale_mem(&self, units: u64) -> u64 { 81 | units * self.0.mem_unit as u64 82 | } 83 | } 84 | 85 | /// returns certain statistics on memory and swap usage, as well as the load average. 86 | pub fn sysinfo() -> Result { 87 | let mut info = mem::MaybeUninit::uninit(); 88 | let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; 89 | Errno::result(res) 90 | .map(|_| unsafe { SysInfo(info.assume_init()) }) 91 | .map_err(Error::from) 92 | } 93 | -------------------------------------------------------------------------------- /src/os/linux/cpu/time.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::SysFs; 2 | use crate::{ 3 | util::{next, skip}, 4 | Result, 5 | }; 6 | 7 | #[cfg(feature = "serialize")] 8 | use serde::{Deserialize, Serialize}; 9 | use std::str::SplitAsciiWhitespace; 10 | 11 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 12 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 13 | /// Represents time spent on processing instructions. Each field represents the amount of time, 14 | /// measured in units of USER_HZ (1/100ths of a second on most architectures, use [`clock_tick`](crate::linux::cpu::clock_tick) 15 | /// to get the precise value), that the system or the specific CPU spent in the state specified by the field name. 16 | pub struct CpuTime { 17 | /// Time spent in user mode. 18 | pub user: u64, 19 | /// Time spent in user mode with low priority (nice). 20 | pub nice: u64, 21 | /// Time spent in system mode. 22 | pub system: u64, 23 | /// Time spent in the idle task. This value should be USER_HZ times the second entry in 24 | /// the /proc/uptime pseudo-file. 25 | pub idle: u64, 26 | /// Time waiting for I/O to complete. Acording to the manual this value is not reliable. 27 | pub iowait: u64, 28 | /// Time servicing interrupts. 29 | pub irq: u64, 30 | /// Time servicing softirqs. 31 | pub softirq: u64, 32 | /// Stolen time, which is the time spent in other operating systems when running in a 33 | /// virtualized environment 34 | pub steal: u64, 35 | /// Time spent running a virtual CPU for guest operating systems under the control of 36 | /// the Linux kernel. 37 | pub guest: u64, 38 | /// Time spent running a niced guest (virtual CPU for guest operating systems under the 39 | /// control of the Linux kernel). 40 | pub guest_nice: u64, 41 | } 42 | 43 | impl CpuTime { 44 | pub fn total_time(&self) -> u64 { 45 | self.user 46 | + self.nice 47 | + self.system 48 | + self.idle 49 | + self.iowait 50 | + self.irq 51 | + self.softirq 52 | + self.steal 53 | + self.guest 54 | + self.guest_nice 55 | } 56 | 57 | pub(crate) fn from_stat(id: &str) -> Result> { 58 | let name = format!("cpu{}", id); 59 | for line in SysFs::Proc.join("stat").read()?.lines() { 60 | if line.starts_with(&name) { 61 | return Ok(Some(CpuTime::from_stat_line(line)?)); 62 | } 63 | } 64 | Ok(None) 65 | } 66 | 67 | pub(crate) fn from_stat_line(stat: &str) -> Result { 68 | let mut elems = stat.split_ascii_whitespace(); 69 | 70 | macro_rules! _next { 71 | ($t:tt) => { 72 | next::<$t, SplitAsciiWhitespace>(&mut elems, &stat)? 73 | }; 74 | } 75 | 76 | Ok(CpuTime { 77 | user: next::(skip(1, &mut elems), stat)?, 78 | nice: _next!(u64), 79 | system: _next!(u64), 80 | idle: _next!(u64), 81 | iowait: _next!(u64), 82 | irq: _next!(u64), 83 | softirq: _next!(u64), 84 | steal: _next!(u64), 85 | guest: _next!(u64), 86 | guest_nice: _next!(u64), 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/os/linux/sysproc.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use crate::{Error, Result}; 4 | 5 | use std::{ 6 | fmt::Display, 7 | fs, 8 | path::{Path, PathBuf}, 9 | str::FromStr, 10 | }; 11 | 12 | /// SysPath is an abstraction around procfs and sysfs. Allows for easy reading and parsing 13 | /// of values in system paths. 14 | #[derive(Clone, Debug)] 15 | pub(crate) enum SysFs { 16 | Sys, 17 | Proc, 18 | Dev, 19 | Custom(PathBuf), 20 | } 21 | 22 | impl AsRef for SysFs { 23 | fn as_ref(&self) -> &str { 24 | match &self { 25 | SysFs::Proc => "/proc", 26 | SysFs::Sys => "/sys", 27 | SysFs::Dev => "/dev", 28 | SysFs::Custom(s) => s.to_str().unwrap_or_default(), 29 | } 30 | } 31 | } 32 | 33 | impl From for SysPath { 34 | fn from(path: SysFs) -> Self { 35 | SysPath(PathBuf::from(path.as_ref())) 36 | } 37 | } 38 | 39 | impl From for PathBuf { 40 | fn from(path: SysPath) -> Self { 41 | path.0 42 | } 43 | } 44 | 45 | impl SysFs { 46 | pub(crate) fn join>(self, path: P) -> SysPath { 47 | self.into_syspath().join(path) 48 | } 49 | 50 | pub(crate) fn into_syspath(self) -> SysPath { 51 | self.into() 52 | } 53 | } 54 | 55 | #[derive(Clone, Debug, PartialEq)] 56 | pub(crate) struct SysPath(PathBuf); 57 | 58 | impl SysPath { 59 | pub(crate) fn join>(mut self, path: P) -> SysPath { 60 | self.0.push(path); 61 | self 62 | } 63 | 64 | /// Extends path with new elements returning a custom SysPath by cloning old one 65 | pub(crate) fn extend>(&self, p: P) -> Self { 66 | let mut path = self.clone(); 67 | path.0.push(p.as_ref()); 68 | path 69 | } 70 | 71 | pub(crate) fn as_path(&self) -> &Path { 72 | self.0.as_path() 73 | } 74 | 75 | pub(crate) fn into_pathbuf(self) -> PathBuf { 76 | PathBuf::from(self) 77 | } 78 | 79 | /// Reads path to a string returning FileReadError on error 80 | pub(crate) fn read(&self) -> Result { 81 | let path = self.as_path(); 82 | fs::read_to_string(path).map_err(|e| Error::FileReadError(path.to_string_lossy().to_string(), e.to_string())) 83 | } 84 | 85 | /// Reads path and parses it as T otherwise returns FileReadError or InvalidInputError on error 86 | pub(crate) fn read_as(&self) -> Result 87 | where 88 | ::Err: Display, 89 | { 90 | let path = self.as_path(); 91 | let data = fs::read_to_string(path) 92 | .map_err(|e| Error::FileReadError(path.to_string_lossy().to_string(), e.to_string()))?; 93 | 94 | T::from_str(data.trim()).map_err(|e| Error::InvalidInputError(data, e.to_string())) 95 | } 96 | 97 | /// Returns iterator over entries of this path 98 | pub(crate) fn read_dir(&self) -> Result { 99 | let path = self.as_path(); 100 | fs::read_dir(path).map_err(|e| Error::FileReadError(path.to_string_lossy().to_string(), e.to_string())) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | use std::path::PathBuf; 108 | 109 | #[test] 110 | fn correctly_joins_paths() { 111 | let mut path = SysPath(PathBuf::from("/proc/12/cpuset")); 112 | assert_eq!(path, SysFs::Proc.join("12").join("cpuset")); 113 | 114 | path = SysPath(PathBuf::from("/sys/block/sda")); 115 | assert_eq!(path, SysFs::Sys.join("block").join("sda")); 116 | 117 | path = SysPath(PathBuf::from("/dev/mapper/vgmain-root")); 118 | assert_eq!(path, SysFs::Dev.join("mapper").join("vgmain-root")); 119 | 120 | path = SysPath(PathBuf::from("/home/user/")); 121 | assert_eq!(path, SysFs::Custom(PathBuf::from("/home")).join("user")); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/os/windows/internal.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(crate) fn last_error() -> u32 { 4 | unsafe { GetLastError() } 5 | } 6 | 7 | pub(crate) fn utf16_buf_to_string(buf: &[u16]) -> Result { 8 | Ok(OsString::from_wide(&buf) 9 | .to_string_lossy() 10 | .to_string() 11 | .trim_end_matches(NUL) 12 | .trim_end_matches(NL) 13 | .trim_end_matches(CARIAGE) 14 | .to_string()) 15 | } 16 | 17 | pub(crate) fn last_error_msg() -> Result<(u32, String)> { 18 | let mut out_buf: Vec = vec![0; BUF_SIZE]; 19 | let mut last_id = 0; 20 | unsafe { 21 | last_id = last_error(); 22 | 23 | FormatMessageW( 24 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 25 | null(), 26 | last_id, 27 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as u32, 28 | out_buf.as_mut_ptr(), 29 | BUF_SIZE as u32, 30 | null_mut(), 31 | ); 32 | } 33 | 34 | utf16_buf_to_string(&out_buf).map(|s| (last_id, s)) 35 | } 36 | 37 | pub(crate) fn system_info() -> SYSTEM_INFO { 38 | unsafe { 39 | let mut info: SYSTEM_INFO = mem::zeroed(); 40 | GetSystemInfo(&mut info); 41 | info 42 | } 43 | } 44 | 45 | pub(crate) fn memory_status() -> Result { 46 | unsafe { 47 | let mut info = mem::zeroed::(); 48 | info.dwLength = mem::size_of::() as u32; 49 | let is_success = GlobalMemoryStatusEx(&mut info) != 0; 50 | if !is_success { 51 | let (id, msg) = last_error_msg()?; 52 | return Err(Error::WinApiError(id, msg)); 53 | } 54 | Ok(info) 55 | } 56 | } 57 | 58 | // #[cfg(target_os = "windows")] 59 | // pub(crate) fn net_wksta() -> WKSTA_INFO_100 { 60 | // unsafe { 61 | // let mut info: WKSTA_INFO_100 = mem::zeroed(); 62 | // // Check for error here 63 | // NetWkstaGetInfo(NULL as *mut u16, 100, &mut info); 64 | // info 65 | // } 66 | // } 67 | 68 | pub(crate) fn logical_processor_information() -> Result> { 69 | unsafe { 70 | let x = mem::zeroed::(); 71 | let mut info = vec![x; BUF_SIZE]; 72 | let mut ret_length: u32 = BUF_SIZE as u32; 73 | let is_success = GetLogicalProcessorInformation(info.as_mut_ptr(), &mut ret_length) != 0; 74 | if !is_success { 75 | let (id, msg) = last_error_msg()?; 76 | return Err(Error::WinApiError(id, msg)); 77 | } 78 | Ok(info) 79 | } 80 | } 81 | 82 | pub(crate) fn is_cpu_hyperthreaded() -> Result { 83 | unsafe { Ok(logical_processor_information()?[0].u.ProcessorCore().Flags == 1) } 84 | } 85 | 86 | #[allow(dead_code)] 87 | pub(crate) fn pagesize() -> u32 { 88 | system_info().dwPageSize 89 | } 90 | 91 | pub(crate) fn reg_val(key: HKEY, subkey: &str, val: &str) -> Result { 92 | unsafe { 93 | let mut hkey = mem::zeroed::(); 94 | let mut subkey = subkey.encode_utf16().collect::>(); 95 | println!("main key = `{:?}`", key); 96 | println!("sub key = `{:?}`", subkey); 97 | let mut is_success = RegOpenKeyExW(key, subkey.as_ptr(), 0, KEY_READ, &mut hkey) as u32 != ERROR_SUCCESS; 98 | if !is_success { 99 | let (id, msg) = last_error_msg()?; 100 | return Err(Error::WinApiError(id, msg)); 101 | } 102 | let mut buf_size = mem::size_of::(); 103 | let mut dbuf = vec![0u8; buf_size]; 104 | let mut val = val.encode_utf16().collect::>(); 105 | let mut tbuf = vec![0u32; buf_size]; 106 | let mut null = 0; 107 | is_success = RegQueryValueExW( 108 | hkey, 109 | val.as_ptr(), 110 | null as *mut u32, 111 | tbuf.as_mut_ptr(), 112 | dbuf.as_mut_ptr(), 113 | buf_size as *mut u32, 114 | ) as u32 115 | != ERROR_SUCCESS; 116 | if !is_success { 117 | let (id, msg) = last_error_msg()?; 118 | return Err(Error::WinApiError(id, msg)); 119 | } 120 | println!("dbuf = `{:?}`", &dbuf); 121 | println!("tbuf = `{:?}`", &tbuf); 122 | Ok(mem::transmute_copy::, T>(&dbuf)) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/os/linux/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod cores; 2 | pub(crate) mod processor; 3 | pub(crate) mod time; 4 | 5 | pub use cores::*; 6 | pub use processor::*; 7 | pub use time::*; 8 | 9 | use crate::linux::{SysFs, SysPath}; 10 | use crate::{Error, Result}; 11 | 12 | use std::{fmt::Display, str::FromStr}; 13 | 14 | const MODEL_NAME: &str = "model name"; 15 | const CACHE_SIZE: &str = "cache size"; 16 | const BOGOMIPS: &str = "bogomips"; 17 | const CPU_CORES: &str = "cpu cores"; 18 | const SIBLINGS: &str = "siblings"; 19 | const CPU_CLOCK: &str = "cpu MHz"; 20 | 21 | //################################################################################ 22 | // Public 23 | //################################################################################ 24 | 25 | /// Returns the name of first seen cpu in /proc/cpuinfo 26 | pub fn model() -> Result { 27 | cpuinfo_extract::(MODEL_NAME) 28 | } 29 | 30 | /// Returns cpu clock of first core in /proc/cpuinfo file. 31 | pub fn clock() -> Result { 32 | cpuinfo_extract::(CPU_CLOCK) 33 | } 34 | 35 | /// Returns total cpu cores available. 36 | pub fn core_count() -> Result { 37 | cpuinfo_extract::(CPU_CORES) 38 | } 39 | 40 | /// Returns total logical cores available. 41 | pub fn logical_cores() -> Result { 42 | cpuinfo_extract::(SIBLINGS) 43 | } 44 | 45 | /// Returns Core objects with frequencies 46 | pub fn cores() -> Result { 47 | let mut cores = Vec::new(); 48 | for id in core_ids(SysFs::Sys.join("devices/system/cpu"))? { 49 | cores.push(Core::from_sys(id)?); 50 | } 51 | 52 | Ok(cores) 53 | } 54 | 55 | /// Returns a Processor object containing gathered information 56 | /// about host machine processor. 57 | pub fn processor() -> Result { 58 | Processor::from_sys() 59 | } 60 | 61 | //################################################################################ 62 | // Internal 63 | //################################################################################ 64 | 65 | fn cpuinfo_extract(line: &str) -> Result 66 | where 67 | ::Err: Display, 68 | { 69 | _cpuinfo_extract(&SysFs::Proc.join("cpuinfo").read()?, line) 70 | } 71 | 72 | fn _cpuinfo_extract(out: &str, line: &str) -> Result 73 | where 74 | ::Err: Display, 75 | { 76 | out.split('\n') 77 | .find(|l| l.starts_with(line)) 78 | .map(|out_line| { 79 | out_line 80 | .split(':') 81 | .skip(1) 82 | .take(1) 83 | .next() 84 | .map(|s| { 85 | s.trim() 86 | .parse::() 87 | .map_err(|e| Error::InvalidInputError(out_line.to_string(), e.to_string())) 88 | }) 89 | .ok_or_else(|| Error::InvalidInputError(line.to_string(), "missing line from cpuinfo".to_string())) 90 | }) 91 | .ok_or_else(|| Error::InvalidInputError(line.to_string(), "missing line from cpuinfo".to_string()))?? 92 | } 93 | 94 | fn core_ids(path: SysPath) -> Result> { 95 | let mut core_ids = Vec::new(); 96 | for entry in path.read_dir()?.flatten() { 97 | let file_name = entry.file_name().to_string_lossy().to_string(); 98 | if !file_name.starts_with("cpu") { 99 | continue; 100 | } 101 | if let Some(digits) = file_name.split("cpu").last() { 102 | if let Some(digit) = digits.chars().next() { 103 | if !digit.is_digit(10) { 104 | continue; 105 | } 106 | 107 | core_ids.push( 108 | digits 109 | .parse::() 110 | .map_err(|e| Error::InvalidInputError(file_name, e.to_string()))?, 111 | ); 112 | } 113 | } 114 | } 115 | 116 | Ok(core_ids) 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use super::*; 122 | use crate::linux::mocks::CPUINFO; 123 | use std::{fs::File, io}; 124 | #[test] 125 | fn extracts_cpuinfo() { 126 | assert_eq!(_cpuinfo_extract::(CPUINFO, CPU_CORES).unwrap(), 6); 127 | assert_eq!(_cpuinfo_extract::(CPUINFO, SIBLINGS).unwrap(), 12); 128 | assert!((_cpuinfo_extract::(CPUINFO, CPU_CLOCK).unwrap() - 2053.971_f32).abs() < f32::EPSILON); 129 | } 130 | 131 | #[test] 132 | fn finds_core_ids() -> io::Result<()> { 133 | let dir = tempfile::tempdir()?; 134 | let mut ids = Vec::new(); 135 | for id in 0..16 { 136 | File::create(dir.path().join(format!("cpu{}", id)))?; 137 | ids.push(id); 138 | } 139 | 140 | let mut out = core_ids(SysFs::Custom(dir.path().to_owned()).into_syspath()).unwrap(); 141 | out.sort_unstable(); 142 | 143 | assert_eq!(ids, out); 144 | 145 | dir.close() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/os/linux/cpu/cores.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::cpu::time::CpuTime; 2 | use crate::linux::{SysFs, SysPath}; 3 | use crate::Result; 4 | 5 | #[cfg(feature = "serialize")] 6 | use serde::{Deserialize, Serialize}; 7 | 8 | pub type Cores = Vec; 9 | 10 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 11 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 12 | /// Represents a virtual core in a cpu 13 | pub struct Core { 14 | pub id: u32, 15 | pub min_freq: u64, 16 | pub cur_freq: u64, 17 | pub max_freq: u64, 18 | } 19 | 20 | enum Frequency { 21 | Minimal, 22 | Current, 23 | Maximal, 24 | } 25 | 26 | impl Core { 27 | /// Updates all frequencies of this core to currently available values 28 | pub fn update(&mut self) -> Result<()> { 29 | let path = SysFs::Sys.join("devices/system/cpu").join(format!("cpu{}", self.id)); 30 | self.min_freq = Core::frequency(&path, Frequency::Minimal)?; 31 | self.cur_freq = Core::frequency(&path, Frequency::Current)?; 32 | self.max_freq = Core::frequency(&path, Frequency::Maximal)?; 33 | 34 | Ok(()) 35 | } 36 | 37 | /// Returns the cpu time spent by this core 38 | pub fn cpu_time(&self) -> Result> { 39 | CpuTime::from_stat(&format!("{}", self.id)) 40 | } 41 | 42 | pub(crate) fn from_sys(id: u32) -> Result { 43 | Self::from_sys_path(&SysFs::Sys.join("devices/system/cpu").join(format!("cpu{}", id))) 44 | } 45 | 46 | fn from_sys_path(p: &SysPath) -> Result { 47 | let freq_p = p.extend("cpufreq"); 48 | let id = Core::core_id(p)?; 49 | Ok(Core { 50 | id, 51 | min_freq: Core::frequency(&freq_p, Frequency::Minimal)?, 52 | cur_freq: Core::frequency(&freq_p, Frequency::Current)?, 53 | max_freq: Core::frequency(&freq_p, Frequency::Maximal)?, 54 | }) 55 | } 56 | 57 | fn core_id(p: &SysPath) -> Result { 58 | p.extend("topology/core_id").read_as::() 59 | } 60 | 61 | fn frequency(p: &SysPath, which: Frequency) -> Result { 62 | let mut new_p; 63 | match which { 64 | Frequency::Minimal => new_p = p.extend("scaling_min_freq"), 65 | Frequency::Current => new_p = p.extend("scaling_cur_freq"), 66 | Frequency::Maximal => new_p = p.extend("scaling_max_freq"), 67 | }; 68 | if !new_p.as_path().exists() { 69 | match which { 70 | Frequency::Minimal => new_p = p.extend("cpuinfo_min_freq"), 71 | Frequency::Current => new_p = p.extend("cpuinfo_cur_freq"), 72 | Frequency::Maximal => new_p = p.extend("cpuinfo_max_freq"), 73 | }; 74 | } 75 | 76 | if !new_p.as_path().exists() { 77 | return Ok(0); 78 | } 79 | 80 | // Value is in KHz so we multiply it by 1000 81 | new_p.read_as::().map(|f| f * 1000) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use std::{fs, io}; 89 | 90 | #[test] 91 | fn creates_core_scaling_frequency() -> io::Result<()> { 92 | let dir = tempfile::tempdir()?; 93 | let freq_p = dir.path().join("cpufreq"); 94 | fs::create_dir(freq_p.as_path())?; 95 | 96 | let topology_p = dir.path().join("topology"); 97 | fs::create_dir(topology_p.as_path())?; 98 | fs::write(topology_p.join("core_id"), b"1")?; 99 | 100 | fs::write(freq_p.join("scaling_min_freq"), b"2200000")?; 101 | fs::write(freq_p.join("scaling_cur_freq"), b"3443204")?; 102 | fs::write(freq_p.join("scaling_max_freq"), b"3600000")?; 103 | 104 | let core = Core { 105 | id: 1, 106 | min_freq: 2_200_000_000, 107 | cur_freq: 3_443_204_000, 108 | max_freq: 3_600_000_000, 109 | }; 110 | 111 | assert_eq!( 112 | core, 113 | Core::from_sys_path(&SysFs::Custom(dir.path().to_owned()).into_syspath()).unwrap() 114 | ); 115 | 116 | dir.close() 117 | } 118 | 119 | #[test] 120 | fn creates_core_fallback_cpuinfo() -> io::Result<()> { 121 | let dir = tempfile::tempdir()?; 122 | let freq_p = dir.path().join("cpufreq"); 123 | fs::create_dir(freq_p.as_path())?; 124 | 125 | let topology_p = dir.path().join("topology"); 126 | fs::create_dir(topology_p.as_path())?; 127 | fs::write(topology_p.join("core_id"), b"1")?; 128 | 129 | fs::write(freq_p.join("cpuinfo_min_freq"), b"2200000")?; 130 | fs::write(freq_p.join("cpuinfo_cur_freq"), b"3443204")?; 131 | fs::write(freq_p.join("cpuinfo_max_freq"), b"3600000")?; 132 | 133 | let core = Core { 134 | id: 1, 135 | min_freq: 2_200_000_000, 136 | cur_freq: 3_443_204_000, 137 | max_freq: 3_600_000_000, 138 | }; 139 | 140 | assert_eq!( 141 | core, 142 | Core::from_sys_path(&SysFs::Custom(dir.path().to_owned()).into_syspath()).unwrap() 143 | ); 144 | 145 | dir.close() 146 | } 147 | 148 | #[test] 149 | fn parses_cputime_from_stat() { 150 | let line = "cpu0 12902 26 1888 731468 332 224 183 0 0 0"; 151 | let time = CpuTime { 152 | user: 12_902, 153 | nice: 26, 154 | system: 1_888, 155 | idle: 731_468, 156 | iowait: 332, 157 | irq: 224, 158 | softirq: 183, 159 | steal: 0, 160 | guest: 0, 161 | guest_nice: 0, 162 | }; 163 | assert_eq!(time, CpuTime::from_stat_line(line).unwrap()); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/os/linux/mocks.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | /// Content of /proc/cpuinfo 4 | pub(crate) static CPUINFO: &str = "processor : 2 5 | vendor_id : AuthenticAMD 6 | cpu family : 23 7 | model : 113 8 | model name : AMD Ryzen 5 3600 6-Core Processor 9 | stepping : 0 10 | microcode : 0x8701013 11 | cpu MHz : 2053.971 12 | cache size : 512 KB 13 | physical id : 0 14 | siblings : 12 15 | core id : 2 16 | cpu cores : 6 17 | apicid : 4 18 | initial apicid : 4 19 | fpu : yes 20 | fpu_exception : yes 21 | cpuid level : 16 22 | wp : yes 23 | flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate sme ssbd mba sev ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif umip rdpid overflow_recov succor smca 24 | bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass 25 | bogomips : 7189.98 26 | TLB size : 3072 4K pages 27 | clflush size : 64 28 | cache_alignment : 64 29 | address sizes : 43 bits physical, 48 bits virtual 30 | power management: ts ttp tm hwpstate eff_freq_ro [13] [14]"; 31 | 32 | /// Content of /proc/{pid}/stat 33 | pub(crate) static PROCESS_STAT: &str = "69035 (alacritty) S 1 69035 69035 0 -1 4194304 32394 0 1 0 3977 293 0 0 20 0 26 0 967628 2158927872 45316 18446744073709551615 94056859889664 94056864021361 140722125732880 0 0 0 0 4100 66624 0 0 0 17 6 0 0 0 0 0 94056865348576 94056865641928 94056873410560 140722125737093 140722125737103 140722125737103 140722125737957 0"; 34 | /// Content of /proc/{pid}/stat with whitespace in process name 35 | pub(crate) static PROCESS_STAT_WHITESPACE_NAME: &str = "1483 (tmux: server) S 1 1483 1483 0 -1 4194368 1521 252 0 0 440 132 0 0 20 0 1 0 8224 12197888 1380 18446744073709551615 93969366433792 93969366876629 140722694246592 0 0 0 0 528386 134433281 0 0 0 17 6 0 0 0 0 0 93969367038768 93969367086920 93969395699712 140722694253341 140722694253346 140722694253346 140722694254570 0"; 36 | 37 | pub(crate) static MOUNTS: &str = "proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 38 | sys /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 39 | dev /dev devtmpfs rw,nosuid,relatime,size=8144744k,nr_inodes=2036186,mode=755,inode64 0 0 40 | run /run tmpfs rw,nosuid,nodev,relatime,mode=755,inode64 0 0 41 | efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0 42 | /dev/mapper/vgroot-root / ext4 rw,relatime 0 0 43 | securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 44 | tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0 45 | devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0 46 | tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,size=4096k,nr_inodes=1024,mode=755,inode64 0 0 47 | cgroup2 /sys/fs/cgroup/unified cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate 0 0 48 | cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,name=systemd 0 0 49 | pstore /sys/fs/pstore pstore rw,nosuid,nodev,noexec,relatime 0 0 50 | none /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0 51 | cgroup /sys/fs/cgroup/rdma cgroup rw,nosuid,nodev,noexec,relatime,rdma 0 0 52 | cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0 53 | cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0 54 | cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0 55 | cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0 56 | cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0 57 | cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0 58 | cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0 59 | cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0 60 | cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0 61 | cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0 62 | systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=30,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=12809 0 0 63 | hugetlbfs /dev/hugepages hugetlbfs rw,relatime,pagesize=2M 0 0 64 | mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0 65 | debugfs /sys/kernel/debug debugfs rw,nosuid,nodev,noexec,relatime 0 0 66 | tracefs /sys/kernel/tracing tracefs rw,nosuid,nodev,noexec,relatime 0 0 67 | tmpfs /tmp tmpfs rw,nosuid,nodev,nr_inodes=409600,inode64 0 0 68 | configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0 69 | fusectl /sys/fs/fuse/connections fusectl rw,nosuid,nodev,noexec,relatime 0 0 70 | /dev/mapper/vgroot-home /home ext4 rw,relatime 0 0 71 | /dev/mapper/vgroot-var /var ext4 rw,relatime 0 0 72 | /dev/nvme0n1p1 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0 73 | /dev/mapper/vgstor-rand /mnt/rand ext4 rw,relatime 0 0 74 | /dev/mapper/vgstor-docs /mnt/docs ext4 rw,relatime 0 0 75 | /dev/mapper/vgstor-media /mnt/media ext4 rw,relatime 0 0 76 | tmpfs /run/user/1000 tmpfs rw,nosuid,nodev,relatime,size=1631388k,nr_inodes=407847,mode=700,uid=1000,gid=1000,inode64 0 0 77 | gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0"; 78 | -------------------------------------------------------------------------------- /src/os/linux/ps/stat.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::ps::{stat_process, ProcessState}; 2 | use crate::linux::SysPath; 3 | use crate::{ 4 | util::{next, skip}, 5 | Result, 6 | }; 7 | 8 | #[cfg(feature = "serialize")] 9 | use serde::{Deserialize, Serialize}; 10 | use std::str::SplitAsciiWhitespace; 11 | 12 | #[derive(Debug, Eq, PartialEq)] 13 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 14 | /// Represents a process stat from /proc/[pid]/stat 15 | pub struct ProcessStat { 16 | pub pid: i32, 17 | pub name: String, 18 | pub state: ProcessState, 19 | pub ppid: i32, 20 | pub pgrp: i32, 21 | pub session: i32, 22 | pub tty_nr: i32, 23 | pub utime: u64, 24 | pub stime: u64, 25 | pub cutime: i64, 26 | pub cstime: i64, 27 | pub priority: i32, 28 | pub nice: i32, 29 | pub num_threads: i32, 30 | pub itrealvalue: i32, 31 | pub starttime: u64, 32 | pub vsize: u64, 33 | pub rss: i32, 34 | pub rsslim: u64, 35 | pub nswap: u32, 36 | pub cnswap: u32, 37 | pub guest_time: u32, 38 | pub cguest_time: u32, 39 | pub processor: u32, 40 | } 41 | 42 | impl ProcessStat { 43 | /// Rereads the current statistics of the process represented by this structure 44 | pub fn update(&mut self) -> Result<()> { 45 | let p = stat_process(self.pid)?; 46 | self.pid = p.pid; 47 | self.name = p.name; 48 | self.state = p.state; 49 | self.ppid = p.ppid; 50 | self.pgrp = p.pgrp; 51 | self.session = p.session; 52 | self.tty_nr = p.tty_nr; 53 | self.utime = p.utime; 54 | self.stime = p.stime; 55 | self.cutime = p.cutime; 56 | self.cstime = p.cstime; 57 | self.priority = p.priority; 58 | self.nice = p.nice; 59 | self.num_threads = p.num_threads; 60 | self.itrealvalue = p.itrealvalue; 61 | self.starttime = p.starttime; 62 | self.vsize = p.vsize; 63 | self.rss = p.rss; 64 | self.rsslim = p.rsslim; 65 | self.nswap = p.nswap; 66 | self.cnswap = p.cnswap; 67 | self.guest_time = p.guest_time; 68 | self.cguest_time = p.cguest_time; 69 | Ok(()) 70 | } 71 | 72 | pub(crate) fn from_stat(stat: &str) -> Result { 73 | let mut elems = stat.split_ascii_whitespace(); 74 | 75 | macro_rules! _next { 76 | ($t:tt) => { 77 | next::<$t, SplitAsciiWhitespace>(&mut elems, &stat)? 78 | }; 79 | } 80 | 81 | Ok(ProcessStat { 82 | pid: _next!(i32), 83 | name: { 84 | let mut s = _next!(String); 85 | // Handle case where cmdline parameter contains whitespace. 86 | // for example: `(tmux: client)` is split into two parts 87 | // that have to be glued together. 88 | if !s.ends_with(')') { 89 | s.push(' '); 90 | s.push_str(&_next!(String)); 91 | } 92 | s 93 | }, 94 | state: ProcessState::from(_next!(String).as_str()), 95 | ppid: _next!(i32), 96 | pgrp: _next!(i32), 97 | session: _next!(i32), 98 | tty_nr: _next!(i32), 99 | utime: next::(skip(6, &mut elems), stat)?, 100 | stime: _next!(u64), 101 | cutime: _next!(i64), 102 | cstime: _next!(i64), 103 | priority: _next!(i32), 104 | nice: _next!(i32), 105 | num_threads: _next!(i32), 106 | itrealvalue: _next!(i32), 107 | starttime: _next!(u64), 108 | vsize: _next!(u64), 109 | rss: _next!(i32), 110 | rsslim: _next!(u64), 111 | nswap: next::(skip(10, &mut elems), stat)?, 112 | cnswap: _next!(u32), 113 | processor: next::(skip(1, &mut elems), stat)?, 114 | guest_time: next::(skip(3, &mut elems), stat)?, 115 | cguest_time: _next!(u32), 116 | }) 117 | } 118 | 119 | pub(crate) fn from_sys_path(path: &SysPath) -> Result { 120 | ProcessStat::from_stat(&path.extend("stat").read()?) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | use crate::linux::mocks::{PROCESS_STAT, PROCESS_STAT_WHITESPACE_NAME}; 128 | 129 | #[test] 130 | fn parses_process_stat() { 131 | let process = ProcessStat { 132 | pid: 69035, 133 | name: "(alacritty)".to_string(), 134 | state: ProcessState::Sleeping, 135 | ppid: 1, 136 | pgrp: 69035, 137 | session: 69035, 138 | tty_nr: 0, 139 | utime: 3977, 140 | stime: 293, 141 | cutime: 0, 142 | cstime: 0, 143 | priority: 20, 144 | nice: 0, 145 | num_threads: 26, 146 | itrealvalue: 0, 147 | starttime: 967628, 148 | vsize: 2158927872, 149 | rss: 45316, 150 | rsslim: 18446744073709551615, 151 | nswap: 0, 152 | cnswap: 0, 153 | guest_time: 0, 154 | cguest_time: 0, 155 | processor: 6, 156 | }; 157 | assert_eq!(ProcessStat::from_stat(PROCESS_STAT).unwrap(), process) 158 | } 159 | 160 | #[test] 161 | fn parses_process_stat_with_whitespace_in_name() { 162 | let process = ProcessStat { 163 | pid: 1483, 164 | name: "(tmux: server)".to_string(), 165 | state: ProcessState::Sleeping, 166 | ppid: 1, 167 | pgrp: 1483, 168 | session: 1483, 169 | tty_nr: 0, 170 | utime: 440, 171 | stime: 132, 172 | cutime: 0, 173 | cstime: 0, 174 | priority: 20, 175 | nice: 0, 176 | num_threads: 1, 177 | itrealvalue: 0, 178 | starttime: 8224, 179 | vsize: 12197888, 180 | rss: 1380, 181 | rsslim: 18446744073709551615, 182 | nswap: 0, 183 | cnswap: 0, 184 | guest_time: 0, 185 | cguest_time: 0, 186 | processor: 6, 187 | }; 188 | assert_eq!(ProcessStat::from_stat(PROCESS_STAT_WHITESPACE_NAME).unwrap(), process) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | use crate::linux::{ 3 | cpu::{Cores, Processor}, 4 | mounts::MountPoints, 5 | ps::{ProcessStat, Processes}, 6 | Linux, 7 | }; 8 | #[cfg(target_os = "macos")] 9 | use crate::macos::MacOS; 10 | #[cfg(target_os = "windows")] 11 | use crate::windows::Windows; 12 | 13 | use crate::{ 14 | os::{OsImpl, OsImplExt}, 15 | Result, 16 | }; 17 | use std::boxed::Box; 18 | use std::env; 19 | 20 | /// Main interface that allows for os-agnostic api. 21 | pub struct Rsys(Box, Box); 22 | 23 | #[cfg(target_os = "linux")] 24 | impl Default for Rsys { 25 | fn default() -> Self { 26 | Self( 27 | Box::new(Linux::new()) as Box, 28 | Box::new(Linux::new()) as Box, 29 | ) 30 | } 31 | } 32 | #[cfg(target_os = "macos")] 33 | impl Default for Rsys { 34 | fn default() -> Self { 35 | Self( 36 | Box::new(MacOS::new()) as Box, 37 | Box::new(MacOS::new()) as Box, 38 | ) 39 | } 40 | } 41 | #[cfg(target_os = "windows")] 42 | impl Default for Rsys { 43 | fn default() -> Self { 44 | Self( 45 | Box::new(Windows::new()) as Box, 46 | Box::new(Windows::new()) as Box, 47 | ) 48 | } 49 | } 50 | 51 | impl Rsys { 52 | /// Creates a new instance of Rsys 53 | pub fn new() -> Self { 54 | Self::default() 55 | } 56 | 57 | /// Returns a hostname. 58 | /// * **linux** 59 | /// * by making a `gethostname` syscall 60 | /// * **macos** 61 | /// * by calling `sysctl("kern.hostname")` 62 | /// * **windows** 63 | /// * by calling win32 api `GetComputerNameA` 64 | pub fn hostname(&self) -> Result { 65 | self.0.hostname() 66 | } 67 | 68 | /// Returns time since boot in seconds. 69 | /// * **linux** 70 | /// * from `SysInfo` structure 71 | /// * **macos** 72 | /// * by calling `sysctl("kern.boottime")` 73 | /// * **windows** 74 | /// * by calling win32 api `GetTickCount64` 75 | pub fn uptime(&self) -> Result { 76 | self.0.uptime() 77 | } 78 | 79 | /// Returns operating system. Reexported `env::consts::OS` for convenience. 80 | pub fn os(&self) -> String { 81 | env::consts::OS.to_string() 82 | } 83 | 84 | /// Returns cpu architecture. 85 | /// * **linux** and **macos** 86 | /// * by calling `utsname::uname` 87 | /// * **windows** 88 | /// * by calling win32 api `GetSystemInfo` 89 | pub fn arch(&self) -> Result { 90 | self.0.arch() 91 | } 92 | /// Returns a cpu model name. 93 | /// * **linux** 94 | /// * by reading `/proc/cpuinfo` 95 | /// * **macos** 96 | /// * by calling `sysctl("machdep.cpu.brand_string")` 97 | /// ... 98 | pub fn cpu(&self) -> Result { 99 | self.0.cpu() 100 | } 101 | 102 | /// Returns clock speed of cpu. 103 | /// * **linux** 104 | /// * by reading `/proc/cpuinfo` 105 | /// * **macos** 106 | /// * by calling `sysctl("hw.cpufrequency")` 107 | /// ... 108 | pub fn cpu_clock(&self) -> Result { 109 | self.0.cpu_clock() 110 | } 111 | 112 | /// Returns cpu cores. 113 | /// * **linux** 114 | /// * by reading `/proc/cpuinfo` 115 | /// * **macos** 116 | /// * by calling `sysctl("hw.physicalcpu")` 117 | /// * **windows** 118 | /// * by determining if cpu is hyperthreaded and calculating from logical cores. 119 | pub fn cpu_cores(&self) -> Result { 120 | self.0.cpu_cores() 121 | } 122 | 123 | /// Returns logical cpu cores. 124 | /// * **linux** 125 | /// * by reading `/proc/cpuinfo` 126 | /// * **macos** 127 | /// * by calling `sysctl("hw.logicalcpu")` 128 | /// * **windows** 129 | /// * by calling win32 api `GetSystemInfo` 130 | pub fn logical_cores(&self) -> Result { 131 | self.0.logical_cores() 132 | } 133 | 134 | /// Returns total ram memory. 135 | /// * **linux** 136 | /// * from `SysInfo` structure 137 | /// * **macos** 138 | /// * by calling `sysctl("hw.memsize")` 139 | /// * **windows** 140 | /// * by calling win32 api `GlobalMemoryStatusEx` 141 | pub fn memory_total(&self) -> Result { 142 | self.0.memory_total() 143 | } 144 | 145 | /// Returns free ram memory. 146 | /// * **linux** 147 | /// * from `SysInfo` structure 148 | /// ... 149 | pub fn memory_free(&self) -> Result { 150 | self.0.memory_free() 151 | } 152 | 153 | /// Returns total swap size. 154 | /// * **linux** 155 | /// * from `SysInfo` structure 156 | /// * **macos** 157 | /// * by calling `sysctl("hw.swapusage")` 158 | /// * **windows** 159 | /// * by calling win32 api `GlobalMemoryStatusEx` 160 | pub fn swap_total(&self) -> Result { 161 | self.0.swap_total() 162 | } 163 | 164 | /// Returns free swap size. 165 | /// * **linux** 166 | /// * from `SysInfo` structure 167 | /// * **macos** 168 | /// * by calling `sysctl("hw.swapusage")` 169 | /// * **windows** 170 | /// * by calling win32 api `GlobalMemoryStatusEx` 171 | pub fn swap_free(&self) -> Result { 172 | self.0.swap_free() 173 | } 174 | 175 | /// Returns a domain name. 176 | /// * **linux** 177 | /// * by calling `libc::getdomainname` 178 | /// * **windows** 179 | /// * by calling win32 api `NetWkstaGetInfo` 180 | /// ... 181 | pub fn domain_name(&self) -> Result { 182 | self.0.domain_name() 183 | } 184 | 185 | #[cfg(target_os = "linux")] 186 | /// Returns detailed Process information parsed from /proc/[pid]/stat 187 | pub fn stat_process(&self, pid: i32) -> Result { 188 | self.1.stat_process(pid) 189 | } 190 | #[cfg(target_os = "linux")] 191 | /// Returns a list of pids read from /proc 192 | pub fn pids(&self) -> Result> { 193 | self.1.pids() 194 | } 195 | #[cfg(target_os = "linux")] 196 | /// Returns all processes currently seen in /proc parsed as Processes 197 | pub fn processes(&self) -> Result { 198 | self.1.processes() 199 | } 200 | #[cfg(target_os = "linux")] 201 | /// Returns kernel version of host os. 202 | pub fn kernel_release(&self) -> Result { 203 | self.1.kernel_release() 204 | } 205 | #[cfg(target_os = "linux")] 206 | /// Returns MountPoints read from /proc/mounts 207 | pub fn mounts(&self) -> Result { 208 | self.1.mounts() 209 | } 210 | #[cfg(target_os = "linux")] 211 | /// Returns virtual Cores of host cpu 212 | pub fn cores(&self) -> Result { 213 | self.1.cores() 214 | } 215 | #[cfg(target_os = "linux")] 216 | /// Returns a Processor object containing gathered information about host cpu 217 | pub fn processor(&self) -> Result { 218 | self.1.processor() 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.2.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 10 | 11 | [[package]] 12 | name = "byteorder" 13 | version = "1.4.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 16 | 17 | [[package]] 18 | name = "cc" 19 | version = "1.0.67" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "getrandom" 31 | version = "0.2.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 34 | dependencies = [ 35 | "cfg-if", 36 | "libc", 37 | "wasi", 38 | ] 39 | 40 | [[package]] 41 | name = "goblin" 42 | version = "0.3.4" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "669cdc3826f69a51d3f8fc3f86de81c2378110254f678b8407977736122057a4" 45 | dependencies = [ 46 | "log", 47 | "plain", 48 | "scroll", 49 | ] 50 | 51 | [[package]] 52 | name = "libc" 53 | version = "0.2.92" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" 56 | 57 | [[package]] 58 | name = "log" 59 | version = "0.4.14" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 62 | dependencies = [ 63 | "cfg-if", 64 | ] 65 | 66 | [[package]] 67 | name = "nix" 68 | version = "0.20.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" 71 | dependencies = [ 72 | "bitflags", 73 | "cc", 74 | "cfg-if", 75 | "libc", 76 | ] 77 | 78 | [[package]] 79 | name = "plain" 80 | version = "0.2.3" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 83 | 84 | [[package]] 85 | name = "ppv-lite86" 86 | version = "0.2.10" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 89 | 90 | [[package]] 91 | name = "proc-macro2" 92 | version = "1.0.24" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 95 | dependencies = [ 96 | "unicode-xid", 97 | ] 98 | 99 | [[package]] 100 | name = "quote" 101 | version = "1.0.9" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 104 | dependencies = [ 105 | "proc-macro2", 106 | ] 107 | 108 | [[package]] 109 | name = "rand" 110 | version = "0.8.3" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 113 | dependencies = [ 114 | "libc", 115 | "rand_chacha", 116 | "rand_core", 117 | "rand_hc", 118 | ] 119 | 120 | [[package]] 121 | name = "rand_chacha" 122 | version = "0.3.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 125 | dependencies = [ 126 | "ppv-lite86", 127 | "rand_core", 128 | ] 129 | 130 | [[package]] 131 | name = "rand_core" 132 | version = "0.6.2" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 135 | dependencies = [ 136 | "getrandom", 137 | ] 138 | 139 | [[package]] 140 | name = "rand_hc" 141 | version = "0.3.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 144 | dependencies = [ 145 | "rand_core", 146 | ] 147 | 148 | [[package]] 149 | name = "redox_syscall" 150 | version = "0.2.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 153 | dependencies = [ 154 | "bitflags", 155 | ] 156 | 157 | [[package]] 158 | name = "remove_dir_all" 159 | version = "0.5.3" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 162 | dependencies = [ 163 | "winapi", 164 | ] 165 | 166 | [[package]] 167 | name = "rsys" 168 | version = "0.5.5" 169 | dependencies = [ 170 | "cfg-if", 171 | "goblin", 172 | "libc", 173 | "nix", 174 | "serde", 175 | "sysctl", 176 | "tempfile", 177 | "thiserror", 178 | "winapi", 179 | ] 180 | 181 | [[package]] 182 | name = "same-file" 183 | version = "1.0.6" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 186 | dependencies = [ 187 | "winapi-util", 188 | ] 189 | 190 | [[package]] 191 | name = "scroll" 192 | version = "0.10.2" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" 195 | dependencies = [ 196 | "scroll_derive", 197 | ] 198 | 199 | [[package]] 200 | name = "scroll_derive" 201 | version = "0.10.5" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" 204 | dependencies = [ 205 | "proc-macro2", 206 | "quote", 207 | "syn", 208 | ] 209 | 210 | [[package]] 211 | name = "serde" 212 | version = "1.0.125" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 215 | dependencies = [ 216 | "serde_derive", 217 | ] 218 | 219 | [[package]] 220 | name = "serde_derive" 221 | version = "1.0.125" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 224 | dependencies = [ 225 | "proc-macro2", 226 | "quote", 227 | "syn", 228 | ] 229 | 230 | [[package]] 231 | name = "syn" 232 | version = "1.0.67" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" 235 | dependencies = [ 236 | "proc-macro2", 237 | "quote", 238 | "unicode-xid", 239 | ] 240 | 241 | [[package]] 242 | name = "sysctl" 243 | version = "0.4.2" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "963488c73b34185a9028742c2be0219ed1d8558e59f85c3b466a4f54affba936" 246 | dependencies = [ 247 | "bitflags", 248 | "byteorder", 249 | "libc", 250 | "thiserror", 251 | "walkdir", 252 | ] 253 | 254 | [[package]] 255 | name = "tempfile" 256 | version = "3.2.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 259 | dependencies = [ 260 | "cfg-if", 261 | "libc", 262 | "rand", 263 | "redox_syscall", 264 | "remove_dir_all", 265 | "winapi", 266 | ] 267 | 268 | [[package]] 269 | name = "thiserror" 270 | version = "1.0.24" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" 273 | dependencies = [ 274 | "thiserror-impl", 275 | ] 276 | 277 | [[package]] 278 | name = "thiserror-impl" 279 | version = "1.0.24" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" 282 | dependencies = [ 283 | "proc-macro2", 284 | "quote", 285 | "syn", 286 | ] 287 | 288 | [[package]] 289 | name = "unicode-xid" 290 | version = "0.2.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 293 | 294 | [[package]] 295 | name = "walkdir" 296 | version = "2.3.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 299 | dependencies = [ 300 | "same-file", 301 | "winapi", 302 | "winapi-util", 303 | ] 304 | 305 | [[package]] 306 | name = "wasi" 307 | version = "0.10.2+wasi-snapshot-preview1" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 310 | 311 | [[package]] 312 | name = "winapi" 313 | version = "0.3.9" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 316 | dependencies = [ 317 | "winapi-i686-pc-windows-gnu", 318 | "winapi-x86_64-pc-windows-gnu", 319 | ] 320 | 321 | [[package]] 322 | name = "winapi-i686-pc-windows-gnu" 323 | version = "0.4.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 326 | 327 | [[package]] 328 | name = "winapi-util" 329 | version = "0.1.5" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 332 | dependencies = [ 333 | "winapi", 334 | ] 335 | 336 | [[package]] 337 | name = "winapi-x86_64-pc-windows-gnu" 338 | version = "0.4.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 341 | -------------------------------------------------------------------------------- /src/os/linux/mounts.rs: -------------------------------------------------------------------------------- 1 | use crate::linux::SysFs; 2 | use crate::Result; 3 | 4 | #[cfg(feature = "serialize")] 5 | use serde::{Deserialize, Serialize}; 6 | use std::convert::AsRef; 7 | 8 | #[derive(Debug, Default, PartialEq)] 9 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 10 | pub struct MountPoints(Vec); 11 | 12 | #[derive(Debug, PartialEq)] 13 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 14 | /// Represents a mountpoint parsed from line of /proc/mounts 15 | pub struct MountPoint { 16 | pub volume: String, 17 | pub path: String, 18 | pub voltype: String, 19 | pub mount_mode: MountMode, 20 | options: Vec, 21 | } 22 | 23 | /// Represents an option `ro` or `rw` deciding wether the mountpoint is mounter with read only or 24 | /// read and write permissions. 25 | #[derive(Debug, Eq, PartialEq)] 26 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 27 | pub enum MountMode { 28 | ReadWrite, 29 | ReadOnly, 30 | } 31 | 32 | impl MountMode { 33 | fn from_str(s: &str) -> Option { 34 | match s { 35 | "ro" => Some(MountMode::ReadOnly), 36 | "rw" => Some(MountMode::ReadWrite), 37 | _ => None, 38 | } 39 | } 40 | } 41 | 42 | impl AsRef for MountMode { 43 | fn as_ref(&self) -> &str { 44 | match &self { 45 | MountMode::ReadOnly => "ro", 46 | MountMode::ReadWrite => "rw", 47 | } 48 | } 49 | } 50 | 51 | impl MountPoint { 52 | pub fn options(&self) -> &[String] { 53 | &self.options 54 | } 55 | 56 | pub(crate) fn new(volume: &str, path: &str, voltype: &str, options: &str) -> MountPoint { 57 | let options: Vec = options.split(',').map(str::to_string).collect(); 58 | let mut mount_mode = MountMode::ReadOnly; 59 | 60 | for opt in options.iter() { 61 | if let Some(mm) = MountMode::from_str(opt) { 62 | mount_mode = mm; 63 | } 64 | } 65 | 66 | MountPoint { 67 | volume: volume.to_string(), 68 | path: path.to_string(), 69 | voltype: voltype.to_string(), 70 | mount_mode, 71 | options, 72 | } 73 | } 74 | 75 | pub(crate) fn from_line(line: &str) -> Option { 76 | let mut elems = line.split_ascii_whitespace().take(4); 77 | if elems.clone().count() >= 4 { 78 | let volume = elems.next()?; 79 | let path = elems.next()?; 80 | let voltype = elems.next()?; 81 | let options = elems.next()?; 82 | return Some(Self::new(volume, path, voltype, options)); 83 | } 84 | 85 | None 86 | } 87 | } 88 | 89 | /// Returns `MountPoints` read from `/proc/mounts` 90 | pub fn mounts() -> Result { 91 | Ok(_mounts(&SysFs::Proc.join("mounts").read()?)) 92 | } 93 | 94 | fn _mounts(out: &str) -> MountPoints { 95 | let mut mps = Vec::new(); 96 | for line in out.split('\n') { 97 | if let Some(mp) = MountPoint::from_line(line) { 98 | mps.push(mp); 99 | } 100 | } 101 | MountPoints(mps) 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | use crate::linux::mocks::MOUNTS; 108 | 109 | #[test] 110 | fn parses_mountpoints() { 111 | let expected = MountPoints(vec![ 112 | MountPoint { 113 | volume: "proc".to_string(), 114 | path: "/proc".to_string(), 115 | voltype: "proc".to_string(), 116 | mount_mode: MountMode::ReadWrite, 117 | options: vec![ 118 | "rw".to_string(), 119 | "nosuid".to_string(), 120 | "nodev".to_string(), 121 | "noexec".to_string(), 122 | "relatime".to_string(), 123 | ], 124 | }, 125 | MountPoint { 126 | volume: "sys".to_string(), 127 | path: "/sys".to_string(), 128 | voltype: "sysfs".to_string(), 129 | mount_mode: MountMode::ReadWrite, 130 | options: vec![ 131 | "rw".to_string(), 132 | "nosuid".to_string(), 133 | "nodev".to_string(), 134 | "noexec".to_string(), 135 | "relatime".to_string(), 136 | ], 137 | }, 138 | MountPoint { 139 | volume: "dev".to_string(), 140 | path: "/dev".to_string(), 141 | voltype: "devtmpfs".to_string(), 142 | mount_mode: MountMode::ReadWrite, 143 | options: vec![ 144 | "rw".to_string(), 145 | "nosuid".to_string(), 146 | "relatime".to_string(), 147 | "size=8144744k".to_string(), 148 | "nr_inodes=2036186".to_string(), 149 | "mode=755".to_string(), 150 | "inode64".to_string(), 151 | ], 152 | }, 153 | MountPoint { 154 | volume: "run".to_string(), 155 | path: "/run".to_string(), 156 | voltype: "tmpfs".to_string(), 157 | mount_mode: MountMode::ReadWrite, 158 | options: vec![ 159 | "rw".to_string(), 160 | "nosuid".to_string(), 161 | "nodev".to_string(), 162 | "relatime".to_string(), 163 | "mode=755".to_string(), 164 | "inode64".to_string(), 165 | ], 166 | }, 167 | MountPoint { 168 | volume: "efivarfs".to_string(), 169 | path: "/sys/firmware/efi/efivars".to_string(), 170 | voltype: "efivarfs".to_string(), 171 | mount_mode: MountMode::ReadWrite, 172 | options: vec![ 173 | "rw".to_string(), 174 | "nosuid".to_string(), 175 | "nodev".to_string(), 176 | "noexec".to_string(), 177 | "relatime".to_string(), 178 | ], 179 | }, 180 | MountPoint { 181 | volume: "/dev/mapper/vgroot-root".to_string(), 182 | path: "/".to_string(), 183 | voltype: "ext4".to_string(), 184 | mount_mode: MountMode::ReadWrite, 185 | options: vec!["rw".to_string(), "relatime".to_string()], 186 | }, 187 | MountPoint { 188 | volume: "securityfs".to_string(), 189 | path: "/sys/kernel/security".to_string(), 190 | voltype: "securityfs".to_string(), 191 | mount_mode: MountMode::ReadWrite, 192 | options: vec![ 193 | "rw".to_string(), 194 | "nosuid".to_string(), 195 | "nodev".to_string(), 196 | "noexec".to_string(), 197 | "relatime".to_string(), 198 | ], 199 | }, 200 | MountPoint { 201 | volume: "tmpfs".to_string(), 202 | path: "/dev/shm".to_string(), 203 | voltype: "tmpfs".to_string(), 204 | mount_mode: MountMode::ReadWrite, 205 | options: vec![ 206 | "rw".to_string(), 207 | "nosuid".to_string(), 208 | "nodev".to_string(), 209 | "inode64".to_string(), 210 | ], 211 | }, 212 | MountPoint { 213 | volume: "devpts".to_string(), 214 | path: "/dev/pts".to_string(), 215 | voltype: "devpts".to_string(), 216 | mount_mode: MountMode::ReadWrite, 217 | options: vec![ 218 | "rw".to_string(), 219 | "nosuid".to_string(), 220 | "noexec".to_string(), 221 | "relatime".to_string(), 222 | "gid=5".to_string(), 223 | "mode=620".to_string(), 224 | "ptmxmode=000".to_string(), 225 | ], 226 | }, 227 | MountPoint { 228 | volume: "tmpfs".to_string(), 229 | path: "/sys/fs/cgroup".to_string(), 230 | voltype: "tmpfs".to_string(), 231 | mount_mode: MountMode::ReadOnly, 232 | options: vec![ 233 | "ro".to_string(), 234 | "nosuid".to_string(), 235 | "nodev".to_string(), 236 | "noexec".to_string(), 237 | "size=4096k".to_string(), 238 | "nr_inodes=1024".to_string(), 239 | "mode=755".to_string(), 240 | "inode64".to_string(), 241 | ], 242 | }, 243 | MountPoint { 244 | volume: "cgroup2".to_string(), 245 | path: "/sys/fs/cgroup/unified".to_string(), 246 | voltype: "cgroup2".to_string(), 247 | mount_mode: MountMode::ReadWrite, 248 | options: vec![ 249 | "rw".to_string(), 250 | "nosuid".to_string(), 251 | "nodev".to_string(), 252 | "noexec".to_string(), 253 | "relatime".to_string(), 254 | "nsdelegate".to_string(), 255 | ], 256 | }, 257 | MountPoint { 258 | volume: "cgroup".to_string(), 259 | path: "/sys/fs/cgroup/systemd".to_string(), 260 | voltype: "cgroup".to_string(), 261 | mount_mode: MountMode::ReadWrite, 262 | options: vec![ 263 | "rw".to_string(), 264 | "nosuid".to_string(), 265 | "nodev".to_string(), 266 | "noexec".to_string(), 267 | "relatime".to_string(), 268 | "xattr".to_string(), 269 | "name=systemd".to_string(), 270 | ], 271 | }, 272 | MountPoint { 273 | volume: "pstore".to_string(), 274 | path: "/sys/fs/pstore".to_string(), 275 | voltype: "pstore".to_string(), 276 | mount_mode: MountMode::ReadWrite, 277 | options: vec![ 278 | "rw".to_string(), 279 | "nosuid".to_string(), 280 | "nodev".to_string(), 281 | "noexec".to_string(), 282 | "relatime".to_string(), 283 | ], 284 | }, 285 | MountPoint { 286 | volume: "none".to_string(), 287 | path: "/sys/fs/bpf".to_string(), 288 | voltype: "bpf".to_string(), 289 | mount_mode: MountMode::ReadWrite, 290 | options: vec![ 291 | "rw".to_string(), 292 | "nosuid".to_string(), 293 | "nodev".to_string(), 294 | "noexec".to_string(), 295 | "relatime".to_string(), 296 | "mode=700".to_string(), 297 | ], 298 | }, 299 | MountPoint { 300 | volume: "cgroup".to_string(), 301 | path: "/sys/fs/cgroup/rdma".to_string(), 302 | voltype: "cgroup".to_string(), 303 | mount_mode: MountMode::ReadWrite, 304 | options: vec![ 305 | "rw".to_string(), 306 | "nosuid".to_string(), 307 | "nodev".to_string(), 308 | "noexec".to_string(), 309 | "relatime".to_string(), 310 | "rdma".to_string(), 311 | ], 312 | }, 313 | MountPoint { 314 | volume: "cgroup".to_string(), 315 | path: "/sys/fs/cgroup/cpu,cpuacct".to_string(), 316 | voltype: "cgroup".to_string(), 317 | mount_mode: MountMode::ReadWrite, 318 | options: vec![ 319 | "rw".to_string(), 320 | "nosuid".to_string(), 321 | "nodev".to_string(), 322 | "noexec".to_string(), 323 | "relatime".to_string(), 324 | "cpu".to_string(), 325 | "cpuacct".to_string(), 326 | ], 327 | }, 328 | MountPoint { 329 | volume: "cgroup".to_string(), 330 | path: "/sys/fs/cgroup/cpuset".to_string(), 331 | voltype: "cgroup".to_string(), 332 | mount_mode: MountMode::ReadWrite, 333 | options: vec![ 334 | "rw".to_string(), 335 | "nosuid".to_string(), 336 | "nodev".to_string(), 337 | "noexec".to_string(), 338 | "relatime".to_string(), 339 | "cpuset".to_string(), 340 | ], 341 | }, 342 | MountPoint { 343 | volume: "cgroup".to_string(), 344 | path: "/sys/fs/cgroup/pids".to_string(), 345 | voltype: "cgroup".to_string(), 346 | mount_mode: MountMode::ReadWrite, 347 | options: vec![ 348 | "rw".to_string(), 349 | "nosuid".to_string(), 350 | "nodev".to_string(), 351 | "noexec".to_string(), 352 | "relatime".to_string(), 353 | "pids".to_string(), 354 | ], 355 | }, 356 | MountPoint { 357 | volume: "cgroup".to_string(), 358 | path: "/sys/fs/cgroup/freezer".to_string(), 359 | voltype: "cgroup".to_string(), 360 | mount_mode: MountMode::ReadWrite, 361 | options: vec![ 362 | "rw".to_string(), 363 | "nosuid".to_string(), 364 | "nodev".to_string(), 365 | "noexec".to_string(), 366 | "relatime".to_string(), 367 | "freezer".to_string(), 368 | ], 369 | }, 370 | MountPoint { 371 | volume: "cgroup".to_string(), 372 | path: "/sys/fs/cgroup/blkio".to_string(), 373 | voltype: "cgroup".to_string(), 374 | mount_mode: MountMode::ReadWrite, 375 | options: vec![ 376 | "rw".to_string(), 377 | "nosuid".to_string(), 378 | "nodev".to_string(), 379 | "noexec".to_string(), 380 | "relatime".to_string(), 381 | "blkio".to_string(), 382 | ], 383 | }, 384 | MountPoint { 385 | volume: "cgroup".to_string(), 386 | path: "/sys/fs/cgroup/net_cls,net_prio".to_string(), 387 | voltype: "cgroup".to_string(), 388 | mount_mode: MountMode::ReadWrite, 389 | options: vec![ 390 | "rw".to_string(), 391 | "nosuid".to_string(), 392 | "nodev".to_string(), 393 | "noexec".to_string(), 394 | "relatime".to_string(), 395 | "net_cls".to_string(), 396 | "net_prio".to_string(), 397 | ], 398 | }, 399 | MountPoint { 400 | volume: "cgroup".to_string(), 401 | path: "/sys/fs/cgroup/memory".to_string(), 402 | voltype: "cgroup".to_string(), 403 | mount_mode: MountMode::ReadWrite, 404 | options: vec![ 405 | "rw".to_string(), 406 | "nosuid".to_string(), 407 | "nodev".to_string(), 408 | "noexec".to_string(), 409 | "relatime".to_string(), 410 | "memory".to_string(), 411 | ], 412 | }, 413 | MountPoint { 414 | volume: "cgroup".to_string(), 415 | path: "/sys/fs/cgroup/hugetlb".to_string(), 416 | voltype: "cgroup".to_string(), 417 | mount_mode: MountMode::ReadWrite, 418 | options: vec![ 419 | "rw".to_string(), 420 | "nosuid".to_string(), 421 | "nodev".to_string(), 422 | "noexec".to_string(), 423 | "relatime".to_string(), 424 | "hugetlb".to_string(), 425 | ], 426 | }, 427 | MountPoint { 428 | volume: "cgroup".to_string(), 429 | path: "/sys/fs/cgroup/devices".to_string(), 430 | voltype: "cgroup".to_string(), 431 | mount_mode: MountMode::ReadWrite, 432 | options: vec![ 433 | "rw".to_string(), 434 | "nosuid".to_string(), 435 | "nodev".to_string(), 436 | "noexec".to_string(), 437 | "relatime".to_string(), 438 | "devices".to_string(), 439 | ], 440 | }, 441 | MountPoint { 442 | volume: "cgroup".to_string(), 443 | path: "/sys/fs/cgroup/perf_event".to_string(), 444 | voltype: "cgroup".to_string(), 445 | mount_mode: MountMode::ReadWrite, 446 | options: vec![ 447 | "rw".to_string(), 448 | "nosuid".to_string(), 449 | "nodev".to_string(), 450 | "noexec".to_string(), 451 | "relatime".to_string(), 452 | "perf_event".to_string(), 453 | ], 454 | }, 455 | MountPoint { 456 | volume: "systemd-1".to_string(), 457 | path: "/proc/sys/fs/binfmt_misc".to_string(), 458 | voltype: "autofs".to_string(), 459 | mount_mode: MountMode::ReadWrite, 460 | options: vec![ 461 | "rw".to_string(), 462 | "relatime".to_string(), 463 | "fd=30".to_string(), 464 | "pgrp=1".to_string(), 465 | "timeout=0".to_string(), 466 | "minproto=5".to_string(), 467 | "maxproto=5".to_string(), 468 | "direct".to_string(), 469 | "pipe_ino=12809".to_string(), 470 | ], 471 | }, 472 | MountPoint { 473 | volume: "hugetlbfs".to_string(), 474 | path: "/dev/hugepages".to_string(), 475 | voltype: "hugetlbfs".to_string(), 476 | mount_mode: MountMode::ReadWrite, 477 | options: vec!["rw".to_string(), "relatime".to_string(), "pagesize=2M".to_string()], 478 | }, 479 | MountPoint { 480 | volume: "mqueue".to_string(), 481 | path: "/dev/mqueue".to_string(), 482 | voltype: "mqueue".to_string(), 483 | mount_mode: MountMode::ReadWrite, 484 | options: vec![ 485 | "rw".to_string(), 486 | "nosuid".to_string(), 487 | "nodev".to_string(), 488 | "noexec".to_string(), 489 | "relatime".to_string(), 490 | ], 491 | }, 492 | MountPoint { 493 | volume: "debugfs".to_string(), 494 | path: "/sys/kernel/debug".to_string(), 495 | voltype: "debugfs".to_string(), 496 | mount_mode: MountMode::ReadWrite, 497 | options: vec![ 498 | "rw".to_string(), 499 | "nosuid".to_string(), 500 | "nodev".to_string(), 501 | "noexec".to_string(), 502 | "relatime".to_string(), 503 | ], 504 | }, 505 | MountPoint { 506 | volume: "tracefs".to_string(), 507 | path: "/sys/kernel/tracing".to_string(), 508 | voltype: "tracefs".to_string(), 509 | mount_mode: MountMode::ReadWrite, 510 | options: vec![ 511 | "rw".to_string(), 512 | "nosuid".to_string(), 513 | "nodev".to_string(), 514 | "noexec".to_string(), 515 | "relatime".to_string(), 516 | ], 517 | }, 518 | MountPoint { 519 | volume: "tmpfs".to_string(), 520 | path: "/tmp".to_string(), 521 | voltype: "tmpfs".to_string(), 522 | mount_mode: MountMode::ReadWrite, 523 | options: vec![ 524 | "rw".to_string(), 525 | "nosuid".to_string(), 526 | "nodev".to_string(), 527 | "nr_inodes=409600".to_string(), 528 | "inode64".to_string(), 529 | ], 530 | }, 531 | MountPoint { 532 | volume: "configfs".to_string(), 533 | path: "/sys/kernel/config".to_string(), 534 | voltype: "configfs".to_string(), 535 | mount_mode: MountMode::ReadWrite, 536 | options: vec![ 537 | "rw".to_string(), 538 | "nosuid".to_string(), 539 | "nodev".to_string(), 540 | "noexec".to_string(), 541 | "relatime".to_string(), 542 | ], 543 | }, 544 | MountPoint { 545 | volume: "fusectl".to_string(), 546 | path: "/sys/fs/fuse/connections".to_string(), 547 | voltype: "fusectl".to_string(), 548 | mount_mode: MountMode::ReadWrite, 549 | options: vec![ 550 | "rw".to_string(), 551 | "nosuid".to_string(), 552 | "nodev".to_string(), 553 | "noexec".to_string(), 554 | "relatime".to_string(), 555 | ], 556 | }, 557 | MountPoint { 558 | volume: "/dev/mapper/vgroot-home".to_string(), 559 | path: "/home".to_string(), 560 | voltype: "ext4".to_string(), 561 | mount_mode: MountMode::ReadWrite, 562 | options: vec!["rw".to_string(), "relatime".to_string()], 563 | }, 564 | MountPoint { 565 | volume: "/dev/mapper/vgroot-var".to_string(), 566 | path: "/var".to_string(), 567 | voltype: "ext4".to_string(), 568 | mount_mode: MountMode::ReadWrite, 569 | options: vec!["rw".to_string(), "relatime".to_string()], 570 | }, 571 | MountPoint { 572 | volume: "/dev/nvme0n1p1".to_string(), 573 | path: "/boot".to_string(), 574 | voltype: "vfat".to_string(), 575 | mount_mode: MountMode::ReadWrite, 576 | options: vec![ 577 | "rw".to_string(), 578 | "relatime".to_string(), 579 | "fmask=0022".to_string(), 580 | "dmask=0022".to_string(), 581 | "codepage=437".to_string(), 582 | "iocharset=iso8859-1".to_string(), 583 | "shortname=mixed".to_string(), 584 | "utf8".to_string(), 585 | "errors=remount-ro".to_string(), 586 | ], 587 | }, 588 | MountPoint { 589 | volume: "/dev/mapper/vgstor-rand".to_string(), 590 | path: "/mnt/rand".to_string(), 591 | voltype: "ext4".to_string(), 592 | mount_mode: MountMode::ReadWrite, 593 | options: vec!["rw".to_string(), "relatime".to_string()], 594 | }, 595 | MountPoint { 596 | volume: "/dev/mapper/vgstor-docs".to_string(), 597 | path: "/mnt/docs".to_string(), 598 | voltype: "ext4".to_string(), 599 | mount_mode: MountMode::ReadWrite, 600 | options: vec!["rw".to_string(), "relatime".to_string()], 601 | }, 602 | MountPoint { 603 | volume: "/dev/mapper/vgstor-media".to_string(), 604 | path: "/mnt/media".to_string(), 605 | voltype: "ext4".to_string(), 606 | mount_mode: MountMode::ReadWrite, 607 | options: vec!["rw".to_string(), "relatime".to_string()], 608 | }, 609 | MountPoint { 610 | volume: "tmpfs".to_string(), 611 | path: "/run/user/1000".to_string(), 612 | voltype: "tmpfs".to_string(), 613 | mount_mode: MountMode::ReadWrite, 614 | options: vec![ 615 | "rw".to_string(), 616 | "nosuid".to_string(), 617 | "nodev".to_string(), 618 | "relatime".to_string(), 619 | "size=1631388k".to_string(), 620 | "nr_inodes=407847".to_string(), 621 | "mode=700".to_string(), 622 | "uid=1000".to_string(), 623 | "gid=1000".to_string(), 624 | "inode64".to_string(), 625 | ], 626 | }, 627 | MountPoint { 628 | volume: "gvfsd-fuse".to_string(), 629 | path: "/run/user/1000/gvfs".to_string(), 630 | voltype: "fuse.gvfsd-fuse".to_string(), 631 | mount_mode: MountMode::ReadWrite, 632 | options: vec![ 633 | "rw".to_string(), 634 | "nosuid".to_string(), 635 | "nodev".to_string(), 636 | "relatime".to_string(), 637 | "user_id=1000".to_string(), 638 | "group_id=1000".to_string(), 639 | ], 640 | }, 641 | ]); 642 | 643 | assert_eq!(_mounts(MOUNTS), expected); 644 | } 645 | } 646 | --------------------------------------------------------------------------------