systemstat-0.2.3/.cargo_vcs_info.json0000644000000001360000000000100132560ustar { "git": { "sha1": "06df861a56c3d26bd1515f77581b71b06aa32897" }, "path_in_vcs": "" }systemstat-0.2.3/.cirrus.yml000064400000000000000000000005531046102023000141610ustar 00000000000000test_task: freebsd_instance: matrix: - image_family: freebsd-12-3 cargo_cache: folder: $CARGO_HOME/registry fingerprint_script: cat Cargo.lock || echo 'nope' before_cache_script: rm -rf $CARGO_HOME/registry/index install_script: pkg install -y rust build_script: cargo build --verbose test_script: cargo run --verbose --example info systemstat-0.2.3/.github/FUNDING.yml000064400000000000000000000000241046102023000152170ustar 00000000000000patreon: valpackett systemstat-0.2.3/.github/workflows/test.yml000064400000000000000000000017101046102023000171440ustar 00000000000000name: Test on: [push, pull_request] jobs: linux: name: Linux ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build run: cargo build --verbose - name: Run example run: cargo run --verbose --example info windows: name: Windows ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-2019, windows-latest] steps: - uses: actions/checkout@v1 - name: Build run: cargo build --verbose - name: Run example run: cargo run --verbose --example info macos: name: macOS-latest runs-on: macOS-latest steps: - uses: actions/checkout@v1 - name: Get Rust run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup && sh ./rustup -y - name: Build run: source ~/.cargo/env; cargo build --verbose - name: Run example run: source ~/.cargo/env; cargo run --verbose --example info systemstat-0.2.3/.gitignore000064400000000000000000000000321046102023000140310ustar 00000000000000/target/ Cargo.lock *.swp systemstat-0.2.3/.woodpecker.yml000064400000000000000000000017461046102023000150210ustar 00000000000000pipeline: test: image: rust:alpine pull: true commands: - apk add musl-dev - RUST_BACKTRACE=1 cargo run --example info - RUST_BACKTRACE=1 cargo test -- --nocapture - RUST_BACKTRACE=1 cargo test -F serde -- --nocapture - rustup target add aarch64-apple-darwin aarch64-pc-windows-msvc aarch64-unknown-linux-gnu i686-pc-windows-gnu i686-unknown-freebsd i686-unknown-linux-gnu x86_64-pc-windows-gnu x86_64-unknown-freebsd x86_64-unknown-linux-musl x86_64-unknown-netbsd - cargo check --target aarch64-apple-darwin - cargo check --target aarch64-pc-windows-msvc - cargo check --target aarch64-unknown-linux-gnu - cargo check --target i686-pc-windows-gnu - cargo check --target i686-unknown-freebsd - cargo check --target i686-unknown-linux-gnu - cargo check --target x86_64-pc-windows-gnu - cargo check --target x86_64-unknown-freebsd - cargo check --target x86_64-unknown-linux-musl - cargo check --target x86_64-unknown-netbsd systemstat-0.2.3/CODE_OF_CONDUCT.md000064400000000000000000000062461046102023000146550ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project owner at hello@unrelenting.technology. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project owner is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ systemstat-0.2.3/Cargo.lock0000644000000102060000000000100112300ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bytesize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" dependencies = [ "serde", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "proc-macro2" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "systemstat" version = "0.2.3" dependencies = [ "bytesize", "lazy_static", "libc", "nom", "serde", "time", "winapi", ] [[package]] name = "time" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ "time-core", ] [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" systemstat-0.2.3/Cargo.toml0000644000000032510000000000100112550ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "systemstat" version = "0.2.3" authors = ["Val Packett "] description = "Get system information/statistics in a cross-platform way" homepage = "https://github.com/valpackett/systemstat" readme = "README.md" keywords = [ "System", "Info", ] license = "Unlicense" repository = "https://github.com/valpackett/systemstat" [package.metadata.docs.rs] targets = [ "x86_64-unknown-freebsd", "x86_64-unknown-openbsd", "x86_64-unknown-netbsd", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", ] [dependencies.bytesize] version = "1.1" [dependencies.lazy_static] version = "1.0" [dependencies.libc] version = "0.2" [dependencies.the_serde] version = "1.0" features = ["derive"] optional = true package = "serde" [dependencies.time] version = "0.3.9" [features] serde = [ "the_serde", "bytesize/serde", "time/serde", ] [target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies.nom] version = "7.0" [target."cfg(windows)".dependencies.winapi] version = "0.3" features = [ "fileapi", "sysinfoapi", "minwindef", "winbase", "winerror", "ws2def", "ws2ipdef", "pdh", ] systemstat-0.2.3/Cargo.toml.orig000064400000000000000000000020471046102023000147400ustar 00000000000000[package] name = "systemstat" version = "0.2.3" edition = "2018" authors = [ "Val Packett " ] keywords = [ "System", "Info" ] description = "Get system information/statistics in a cross-platform way" license = "Unlicense" readme = "README.md" homepage = "https://github.com/valpackett/systemstat" repository = "https://github.com/valpackett/systemstat" [dependencies] time = "0.3.9" lazy_static = "1.0" bytesize = "1.1" libc = "0.2" the_serde = { package = "serde", version = "1.0", features = ["derive"], optional = true } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] nom = "7.0" [target.'cfg(windows)'.dependencies.winapi] version = "0.3" features = ["fileapi", "sysinfoapi", "minwindef", "winbase", "winerror", "ws2def", "ws2ipdef", "pdh"] [package.metadata.docs.rs] targets = [ "x86_64-unknown-freebsd", "x86_64-unknown-openbsd", "x86_64-unknown-netbsd", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc" ] [features] serde = ["the_serde", "bytesize/serde", "time/serde"] systemstat-0.2.3/README.md000064400000000000000000000027321046102023000133310ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/systemstat.svg)](https://crates.io/crates/systemstat) [![API Docs](https://docs.rs/systemstat/badge.svg)](https://docs.rs/systemstat/) [![CI status](https://ci.codeberg.org/api/badges/valpackett/systemstat/status.svg)](https://ci.codeberg.org/valpackett/systemstat) [![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](https://unlicense.org) # systemstat A Rust library for getting system information/statistics: - CPU load - load average - memory usage - uptime / boot time - battery life - filesystem mounts (and disk usage) - disk I/O statistics - network interfaces - network traffic statistics - CPU temperature Unlike [sys-info-rs](https://github.com/FillZpp/sys-info-rs), this one is written purely in Rust. Supported platforms (roughly ordered by completeness of support): - FreeBSD - Linux - OpenBSD - Windows - macOS - NetBSD - *more coming soon* ## Usage See [examples/info.rs](https://github.com/valpackett/systemstat/blob/master/examples/info.rs). ## Contributing Please feel free to submit pull requests! By participating in this project you agree to follow the [Contributor Code of Conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct/) and to release your contributions under the Unlicense. ## License This is free and unencumbered software released into the public domain. For more information, please refer to the `UNLICENSE` file or [unlicense.org](https://unlicense.org). systemstat-0.2.3/UNLICENSE000064400000000000000000000022731046102023000133220ustar 00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to systemstat-0.2.3/examples/info.rs000064400000000000000000000077511046102023000151770ustar 00000000000000extern crate systemstat; use std::thread; use std::time::Duration; use systemstat::{System, Platform, saturating_sub_bytes}; fn main() { let sys = System::new(); match sys.mounts() { Ok(mounts) => { println!("\nMounts:"); for mount in mounts.iter() { println!("{} ---{}---> {} (available {} of {})", mount.fs_mounted_from, mount.fs_type, mount.fs_mounted_on, mount.avail, mount.total); } } Err(x) => println!("\nMounts: error: {}", x) } match sys.mount_at("/") { Ok(mount) => { println!("\nMount at /:"); println!("{} ---{}---> {} (available {} of {})", mount.fs_mounted_from, mount.fs_type, mount.fs_mounted_on, mount.avail, mount.total); } Err(x) => println!("\nMount at /: error: {}", x) } match sys.block_device_statistics() { Ok(stats) => { for blkstats in stats.values() { println!("{}: {:?}", blkstats.name, blkstats); } } Err(x) => println!("\nBlock statistics error: {}", x) } match sys.networks() { Ok(netifs) => { println!("\nNetworks:"); for netif in netifs.values() { println!("{} ({:?})", netif.name, netif.addrs); } } Err(x) => println!("\nNetworks: error: {}", x) } match sys.networks() { Ok(netifs) => { println!("\nNetwork interface statistics:"); for netif in netifs.values() { println!("{} statistics: ({:?})", netif.name, sys.network_stats(&netif.name)); } } Err(x) => println!("\nNetworks: error: {}", x) } match sys.battery_life() { Ok(battery) => print!("\nBattery: {}%, {}h{}m remaining", battery.remaining_capacity*100.0, battery.remaining_time.as_secs() / 3600, battery.remaining_time.as_secs() % 60), Err(x) => print!("\nBattery: error: {}", x) } match sys.on_ac_power() { Ok(power) => println!(", AC power: {}", power), Err(x) => println!(", AC power: error: {}", x) } match sys.memory() { Ok(mem) => println!("\nMemory: {} used / {} ({} bytes) total ({:?})", saturating_sub_bytes(mem.total, mem.free), mem.total, mem.total.as_u64(), mem.platform_memory), Err(x) => println!("\nMemory: error: {}", x) } match sys.swap() { Ok(swap) => println!("\nSwap: {} used / {} ({} bytes) total ({:?})", saturating_sub_bytes(swap.total, swap.free), swap.total, swap.total.as_u64(), swap.platform_swap), Err(x) => println!("\nSwap: error: {}", x) } match sys.load_average() { Ok(loadavg) => println!("\nLoad average: {} {} {}", loadavg.one, loadavg.five, loadavg.fifteen), Err(x) => println!("\nLoad average: error: {}", x) } match sys.uptime() { Ok(uptime) => println!("\nUptime: {:?}", uptime), Err(x) => println!("\nUptime: error: {}", x) } match sys.boot_time() { Ok(boot_time) => println!("\nBoot time: {}", boot_time), Err(x) => println!("\nBoot time: error: {}", x) } match sys.cpu_load_aggregate() { Ok(cpu)=> { println!("\nMeasuring CPU load..."); thread::sleep(Duration::from_secs(1)); let cpu = cpu.done().unwrap(); println!("CPU load: {}% user, {}% nice, {}% system, {}% intr, {}% idle ", cpu.user * 100.0, cpu.nice * 100.0, cpu.system * 100.0, cpu.interrupt * 100.0, cpu.idle * 100.0); }, Err(x) => println!("\nCPU load: error: {}", x) } match sys.cpu_temp() { Ok(cpu_temp) => println!("\nCPU temp: {}", cpu_temp), Err(x) => println!("\nCPU temp: {}", x) } match sys.socket_stats() { Ok(stats) => println!("\nSystem socket statistics: {:?}", stats), Err(x) => println!("\nSystem socket statistics: error: {}", x) } } systemstat-0.2.3/src/data.rs000064400000000000000000000261601046102023000141210ustar 00000000000000//! This module provides the data structures that represent system information. //! //! They're always the same across all platforms. pub use bytesize::ByteSize; pub use std::collections::BTreeMap; use std::io; pub use std::net::{Ipv4Addr, Ipv6Addr}; use std::ops::Sub; pub use std::time::Duration; pub use time::OffsetDateTime; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[inline(always)] pub fn saturating_sub_bytes(l: ByteSize, r: ByteSize) -> ByteSize { ByteSize::b(l.as_u64().saturating_sub(r.as_u64())) } /// A wrapper for a measurement that takes time. /// /// Time should pass between getting the object and calling .done() on it. pub struct DelayedMeasurement { res: Box io::Result + Send>, } impl DelayedMeasurement { #[inline(always)] pub fn new(f: Box io::Result + Send>) -> DelayedMeasurement { DelayedMeasurement { res: f } } #[inline(always)] pub fn done(&self) -> io::Result { (self.res)() } } #[cfg(not(target_os = "linux"))] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformCpuLoad {} #[cfg(target_os = "linux")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformCpuLoad { pub iowait: f32, } impl PlatformCpuLoad { #[cfg(target_os = "linux")] #[inline(always)] pub fn avg_add(self, rhs: &Self) -> Self { PlatformCpuLoad { iowait: (self.iowait + rhs.iowait) / 2.0, } } #[cfg(not(target_os = "linux"))] #[inline(always)] pub fn avg_add(self, _rhs: &Self) -> Self { PlatformCpuLoad {} } #[cfg(target_os = "linux")] #[inline(always)] pub fn zero() -> Self { PlatformCpuLoad { iowait: 0.0 } } #[cfg(not(target_os = "linux"))] #[inline(always)] pub fn zero() -> Self { PlatformCpuLoad {} } #[cfg(target_os = "linux")] #[inline(always)] pub fn from(input: f32) -> Self { PlatformCpuLoad { iowait: input } } #[cfg(not(target_os = "linux"))] #[inline(always)] pub fn from(_input: f32) -> Self { PlatformCpuLoad {} } #[cfg(target_os = "linux")] #[inline(always)] pub fn sum(&self) -> f32 { self.iowait } #[cfg(not(target_os = "linux"))] #[inline(always)] pub fn sum(&self) -> f32 { 0.0 } } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct CPULoad { pub user: f32, pub nice: f32, pub system: f32, pub interrupt: f32, pub idle: f32, pub platform: PlatformCpuLoad, } impl CPULoad { #[inline(always)] pub fn avg_add(self, rhs: &Self) -> Self { CPULoad { user: (self.user + rhs.user) / 2.0, nice: (self.nice + rhs.nice) / 2.0, system: (self.system + rhs.system) / 2.0, interrupt: (self.interrupt + rhs.interrupt) / 2.0, idle: (self.idle + rhs.idle) / 2.0, platform: self.platform.avg_add(&rhs.platform), } } } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone, Copy)] pub struct CpuTime { pub user: usize, pub nice: usize, pub system: usize, pub interrupt: usize, pub idle: usize, pub other: usize, } impl<'a> Sub<&'a CpuTime> for CpuTime { type Output = CpuTime; #[inline(always)] fn sub(self, rhs: &CpuTime) -> CpuTime { CpuTime { user: self.user.saturating_sub(rhs.user), nice: self.nice.saturating_sub(rhs.nice), system: self.system.saturating_sub(rhs.system), interrupt: self.interrupt.saturating_sub(rhs.interrupt), idle: self.idle.saturating_sub(rhs.idle), other: self.other.saturating_sub(rhs.other), } } } impl CpuTime { pub fn to_cpuload(&self) -> CPULoad { let total = self.user + self.nice + self.system + self.interrupt + self.idle + self.other; if total == 0 { CPULoad { user: 0.0, nice: 0.0, system: 0.0, interrupt: 0.0, idle: 0.0, platform: PlatformCpuLoad::zero(), } } else { CPULoad { user: self.user as f32 / total as f32, nice: self.nice as f32 / total as f32, system: self.system as f32 / total as f32, interrupt: self.interrupt as f32 / total as f32, idle: self.idle as f32 / total as f32, platform: PlatformCpuLoad::from(self.other as f32 / total as f32), } } } } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct LoadAverage { pub one: f32, pub five: f32, pub fifteen: f32, } #[cfg(target_os = "windows")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformMemory { pub load: u32, pub total_phys: ByteSize, pub avail_phys: ByteSize, pub total_pagefile: ByteSize, pub avail_pagefile: ByteSize, pub total_virt: ByteSize, pub avail_virt: ByteSize, pub avail_ext: ByteSize, } #[cfg(target_os = "freebsd")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformMemory { pub active: ByteSize, pub inactive: ByteSize, pub wired: ByteSize, pub cache: ByteSize, pub zfs_arc: ByteSize, pub free: ByteSize, } #[cfg(target_os = "openbsd")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformMemory { pub total: ByteSize, pub active: ByteSize, pub inactive: ByteSize, pub wired: ByteSize, pub cache: ByteSize, pub free: ByteSize, pub paging: ByteSize, pub sw: ByteSize, pub swinuse: ByteSize, pub swonly: ByteSize, } #[cfg(target_os = "netbsd")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformMemory { pub pageshift: i64, pub total: ByteSize, pub active: ByteSize, pub inactive: ByteSize, pub wired: ByteSize, pub free: ByteSize, pub paging: ByteSize, pub anon: ByteSize, pub files: ByteSize, pub exec: ByteSize, pub sw: ByteSize, pub swinuse: ByteSize, pub swonly: ByteSize, } #[cfg(target_os = "macos")] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformMemory { pub total: ByteSize, pub active: ByteSize, pub inactive: ByteSize, pub wired: ByteSize, pub free: ByteSize, pub purgeable: ByteSize, pub speculative: ByteSize, pub compressor: ByteSize, pub throttled: ByteSize, pub external: ByteSize, pub internal: ByteSize, pub uncompressed_in_compressor: ByteSize, } #[cfg(any(target_os = "linux", target_os = "android"))] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformMemory { pub meminfo: BTreeMap, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct Memory { pub total: ByteSize, pub free: ByteSize, pub platform_memory: PlatformMemory, } #[cfg(any( target_os = "windows", target_os = "linux", target_os = "android", target_os = "openbsd", target_os = "netbsd" ))] pub type PlatformSwap = PlatformMemory; #[cfg(any(target_os = "macos", target_os = "freebsd"))] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct PlatformSwap { pub total: ByteSize, pub avail: ByteSize, pub used: ByteSize, pub pagesize: ByteSize, pub encrypted: bool, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct Swap { pub total: ByteSize, pub free: ByteSize, pub platform_swap: PlatformSwap, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct BatteryLife { pub remaining_capacity: f32, pub remaining_time: Duration, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct Filesystem { /// Used file nodes in filesystem pub files: usize, /// Total file nodes in filesystem pub files_total: usize, /// Free nodes available to non-superuser pub files_avail: usize, /// Free bytes in filesystem pub free: ByteSize, /// Free bytes available to non-superuser pub avail: ByteSize, /// Total bytes in filesystem pub total: ByteSize, /// Maximum filename length pub name_max: usize, pub fs_type: String, pub fs_mounted_from: String, pub fs_mounted_on: String, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct BlockDeviceStats { pub name: String, pub read_ios: usize, pub read_merges: usize, pub read_sectors: usize, pub read_ticks: usize, pub write_ios: usize, pub write_merges: usize, pub write_sectors: usize, pub write_ticks: usize, pub in_flight: usize, pub io_ticks: usize, pub time_in_queue: usize, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone, PartialEq)] pub enum IpAddr { Empty, Unsupported, V4(Ipv4Addr), V6(Ipv6Addr), } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct NetworkAddrs { pub addr: IpAddr, pub netmask: IpAddr, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct Network { pub name: String, pub addrs: Vec, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct NetworkStats { pub rx_bytes: ByteSize, pub tx_bytes: ByteSize, pub rx_packets: u64, pub tx_packets: u64, pub rx_errors: u64, pub tx_errors: u64, } #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "the_serde") )] #[derive(Debug, Clone)] pub struct SocketStats { pub tcp_sockets_in_use: usize, pub tcp_sockets_orphaned: usize, pub udp_sockets_in_use: usize, pub tcp6_sockets_in_use: usize, pub udp6_sockets_in_use: usize, } systemstat-0.2.3/src/lib.rs000064400000000000000000000010101046102023000137410ustar 00000000000000//! This library provides a way to access system information such as CPU load, mounted filesystems, //! network interfaces, etc. #[cfg_attr( any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos" ), macro_use )] extern crate lazy_static; #[cfg(feature = "serde")] extern crate the_serde as serde; pub mod data; pub mod platform; pub use self::data::*; pub use self::platform::Platform; pub use self::platform::PlatformImpl as System; systemstat-0.2.3/src/platform/bsd.rs000064400000000000000000000015011046102023000155740ustar 00000000000000use libc::c_int; use crate::data::*; lazy_static! { pub static ref PAGESHIFT: c_int = { let mut pagesize = unsafe { getpagesize() }; let mut pageshift = 0; while pagesize > 1 { pageshift += 1; pagesize >>= 1; } pageshift - 10 // LOG1024 }; } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct sysctl_cpu { user: usize, nice: usize, system: usize, interrupt: usize, idle: usize, } impl From for CpuTime { fn from(cpu: sysctl_cpu) -> CpuTime { CpuTime { user: cpu.user, nice: cpu.nice, system: cpu.system, interrupt: cpu.interrupt, idle: cpu.idle, other: 0, } } } #[link(name = "c")] extern "C" { fn getpagesize() -> c_int; } systemstat-0.2.3/src/platform/common.rs000064400000000000000000000106001046102023000163140ustar 00000000000000use std::{io, path, convert::{TryFrom, TryInto}}; use crate::data::*; /// The Platform trait declares all the functions for getting system information. /// /// NOTE: any impl MUST override one of `uptime` or `boot_time`. pub trait Platform { fn new() -> Self; /// Returns a delayed vector of CPU load statistics, one object per CPU (core). /// /// You need to wait some time (about a second is good) before unwrapping the /// `DelayedMeasurement` with `.done()`. fn cpu_load(&self) -> io::Result>>; /// Returns a delayed CPU load statistics object, average over all CPUs (cores). /// /// You need to wait some time (about a second is good) before unwrapping the /// `DelayedMeasurement` with `.done()`. fn cpu_load_aggregate(&self) -> io::Result> { let measurement = self.cpu_load()?; Ok(DelayedMeasurement::new( Box::new(move || measurement.done().map(|ls| { let mut it = ls.iter(); let first = it.next().unwrap().clone(); // has to be a variable, rust moves the iterator otherwise it.fold(first, |acc, l| acc.avg_add(l)) })))) } /// Returns a load average object. fn load_average(&self) -> io::Result; /// Returns a memory information object. fn memory(&self) -> io::Result; /// Returns a swap memory information object. fn swap(&self) -> io::Result; /// Returns a swap and a memory information object. /// On some platforms this is more efficient than calling memory() and swap() separately /// If memory() or swap() are not implemented for a platform, this function will fail. fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { // Do swap first, in order to fail fast if it's not implemented let swap = self.swap()?; let memory = self.memory()?; Ok((memory, swap)) } /// Returns the system uptime. fn uptime(&self) -> io::Result { self.boot_time().and_then(|bt| { (OffsetDateTime::now_utc() - bt) .try_into() .map_err(|_| io::Error::new(io::ErrorKind::Other, "Could not process time")) }) } /// Returns the system boot time. fn boot_time(&self) -> io::Result { self.uptime().and_then(|ut| { Ok(OffsetDateTime::now_utc() - time::Duration::try_from(ut) .map_err(|_| io::Error::new(io::ErrorKind::Other, "Could not process time"))?) }) } /// Returns a battery life information object. fn battery_life(&self) -> io::Result; /// Returns whether AC power is plugged in. fn on_ac_power(&self) -> io::Result; /// Returns a vector of filesystem mount information objects. fn mounts(&self) -> io::Result>; /// Returns a filesystem mount information object for the filesystem at a given path. fn mount_at>(&self, path: P) -> io::Result { self.mounts() .and_then(|mounts| { mounts .into_iter() .find(|mount| path::Path::new(&mount.fs_mounted_on) == path.as_ref()) .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No such mount")) }) } /// Returns a map of block device statistics objects fn block_device_statistics(&self) -> io::Result>; /// Returns a map of network intefrace information objects. /// /// It's a map because most operating systems return an object per IP address, not per /// interface, and we're doing deduplication and packing everything into one object per /// interface. You can use the .values() iterator if you need to iterate over all of them. fn networks(&self) -> io::Result>; /// Returns statistics for a given interface (bytes/packets sent/received) fn network_stats(&self, interface: &str) -> io::Result; /// Returns the current CPU temperature in degrees Celsius. /// /// Depending on the platform, this might be core 0, package, etc. fn cpu_temp(&self) -> io::Result; /// Returns information about the number of sockets in use fn socket_stats(&self) -> io::Result; } systemstat-0.2.3/src/platform/freebsd.rs000064400000000000000000000205771046102023000164540ustar 00000000000000// You are likely to be eaten by a grue. use std::{io, path, ptr, mem, ffi, slice, time}; use std::os::unix::ffi::OsStrExt; use libc::{c_void, c_int, size_t, sysctl, sysctlnametomib, timeval, statfs}; use crate::data::*; use super::common::*; use super::unix; use super::bsd; pub struct PlatformImpl; macro_rules! sysctl_mib { ($len:expr, $name:expr) => { { let mut mib: [c_int; $len] = [0; $len]; let mut sz: size_t = mib.len(); let s = ffi::CString::new($name).unwrap(); unsafe { sysctlnametomib(s.as_ptr(), &mut mib[0], &mut sz) }; mib } } } macro_rules! sysctl { ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => { { let mib = &$mib; let mut size = $size; if unsafe { sysctl(&mib[0], mib.len() as u32, $dataptr as *mut _ as *mut c_void, &mut size, ptr::null(), 0) } != 0 && $shouldcheck { return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) } size } }; ($mib:expr, $dataptr:expr, $size:expr) => { sysctl!($mib, $dataptr, $size, true) } } lazy_static! { static ref KERN_CP_TIMES: [c_int; 2] = sysctl_mib!(2, "kern.cp_times"); static ref KERN_BOOTTIME: [c_int; 2] = sysctl_mib!(2, "kern.boottime"); static ref V_ACTIVE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_active_count"); static ref V_INACTIVE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_inactive_count"); static ref V_WIRE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_wire_count"); static ref V_CACHE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_cache_count"); static ref V_FREE_COUNT: [c_int; 4] = sysctl_mib!(4, "vm.stats.vm.v_free_count"); static ref ZFS_ARC_SIZE: [c_int; 5] = sysctl_mib!(5, "kstat.zfs.misc.arcstats.size"); static ref BATTERY_LIFE: [c_int; 4] = sysctl_mib!(4, "hw.acpi.battery.life"); static ref BATTERY_TIME: [c_int; 4] = sysctl_mib!(4, "hw.acpi.battery.time"); static ref ACLINE: [c_int; 3] = sysctl_mib!(3, "hw.acpi.acline"); static ref CPU0TEMP: [c_int; 4] = sysctl_mib!(4, "dev.cpu.0.temperature"); static ref CP_TIMES_SIZE: usize = { let mut size: usize = 0; unsafe { sysctl(&KERN_CP_TIMES[0], KERN_CP_TIMES.len() as u32, ptr::null_mut(), &mut size, ptr::null(), 0) }; size }; } /// An implementation of `Platform` for FreeBSD. /// See `Platform` for documentation. impl Platform for PlatformImpl { #[inline(always)] fn new() -> Self { PlatformImpl } fn cpu_load(&self) -> io::Result>> { let loads = measure_cpu()?; Ok(DelayedMeasurement::new( Box::new(move || Ok(loads.iter() .zip(measure_cpu()?.iter()) .map(|(prev, now)| (*now - prev).to_cpuload()) .collect::>())))) } fn load_average(&self) -> io::Result { unix::load_average() } fn memory(&self) -> io::Result { let mut active: usize = 0; sysctl!(V_ACTIVE_COUNT, &mut active, mem::size_of::()); let mut inactive: usize = 0; sysctl!(V_INACTIVE_COUNT, &mut inactive, mem::size_of::()); let mut wired: usize = 0; sysctl!(V_WIRE_COUNT, &mut wired, mem::size_of::()); let mut cache: usize = 0; sysctl!(V_CACHE_COUNT, &mut cache, mem::size_of::(), false); let mut free: usize = 0; sysctl!(V_FREE_COUNT, &mut free, mem::size_of::()); let arc = ByteSize::b(zfs_arc_size().unwrap_or(0)); let pmem = PlatformMemory { active: ByteSize::kib((active as u64) << *bsd::PAGESHIFT), inactive: ByteSize::kib((inactive as u64) << *bsd::PAGESHIFT), wired: saturating_sub_bytes(ByteSize::kib((wired as u64) << *bsd::PAGESHIFT), arc), cache: ByteSize::kib((cache as u64) << *bsd::PAGESHIFT), zfs_arc: arc, free: ByteSize::kib((free as u64) << *bsd::PAGESHIFT), }; Ok(Memory { total: pmem.active + pmem.inactive + pmem.wired + pmem.cache + arc + pmem.free, free: pmem.inactive + pmem.cache + arc + pmem.free, platform_memory: pmem, }) } fn swap(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn boot_time(&self) -> io::Result { let mut data: timeval = unsafe { mem::zeroed() }; sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); Ok(ts) } fn battery_life(&self) -> io::Result { let mut life: usize = 0; sysctl!(BATTERY_LIFE, &mut life, mem::size_of::()); let mut time: i32 = 0; sysctl!(BATTERY_TIME, &mut time, mem::size_of::()); Ok(BatteryLife { remaining_capacity: life as f32 / 100.0, remaining_time: time::Duration::from_secs(if time < 0 { 0 } else { time as u64 }), }) } fn on_ac_power(&self) -> io::Result { let mut on: usize = 0; sysctl!(ACLINE, &mut on, mem::size_of::()); Ok(on == 1) } fn mounts(&self) -> io::Result> { let mut mptr: *mut statfs = ptr::null_mut(); let len = unsafe { getmntinfo(&mut mptr, 1_i32) }; if len < 1 { return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) } let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; Ok(mounts.iter().map(|m| statfs_to_fs(&m)).collect::>()) } fn mount_at>(&self, path: P) -> io::Result { let path = ffi::CString::new(path.as_ref().as_os_str().as_bytes())?; let mut sfs: statfs = unsafe { mem::zeroed() }; if unsafe { statfs(path.as_ptr() as *const _, &mut sfs) } != 0 { return Err(io::Error::new(io::ErrorKind::Other, "statfs() failed")); } Ok(statfs_to_fs(&sfs)) } fn block_device_statistics(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn networks(&self) -> io::Result> { unix::networks() } fn network_stats(&self, _interface: &str) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn cpu_temp(&self) -> io::Result { let mut temp: i32 = 0; sysctl!(CPU0TEMP, &mut temp, mem::size_of::()); // The sysctl interface supports more units, but both amdtemp and coretemp always // use IK (deciKelvin) Ok((temp as f32 - 2731.5) / 10.0) } fn socket_stats(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } } fn measure_cpu() -> io::Result> { let cpus = *CP_TIMES_SIZE / mem::size_of::(); let mut data: Vec = Vec::with_capacity(cpus); unsafe { data.set_len(cpus) }; sysctl!(KERN_CP_TIMES, &mut data[0], *CP_TIMES_SIZE); Ok(data.into_iter().map(|cpu| cpu.into()).collect()) } fn zfs_arc_size() -> io::Result { let mut zfs_arc: usize = 0; sysctl!(ZFS_ARC_SIZE, &mut zfs_arc, mem::size_of::()); Ok(zfs_arc as u64) } fn statfs_to_fs(fs: &statfs) -> Filesystem { Filesystem { files: (fs.f_files as usize).saturating_sub(fs.f_ffree as usize), files_total: fs.f_files as usize, files_avail: fs.f_ffree as usize, free: ByteSize::b(fs.f_bfree * fs.f_bsize), avail: ByteSize::b(fs.f_bavail as u64 * fs.f_bsize), total: ByteSize::b(fs.f_blocks * fs.f_bsize), name_max: fs.f_namemax as usize, fs_type: unsafe { ffi::CStr::from_ptr(&fs.f_fstypename[0]).to_string_lossy().into_owned() }, fs_mounted_from: unsafe { ffi::CStr::from_ptr(&fs.f_mntfromname[0]).to_string_lossy().into_owned() }, fs_mounted_on: unsafe { ffi::CStr::from_ptr(&fs.f_mntonname[0]).to_string_lossy().into_owned() }, } } #[link(name = "c")] extern "C" { #[link_name = "getmntinfo@FBSD_1.0"] fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; } systemstat-0.2.3/src/platform/linux.rs000064400000000000000000000743561046102023000162050ustar 00000000000000use super::common::*; use super::unix; use crate::data::*; use libc::statvfs; use libc::{c_char, c_long, c_schar, c_uint, c_ulong, c_ushort}; use nom::bytes::complete::{tag, take_till, take_until}; use nom::character::complete::{digit1, multispace0, not_line_ending, space1}; use nom::character::is_space; use nom::combinator::{complete, map, map_res, opt, verify}; use nom::error::ParseError; use nom::multi::{fold_many0, many0, many1}; use nom::sequence::{delimited, preceded, tuple}; use nom::{IResult, Parser}; use std::io::Read; use std::path::Path; use std::str; use std::time::Duration; use std::{fs, io, mem, path}; fn read_file(path: &str) -> io::Result { let mut s = String::new(); fs::File::open(path) .and_then(|mut f| f.read_to_string(&mut s)) .map(|_| s) } fn value_from_file(path: &str) -> io::Result { read_file(path)? .trim_end_matches('\n') .parse() .map_err(|_| { io::Error::new( io::ErrorKind::Other, format!("File: \"{}\" doesn't contain an int value", &path), ) }) } fn capacity(charge_full: i32, charge_now: i32) -> f32 { charge_now as f32 / charge_full as f32 } fn time(on_ac: bool, charge_full: i32, charge_now: i32, current_now: i32) -> Duration { if current_now != 0 { if on_ac { // Charge time Duration::from_secs( charge_full.saturating_sub(charge_now).abs() as u64 * 3600u64 / current_now as u64, ) } else { // Discharge time Duration::from_secs(charge_now as u64 * 3600u64 / current_now as u64) } } else { Duration::new(0, 0) } } /// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and /// trailing whitespace, returning the output of `inner`. fn ws<'a, F: 'a, O, E: ParseError<&'a str>>( inner: F, ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> where F: Parser<&'a str, O, E>, { delimited(multispace0, inner, multispace0) } /// Parse an unsigned integer out of a string, surrounded by whitespace fn usize_s(input: &str) -> IResult<&str, usize> { map_res( map_res(map(ws(digit1), str::as_bytes), str::from_utf8), str::FromStr::from_str, )(input) } // Parse `cpuX`, where X is a number fn proc_stat_cpu_prefix(input: &str) -> IResult<&str, ()> { map(tuple((tag("cpu"), digit1)), |_| ())(input) } // Parse a `/proc/stat` CPU line into a `CpuTime` struct fn proc_stat_cpu_time(input: &str) -> IResult<&str, CpuTime> { map( preceded( ws(proc_stat_cpu_prefix), tuple((usize_s, usize_s, usize_s, usize_s, usize_s, usize_s)), ), |(user, nice, system, idle, iowait, irq)| CpuTime { user, nice, system, idle, interrupt: irq, other: iowait, }, )(input) } // Parse the top CPU load aggregate line of `/proc/stat` fn proc_stat_cpu_aggregate(input: &str) -> IResult<&str, ()> { map(tuple((tag("cpu"), space1)), |_| ())(input) } // Parse `/proc/stat` to extract per-CPU loads fn proc_stat_cpu_times(input: &str) -> IResult<&str, Vec> { preceded( map(ws(not_line_ending), proc_stat_cpu_aggregate), many1(map_res(ws(not_line_ending), |input| { proc_stat_cpu_time(input) .map(|(_, res)| res) .map_err(|_| ()) })), )(input) } #[test] fn test_proc_stat_cpu_times() { let input = "cpu 5972658 30964 2383250 392840200 70075 0 43945 0 0 0 cpu0 444919 3155 198700 24405593 4622 0 36738 0 0 0 cpu1 296558 428 76249 24715635 1426 0 1280 0 0 0 cpu2 402963 949 231689 24417433 6386 0 1780 0 0 0 cpu3 301571 2452 88088 24698799 1906 0 177 0 0 0 cpu4 427192 2896 200043 24427598 4640 0 519 0 0 0 cpu5 301433 515 86228 24695368 3925 0 107 0 0 0 cpu6 432794 2884 202838 24426726 4213 0 380 0 0 0 cpu7 304364 337 89802 24709831 2965 0 78 0 0 0 cpu8 475829 3608 211253 24379789 5645 0 438 0 0 0 cpu9 306784 880 86744 24704036 4669 0 81 0 0 0 cpu10 444170 3768 212504 24415053 5346 0 331 0 0 0 cpu11 300957 519 87052 24712048 4294 0 77 0 0 0 cpu12 445953 3608 209153 24415924 5458 0 288 0 0 0 cpu13 318262 752 89195 24681010 4133 0 1254 0 0 0 cpu14 451390 3802 216997 24404205 4852 0 283 0 0 0 cpu15 317509 401 96705 24631145 5588 0 124 0 0 0 intr 313606509 40 27 0 0 0 0 0 58 1 94578 0 2120 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 31 170440 151744 109054 197097 174402 169253 171292 0 0 0 1251812 0 0 0 0 0 0 0 0 6302 0 0 0 0 0 0 0 58 0 0 0 0 0 916279 10132 140390 8096 69021 79664 26669 79961 34865 33195 102807 124189 76108 69587 7073 3 9710 116522 10436256 0 2079496 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ctxt 535905166 btime 1605203377 processes 1360293 procs_running 1 procs_blocked 0 softirq 81473629 1251347 8827732 10 325789 37 0 177903 43807896 2777 27080138 "; let result = proc_stat_cpu_times(input).unwrap().1; assert_eq!(result.len(), 16); assert_eq!(result[0].user, 444919); assert_eq!(result[0].nice, 3155); assert_eq!(result[0].system, 198700); assert_eq!(result[0].idle, 24405593); assert_eq!(result[0].other, 4622); assert_eq!(result[0].interrupt, 0); } /// Get the current per-CPU `CpuTime` statistics fn cpu_time() -> io::Result> { read_file("/proc/stat").and_then(|data| { proc_stat_cpu_times(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) }) } // Parse a `/proc/meminfo` line into (key, ByteSize) fn proc_meminfo_line(input: &str) -> IResult<&str, (&str, ByteSize)> { complete(map( tuple((take_until(":"), delimited(tag(":"), usize_s, ws(tag("kB"))))), |(key, value)| (key, ByteSize::kib(value as u64)), ))(input) } // Optionally parse a `/proc/meminfo` line` fn proc_meminfo_line_opt(input: &str) -> IResult<&str, Option<(&str, ByteSize)>> { opt(proc_meminfo_line)(input) } // Parse `/proc/meminfo` into a hashmap fn proc_meminfo(input: &str) -> IResult<&str, BTreeMap> { fold_many0( map_res( verify(ws(not_line_ending), |item: &str| !item.is_empty()), |input| { proc_meminfo_line_opt(input) .map(|(_, res)| res) .map_err(|_| ()) }, ), BTreeMap::new, |mut map: BTreeMap, opt| { if let Some((key, val)) = opt { map.insert(key.to_string(), val); } map }, )(input) } #[test] fn test_proc_meminfo() { let input = "MemTotal: 32345596 kB MemFree: 13160208 kB MemAvailable: 27792164 kB Buffers: 4724 kB Cached: 14776312 kB SwapCached: 0 kB Active: 8530160 kB Inactive: 9572028 kB Active(anon): 18960 kB Inactive(anon): 3415400 kB Active(file): 8511200 kB Inactive(file): 6156628 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 6143996 kB SwapFree: 6143996 kB Dirty: 66124 kB Writeback: 0 kB AnonPages: 3313376 kB Mapped: 931060 kB Shmem: 134716 kB KReclaimable: 427080 kB Slab: 648316 kB SReclaimable: 427080 kB SUnreclaim: 221236 kB KernelStack: 18752 kB PageTables: 30576 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 22316792 kB Committed_AS: 7944504 kB VmallocTotal: 34359738367 kB VmallocUsed: 78600 kB VmallocChunk: 0 kB Percpu: 10496 kB HardwareCorrupted: 0 kB AnonHugePages: 0 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 0 kB FilePmdMapped: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 1696884 kB DirectMap2M: 17616896 kB DirectMap1G: 13631488 kB "; let result = proc_meminfo(input).unwrap().1; assert_eq!(result.len(), 47); assert_eq!( result.get(&"Buffers".to_string()), Some(&ByteSize::kib(4724)) ); assert_eq!( result.get(&"KReclaimable".to_string()), Some(&ByteSize::kib(427080)) ); } /// Get memory statistics fn memory_stats() -> io::Result> { read_file("/proc/meminfo").and_then(|data| { proc_meminfo(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) }) } // Parse a single word fn word_s(input: &str) -> IResult<&str, &str> { take_till(|c| is_space(c as u8))(input) } /// `/proc/mounts` data struct ProcMountsData { source: String, target: String, fstype: String, } // Parse a `/proc/mounts` line to get a mountpoint fn proc_mounts_line(input: &str) -> IResult<&str, ProcMountsData> { map( tuple((ws(word_s), ws(word_s), ws(word_s))), |(source, target, fstype)| ProcMountsData { source: source.to_string(), target: target.to_string(), fstype: fstype.to_string(), }, )(input) } // Parse `/proc/mounts` to get a list of mountpoints fn proc_mounts(input: &str) -> IResult<&str, Vec> { many1(map_res(ws(not_line_ending), |input| { if input.is_empty() { Err(()) } else { proc_mounts_line(input).map(|(_, res)| res).map_err(|_| ()) } }))(input) } #[test] fn test_proc_mounts() { let test_input_1 = r#"/dev/md0 / btrfs rw,noatime,space_cache,subvolid=15192,subvol=/var/lib/docker/btrfs/subvolumes/df6eb8d3ce1a295bcc252e51ba086cb7705a046a79a342b74729f3f738129f04 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755,inode64 0 0 devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0 sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0 tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755,inode64 0 0"#; let mounts = proc_mounts(test_input_1).unwrap().1; assert!(mounts.len() == 6); let root = mounts.iter().find(|m| m.target == "/").unwrap(); assert!(root.source == "/dev/md0"); assert!(root.target == "/"); assert!(root.fstype == "btrfs"); let test_input_2 = r#"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755,inode64 0 0 devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0 sysfs /sys sysfs ro,nosuid,nodev,noexec,relatime 0 0 tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755,inode64 0 0 /dev/md0 / btrfs rw,noatime,space_cache,subvolid=15192,subvol=/var/lib/docker/btrfs/subvolumes/df6eb8d3ce1a295bcc252e51ba086cb7705a046a79a342b74729f3f738129f04 0 0"#; let mounts = proc_mounts(test_input_2).unwrap().1; assert!(mounts.len() == 6); let root = mounts.iter().find(|m| m.target == "/").unwrap(); assert!(root.source == "/dev/md0"); assert!(root.target == "/"); assert!(root.fstype == "btrfs"); // On some distros, there is a blank line at the end of `/proc/mounts`, // so we test here to make sure we do not crash on that let test_input_3 = r#"proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 sys /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 dev /dev devtmpfs rw,nosuid,relatime,size=16131864k,nr_inodes=4032966,mode=755,inode64 0 0 run /run tmpfs rw,nosuid,nodev,relatime,mode=755,inode64 0 0 efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0 /dev/nvme0n1p3 / btrfs rw,noatime,ssd,space_cache,subvolid=5,subvol=/ 0 0 securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0 "#; let mounts = proc_mounts(test_input_3).unwrap().1; assert!(mounts.len() == 8); let root = mounts.iter().find(|m| m.target == "/").unwrap(); assert!(root.source == "/dev/nvme0n1p3"); assert!(root.target == "/"); assert!(root.fstype == "btrfs"); } /// `/proc/net/sockstat` data struct ProcNetSockStat { tcp_in_use: usize, tcp_orphaned: usize, udp_in_use: usize, } // Parse `/proc/net/sockstat` to get socket statistics fn proc_net_sockstat(input: &str) -> IResult<&str, ProcNetSockStat> { map( preceded( not_line_ending, tuple(( preceded(ws(tag("TCP: inuse")), usize_s), delimited(ws(tag("orphan")), usize_s, not_line_ending), preceded(ws(tag("UDP: inuse")), usize_s), )), ), |(tcp_in_use, tcp_orphaned, udp_in_use)| ProcNetSockStat { tcp_in_use, tcp_orphaned, udp_in_use, }, )(input) } #[test] fn test_proc_net_sockstat() { let input = "sockets: used 925 TCP: inuse 20 orphan 0 tw 12 alloc 23 mem 2 UDP: inuse 1 mem 2 UDPLITE: inuse 0 RAW: inuse 0 FRAG: inuse 0 memory 0 "; let result = proc_net_sockstat(input).unwrap().1; assert_eq!(result.tcp_in_use, 20); assert_eq!(result.tcp_orphaned, 0); assert_eq!(result.udp_in_use, 1); } /// `/proc/net/sockstat6` data struct ProcNetSockStat6 { tcp_in_use: usize, udp_in_use: usize, } // Parse `/proc/net/sockstat6` to get socket statistics fn proc_net_sockstat6(input: &str) -> IResult<&str, ProcNetSockStat6> { map( ws(tuple(( preceded(tag("TCP6: inuse"), usize_s), preceded(tag("UDP6: inuse"), usize_s), ))), |(tcp_in_use, udp_in_use)| ProcNetSockStat6 { tcp_in_use, udp_in_use, }, )(input) } #[test] fn test_proc_net_sockstat6() { let input = "TCP6: inuse 3 UDP6: inuse 1 UDPLITE6: inuse 0 RAW6: inuse 1 FRAG6: inuse 0 memory 0 "; let result = proc_net_sockstat6(input).unwrap().1; assert_eq!(result.tcp_in_use, 3); assert_eq!(result.udp_in_use, 1); } /// Stat a mountpoint to gather filesystem statistics fn stat_mount(mount: ProcMountsData) -> io::Result { let mut info: statvfs = unsafe { mem::zeroed() }; let target = format!("{}\0", mount.target); let result = unsafe { statvfs(target.as_ptr() as *const c_char, &mut info) }; match result { 0 => Ok(Filesystem { files: (info.f_files as usize).saturating_sub(info.f_ffree as usize), files_total: info.f_files as usize, files_avail: info.f_favail as usize, free: ByteSize::b(info.f_bfree as u64 * info.f_bsize as u64), avail: ByteSize::b(info.f_bavail as u64 * info.f_bsize as u64), total: ByteSize::b(info.f_blocks as u64 * info.f_bsize as u64), name_max: info.f_namemax as usize, fs_type: mount.fstype, fs_mounted_from: mount.source, fs_mounted_on: mount.target, }), _ => Err(io::Error::last_os_error()), } } // Parse a line of `/proc/diskstats` fn proc_diskstats_line(input: &str) -> IResult<&str, BlockDeviceStats> { map( ws(tuple(( usize_s, usize_s, word_s, usize_s, usize_s, usize_s, usize_s, usize_s, usize_s, usize_s, usize_s, usize_s, usize_s, usize_s, ))), |( _major_number, _minor_number, name, read_ios, read_merges, read_sectors, read_ticks, write_ios, write_merges, write_sectors, write_ticks, in_flight, io_ticks, time_in_queue, )| BlockDeviceStats { name: name.to_string(), read_ios, read_merges, read_sectors, read_ticks, write_ios, write_merges, write_sectors, write_ticks, in_flight, io_ticks, time_in_queue, }, )(input) } // Parse `/proc/diskstats` to get a Vec fn proc_diskstats(input: &str) -> IResult<&str, Vec> { many0(ws(map_res(not_line_ending, |input| { proc_diskstats_line(input) .map(|(_, res)| res) .map_err(|_| ()) })))(input) } #[test] fn test_proc_diskstats() { let input = " 259 0 nvme0n1 142537 3139 15957288 470540 1235382 57191 140728002 5369037 0 1801270 5898257 0 0 0 0 102387 58679 259 1 nvme0n1p1 767 2505 20416 1330 2 0 2 38 0 200 1369 0 0 0 0 0 0 259 2 nvme0n1p2 65 0 4680 37 0 0 0 0 0 44 37 0 0 0 0 0 0 259 3 nvme0n1p3 141532 634 15927512 469040 1132993 57191 140728000 5308878 0 1801104 5777919 0 0 0 0 0 0 "; let result = proc_diskstats(input).unwrap().1; assert_eq!(result.len(), 4); assert_eq!(&result[3].name, "nvme0n1p3"); assert_eq!(result[3].read_ios, 141532); assert_eq!(result[3].write_ios, 1132993); } pub struct PlatformImpl; /// An implementation of `Platform` for Linux. /// See `Platform` for documentation. impl Platform for PlatformImpl { #[inline(always)] fn new() -> Self { PlatformImpl } fn cpu_load(&self) -> io::Result>> { cpu_time().map(|times| { DelayedMeasurement::new(Box::new(move || { cpu_time().map(|delay_times| { delay_times .iter() .zip(times.iter()) .map(|(now, prev)| (*now - prev).to_cpuload()) .collect::>() }) })) }) } fn load_average(&self) -> io::Result { unix::load_average() } fn memory(&self) -> io::Result { PlatformMemory::new().map(PlatformMemory::to_memory) } fn swap(&self) -> io::Result { PlatformMemory::new().map(PlatformMemory::to_swap) } fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { let pm = PlatformMemory::new()?; Ok((pm.clone().to_memory(), pm.to_swap())) } fn uptime(&self) -> io::Result { let mut info: sysinfo = unsafe { mem::zeroed() }; unsafe { sysinfo(&mut info) }; Ok(Duration::from_secs(info.uptime as u64)) } fn boot_time(&self) -> io::Result { read_file("/proc/stat").and_then(|data| { data.lines() .find(|line| line.starts_with("btime ")) .ok_or(io::Error::new( io::ErrorKind::InvalidData, "Could not find btime in /proc/stat", )) .and_then(|line| { let timestamp_str = line .strip_prefix("btime ") .expect("line starts with 'btime '"); timestamp_str .parse::() .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) .and_then(|timestamp| { OffsetDateTime::from_unix_timestamp(timestamp).map_err(|err| { io::Error::new(io::ErrorKind::InvalidData, err.to_string()) }) }) }) }) } fn battery_life(&self) -> io::Result { let dir = "/sys/class/power_supply"; let entries = fs::read_dir(&dir)?; let mut full = 0; let mut now = 0; let mut current = 0; for e in entries { let p = e.unwrap().path(); let s = p.to_str().unwrap(); if value_from_file::(&(s.to_string() + "/type")) .map(|t| t == "Battery") .unwrap_or(false) { let f = value_from_file::(&(s.to_string() + "/energy_full")) .or_else(|_| value_from_file::(&(s.to_string() + "/charge_full"))); let n = value_from_file::(&(s.to_string() + "/energy_now")) .or_else(|_| value_from_file::(&(s.to_string() + "/charge_now"))); let c = value_from_file::(&(s.to_string() + "/power_now")) .or_else(|_| value_from_file::(&(s.to_string() + "/current_now"))); if let (Ok(f), Ok(n), Ok(c)) = (f, n, c) { full += f; now += n; current += c; } } } if full != 0 { let on_ac = matches!(self.on_ac_power(), Ok(true)); Ok(BatteryLife { remaining_capacity: capacity(full, now), remaining_time: time(on_ac, full, now, current), }) } else { Err(io::Error::new( io::ErrorKind::Other, "Missing battery information", )) } } fn on_ac_power(&self) -> io::Result { let dir = "/sys/class/power_supply"; let entries = fs::read_dir(&dir)?; let mut on_ac = false; for e in entries { let p = e.unwrap().path(); let s = p.to_str().unwrap(); if value_from_file::(&(s.to_string() + "/type")) .map(|t| t == "Mains") .unwrap_or(false) { on_ac |= value_from_file::(&(s.to_string() + "/online")).map(|v| v == 1)? } } Ok(on_ac) } fn mounts(&self) -> io::Result> { read_file("/proc/mounts") .and_then(|data| { proc_mounts(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) }) .map(|mounts| { mounts .into_iter() .filter_map(|mount| stat_mount(mount).ok()) .collect() }) } fn mount_at>(&self, path: P) -> io::Result { read_file("/proc/mounts") .and_then(|data| { proc_mounts(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) }) .and_then(|mounts| { mounts .into_iter() .find(|mount| Path::new(&mount.target) == path.as_ref()) .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No such mount")) }) .and_then(stat_mount) } fn block_device_statistics(&self) -> io::Result> { let mut result: BTreeMap = BTreeMap::new(); let stats: Vec = read_file("/proc/diskstats").and_then(|data| { proc_diskstats(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) })?; for blkstats in stats { result.entry(blkstats.name.clone()).or_insert(blkstats); } Ok(result) } fn networks(&self) -> io::Result> { unix::networks() } fn network_stats(&self, interface: &str) -> io::Result { let path_root: String = ("/sys/class/net/".to_string() + interface) + "/statistics/"; let stats_file = |file: &str| (&path_root).to_string() + file; let rx_bytes: u64 = value_from_file::(&stats_file("rx_bytes"))?; let tx_bytes: u64 = value_from_file::(&stats_file("tx_bytes"))?; let rx_packets: u64 = value_from_file::(&stats_file("rx_packets"))?; let tx_packets: u64 = value_from_file::(&stats_file("tx_packets"))?; let rx_errors: u64 = value_from_file::(&stats_file("rx_errors"))?; let tx_errors: u64 = value_from_file::(&stats_file("tx_errors"))?; Ok(NetworkStats { rx_bytes: ByteSize::b(rx_bytes), tx_bytes: ByteSize::b(tx_bytes), rx_packets, tx_packets, rx_errors, tx_errors, }) } fn cpu_temp(&self) -> io::Result { read_file("/sys/class/thermal/thermal_zone0/temp") .or(read_file("/sys/class/hwmon/hwmon0/temp1_input")) .and_then(|data| match data.trim().parse::() { Ok(x) => Ok(x), Err(_) => Err(io::Error::new( io::ErrorKind::Other, "Could not parse float", )), }) .map(|num| num / 1000.0) } fn socket_stats(&self) -> io::Result { let sockstats: ProcNetSockStat = read_file("/proc/net/sockstat").and_then(|data| { proc_net_sockstat(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) })?; let sockstats6: ProcNetSockStat6 = read_file("/proc/net/sockstat6").and_then(|data| { proc_net_sockstat6(&data) .map(|(_, res)| res) .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) })?; let result: SocketStats = SocketStats { tcp_sockets_in_use: sockstats.tcp_in_use, tcp_sockets_orphaned: sockstats.tcp_orphaned, udp_sockets_in_use: sockstats.udp_in_use, tcp6_sockets_in_use: sockstats6.tcp_in_use, udp6_sockets_in_use: sockstats6.udp_in_use, }; Ok(result) } } impl PlatformMemory { // Retrieve platform memory information fn new() -> io::Result { memory_stats() .or_else(|_| { // If there's no procfs, e.g. in a chroot without mounting it or something let mut meminfo = BTreeMap::new(); let mut info: sysinfo = unsafe { mem::zeroed() }; unsafe { sysinfo(&mut info) }; let unit = info.mem_unit as u64; meminfo.insert( "MemTotal".to_owned(), ByteSize::b(info.totalram as u64 * unit), ); meminfo.insert( "MemFree".to_owned(), ByteSize::b(info.freeram as u64 * unit), ); meminfo.insert( "Shmem".to_owned(), ByteSize::b(info.sharedram as u64 * unit), ); meminfo.insert( "Buffers".to_owned(), ByteSize::b(info.bufferram as u64 * unit), ); meminfo.insert( "SwapTotal".to_owned(), ByteSize::b(info.totalswap as u64 * unit), ); meminfo.insert( "SwapFree".to_owned(), ByteSize::b(info.freeswap as u64 * unit), ); Ok(meminfo) }) .map(|meminfo| PlatformMemory { meminfo }) } // Convert the platform memory information to Memory fn to_memory(self) -> Memory { let meminfo = &self.meminfo; Memory { total: meminfo.get("MemTotal").copied().unwrap_or(ByteSize::b(0)), free: saturating_sub_bytes( meminfo.get("MemFree").copied().unwrap_or(ByteSize::b(0)) + meminfo.get("Buffers").copied().unwrap_or(ByteSize::b(0)) + meminfo.get("Cached").copied().unwrap_or(ByteSize::b(0)) + meminfo .get("SReclaimable") .copied() .unwrap_or(ByteSize::b(0)), meminfo.get("Shmem").copied().unwrap_or(ByteSize::b(0)), ), platform_memory: self, } } // Convert the platform memory information to Swap fn to_swap(self) -> Swap { let meminfo = &self.meminfo; Swap { total: meminfo.get("SwapTotal").copied().unwrap_or(ByteSize::b(0)), free: meminfo.get("SwapFree").copied().unwrap_or(ByteSize::b(0)), platform_swap: self, } } } #[repr(C)] #[derive(Debug)] struct sysinfo { uptime: c_long, loads: [c_ulong; 3], totalram: c_ulong, freeram: c_ulong, sharedram: c_ulong, bufferram: c_ulong, totalswap: c_ulong, freeswap: c_ulong, procs: c_ushort, totalhigh: c_ulong, freehigh: c_ulong, mem_unit: c_uint, padding: [c_schar; 8], } #[link(name = "c")] extern "C" { fn sysinfo(info: *mut sysinfo); } systemstat-0.2.3/src/platform/macos.rs000064400000000000000000000166751046102023000161500ustar 00000000000000use std::{io, ptr, mem::{self, MaybeUninit}, ffi, slice}; use libc::{ c_int, c_void, host_statistics64, mach_host_self, size_t, statfs, sysconf, sysctl, sysctlnametomib, timeval, vm_statistics64, xsw_usage, CTL_VM, HOST_VM_INFO64, HOST_VM_INFO64_COUNT, KERN_SUCCESS, VM_SWAPUSAGE, _SC_PHYS_PAGES, }; use crate::data::*; use super::common::*; use super::unix; use super::bsd; pub struct PlatformImpl; macro_rules! sysctl_mib { ($len:expr, $name:expr) => { { let mut mib: [c_int; $len] = [0; $len]; let mut sz: size_t = mib.len(); let s = ffi::CString::new($name).unwrap(); unsafe { sysctlnametomib(s.as_ptr(), &mut mib[0], &mut sz) }; mib } } } macro_rules! sysctl { ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => { { let mib = &$mib; let mut size = $size; if unsafe { sysctl(&mib[0] as *const _ as *mut _, mib.len() as u32, $dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck { return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) } size } }; ($mib:expr, $dataptr:expr, $size:expr) => { sysctl!($mib, $dataptr, $size, true) } } lazy_static! { static ref KERN_BOOTTIME: [c_int; 2] = sysctl_mib!(2, "kern.boottime"); } /// An implementation of `Platform` for macOS. /// See `Platform` for documentation. impl Platform for PlatformImpl { #[inline(always)] fn new() -> Self { PlatformImpl } fn cpu_load(&self) -> io::Result>> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn load_average(&self) -> io::Result { unix::load_average() } fn memory(&self) -> io::Result { // Get Total Memory let total = match unsafe { sysconf(_SC_PHYS_PAGES) } { -1 => { return Err(io::Error::new( io::ErrorKind::Other, "sysconf(_SC_PHYS_PAGES) failed", )) } n => n as u64, }; // Get Usage Info let host_port = unsafe { mach_host_self() }; let mut stat = MaybeUninit::::zeroed(); let mut stat_count = HOST_VM_INFO64_COUNT; let ret = unsafe { host_statistics64( host_port, HOST_VM_INFO64, stat.as_mut_ptr() as *mut i32, &mut stat_count, ) }; if ret != KERN_SUCCESS { return Err(io::Error::new( io::ErrorKind::Other, "host_statistics64() failed", )); } let stat = unsafe { stat.assume_init() }; let pmem = PlatformMemory { total: ByteSize::kib(total << *bsd::PAGESHIFT), active: ByteSize::kib((stat.active_count as u64) << *bsd::PAGESHIFT), inactive: ByteSize::kib((stat.inactive_count as u64) << *bsd::PAGESHIFT), wired: ByteSize::kib((stat.wire_count as u64) << *bsd::PAGESHIFT), free: ByteSize::kib((stat.free_count as u64) << *bsd::PAGESHIFT), purgeable: ByteSize::kib((stat.purgeable_count as u64) << *bsd::PAGESHIFT), speculative: ByteSize::kib((stat.speculative_count as u64) << *bsd::PAGESHIFT), compressor: ByteSize::kib((stat.compressor_page_count as u64) << *bsd::PAGESHIFT), throttled: ByteSize::kib((stat.throttled_count as u64) << *bsd::PAGESHIFT), external: ByteSize::kib((stat.external_page_count as u64) << *bsd::PAGESHIFT), internal: ByteSize::kib((stat.internal_page_count as u64) << *bsd::PAGESHIFT), uncompressed_in_compressor: ByteSize::kib( (stat.total_uncompressed_pages_in_compressor as u64) << *bsd::PAGESHIFT, ), }; Ok(Memory { total: pmem.total, // This is the available memory, but free is more akin to: // pmem.free - pmem.speculative free: pmem.free + pmem.inactive, platform_memory: pmem, }) } fn swap(&self) -> io::Result { let mut xsw_usage = MaybeUninit::::zeroed(); sysctl!([CTL_VM, VM_SWAPUSAGE], &mut xsw_usage, mem::size_of::()); let xsw_usage = unsafe { xsw_usage.assume_init() }; let ps = PlatformSwap { total: ByteSize::b(xsw_usage.xsu_total), used: ByteSize::b(xsw_usage.xsu_used), avail: ByteSize::b(xsw_usage.xsu_avail), pagesize: ByteSize::b(xsw_usage.xsu_pagesize as u64), encrypted: xsw_usage.xsu_encrypted != 0, }; Ok(Swap { total: ps.total, free: ps.avail, platform_swap: ps }) } fn boot_time(&self) -> io::Result { let mut data: timeval = unsafe { mem::zeroed() }; sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); Ok(ts) } fn battery_life(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn on_ac_power(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn mounts(&self) -> io::Result> { let mut mptr: *mut statfs = ptr::null_mut(); let len = unsafe { getmntinfo(&mut mptr, 2_i32) }; if len < 1 { return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) } let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; Ok(mounts.iter().map(statfs_to_fs).collect::>()) } fn block_device_statistics(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn networks(&self) -> io::Result> { unix::networks() } fn network_stats(&self, _interface: &str) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn cpu_temp(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn socket_stats(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } } fn statfs_to_fs(x: &statfs) -> Filesystem { Filesystem { files: (x.f_files as usize).saturating_sub(x.f_ffree as usize), files_total: x.f_files as usize, files_avail: x.f_ffree as usize, free: ByteSize::b(x.f_bfree * x.f_bsize as u64), avail: ByteSize::b(x.f_bavail * x.f_bsize as u64), total: ByteSize::b(x.f_blocks * x.f_bsize as u64), name_max: 256, fs_type: unsafe { ffi::CStr::from_ptr(&x.f_fstypename[0]).to_string_lossy().into_owned() }, fs_mounted_from: unsafe { ffi::CStr::from_ptr(&x.f_mntfromname[0]).to_string_lossy().into_owned() }, fs_mounted_on: unsafe { ffi::CStr::from_ptr(&x.f_mntonname[0]).to_string_lossy().into_owned() }, } } #[link(name = "c")] extern "C" { #[cfg_attr(not(target_arch = "aarch64"), link_name = "getmntinfo$INODE64")] fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; } systemstat-0.2.3/src/platform/mod.rs000064400000000000000000000074741046102023000156220ustar 00000000000000//! This module reexports the OS-specific module that actually implements Platform. pub mod common; pub use self::common::*; #[cfg(windows)] pub mod windows; #[cfg(windows)] pub use self::windows::PlatformImpl; #[cfg(unix)] pub mod unix; #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "macos" ))] pub mod bsd; #[cfg(target_os = "freebsd")] pub mod freebsd; #[cfg(target_os = "freebsd")] pub use self::freebsd::PlatformImpl; #[cfg(target_os = "openbsd")] pub mod openbsd; #[cfg(target_os = "openbsd")] pub use self::openbsd::PlatformImpl; #[cfg(target_os = "netbsd")] pub mod netbsd; #[cfg(target_os = "netbsd")] pub use self::netbsd::PlatformImpl; #[cfg(target_os = "macos")] pub mod macos; #[cfg(target_os = "macos")] pub use self::macos::PlatformImpl; #[cfg(any(target_os = "linux", target_os = "android"))] pub mod linux; #[cfg(any(target_os = "linux", target_os = "android"))] pub use self::linux::PlatformImpl; #[cfg(test)] mod tests { use super::*; use std::thread; use std::time::Duration; #[test] fn test_cpu_load() { let load = PlatformImpl::new().cpu_load().unwrap(); thread::sleep(Duration::from_millis(300)); let load = load.done().unwrap(); assert!(!load.is_empty()); for cpu in load.iter() { let sum = cpu.user + cpu.nice + cpu.system + cpu.interrupt + cpu.idle + cpu.platform.sum(); assert!(sum > 0.95 && sum < 1.05); } } #[test] fn test_cpu_load_aggregate() { let cpu = PlatformImpl::new().cpu_load_aggregate().unwrap(); thread::sleep(Duration::from_millis(300)); let cpu = cpu.done().unwrap(); let sum = cpu.user + cpu.nice + cpu.system + cpu.interrupt + cpu.idle + cpu.platform.sum(); assert!(sum > 0.95 && sum < 1.05); } #[test] fn test_load_average() { let load = PlatformImpl::new().load_average().unwrap(); assert!(load.one > 0.00001 && load.five > 0.00001 && load.fifteen > 0.00001); } #[test] fn test_memory() { let mem = PlatformImpl::new().memory().unwrap(); assert!(mem.free.as_u64() > 1024 && mem.total.as_u64() > 1024); } #[test] fn test_swap() { let swap = PlatformImpl::new().swap().unwrap(); assert!(swap.free <= swap.total); } #[test] fn test_mem_and_swap() { let (mem, swap) = PlatformImpl::new().memory_and_swap().unwrap(); assert!(mem.free.as_u64() > 1024 && mem.total.as_u64() > 1024); assert!(swap.free <= swap.total); } #[test] fn test_battery_life() { if let Ok(bat) = PlatformImpl::new().battery_life() { assert!(bat.remaining_capacity <= 100.0 && bat.remaining_capacity >= 0.0); } } #[test] fn test_on_ac_power() { PlatformImpl::new().on_ac_power().unwrap(); } #[test] fn test_mounts() { let mounts = PlatformImpl::new().mounts().unwrap(); assert!(!mounts.is_empty()); assert!(mounts.iter().find(|m| m.fs_mounted_on == "/").unwrap().fs_mounted_on == "/"); } #[test] fn test_mount_at() { // XXX: PathBuf required instead of constant string at least on FreeBSD?? let mount = PlatformImpl::new().mount_at(std::path::PathBuf::from("/")).unwrap(); assert!(mount.fs_mounted_on == "/"); } #[test] fn test_networks() { let networks = PlatformImpl::new().networks().unwrap(); assert!(!networks.values().find(|n| n.name == "lo" || n.name == "lo0").unwrap().addrs.is_empty()); } #[test] fn test_cpu_measurement_is_send() { use crate::{DelayedMeasurement, CPULoad}; #[allow(dead_code)] fn take_delayed(dm: DelayedMeasurement>) { use std::thread; thread::spawn(move || dm); } } } systemstat-0.2.3/src/platform/netbsd.rs000064400000000000000000000151421046102023000163110ustar 00000000000000// use super::bsd; use super::common::*; use super::unix; use crate::data::*; use libc::{c_int, c_void, sysctl, CTL_VM}; use std::{io, mem, path, ptr}; pub struct PlatformImpl; // https://github.com/NetBSD/src/blob/8e2e7cb174ca27b848b18119f33cf4c212fe22ee/sys/uvm/uvm_param.h#L169 static VM_UVMEXP2: c_int = 5; macro_rules! sysctl { ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => {{ let mib = &$mib; let mut size = $size; if unsafe { sysctl( &mib[0] as *const _ as *mut _, mib.len() as u32, $dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0, ) } != 0 && $shouldcheck { return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")); } size }}; ($mib:expr, $dataptr:expr, $size:expr) => { sysctl!($mib, $dataptr, $size, true) }; } /// An implementation of `Platform` for NetBSD. /// See `Platform` for documentation. impl Platform for PlatformImpl { #[inline(always)] fn new() -> Self { PlatformImpl } fn cpu_load(&self) -> io::Result>> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn load_average(&self) -> io::Result { unix::load_average() } fn memory(&self) -> io::Result { PlatformMemory::new().map(|pm| pm.to_memory()) } fn swap(&self) -> io::Result { PlatformMemory::new().map(|pm| pm.to_swap()) } fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { let pm = PlatformMemory::new()?; Ok((pm.clone().to_memory(), pm.to_swap())) } fn boot_time(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn battery_life(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn on_ac_power(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn mounts(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn mount_at>(&self, _: P) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn block_device_statistics(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn networks(&self) -> io::Result> { unix::networks() } fn network_stats(&self, _interface: &str) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn cpu_temp(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn socket_stats(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } } impl PlatformMemory { // Retrieve platform memory information fn new() -> io::Result { let mut uvm_info = uvmexp_sysctl::default(); sysctl!( &[CTL_VM, VM_UVMEXP2], &mut uvm_info, mem::size_of::() ); Ok(Self { pageshift: uvm_info.pageshift, total: ByteSize::b((uvm_info.npages << uvm_info.pageshift) as u64), active: ByteSize::b((uvm_info.active << uvm_info.pageshift) as u64), inactive: ByteSize::b((uvm_info.inactive << uvm_info.pageshift) as u64), wired: ByteSize::b((uvm_info.wired << uvm_info.pageshift) as u64), anon: ByteSize::b((uvm_info.anonpages << uvm_info.pageshift) as u64), files: ByteSize::b((uvm_info.filepages << uvm_info.pageshift) as u64), exec: ByteSize::b((uvm_info.execpages << uvm_info.pageshift) as u64), free: ByteSize::b((uvm_info.free << uvm_info.pageshift) as u64), paging: ByteSize::b((uvm_info.paging << uvm_info.pageshift) as u64), sw: ByteSize::b((uvm_info.swpages << uvm_info.pageshift) as u64), swinuse: ByteSize::b((uvm_info.swpginuse << uvm_info.pageshift) as u64), swonly: ByteSize::b((uvm_info.swpgonly << uvm_info.pageshift) as u64), }) } fn to_memory(self) -> Memory { Memory { total: self.total, free: self.free, platform_memory: self, } } fn to_swap(self) -> Swap { Swap { total: self.sw, free: saturating_sub_bytes(self.sw, self.swinuse), platform_swap: self, } } } // https://github.com/NetBSD/src/blob/038135cba4b80f5c8d1e32fbc5b73c91c2f276d9/sys/uvm/uvm_extern.h#L420-L515 #[repr(C)] #[derive(Debug, Default)] struct uvmexp_sysctl { pagesize: i64, pagemask: i64, pageshift: i64, npages: i64, free: i64, active: i64, inactive: i64, paging: i64, wired: i64, zeropages: i64, reserve_pagedaemon: i64, reserve_kernel: i64, freemin: i64, freetarg: i64, inactarg: i64, // unused wiredmax: i64, nswapdev: i64, swpages: i64, swpginuse: i64, swpgonly: i64, nswget: i64, unused1: i64, // unused; was nanon cpuhit: i64, cpumiss: i64, faults: i64, traps: i64, intrs: i64, swtch: i64, softs: i64, syscalls: i64, pageins: i64, swapins: i64, // unused swapouts: i64, // unused pgswapin: i64, // unused pgswapout: i64, forks: i64, forks_ppwait: i64, forks_sharevm: i64, pga_zerohit: i64, pga_zeromiss: i64, zeroaborts: i64, fltnoram: i64, fltnoanon: i64, fltpgwait: i64, fltpgrele: i64, fltrelck: i64, fltrelckok: i64, fltanget: i64, fltanretry: i64, fltamcopy: i64, fltnamap: i64, fltnomap: i64, fltlget: i64, fltget: i64, flt_anon: i64, flt_acow: i64, flt_obj: i64, flt_prcopy: i64, flt_przero: i64, pdwoke: i64, pdrevs: i64, unused4: i64, pdfreed: i64, pdscans: i64, pdanscan: i64, pdobscan: i64, pdreact: i64, pdbusy: i64, pdpageouts: i64, pdpending: i64, pddeact: i64, anonpages: i64, filepages: i64, execpages: i64, colorhit: i64, colormiss: i64, ncolors: i64, bootpages: i64, poolpages: i64, countsyncone: i64, countsyncall: i64, anonunknown: i64, anonclean: i64, anondirty: i64, fileunknown: i64, fileclean: i64, filedirty: i64, fltup: i64, fltnoup: i64, } systemstat-0.2.3/src/platform/openbsd.rs000064400000000000000000000426731046102023000164750ustar 00000000000000use std::{io, path, ptr, time, fs, mem, ffi, slice}; use std::os::unix::io::AsRawFd; use std::os::unix::ffi::OsStrExt; use std::mem::size_of; use libc::{c_void, c_int, c_uint, c_ulong, c_uchar, ioctl, sysctl, timeval, statfs, ifaddrs, getifaddrs, if_data, freeifaddrs}; use crate::data::*; use super::common::*; use super::unix; use super::bsd; pub struct PlatformImpl; macro_rules! sysctl { ($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => { { let mib = &$mib; let mut size = $size; if unsafe { sysctl(&mib[0], mib.len() as u32, $dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck { return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed")) } size } }; ($mib:expr, $dataptr:expr, $size:expr) => { sysctl!($mib, $dataptr, $size, true) } } lazy_static! { static ref APM_IOC_GETPOWER: c_ulong = 0x40000000u64 | ((size_of::() & 0x1fff) << 16) as u64 | (0x41 << 8) | 3; // OpenBSD does not have sysctlnametomib, so more copy-pasting of magic numbers from C headers :( static ref HW_NCPU: [c_int; 2] = [6, 3]; static ref KERN_CPTIME2: [c_int; 3] = [1, 71, 0]; static ref KERN_BOOTTIME: [c_int; 2] = [1, 21]; static ref VM_UVMEXP: [c_int; 2] = [2, 4]; static ref VFS_BCACHESTAT: [c_int; 3] = [10, 0, 3]; } #[link(name = "c")] extern "C" { fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; } /// An implementation of `Platform` for OpenBSD. /// See `Platform` for documentation. impl Platform for PlatformImpl { #[inline(always)] fn new() -> Self { PlatformImpl } fn cpu_load(&self) -> io::Result>> { let loads = measure_cpu()?; Ok(DelayedMeasurement::new( Box::new(move || Ok(loads.iter() .zip(measure_cpu()?.iter()) .map(|(prev, now)| (*now - prev).to_cpuload()) .collect::>())))) } fn load_average(&self) -> io::Result { unix::load_average() } fn memory(&self) -> io::Result { PlatformMemory::new().map(|pm| pm.to_memory()) } fn swap(&self) -> io::Result { PlatformMemory::new().map(|pm| pm.to_swap()) } fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { let pm = PlatformMemory::new()?; Ok((pm.clone().to_memory(), pm.to_swap())) } fn boot_time(&self) -> io::Result { let mut data: timeval = unsafe { mem::zeroed() }; sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::()); let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64); Ok(ts) } // /dev/apm is probably the nicest interface I've seen :) fn battery_life(&self) -> io::Result { let f = fs::File::open("/dev/apm")?; let mut info = apm_power_info::default(); if unsafe { ioctl(f.as_raw_fd(), *APM_IOC_GETPOWER, &mut info) } == -1 { return Err(io::Error::new(io::ErrorKind::Other, "ioctl() failed")) } if info.battery_state == 0xff { // APM_BATT_UNKNOWN return Err(io::Error::new(io::ErrorKind::Other, "Battery state unknown")) } if info.battery_state == 4 { // APM_BATTERY_ABSENT return Err(io::Error::new(io::ErrorKind::Other, "Battery absent")) } Ok(BatteryLife { remaining_capacity: info.battery_life as f32, remaining_time: time::Duration::from_secs(info.minutes_left as u64), }) } fn on_ac_power(&self) -> io::Result { let f = fs::File::open("/dev/apm")?; let mut info = apm_power_info::default(); if unsafe { ioctl(f.as_raw_fd(), *APM_IOC_GETPOWER, &mut info) } == -1 { return Err(io::Error::new(io::ErrorKind::Other, "ioctl() failed")) } Ok(info.ac_state == 0x01) // APM_AC_ON } fn mounts(&self) -> io::Result> { let mut mptr: *mut statfs = ptr::null_mut(); let len = unsafe { getmntinfo(&mut mptr, 1 as i32) }; if len < 1 { return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed")) } let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; Ok(mounts.iter().map(|m| statfs_to_fs(&m)).collect::>()) } fn mount_at>(&self, path: P) -> io::Result { let path = ffi::CString::new(path.as_ref().as_os_str().as_bytes())?; let mut sfs: statfs = unsafe { mem::zeroed() }; if unsafe { statfs(path.as_ptr() as *const _, &mut sfs) } != 0 { return Err(io::Error::new(io::ErrorKind::Other, "statfs() failed")); } Ok(statfs_to_fs(&sfs)) } fn block_device_statistics(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn networks(&self) -> io::Result> { unix::networks() } fn network_stats(&self, interface: &str) -> io::Result { let mut rx_bytes: u64 = 0; let mut tx_bytes: u64 = 0; let mut rx_packets: u64 = 0; let mut tx_packets: u64 = 0; let mut rx_errors: u64 = 0; let mut tx_errors: u64 = 0; let mut ifap: *mut ifaddrs = std::ptr::null_mut(); let mut ifa: *mut ifaddrs; let mut data: *mut if_data; unsafe { getifaddrs(&mut ifap); ifa = ifap; // Multiple entries may be same network but for different addresses (ipv4, ipv6, link // layer) while !ifa.is_null() { let c_str: &std::ffi::CStr = std::ffi::CStr::from_ptr((*ifa).ifa_name); let str_net: &str = match c_str.to_str() { Ok(v) => v, Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "C string cannot be converted")) }; if interface == str_net { data = (*ifa).ifa_data as *mut if_data; // if_data may not be present in every table if !data.is_null() { rx_bytes += (*data).ifi_ibytes; tx_bytes += (*data).ifi_obytes; rx_packets += (*data).ifi_ipackets; tx_packets += (*data).ifi_opackets; rx_errors += (*data).ifi_ierrors; tx_errors += (*data).ifi_oerrors; } } ifa = (*ifa).ifa_next; } freeifaddrs(ifap); } Ok(NetworkStats { rx_bytes: ByteSize::b(rx_bytes), tx_bytes: ByteSize::b(tx_bytes), rx_packets, tx_packets, rx_errors, tx_errors, }) } fn cpu_temp(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn socket_stats(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } } fn measure_cpu() -> io::Result> { let mut cpus: usize = 0; sysctl!(HW_NCPU, &mut cpus, mem::size_of::()); let mut data: Vec = Vec::with_capacity(cpus); unsafe { data.set_len(cpus) }; for i in 0..cpus { let mut mib = KERN_CPTIME2.clone(); mib[2] = i as i32; sysctl!(mib, &mut data[i], mem::size_of::()); } Ok(data.into_iter().map(|cpu| cpu.into()).collect()) } impl PlatformMemory { // Retrieve platform memory information fn new() -> io::Result { let mut uvm_info = uvmexp::default(); sysctl!(VM_UVMEXP, &mut uvm_info, mem::size_of::()); let mut bcache_info = bcachestats::default(); sysctl!( VFS_BCACHESTAT, &mut bcache_info, mem::size_of::() ); Ok(Self { total: ByteSize::kib((uvm_info.npages << *bsd::PAGESHIFT) as u64), active: ByteSize::kib((uvm_info.active << *bsd::PAGESHIFT) as u64), inactive: ByteSize::kib((uvm_info.inactive << *bsd::PAGESHIFT) as u64), wired: ByteSize::kib((uvm_info.wired << *bsd::PAGESHIFT) as u64), cache: ByteSize::kib((bcache_info.numbufpages << *bsd::PAGESHIFT) as u64), free: ByteSize::kib((uvm_info.free << *bsd::PAGESHIFT) as u64), paging: ByteSize::kib((uvm_info.paging << *bsd::PAGESHIFT) as u64), sw: ByteSize::kib((uvm_info.swpages << *bsd::PAGESHIFT) as u64), swinuse: ByteSize::kib((uvm_info.swpginuse << *bsd::PAGESHIFT) as u64), swonly: ByteSize::kib((uvm_info.swpgonly << *bsd::PAGESHIFT) as u64), }) } fn to_memory(self) -> Memory { Memory { total: self.total, free: self.inactive + self.cache + self.free + self.paging, platform_memory: self, } } fn to_swap(self) -> Swap { Swap { total: self.sw, free: saturating_sub_bytes(self.sw, self.swinuse), platform_swap: self, } } } fn statfs_to_fs(fs: &statfs) -> Filesystem { Filesystem { files: (fs.f_files as usize).saturating_sub(fs.f_ffree as usize), files_total: fs.f_files as usize, files_avail: fs.f_ffree as usize, free: ByteSize::b(fs.f_bfree * fs.f_bsize as u64), avail: ByteSize::b(fs.f_bavail as u64 * fs.f_bsize as u64), total: ByteSize::b(fs.f_blocks * fs.f_bsize as u64), name_max: fs.f_namemax as usize, fs_type: unsafe { ffi::CStr::from_ptr(&fs.f_fstypename[0]).to_string_lossy().into_owned() }, fs_mounted_from: unsafe { ffi::CStr::from_ptr(&fs.f_mntfromname[0]).to_string_lossy().into_owned() }, fs_mounted_on: unsafe { ffi::CStr::from_ptr(&fs.f_mntonname[0]).to_string_lossy().into_owned() }, } } #[repr(C)] #[derive(Debug, Clone, Copy)] /// Fields of KERN_CPTIME2: https://github.com/openbsd/src/blob/0403d5bcc6af6e3b8d03ad1c6de319d5acc58295/sys/sys/sched.h#L83 pub struct sysctl_cpu { user: usize, nice: usize, system: usize, spin: usize, interrupt: usize, idle: usize, } impl From for CpuTime { fn from(cpu: sysctl_cpu) -> CpuTime { CpuTime { user: cpu.user, nice: cpu.nice, system: cpu.system, interrupt: cpu.interrupt, idle: cpu.idle, other: 0, } } } #[derive(Default, Debug)] #[repr(C)] struct apm_power_info { battery_state: c_uchar, ac_state: c_uchar, battery_life: c_uchar, spare1: c_uchar, minutes_left: c_uint, spare2: [c_uint; 6], } #[derive(Default, Debug)] #[repr(C)] struct bcachestats { numbufs: i64, /* number of buffers allocated */ numbufpages: i64, /* number of pages in buffer cache */ numdirtypages: i64, /* number of dirty free pages */ numcleanpages: i64, /* number of clean free pages */ pendingwrites: i64, /* number of pending writes */ pendingreads: i64, /* number of pending reads */ numwrites: i64, /* total writes started */ numreads: i64, /* total reads started */ cachehits: i64, /* total reads found in cache */ busymapped: i64, /* number of busy and mapped buffers */ dmapages: i64, /* dma reachable pages in buffer cache */ highpages: i64, /* pages above dma region */ delwribufs: i64, /* delayed write buffers */ kvaslots: i64, /* kva slots total */ kvaslots_avail: i64, /* available kva slots */ highflips: i64, /* total flips to above DMA */ highflops: i64, /* total failed flips to above DMA */ dmaflips: i64, /* total flips from high to DMA */ } #[derive(Default, Debug)] #[repr(C)] struct uvmexp { /* vm_page constants */ pagesize: c_int, /* size of a page (PAGE_SIZE): must be power of 2 */ pagemask: c_int, /* page mask */ pageshift: c_int, /* page shift */ /* vm_page counters */ npages: c_int, /* number of pages we manage */ free: c_int, /* number of free pages */ active: c_int, /* number of active pages */ inactive: c_int, /* number of pages that we free'd but may want back */ paging: c_int, /* number of pages in the process of being paged out */ wired: c_int, /* number of wired pages */ zeropages: c_int, /* number of zero'd pages */ reserve_pagedaemon: c_int, /* number of pages reserved for pagedaemon */ reserve_kernel: c_int, /* number of pages reserved for kernel */ anonpages: c_int, /* number of pages used by anon pagers */ vnodepages: c_int, /* number of pages used by vnode page cache */ vtextpages: c_int, /* number of pages used by vtext vnodes */ /* pageout params */ freemin: c_int, /* min number of free pages */ freetarg: c_int, /* target number of free pages */ inactarg: c_int, /* target number of inactive pages */ wiredmax: c_int, /* max number of wired pages */ anonmin: c_int, /* min threshold for anon pages */ vtextmin: c_int, /* min threshold for vtext pages */ vnodemin: c_int, /* min threshold for vnode pages */ anonminpct: c_int, /* min percent anon pages */ vtextminpct: c_int,/* min percent vtext pages */ vnodeminpct: c_int,/* min percent vnode pages */ /* swap */ nswapdev: c_int, /* number of configured swap devices in system */ swpages: c_int, /* number of PAGE_SIZE'ed swap pages */ swpginuse: c_int, /* number of swap pages in use */ swpgonly: c_int, /* number of swap pages in use, not also in RAM */ nswget: c_int, /* number of times fault calls uvm_swap_get() */ nanon: c_int, /* number total of anon's in system */ nanonneeded: c_int,/* number of anons currently needed */ nfreeanon: c_int, /* number of free anon's */ /* stat counters */ faults: c_int, /* page fault count */ traps: c_int, /* trap count */ intrs: c_int, /* interrupt count */ swtch: c_int, /* context switch count */ softs: c_int, /* software interrupt count */ syscalls: c_int, /* system calls */ pageins: c_int, /* pagein operation count */ /* pageouts are in pdpageouts below */ obsolete_swapins: c_int, /* swapins */ obsolete_swapouts: c_int, /* swapouts */ pgswapin: c_int, /* pages swapped in */ pgswapout: c_int, /* pages swapped out */ forks: c_int, /* forks */ forks_ppwait: c_int, /* forks where parent waits */ forks_sharevm: c_int, /* forks where vmspace is shared */ pga_zerohit: c_int, /* pagealloc where zero wanted and zero was available */ pga_zeromiss: c_int, /* pagealloc where zero wanted and zero not available */ zeroaborts: c_int, /* number of times page zeroing was aborted */ /* fault subcounters */ fltnoram: c_int, /* number of times fault was out of ram */ fltnoanon: c_int, /* number of times fault was out of anons */ fltnoamap: c_int, /* number of times fault was out of amap chunks */ fltpgwait: c_int, /* number of times fault had to wait on a page */ fltpgrele: c_int, /* number of times fault found a released page */ fltrelck: c_int, /* number of times fault relock called */ fltrelckok: c_int, /* number of times fault relock is a success */ fltanget: c_int, /* number of times fault gets anon page */ fltanretry: c_int, /* number of times fault retrys an anon get */ fltamcopy: c_int, /* number of times fault clears "needs copy" */ fltnamap: c_int, /* number of times fault maps a neighbor anon page */ fltnomap: c_int, /* number of times fault maps a neighbor obj page */ fltlget: c_int, /* number of times fault does a locked pgo_get */ fltget: c_int, /* number of times fault does an unlocked get */ flt_anon: c_int, /* number of times fault anon (case 1a) */ flt_acow: c_int, /* number of times fault anon cow (case 1b) */ flt_obj: c_int, /* number of times fault is on object page (2a) */ flt_prcopy: c_int, /* number of times fault promotes with copy (2b) */ flt_przero: c_int, /* number of times fault promotes with zerofill (2b) */ /* daemon counters */ pdwoke: c_int, /* number of times daemon woke up */ pdrevs: c_int, /* number of times daemon rev'd clock hand */ pdswout: c_int, /* number of times daemon called for swapout */ pdfreed: c_int, /* number of pages daemon freed since boot */ pdscans: c_int, /* number of pages daemon scanned since boot */ pdanscan: c_int, /* number of anonymous pages scanned by daemon */ pdobscan: c_int, /* number of object pages scanned by daemon */ pdreact: c_int, /* number of pages daemon reactivated since boot */ pdbusy: c_int, /* number of times daemon found a busy page */ pdpageouts: c_int, /* number of times daemon started a pageout */ pdpending: c_int, /* number of times daemon got a pending pagout */ pddeact: c_int, /* number of pages daemon deactivates */ pdreanon: c_int, /* anon pages reactivated due to min threshold */ pdrevnode: c_int, /* vnode pages reactivated due to min threshold */ pdrevtext: c_int, /* vtext pages reactivated due to min threshold */ fpswtch: c_int, /* FPU context switches */ kmapent: c_int, /* number of kernel map entries */ } systemstat-0.2.3/src/platform/unix.rs000064400000000000000000000044301046102023000160130ustar 00000000000000use std::{io, ffi, ptr, mem}; use libc::{c_int, getifaddrs, freeifaddrs, ifaddrs, sockaddr, sockaddr_in6, AF_INET, AF_INET6}; use crate::data::*; pub fn load_average() -> io::Result { let mut loads: [f64; 3] = [0.0, 0.0, 0.0]; if unsafe { getloadavg(&mut loads[0], 3) } != 3 { return Err(io::Error::new(io::ErrorKind::Other, "getloadavg() failed")) } Ok(LoadAverage { one: loads[0] as f32, five: loads[1] as f32, fifteen: loads[2] as f32 }) } pub fn networks() -> io::Result> { let mut ifap: *mut ifaddrs = ptr::null_mut(); if unsafe { getifaddrs(&mut ifap) } != 0 { return Err(io::Error::new(io::ErrorKind::Other, "getifaddrs() failed")) } let ifirst = ifap; let mut result = BTreeMap::new(); while !ifap.is_null() { let ifa = unsafe { *ifap }; let name = unsafe { ffi::CStr::from_ptr(ifa.ifa_name).to_string_lossy().into_owned() }; let entry = result.entry(name.clone()).or_insert(Network { name, addrs: Vec::new(), }); let addr = parse_addr(ifa.ifa_addr); if addr != IpAddr::Unsupported { entry.addrs.push(NetworkAddrs { addr, netmask: parse_addr(ifa.ifa_netmask), }); } ifap = unsafe { (*ifap).ifa_next }; } unsafe { freeifaddrs(ifirst) }; Ok(result) } fn parse_addr(aptr: *const sockaddr) -> IpAddr { if aptr.is_null() { return IpAddr::Empty; } let addr = unsafe { *aptr }; match addr.sa_family as i32 { AF_INET => IpAddr::V4(Ipv4Addr::new(addr.sa_data[2] as u8, addr.sa_data[3] as u8, addr.sa_data[4] as u8, addr.sa_data[5] as u8)), AF_INET6 => { // This is horrible. let addr6: *const sockaddr_in6 = unsafe { mem::transmute(aptr) }; let mut a: [u8; 16] = unsafe { (*addr6).sin6_addr.s6_addr }; a[..].reverse(); let a: [u16; 8] = unsafe { mem::transmute(a) }; IpAddr::V6(Ipv6Addr::new(a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0])) }, _ => IpAddr::Unsupported, } } #[link(name = "c")] extern "C" { fn getloadavg(loadavg: *mut f64, nelem: c_int) -> c_int; } systemstat-0.2.3/src/platform/windows/disk.rs000075500000000000000000000107251046102023000174630ustar 00000000000000use winapi::ctypes::c_ulong; use winapi::shared::minwindef::FALSE; use winapi::um::fileapi::{GetDiskFreeSpaceExW, GetLogicalDriveStringsW, GetVolumeInformationW, GetDriveTypeW}; use winapi::um::winnt::ULARGE_INTEGER; use super::{last_os_error, u16_array_to_string}; use crate::data::*; use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; use std::{io, ptr}; use std::mem::MaybeUninit; pub fn drives() -> io::Result> { let logical_drives = unsafe { GetLogicalDriveStringsW(0, ptr::null_mut()) }; let mut u16s = Vec::with_capacity(logical_drives as usize); let p_u16s = u16s.as_mut_ptr(); let get_logical_drives = unsafe { GetLogicalDriveStringsW(logical_drives, p_u16s) }; // (X://\0)*\0 if get_logical_drives + 1 != logical_drives { last_os_error()?; } unsafe { u16s.set_len(logical_drives as usize) }; // (X://\0)*\0 let drives = u16s.split(|c| *c == 0).filter(|iter| !iter.is_empty()); let mut vec: Vec = Vec::new(); for us in drives { let name = decode_utf16(us.iter().cloned()) .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) .collect::(); let (max, fs, tag) = get_volume_information(us)?; let tmp = if max == 0 { Filesystem { name_max: max as _, fs_type: fs, fs_mounted_from: tag, fs_mounted_on: name, total: ByteSize::b(0), avail: ByteSize::b(0), free: ByteSize::b(0), files: 0, files_total: 0, files_avail: 0 } } else { let (total, avail, free) = get_disk_space_ext(us)?; Filesystem { name_max: max as _, fs_type: fs, fs_mounted_from: tag, fs_mounted_on: name, total: ByteSize::b(total), avail: ByteSize::b(avail), free: ByteSize::b(free), files: 0, // don't find.. files_total: 0, files_avail: 0, } }; vec.push(tmp); } Ok(vec) } // https://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx fn get_volume_information(name: &[u16]) -> io::Result<(c_ulong, String, String)> { let p_name = name.as_ptr(); let mut volume_name = Vec::with_capacity(255); let p_volume_name = volume_name.as_mut_ptr(); let mut fs_name = Vec::with_capacity(255); let p_fs_name = fs_name.as_mut_ptr(); let mut volume_serial = Vec::with_capacity(255); let p_volume_serial = volume_serial.as_mut_ptr(); let mut max_component_length: c_ulong = 0; let mut fs_flags: c_ulong = 0; if FALSE == unsafe { GetVolumeInformationW( p_name, p_volume_name, 255, p_volume_serial, &mut max_component_length as *mut _, &mut fs_flags as *mut _, p_fs_name, 255, ) } { match unsafe { GetDriveTypeW(p_name) } { 2 => { // REMOVABLE DRIVE (Floppy, USB, etc) return Ok(( max_component_length, String::from("REM"), u16_array_to_string(p_volume_name) )) }, 5 => { // DRIVE_CDROM return Ok(( max_component_length, String::from("CDROM"), u16_array_to_string(p_volume_name) )) }, _ => last_os_error()? }; } Ok(( max_component_length, u16_array_to_string(p_fs_name), u16_array_to_string(p_volume_name), )) } fn get_disk_space_ext(name: &[u16]) -> io::Result<(u64, u64, u64)> { let p_name = name.as_ptr(); let mut avail: MaybeUninit = MaybeUninit::uninit(); let mut total: MaybeUninit = MaybeUninit::uninit(); let mut free: MaybeUninit = MaybeUninit::uninit(); if FALSE == unsafe { GetDiskFreeSpaceExW( p_name, avail.as_mut_ptr(), total.as_mut_ptr(), free.as_mut_ptr(), ) } { last_os_error()?; } let avail = unsafe { avail.assume_init() }; let total = unsafe { total.assume_init() }; let free = unsafe { free.assume_init() }; unsafe { Ok((*total.QuadPart(), *avail.QuadPart(), *free.QuadPart())) } } systemstat-0.2.3/src/platform/windows/mod.rs000075500000000000000000000273601046102023000173130ustar 00000000000000use winapi::ctypes::c_char; use winapi::shared::minwindef::*; use winapi::shared::winerror::ERROR_SUCCESS; use winapi::um::{sysinfoapi, winbase}; use winapi::um::pdh::{ PDH_FMT_COUNTERVALUE_ITEM_A, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, PDH_FMT_NOCAP100, PdhAddEnglishCounterA, PdhCloseQuery, PdhCollectQueryData, PdhGetFormattedCounterArrayA, PdhOpenQueryA, }; mod disk; mod network_interfaces; mod socket; use super::common::*; use crate::data::*; use std::ffi::CStr; use std::slice::from_raw_parts; use std::cmp; use std::{io, mem, path}; fn u16_array_to_string(p: *const u16) -> String { use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; unsafe { if p.is_null() { return String::new(); } let mut amt = 0usize; while !p.add(amt).is_null() && *p.add(amt) != 0u16 { amt += 1; } let u16s = from_raw_parts(p, amt); decode_utf16(u16s.iter().cloned()) .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) .collect::() } } fn c_char_array_to_string(p: *const c_char) -> String { unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() } } fn last_os_error() -> io::Result<()> { Err(io::Error::last_os_error()) } pub struct PlatformImpl; /// An implementation of `Platform` for Windows. /// See `Platform` for documentation. impl Platform for PlatformImpl { #[inline(always)] fn new() -> Self { PlatformImpl } fn cpu_load(&self) -> io::Result>> { const PDH_MORE_DATA: u32 = 0x8000_07D2; struct QueryHandle(PDH_HQUERY); // Pdh is supposedly synchronized internally with a mutex unsafe impl Send for QueryHandle {} unsafe impl Sync for QueryHandle {} impl Drop for QueryHandle { fn drop(&mut self){ unsafe { PdhCloseQuery(self.0); } } } struct CounterHandle(PDH_HCOUNTER); unsafe impl Send for CounterHandle {} unsafe impl Sync for CounterHandle {} struct PerformanceCounter { query: QueryHandle, counter: CounterHandle, } impl PerformanceCounter { pub fn new(key: &CStr) -> io::Result { let mut query = std::ptr::null_mut(); let status = unsafe { PdhOpenQueryA(std::ptr::null(), 0, &mut query) }; if status as u32 != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(status)); } let query = QueryHandle(query); let mut counter = std::ptr::null_mut(); let status = unsafe { PdhAddEnglishCounterA(query.0, key.as_ptr(), 0, &mut counter) }; if status as u32 != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(status)); } let counter = CounterHandle(counter); let status = unsafe { PdhCollectQueryData(query.0) }; if status as u32 != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(status)); } Ok(Self { query, counter, }) } fn next_value(&self) -> io::Result> { let status = unsafe { PdhCollectQueryData(self.query.0) }; if status as u32 != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(status)); } let mut buffer_size = 0; let mut item_count = 0; let status = unsafe { PdhGetFormattedCounterArrayA(self.counter.0, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &mut buffer_size, &mut item_count, std::ptr::null_mut()) }; match status as u32 { PDH_MORE_DATA => {}, ERROR_SUCCESS => { return Ok(Vec::new()); } _ => { return Err(io::Error::from_raw_os_error(status)); } } let mut items = Vec::new(); items.reserve(item_count as usize * std::mem::size_of::()); let status = unsafe { PdhGetFormattedCounterArrayA(self.counter.0, PDH_FMT_DOUBLE, &mut buffer_size, &mut item_count, items.as_mut_ptr()) }; if status as u32 != ERROR_SUCCESS { return Err(io::Error::from_raw_os_error(status)); } unsafe { items.set_len(item_count as usize); } Ok(items) } } let user_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% User Time\0").unwrap())?; let idle_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% Idle Time\0").unwrap())?; let system_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% Privileged Time\0").unwrap())?; let interrupt_counter = PerformanceCounter::new(CStr::from_bytes_with_nul(b"\\Processor(*)\\% Interrupt Time\0").unwrap())?; Ok(DelayedMeasurement::new(Box::new(move || { let user = user_counter.next_value()?; let idle = idle_counter.next_value()?; let system = system_counter.next_value()?; let interrupt = interrupt_counter.next_value()?; let count = user.iter().filter(|item| unsafe { CStr::from_ptr(item.szName).to_string_lossy() } != "_Total").count(); let mut ret = vec![ CPULoad { user: 0.0, nice: 0.0, system: 0.0, interrupt: 0.0, idle: 0.0, platform: PlatformCpuLoad {}, }; count ]; for item in user { let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; if let Ok(n) = name.parse::(){ ret[n].user = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; } } for item in idle { let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; if let Ok(n) = name.parse::(){ ret[n].idle = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; } } for item in system { let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; if let Ok(n) = name.parse::(){ ret[n].system = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; } } for item in interrupt { let name = unsafe { CStr::from_ptr(item.szName).to_string_lossy() }; if let Ok(n) = name.parse::(){ ret[n].interrupt = unsafe { (*item.FmtValue.u.doubleValue() / 100.0) as f32 }; } } Ok(ret) }))) } fn load_average(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn memory(&self) -> io::Result { PlatformMemory::new().map(|pm| pm.to_memory()) } fn swap(&self) -> io::Result { PlatformMemory::new().map(|pm| pm.to_swap()) } fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> { let pm = PlatformMemory::new()?; Ok((pm.clone().to_memory(), pm.to_swap())) } fn uptime(&self) -> io::Result { let since_boot: u64 = unsafe { sysinfoapi::GetTickCount64() }; Ok(Duration::from_millis(since_boot)) } fn battery_life(&self) -> io::Result { let status = power_status(); if status.BatteryFlag == 128 { return Err(io::Error::new(io::ErrorKind::Other, "Battery absent")); } if status.BatteryFlag == 255 { return Err(io::Error::new( io::ErrorKind::Other, "Battery status unknown", )); } Ok(BatteryLife { remaining_capacity: status.BatteryLifePercent as f32 / 100.0, remaining_time: Duration::from_secs(status.BatteryLifeTime as u64), }) } fn on_ac_power(&self) -> io::Result { Ok(power_status().ACLineStatus == 1) } fn mounts(&self) -> io::Result> { disk::drives() } fn block_device_statistics(&self) -> io::Result> { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn networks(&self) -> io::Result> { network_interfaces::get() } fn network_stats(&self, _interface: &str) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn cpu_temp(&self) -> io::Result { Err(io::Error::new(io::ErrorKind::Other, "Not supported")) } fn socket_stats(&self) -> io::Result { socket::get() } } fn power_status() -> winbase::SYSTEM_POWER_STATUS { let mut status = winbase::SYSTEM_POWER_STATUS { ACLineStatus: 0, BatteryFlag: 0, BatteryLifePercent: 0, Reserved1: 0, BatteryLifeTime: 0, BatteryFullLifeTime: 0, }; unsafe { winbase::GetSystemPowerStatus(&mut status); } status } impl PlatformMemory { // Retrieve platform memory information fn new() -> io::Result { let mut status = sysinfoapi::MEMORYSTATUSEX { dwLength: mem::size_of::() as DWORD, dwMemoryLoad: 0, ullTotalPhys: 0, ullAvailPhys: 0, ullTotalPageFile: 0, ullAvailPageFile: 0, ullTotalVirtual: 0, ullAvailVirtual: 0, ullAvailExtendedVirtual: 0, }; let ret = unsafe { sysinfoapi::GlobalMemoryStatusEx(&mut status) }; if ret == 0 { return Err(io::Error::last_os_error()) } Ok(Self { load: status.dwMemoryLoad, total_phys: ByteSize::b(status.ullTotalPhys), avail_phys: ByteSize::b(status.ullAvailPhys), total_pagefile: ByteSize::b(status.ullTotalPageFile), avail_pagefile: ByteSize::b(status.ullAvailPageFile), total_virt: ByteSize::b(status.ullTotalVirtual), avail_virt: ByteSize::b(status.ullAvailVirtual), avail_ext: ByteSize::b(status.ullAvailExtendedVirtual), }) } // Convert the platform memory information to Memory fn to_memory(self) -> Memory { Memory { total: self.total_phys, free: self.avail_phys, platform_memory: self, } } // Convert the platform memory information to Swap fn to_swap(self) -> Swap { // Be catious because pagefile and phys don't always sync up // Despite the name, pagefile includes both physical and swap memory let total = saturating_sub_bytes(self.total_pagefile, self.total_phys); let free = saturating_sub_bytes(self.avail_pagefile, self.avail_phys); Swap { total, // Sometimes, especially when swap total is 0, free can exceed total free: cmp::min(total, free), platform_swap: self, } } } systemstat-0.2.3/src/platform/windows/network_interfaces.rs000075500000000000000000000360701046102023000224260ustar 00000000000000use libc::{c_void, free, malloc, size_t}; use winapi::ctypes::*; use winapi::shared::minwindef::*; use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS}; use winapi::shared::ws2def::{AF_INET6, AF_INET, AF_UNSPEC, SOCKADDR}; use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH; use super::{c_char_array_to_string, last_os_error, u16_array_to_string}; use crate::data::*; use std::collections::BTreeMap; use std::io::Write; use std::{io, mem, ptr}; // unions with non-`Copy` fields are unstable (see issue #32836) // #[repr(C)] // union AlimentOrLengthIfIndex { // aliment: c_ulonglong, // length_ifindex: LengthIfIndex, // } #[repr(C)] struct LengthIfIndex { length: ULONG, ifindex: DWORD, } #[repr(C)] struct LengthFlags { length: ULONG, flags: DWORD, } #[repr(C)] struct SoketAddress { lp_sockaddr: *mut SOCKADDR, i_sockaddr_length: c_int, } #[repr(C)] struct IpAdapterPrefix { aol: LengthIfIndex, next: *mut IpAdapterPrefix, address: SoketAddress, prefix_length: ULONG, } #[repr(C)] struct IpAdapterUnicastAddress { aol: LengthFlags, next: *mut IpAdapterUnicastAddress, address: SoketAddress, prefix_origin: c_int, suffix_origin: c_int, dad_state: c_int, valid_lifetime: ULONG, preferred_lifetime: ULONG, lease_lifetime: ULONG, on_link_prefix_length: u8, } const MAX_ADAPTER_ADDRESS_LENGTH: usize = 8; #[repr(C)] struct IpAdapterAddresses { aol: LengthIfIndex, next: *mut IpAdapterAddresses, adapter_name: *mut c_char, first_unicass_address: *mut IpAdapterUnicastAddress, first_anycass_address: *const c_void, first_multicass_address: *const c_void, first_dns_server_address: *const c_void, dns_suffix: *mut wchar_t, description: *mut wchar_t, friendly_name: *mut wchar_t, physical_address: [u8; MAX_ADAPTER_ADDRESS_LENGTH], physical_address_length: DWORD, flags: DWORD, mtu: DWORD, if_type: DWORD, oper_status: c_int, ipv6_if_index: DWORD, zone_indices: [DWORD; 16], first_prefix: *mut IpAdapterPrefix, } // https://msdn.microsoft.com/en-us/library/aa365915(v=vs.85).aspx // https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366066(d=printer,v=vs.85).aspx // C:\Program Files (x86)\Windows Kits\8.1\Include\um\IPHlpApi.h #[link(name = "iphlpapi")] extern "system" { fn GetAdaptersAddresses( family: ULONG, flags: ULONG, reserved: *const c_void, addresses: *mut IpAdapterAddresses, size: *mut ULONG, ) -> ULONG; } const WORKING_BUFFER_SIZEL: size_t = 15000; pub fn get() -> io::Result> { let mut new_size: ULONG = WORKING_BUFFER_SIZEL as ULONG; let mut p_adapter: *mut IpAdapterAddresses; loop { unsafe { p_adapter = malloc(new_size as size_t) as *mut IpAdapterAddresses; if p_adapter.is_null() { panic!("Failed: malloc!"); } let res_code = GetAdaptersAddresses( 0, AF_UNSPEC as ULONG, // ipv4 & ipv6 ptr::null(), p_adapter, &mut new_size as *mut ULONG, ); match res_code { // 0 ERROR_SUCCESS => break, // 111, retry ERROR_BUFFER_OVERFLOW => { new_size *= 2; free(p_adapter as *mut c_void); continue; } _ => { free(p_adapter as *mut c_void); last_os_error()?; } } } } let mut map = BTreeMap::new(); // key->adapter_name, name-> friendly_name, maybe should use the adapter_name all. unsafe { let mut cur_p_adapter = p_adapter; while !cur_p_adapter.is_null() { // name, mac, etc let adapter_name = c_char_array_to_string((*cur_p_adapter).adapter_name); // println!("adapter_name : {}", adapter_name); // let dns_suffix = u16_array_to_string((*cur_p_adapter).dns_suffix); // println!("dns_suffix : {}", dns_suffix); let friendly_name = u16_array_to_string((*cur_p_adapter).friendly_name); // println!("friendly_name: {}", friendly_name); // let description = u16_array_to_string((*cur_p_adapter).description); // println!("description : {}", description); // let mac = physical_address_to_string(&(*cur_p_adapter).physical_address, (*cur_p_adapter).physical_address_length); // println!("mac : {}", mac); let mut addrs = Vec::new(); // ip let mut cur_p_addr = (*cur_p_adapter).first_unicass_address; while !cur_p_addr.is_null() { let addr = parse_addr_and_netmask( (*cur_p_addr).address.lp_sockaddr, (*cur_p_addr).on_link_prefix_length, ); addrs.push(addr); // println!("{:?}", addr); // next addr cur_p_addr = (*cur_p_addr).next; } let network = Network { name: friendly_name, addrs, }; map.insert(adapter_name, network); // next adapter cur_p_adapter = (*cur_p_adapter).next; } } unsafe { free(p_adapter as *mut c_void); } Ok(map) } fn _physical_address_to_string(array: [u8; 8], length: DWORD) -> String { let mut bytes = Vec::with_capacity(length as usize); for (idx, b) in array.iter().enumerate().take(length as usize) { if idx == 0 { write!(&mut bytes, "{:02X}", b).unwrap(); } else { write!(&mut bytes, "-{:02X}", b).unwrap(); } } String::from_utf8_lossy(&bytes[..]).into_owned() } // Thanks , copy from unix.rs and some modify fn parse_addr_and_netmask(aptr: *const SOCKADDR, net_bits: u8) -> NetworkAddrs { if aptr.is_null() { return NetworkAddrs { addr: IpAddr::Empty, netmask: IpAddr::Empty, }; } let addr = unsafe { *aptr }; match addr.sa_family as i32 { AF_INET => { let addr = IpAddr::V4(Ipv4Addr::new( addr.sa_data[2] as u8, addr.sa_data[3] as u8, addr.sa_data[4] as u8, addr.sa_data[5] as u8, )); let netmask = if net_bits <= 32 { IpAddr::V4(netmask_v4(net_bits)) } else { IpAddr::Empty }; NetworkAddrs { addr, netmask } } AF_INET6 => { // This is horrible. #[allow(clippy::cast_ptr_alignment)] let addr6: *const SOCKADDR_IN6_LH = aptr as *const SOCKADDR_IN6_LH; let mut a: [u8; 16] = unsafe { *std::ptr::read_unaligned(addr6).sin6_addr.u.Byte() }; a[..].reverse(); let a: [u16; 8] = unsafe { mem::transmute(a) }; let addr = IpAddr::V6(Ipv6Addr::new( a[7], a[6], a[5], a[4], a[3], a[2], a[1], a[0], )); let netmask = if net_bits <= 128 { IpAddr::V6(netmask_v6(net_bits)) } else { IpAddr::Empty }; NetworkAddrs { addr, netmask } } _ => NetworkAddrs { addr: IpAddr::Empty, netmask: IpAddr::Empty, }, } } // This faster than [u8;4], but v6 is slower if use this.. // And the scan() method is slower also. fn netmask_v4(bits: u8) -> Ipv4Addr { let mut i = (0..4).map(|idx| { let idx8 = idx << 3; match (bits as usize > idx8, bits as usize > idx8 + 8) { (true, true) => 255, (true, false) => 255u8.wrapping_shl((8 - bits % 8) as u32), _ => 0, } }); Ipv4Addr::new( i.next().unwrap(), i.next().unwrap(), i.next().unwrap(), i.next().unwrap(), ) } fn netmask_v6(bits: u8) -> Ipv6Addr { let mut tmp = [0u16; 8]; (0..8).for_each(|idx| { let idx16 = idx << 4; match (bits as usize > idx16, bits as usize > idx16 + 16) { (true, true) => { tmp[idx] = 0xffff; } (true, false) => { tmp[idx] = 0xffffu16.wrapping_shl((16 - bits % 16) as u32); } _ => {} } }); Ipv6Addr::new( tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], ) } #[test] fn netmask_v4_test() { vec![ (0, "0.0.0.0"), (1, "128.0.0.0"), (2, "192.0.0.0"), (3, "224.0.0.0"), (4, "240.0.0.0"), (5, "248.0.0.0"), (6, "252.0.0.0"), (7, "254.0.0.0"), (8, "255.0.0.0"), (9, "255.128.0.0"), (10, "255.192.0.0"), (11, "255.224.0.0"), (12, "255.240.0.0"), (13, "255.248.0.0"), (14, "255.252.0.0"), (15, "255.254.0.0"), (16, "255.255.0.0"), (17, "255.255.128.0"), (18, "255.255.192.0"), (19, "255.255.224.0"), (20, "255.255.240.0"), (21, "255.255.248.0"), (22, "255.255.252.0"), (23, "255.255.254.0"), (24, "255.255.255.0"), (25, "255.255.255.128"), (26, "255.255.255.192"), (27, "255.255.255.224"), (28, "255.255.255.240"), (29, "255.255.255.248"), (30, "255.255.255.252"), (31, "255.255.255.254"), (32, "255.255.255.255"), ].into_iter() .for_each(|(i, addr)| assert_eq!(netmask_v4(i), addr.parse::().unwrap())) } #[test] fn netmask_v6_test() { vec![ (0, "::"), (1, "8000::"), (2, "c000::"), (3, "e000::"), (4, "f000::"), (5, "f800::"), (6, "fc00::"), (7, "fe00::"), (8, "ff00::"), (9, "ff80::"), (10, "ffc0::"), (11, "ffe0::"), (12, "fff0::"), (13, "fff8::"), (14, "fffc::"), (15, "fffe::"), (16, "ffff::"), (17, "ffff:8000::"), (18, "ffff:c000::"), (19, "ffff:e000::"), (20, "ffff:f000::"), (21, "ffff:f800::"), (22, "ffff:fc00::"), (23, "ffff:fe00::"), (24, "ffff:ff00::"), (25, "ffff:ff80::"), (26, "ffff:ffc0::"), (27, "ffff:ffe0::"), (28, "ffff:fff0::"), (29, "ffff:fff8::"), (30, "ffff:fffc::"), (31, "ffff:fffe::"), (32, "ffff:ffff::"), (33, "ffff:ffff:8000::"), (34, "ffff:ffff:c000::"), (35, "ffff:ffff:e000::"), (36, "ffff:ffff:f000::"), (37, "ffff:ffff:f800::"), (38, "ffff:ffff:fc00::"), (39, "ffff:ffff:fe00::"), (40, "ffff:ffff:ff00::"), (41, "ffff:ffff:ff80::"), (42, "ffff:ffff:ffc0::"), (43, "ffff:ffff:ffe0::"), (44, "ffff:ffff:fff0::"), (45, "ffff:ffff:fff8::"), (46, "ffff:ffff:fffc::"), (47, "ffff:ffff:fffe::"), (48, "ffff:ffff:ffff::"), (49, "ffff:ffff:ffff:8000::"), (50, "ffff:ffff:ffff:c000::"), (51, "ffff:ffff:ffff:e000::"), (52, "ffff:ffff:ffff:f000::"), (53, "ffff:ffff:ffff:f800::"), (54, "ffff:ffff:ffff:fc00::"), (55, "ffff:ffff:ffff:fe00::"), (56, "ffff:ffff:ffff:ff00::"), (57, "ffff:ffff:ffff:ff80::"), (58, "ffff:ffff:ffff:ffc0::"), (59, "ffff:ffff:ffff:ffe0::"), (60, "ffff:ffff:ffff:fff0::"), (61, "ffff:ffff:ffff:fff8::"), (62, "ffff:ffff:ffff:fffc::"), (63, "ffff:ffff:ffff:fffe::"), (64, "ffff:ffff:ffff:ffff::"), (65, "ffff:ffff:ffff:ffff:8000::"), (66, "ffff:ffff:ffff:ffff:c000::"), (67, "ffff:ffff:ffff:ffff:e000::"), (68, "ffff:ffff:ffff:ffff:f000::"), (69, "ffff:ffff:ffff:ffff:f800::"), (70, "ffff:ffff:ffff:ffff:fc00::"), (71, "ffff:ffff:ffff:ffff:fe00::"), (72, "ffff:ffff:ffff:ffff:ff00::"), (73, "ffff:ffff:ffff:ffff:ff80::"), (74, "ffff:ffff:ffff:ffff:ffc0::"), (75, "ffff:ffff:ffff:ffff:ffe0::"), (76, "ffff:ffff:ffff:ffff:fff0::"), (77, "ffff:ffff:ffff:ffff:fff8::"), (78, "ffff:ffff:ffff:ffff:fffc::"), (79, "ffff:ffff:ffff:ffff:fffe::"), (80, "ffff:ffff:ffff:ffff:ffff::"), (81, "ffff:ffff:ffff:ffff:ffff:8000::"), (82, "ffff:ffff:ffff:ffff:ffff:c000::"), (83, "ffff:ffff:ffff:ffff:ffff:e000::"), (84, "ffff:ffff:ffff:ffff:ffff:f000::"), (85, "ffff:ffff:ffff:ffff:ffff:f800::"), (86, "ffff:ffff:ffff:ffff:ffff:fc00::"), (87, "ffff:ffff:ffff:ffff:ffff:fe00::"), (88, "ffff:ffff:ffff:ffff:ffff:ff00::"), (89, "ffff:ffff:ffff:ffff:ffff:ff80::"), (90, "ffff:ffff:ffff:ffff:ffff:ffc0::"), (91, "ffff:ffff:ffff:ffff:ffff:ffe0::"), (92, "ffff:ffff:ffff:ffff:ffff:fff0::"), (93, "ffff:ffff:ffff:ffff:ffff:fff8::"), (94, "ffff:ffff:ffff:ffff:ffff:fffc::"), (95, "ffff:ffff:ffff:ffff:ffff:fffe::"), (96, "ffff:ffff:ffff:ffff:ffff:ffff::"), (97, "ffff:ffff:ffff:ffff:ffff:ffff:8000:0"), (98, "ffff:ffff:ffff:ffff:ffff:ffff:c000:0"), (99, "ffff:ffff:ffff:ffff:ffff:ffff:e000:0"), (100, "ffff:ffff:ffff:ffff:ffff:ffff:f000:0"), (101, "ffff:ffff:ffff:ffff:ffff:ffff:f800:0"), (102, "ffff:ffff:ffff:ffff:ffff:ffff:fc00:0"), (103, "ffff:ffff:ffff:ffff:ffff:ffff:fe00:0"), (104, "ffff:ffff:ffff:ffff:ffff:ffff:ff00:0"), (105, "ffff:ffff:ffff:ffff:ffff:ffff:ff80:0"), (106, "ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0"), (107, "ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0"), (108, "ffff:ffff:ffff:ffff:ffff:ffff:fff0:0"), (109, "ffff:ffff:ffff:ffff:ffff:ffff:fff8:0"), (110, "ffff:ffff:ffff:ffff:ffff:ffff:fffc:0"), (111, "ffff:ffff:ffff:ffff:ffff:ffff:fffe:0"), (112, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:0"), (113, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000"), (114, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000"), (115, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000"), (116, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000"), (117, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800"), (118, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00"), (119, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00"), (120, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00"), (121, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80"), (122, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0"), (123, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0"), (124, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0"), (125, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8"), (126, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc"), (127, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"), (128, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), ].into_iter() .for_each(|(i, addr)| assert_eq!(netmask_v6(i), addr.parse::().unwrap())) } systemstat-0.2.3/src/platform/windows/socket.rs000075500000000000000000000044421046102023000200200ustar 00000000000000use winapi::ctypes::c_ulong; use winapi::shared::winerror::ERROR_SUCCESS; use winapi::shared::ws2def::{AF_INET6, AF_INET}; use super::last_os_error; use crate::data::*; use std::io; #[derive(Debug, Default)] #[repr(C)] struct TcpStats { rto_algorithm: c_ulong, rto_min: c_ulong, rto_max: c_ulong, max_conn: c_ulong, active_opens: c_ulong, passive_opens: c_ulong, attemp_fails: c_ulong, estab_resets: c_ulong, curr_estab: c_ulong, in_segs: c_ulong, out_segs: c_ulong, retrans_segs: c_ulong, in_errs: c_ulong, out_rsts: c_ulong, num_conns: c_ulong, } #[derive(Debug, Default)] #[repr(C)] struct UdpStats { in_datagrams: c_ulong, no_ports: c_ulong, in_errors: c_ulong, out_datagrams: c_ulong, num_addrs: c_ulong, } #[link(name = "iphlpapi")] extern "system" { // https://msdn.microsoft.com/en-us/library/aa366023(v=vs.85).aspx fn GetTcpStatisticsEx(pStats: *mut TcpStats, dwFamily: c_ulong) -> c_ulong; // https://msdn.microsoft.com/en-us/library/aa366031(v=vs.85).aspx fn GetUdpStatisticsEx(pStats: *mut UdpStats, dwFamily: c_ulong) -> c_ulong; } pub fn get() -> io::Result { let mut tcp4 = TcpStats::default(); let mut tcp6 = TcpStats::default(); let mut udp4 = UdpStats::default(); let mut udp6 = UdpStats::default(); if ERROR_SUCCESS != unsafe { GetTcpStatisticsEx(&mut tcp4 as *mut _, AF_INET as c_ulong) } { last_os_error()?; } if ERROR_SUCCESS != unsafe { GetTcpStatisticsEx(&mut tcp6 as *mut _, AF_INET6 as c_ulong) } { last_os_error()?; } if ERROR_SUCCESS != unsafe { GetUdpStatisticsEx(&mut udp4 as *mut _, AF_INET as c_ulong) } { last_os_error()?; } if ERROR_SUCCESS != unsafe { GetUdpStatisticsEx(&mut udp6 as *mut _, AF_INET6 as c_ulong) } { last_os_error()?; } // println!("4: {:?}", tcp4 ); // println!("6: {:?}", tcp6 ); // println!("4: {:?}", udp4 ); // println!("6: {:?}", udp6 ); let stat = SocketStats { tcp_sockets_in_use: tcp4.num_conns as usize, tcp_sockets_orphaned: 0, // ? who or how to compute? tcp6_sockets_in_use: tcp6.num_conns as usize, udp_sockets_in_use: udp4.num_addrs as usize, udp6_sockets_in_use: udp6.num_addrs as usize, }; Ok(stat) }