sysinfo-0.28.4/.cargo_vcs_info.json0000644000000001360000000000100126210ustar { "git": { "sha1": "aec82898ebb4312a2f719060d39a89382657e4b8" }, "path_in_vcs": "" }sysinfo-0.28.4/.cirrus.yml000064400000000000000000000035231046102023000135240ustar 00000000000000task: name: rust 1.59 on freebsd 13 freebsd_instance: image: freebsd-13-1-release-amd64 setup_script: - pkg install -y curl - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain=1.59 - . $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - . $HOME/.cargo/env - cargo check - cargo check --no-default-features - cargo clippy -- -D warnings - cargo check --example simple - FREEBSD_CI=1 cargo test -j1 - FREEBSD_CI=1 cargo test --lib -j1 -- --ignored task: name: rust nightly on freebsd 13 freebsd_instance: image: freebsd-13-1-release-amd64 setup_script: - pkg install -y curl - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain=nightly - . $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - . $HOME/.cargo/env - cargo check - cargo check --no-default-features - cargo clippy -- -D warnings - cargo check --example simple - FREEBSD_CI=1 cargo test -j1 - FREEBSD_CI=1 cargo test --lib -j1 -- --ignored task: name: rust 1.59 on mac m1 macos_instance: image: ghcr.io/cirruslabs/macos-monterey-base:latest setup_script: - brew install curl - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh -y --profile=minimal --default-toolchain=1.59 - source $HOME/.cargo/env - rustup --version - rustup component add clippy test_script: - source $HOME/.cargo/env - cargo check - cargo check --no-default-features - cargo check --no-default-features --features apple-sandbox - cargo clippy -- -D warnings - cargo check --example simple - APPLE_CI=1 cargo test -j1 - APPLE_CI=1 cargo test --lib -j1 -- --ignored sysinfo-0.28.4/.github/FUNDING.yml000064400000000000000000000002121046102023000145610ustar 00000000000000# These are supported funding model platforms github: [GuillaumeGomez] patreon: GuillaumeGomez custom: ["https://paypal.me/imperioland"] sysinfo-0.28.4/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000005341046102023000176300ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- If you like my work, please consider sponsoring me! **Describe the bug** Please provide the system on which you're using `sysinfo` and also `sysinfo` version. **To Reproduce** Please provide the code showing the bug so it can be reproduced. sysinfo-0.28.4/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000004521046102023000206620ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: feature request assignees: '' --- If you like my work, please consider sponsoring me! Please describe what you'd like to be added. For example: * Adding a new system support. * Getting new system information. sysinfo-0.28.4/.github/workflows/CI.yml000064400000000000000000000165731046102023000160400ustar 00000000000000on: push: branches: [master] pull_request: name: CI # If a new push on the PR is done, cancel the build concurrency: group: ${{ github.head_ref }} cancel-in-progress: true jobs: rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true components: rustfmt - run: cargo fmt -- --check clippy: runs-on: ${{ matrix.os }} strategy: matrix: os: - ubuntu-latest - macos-latest - windows-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly override: true components: clippy - run: cargo clippy --all-targets --features serde -- -D warnings check: name: Check ${{ matrix.toolchain }} / ${{ matrix.triple.target }} runs-on: ${{ matrix.triple.os }} strategy: fail-fast: false matrix: triple: - { os: 'ubuntu-latest', target: 'x86_64-unknown-linux-gnu', cross: false } - { os: 'ubuntu-latest', target: 'i686-unknown-linux-gnu', cross: true } - { os: 'macos-latest', target: 'x86_64-apple-darwin', cross: false } - { os: 'windows-latest', target: 'x86_64-pc-windows-msvc', cross: false } ## iOS - { os: 'macos-latest', target: 'aarch64-apple-ios', cross: true } - { os: 'macos-latest', target: 'x86_64-apple-ios', cross: true } ## ARM64 - { os: 'ubuntu-latest', target: 'aarch64-unknown-linux-gnu', cross: true } - { os: 'ubuntu-latest', target: 'aarch64-unknown-linux-musl', cross: true } ## ARMv7 - { os: 'ubuntu-latest', target: 'armv7-unknown-linux-gnueabihf', cross: true } - { os: 'ubuntu-latest', target: 'armv7-unknown-linux-musleabihf', cross: true } ## ARMv6 - { os: 'ubuntu-latest', target: 'arm-unknown-linux-gnueabihf', cross: true } - { os: 'ubuntu-latest', target: 'arm-unknown-linux-musleabihf', cross: true } # Android - { os: 'ubuntu-latest', target: 'aarch64-linux-android', cross: true } - { os: 'ubuntu-latest', target: 'armv7-linux-androideabi', cross: true } - { os: 'ubuntu-latest', target: 'x86_64-linux-android', cross: true } - { os: 'ubuntu-latest', target: 'i686-linux-android', cross: true } toolchain: - "1.59.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - name: Install cross-target if: matrix.triple.cross run: rustup target add ${{ matrix.triple.target }} - name: Check uses: actions-rs/cargo@v1 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml -- -D warnings use-cross: ${{ matrix.triple.cross }} - name: Check debug feature uses: actions-rs/cargo@v1 with: command: rustc args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --features=debug -- -D warnings use-cross: ${{ matrix.triple.cross }} - name: Check without multithreading uses: actions-rs/cargo@v1 with: command: check args: --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features use-cross: ${{ matrix.triple.cross }} - name: Check example uses: actions-rs/cargo@v1 with: command: check args: --target=${{ matrix.triple.target }} --example simple use-cross: ${{ matrix.triple.cross }} - name: Check (Apple app store restrictions) if: matrix.triple.os == 'macos-latest' uses: actions-rs/cargo@v1 with: command: check args: --features apple-sandbox --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml use-cross: ${{ matrix.triple.cross }} - name: Check without multithreading (Apple app store restrictions) if: matrix.triple.os == 'macos-latest' uses: actions-rs/cargo@v1 with: command: check args: --features apple-sandbox --target=${{ matrix.triple.target }} --manifest-path=Cargo.toml --no-default-features use-cross: ${{ matrix.triple.cross }} tests: needs: [check] name: Test ${{ matrix.os }} (rust ${{matrix.toolchain}}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest - macos-latest - windows-latest toolchain: - "1.59.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v2 - name: Install toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - name: Check docs run: cargo doc if: matrix.toolchain == 'nightly' - name: Check docs with serde run: cargo doc --features serde if: matrix.toolchain == 'nightly' - name: Execute tests (not mac) run: cargo test if: matrix.os != 'macos-latest' env: RUST_BACKTRACE: full - name: Execute tests (mac) run: cargo test -- --test-threads 1 if: matrix.os == 'macos-latest' env: RUST_BACKTRACE: full APPLE_CI: 1 # FIXME: remove this once CI mac tests are fixed - name: Execute tests (not mac, no features) run: cargo test --no-default-features if: matrix.os != 'macos-latest' env: RUST_BACKTRACE: full - name: Execute tests (mac, no features) run: cargo test --no-default-features -- --test-threads 1 if: matrix.os == 'macos-latest' env: RUST_BACKTRACE: full APPLE_CI: 1 # FIXME: remove this once CI mac tests are fixed - name: Run CPU test run: cargo test --lib -- --ignored --test-threads 1 env: RUST_BACKTRACE: full - name: Check serde feature run: | cargo check --features serde cargo test --features serde --doc env: RUST_BACKTRACE: full c_interface: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: make unknown-targets: runs-on: ubuntu-latest strategy: matrix: toolchain: - "1.59.0" # minimum supported rust version - stable - nightly steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.toolchain }} override: true components: clippy - run: cargo clippy --features unknown-ci -- -D warnings - run: cargo check --features unknown-ci - run: cargo test --features unknown-ci - run: cargo install wasm-pack - run: cd test-unknown && wasm-pack build --target web sysinfo-0.28.4/.gitignore000064400000000000000000000015361046102023000134060ustar 00000000000000 # Created by https://www.gitignore.io/api/osx,rust ### OSX ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Rust ### # Generated by Cargo # will have compiled files and executables /target/ examples/target test-unknown/target test-unknown/pkg # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk rusty-tags.vi tags **.o simplesysinfo-0.28.4/ADDING_NEW_PLATFORMS.md000064400000000000000000000002201046102023000150130ustar 00000000000000To get the `target_os` etc: ``` rustc --print cfg ``` In `src/lib.rs` add the matching based on the `cfg` data. Create `src/`. sysinfo-0.28.4/CHANGELOG.md000064400000000000000000000365011046102023000132270ustar 00000000000000# 0.28.4 * macOS: Improve CPU computation. * Strengthen a process test (needed for debian). # 0.28.3 * FreeBSD/Windows: Add missing frequency for global CPU. * macOS: Fix used memory computation. * macOS: Improve available memory computation. * Windows: Fix potential panic when getting process data. # 0.28.2 * Linux: Improve CPU usage computation. # 0.28.1 * macOS: Fix overflow when computing CPU usage. # 0.28.0 * Linux: Fix name and CPU usage for processes tasks. * unix: Keep all users, even "not real" accounts. * Windows: Use SID for Users ID. * Fix C API. * Disable default cdylib compilation. * Add `serde` feature to enable serialization. * Linux: Handle `Idle` state in `ProcessStatus`. * Linux: Add brand and name of ARM CPUs. # 0.27.8 * macOS: Fix overflow when computing CPU usage. # 0.27.7 * macOS: Fix process CPU usage computation * Linux: Improve ARM CPU `brand` and `name` information. * Windows: Fix resource leak. * Documentation improvements. # 0.27.6 * Make `MacAddr` public. # 0.27.5 * Linux: Improve compatibility with upcoming `libc` changes for musl targets. # 0.27.4 * Create `SystemExt::MINIMUM_CPU_UPDATE_INTERVAL` constant. * Fix consecutive processes updates CPU usage computation. # 0.27.3 * macOS: Fix free/available memory computation. * Fix processes_by_name* lifetimes # 0.27.2 * Linux: Fix consecutive process CPU usage updates. * Linux: Ignore NFS disks. # 0.27.1 * Unix systems: Fix network address segfault issue. # 0.27.0 * Add `NetworkExt::mac_address` method and `MacAddr` type. * Linux: Fix truncated environment retrieval. * Implement `TryFrom` and `FromStr` for `Gid` and `Uid`. * Implement `TryFrom` for `Pid`. * Fix documentation of `System::new` about CPU list not loaded by default. # 0.26.9 * (backport) Linux: Improve compatibility with upcoming `libc` changes for musl targets. # 0.26.8 * Add `ProcessExt::session_id` method. * Linux: Ignore NFS disks. # 0.26.7 * Apple: Greatly improve disk retrieval (I recommend reading the pull request first comment for more information here: ). * Remove build script. # 0.26.6 * Add `Process::wait`. * Add "Good pratice" entry into the crate level documentation and in the README. * Linux: More precise used memory computation. # 0.26.5 * Windows: Fix disk information retrieval. * Linux: Improve `Process` document. * Linux: Fix a compilation error if the `apple-sandbox` feature is enabled. * Internal code improvements. # 0.26.4 * Add `SystemExt::distribution_id` method. * Update `ntapi` version to `0.4`. * Update minimum supported Rust version (MSRV) to `1.59` for `ntapi` 0.4. # 0.26.3 * Update minimum supported Rust version (MSRV) to `1.56` to follow `once_cell` minor update. # 0.26.2 * Linux: Fix process information retrieval. * Linux: Get more components temperature. * Linux: Fix disk name retrieval (which in turn fixed disk type retrieval). # 0.26.1 * macOS M1: Fix segmentation fault crash. # 0.26.0 * Switch memory unit from kilobytes to bytes. * Windows: Fix Windows version display on Windows 11. # 0.25.3 * Add macOS M1 CI checks. * macOS M1: Add temperature support. * macOS: Fix leak in disk retrieval. # 0.25.2 * Windows: Fix `Process::exe` information retrieval. * All supported platforms: Correctly handle a PID owner change (#809). # 0.25.1 * Linux: Fix potential problem on `ProcessExt::exe` in case `/proc//exe` cannot be read. * Add `SystemExt::sort_disks_by`. # 0.25.0 * Linux: CPU frequency is now retrieved on-demand as expected when `CpuRefreshKind::frequency` is `true`. * `System::refresh_cpu` behaviour changed: it only computes CPU usage and doesn't retrieve CPU frequency. # 0.24.7 * Windows: Fix boot time computation. * macOS: Fix available memory computation. * Some documentation fixes. # 0.24.6 * macOS: Don't compute CPU usage when elapsed time is 0. * macOS: Fix memory leak when retrieving disks. * C interface: Fix `char` cast when platform is using unsigned `char`s. # 0.24.5 * Implement `Hash` trait on `Uid` and `Gid` types. * Remove dependency `once_cell` for targets other than `linux`, `android` and `windows`. # 0.24.4 * Windows: Fix `System::refresh_process` when required higher priviledges. # 0.24.3 * macOS: Fix `System::refresh_processes` badly handling updates. * FreeBSD: Improve performance of `System::refresh_processes`. # 0.24.2 * Windows: Fix CPU usage computation. * Windows: Enable extra feature on `winapi`. * macOS: Fix executable path retrieval. # 0.24.1 * Use `saturating_*` function for mathematical operations to prevent overflows/underflows. # 0.24.0 * Rename `Processor` into `Cpu` and `ProcessorExt` into `CpuExt`. * Retrieve information about a process' owner. * Add `SystemExt::get_user_by_id`. * Add `ProcessExt::user_id`. * Add `ProcessExt::group_id`. * Add `user`-related methods to `ProcessRefreshKind`. * Linux: Improve performance when creating new `Process` by improving retrieval of user ID and group ID. # 0.23.14 * Linux: Fix processes' virtual memory computation. # 0.23.13 * macOS/FreeBSD: Fix `System::refresh_process` and `System::refresh_process_specifics` returned value. * Linux: Small performance improvement when updating process list. # 0.23.12 * Linux: Improve `System::refresh_cpu` performance. * Fix clippy lints. # 0.23.11 * Add FreeBSD to the "supported OS" list * Remove useless benchmark results # 0.23.10 * Improve documentation of `SystemExt::refresh_cpu`. # 0.23.9 * macOS: Fix disk retrieval # 0.23.8 * Windows: Fix underflow for `Process` run_time computation # 0.23.7 * macOS: Ignore non-root drive partitions # 0.23.6 * Windows: Fix process name retrieval * Windows: Unify internal process creation methods * FreeBSD: Simplify code for process update # 0.23.5 * Windows: Fix a bug which prevent all disks to be listed. # 0.23.4 * Linux (raspberry): Fix physical core count. # 0.23.3 * Impl `From` for Pid inner type. * Code cleanup. # 0.23.2 * Fix unsafe "correctness". * Correctly handle `MaybeUninit::assume_init`. # 0.23.1 * Implement `Into` trait on `Pid` * Add `#[repr(transparent)]` on `Pid` * Clean up `refresh_process` and `refresh_processes`: only `refresh_processes` removes non-existing processes. # 0.23.0 * Linux: Fix process uptime. * Rename `process_by_name` into `processes_by_name`. * Rename `process_by_name_exact` into `processes_by_name_exact`. * Change returned type of `process_by_name` and of `process_by_name_exact` into an iterator. * Improved `Signal` documentation. * Turned `Pid` type alias into a newtype. # 0.22.5 * Linux: Improve documentation on how processes queries are handled. * FreeBSD: Fix type error for 32-bit (on i386, armv6, armv7, powerpc). * Improve Pid type documentation. * Add `SystemExt::process_by_exact_name` method. * Add `SUPPORTED_SIGNALS` constant on `SystemExt`. * Fix common type aliases. * Implement `Display` for `Signal`. # 0.22.4 * Windows: Correctly handle COM initialization/deinitialization. * Linux: Fix panic when changing the limit of open files. # 0.22.3 * FreeBSD: Take ZFS ARC value into account when computing used system memory. * Add some missing `#[must_use]`. # 0.22.2 * FreeBSD: Improve memory information retrieval. # 0.22.1 * Remove forgotten debug. # 0.22.0 * Add FreeBSD support. * Create `SystemExt::refresh_processes_specifics` and `SystemExt::refresh_process_specifics` methods. * Update `ProcessExt::kill` API and add `ProcessExt::kill_with`. * Add `ProcessExt::run_time`. # 0.21.2 * Unsupported targets: Fix build. * Linux: Exclude rootfs disk type as well. * Windows: Performance improvement by lazily creating queries. # 0.21.1 * Linux: Process CPU usage cannot go above maximum value (number of CPUs * 100) anymore. * Linux: Improve processors update. * Linux: Improve processes CPU usage computation speed. # 0.21.0 * Linux: Fix processes CPU computation (if `System::refresh_cpu` wasn't used). * Fix build for unsupported targets. * Make `ProcessStatus` enum unique for all platforms. * Unify documentation over all platforms. # 0.20.5 * Linux: Prevented overflow in disk size computation (bug in `davfs2`). * Fixed clippy lints # 0.20.4 * Update libc version, allowing to remove a lot of FFI bindings. # 0.20.3 * Windows: Reworked process information retrieval * Windows: Fixed issue on `c_void` size. * Improved documentation of `ProcessExt::environ`. # 0.20.2 * Windows: Added support for getting process' current working directory * Windows: Added support for getting process' environment variables * Removed more FFI bindings and replaced them with libc's. # 0.20.1 * macOS: Added better support for sandboxing. * macOS: Added support for getting process current working directory. * Added more explanations in crate level code example. * Updated rayon version to 1.5.1. # 0.20.0 * macOS: Improved code readability. * Windows: Prevented the `taskkill.exe` console window from appearing when using `kill`. * Fixed benchmarks compilation issue. * Upgraded minimum supported Rust version to 1.54. * Removed doc-comment dependency. * Merged README and crate documentation. # 0.19.2 * Windows: Fixed swap memory information computation. # 0.19.1 * Windows: Got swap memory information. * Linux: Fixed memory information gathering (bad parsing of `/proc/meminfo`). # 0.19.0 * Renamed functions/methods to follow [Rust API guidelines on naming](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter). * Linux: Set processes' executable path from command line if not found. * Linux: Added extra information about `ProcessExt::name()`. * macOS: Removed unneeded (re)import of CoreFoundation library at compile-time. * Reworked `DiskType` enum: there is no more `Removable` variant, it's now set into the `Disk` struct. `DiskExt::is_removable` was added. * Linux: Added support for removable disks. * Linux: Ensured there's a value in `global_processor` frequency. * Fixed tests to make them a bit less strict (which was problematic when run on VMs). * Linux: Fixed CPU usage subtraction overflow. # 0.18.2 * macOS: Brand and vendor ID information were reversed. * macOS: On Apple M1 processors, the vendor ID is empty, so instead we return "Apple". * Added tests to ensure that the processors are always set after `System::new()`. # 0.18.1 * Added `SystemExt::IS_SUPPORTED` constant to allow to easily query if a system is supported or not. * Used `SystemExt::IS_SUPPORTED` to fix tests on non-supported platforms and simplify others. # 0.18.0 * Improved documentation to make it more clear how to use the different information. * Turned the `Signal` enum into a full rust one by removing the `#[repr(C)]` attribute on it. Each platform now implements its own conversion. * Removed `Signal::Stklft` which wasn't used on any supported system. * Linux: Added support for paravirtualized disks. # 0.17.5 * Improved network code: network interfaces were handled a bit differently depending on the platform, it is now unified. # 0.17.4 * Linux: fixed invalid network interface cleanup when an interface was removed from the system in `refresh_networks_list`. * Added freebsd to CI runs. * Added `cargo test` command for freebsd on CI. * freebsd: Fixed build. # 0.17.3 * Removed manual FFI bindings in both Apple and Windows targets. * Fixed C-interface compilation. * Added information on how to add new platform. # 0.17.2 * Linux: fixed `System::refresh_process` return value. # 0.17.1 * Windows: fixed process CPU usage computation. * Linux: improved CPU usage values on first query by returning 0: it now waits the second cycle before computing it to avoid abherent values. * Linux: fixed process name retrieval by using `stat` information instead. * Apple: only list local users. # 0.17.0 * Linux: fixed OS version retrieval by adding a fallback to `/etc/lsb-release`. * iOS: fixed warnings. * Renamed `ProcessStatus::to_string` method to `as_str`. * macOS: fixed CPU usage computation. # 0.16.5 * Windows: Removed trailing NUL bytes in hostname. * Added user ID and group ID. # 0.16.4 * macOS: Removed trailing NUL bytes in various values returned by the `sysctl` calls. # 0.16.3 * Updated minimum libc version to 0.2.86. # 0.16.2 * Fixed network values computation: replaced the simple arithmetic with `saturating_sub` and `saturating_add`. * Converted values read in `/proc/meminfo` from KiB to KB (because contrary to what is said in the manual, they are in KiB, not in KB). * macOS: Rewrote `get_disks` function to remove the Objective-C dependency. * Added `SystemExt::get_long_os_version`. * Linux: Fixed sequences for disks. * Linux: Allowed `/run/media` as a mount path. * Windows: Fixed disk size computation. * Linux: Fixed virtual memory size computation. # 0.16.1 * Added support for Android. * Added flag to remove APIs prohibited in Apple store. # 0.16.0 * Windows: show removeable drives on Windows. * Switched to Rust 2018 edition. * Split `SystemExt::get_version` into `SystemExt::get_kernel_version` and `SystemExt::get_os_version`. * Windows: added support for `get_kernel_version` and `get_os_version`. * Changed return type of `SystemExt::get_physical_core_count` from `usize` to `Option`. * Added `SystemExt::get_physical_core_numbers`. # 0.15.9 * iOS: Fixed build. * Fixed cross-compilation. # 0.15.8 * Apple: fixed Objective-C library imports. # 0.15.7 * Added `SystemExt::get_host_name`. # 0.15.6 * Upgraded `cfg-if` dependency version to `1.0`. # 0.15.5 * Added `SystemExt::get_name` and `SystemExt::get_version`. * Added `multithread` feature, making the `rayon` dependency optional. # 0.15.4 * Apple: gig source code cleanup. * Apple: improved disk handling. * Removed manual FFI code and used libc's instead. # 0.15.3 * Prevented CPU value to be NaN. # 0.15.2 * macOS: fixed disk space computation. # 0.15.1 * Improved documentation. * Extended example. # 0.15.0 * Added `SystemExt::get_available_memory`. # 0.14.15 * Linux: improved task source code. # 0.14.14 * macOS: renamed "CPU" into "CPU Die". * macOS: added "CPU proximity" information. # 0.14.13 * Linux: improved process name retrieval. # 0.14.12 * Linux: fixed infinite recursion when gathering disk information. # 0.14.11 * Added iOS support. # 0.14.10 * Simplified `DiskType` handling by removing `From` implementation. * Linux: fixed SSD/HDD detection. # 0.14.9 * Linux: fixed CPU usage computation. * Windows: fixed load average constants. # 0.14.8 * Linux: fixed network information retrieval by replacing `usize` with `u64` because it was too small on 32 bits systems. * Linux: get each core frequency. # 0.14.7 * Raspberry Pi: fixed temperature retrieval. # 0.14.6 * Linux: fixed infinite recursion when getting disk. # 0.14.5 * Strengthened cfg checks: use "linux" and "android" instead of "unix". # 0.14.4 * Linux: fixed memory usage computation. # 0.14.3 * Linux: fixed memory usage computation. # 0.14.2 * Windows: fixed CPU usage computation overflow. * macOS: fixed CPU usage computation overflow. * Windows: retrieved command line. # 0.14.1 * Removed empty disks. # 0.14.0 * Converted KiB to KB. # 0.13.4 * Code improvements. # 0.13.3 * Linux: fixed some issues on disks retrieval. * Linux: fixed out-of-bound access in `boot_time`. * Added benchmark on `Disk::refresh`. sysinfo-0.28.4/Cargo.lock0000644000000161360000000000100106030ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "core-foundation-sys" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "crossbeam-channel" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if", "lazy_static", ] [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "fastrand" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[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.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "ntapi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" dependencies = [ "winapi", ] [[package]] name = "num_cpus" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "rayon" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "redox_syscall" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" [[package]] name = "serde_json" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "sysinfo" version = "0.28.4" dependencies = [ "cfg-if", "core-foundation-sys", "libc", "ntapi", "once_cell", "rayon", "serde", "serde_json", "tempfile", "winapi", ] [[package]] name = "tempfile" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", "fastrand", "libc", "redox_syscall", "remove_dir_all", "winapi", ] [[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" sysinfo-0.28.4/Cargo.toml0000644000000046500000000000100106240ustar # 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" rust-version = "1.59" name = "sysinfo" version = "0.28.4" authors = ["Guillaume Gomez "] exclude = ["/test-unknown"] description = "Library to get system information such as processes, CPUs, disks, components and networks" readme = "README.md" categories = [ "filesystem", "os", "api-bindings", ] license = "MIT" repository = "https://github.com/GuillaumeGomez/sysinfo" [package.metadata.docs.rs] features = ["serde"] [lib] name = "sysinfo" [dependencies.cfg-if] version = "1.0" [dependencies.rayon] version = "^1.5.1" optional = true [dependencies.serde] version = "^1.0.152" optional = true [dev-dependencies.serde_json] version = "1.0" [features] apple-app-store = ["apple-sandbox"] apple-sandbox = [] c-interface = [] debug = ["libc/extra_traits"] default = ["multithread"] multithread = ["rayon"] unknown-ci = [] [target."cfg(all(target_os = \"linux\", not(target_os = \"android\")))".dev-dependencies.tempfile] version = "3.2" [target."cfg(any(target_os = \"macos\", target_os = \"ios\"))".dependencies.core-foundation-sys] version = "0.8" [target."cfg(any(windows, target_os = \"linux\", target_os = \"android\"))".dependencies.once_cell] version = "1.0" [target."cfg(not(any(target_os = \"unknown\", target_arch = \"wasm32\")))".dependencies.libc] version = "^0.2.112" [target."cfg(windows)".dependencies.ntapi] version = "0.4" [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = [ "errhandlingapi", "fileapi", "handleapi", "heapapi", "ifdef", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "winbase", "winerror", "winioctl", "winnt", "oleauto", "wbemcli", "rpcdce", "combaseapi", "objidl", "powerbase", "netioapi", "lmcons", "lmaccess", "lmapibuf", "memoryapi", "ntlsa", "securitybaseapi", "shellapi", "std", "iphlpapi", "winsock2", "sddl", ] sysinfo-0.28.4/Cargo.toml.orig000064400000000000000000000037171046102023000143100ustar 00000000000000[package] name = "sysinfo" version = "0.28.4" authors = ["Guillaume Gomez "] description = "Library to get system information such as processes, CPUs, disks, components and networks" repository = "https://github.com/GuillaumeGomez/sysinfo" license = "MIT" readme = "README.md" rust-version = "1.59" exclude = ["/test-unknown"] categories = ["filesystem", "os", "api-bindings"] edition = "2018" [lib] name = "sysinfo" [features] default = ["multithread"] apple-sandbox = [] apple-app-store = ["apple-sandbox"] c-interface = [] multithread = ["rayon"] debug = ["libc/extra_traits"] # This feature is used on CI to emulate unknown/unsupported target. unknown-ci = [] [package.metadata.docs.rs] features = ["serde"] [dependencies] cfg-if = "1.0" rayon = { version = "^1.5.1", optional = true } serde = { version = "^1.0.152", optional = true } [target.'cfg(any(windows, target_os = "linux", target_os = "android"))'.dependencies] once_cell = "1.0" [target.'cfg(windows)'.dependencies] # FIXME: remove std feature once https://github.com/retep998/winapi-rs/pull/1003 has been merged. winapi = { version = "0.3.9", features = [ "errhandlingapi", "fileapi", "handleapi", "heapapi", "ifdef", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "winbase", "winerror", "winioctl", "winnt", "oleauto", "wbemcli", "rpcdce", "combaseapi", "objidl", "powerbase", "netioapi", "lmcons", "lmaccess", "lmapibuf", "memoryapi", "ntlsa", "securitybaseapi", "shellapi", "std", "iphlpapi", "winsock2", "sddl", ]} ntapi = "0.4" [target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies] libc = "^0.2.112" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] core-foundation-sys = "0.8" [target.'cfg(all(target_os = "linux", not(target_os = "android")))'.dev-dependencies] tempfile = "3.2" [dev-dependencies] serde_json = "1.0" # Used in documentation tests. sysinfo-0.28.4/LICENSE000064400000000000000000000020731046102023000124200ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Guillaume Gomez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 OR COPYRIGHT HOLDERS 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. sysinfo-0.28.4/Makefile000064400000000000000000000015111046102023000130470ustar 00000000000000# # Sysinfo # # Copyright (c) 2017 Guillaume Gomez # # # Please note that this Makefile only generates the c example. # IDIR = ./src CC = gcc CFLAGS = -I$(IDIR) ODIR = examples/ LDIR = ./target/debug/ LDIR-RELEASE = ./target/release/ LIBS = -lsysinfo -lpthread _DEPS = sysinfo.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) _OBJ = simple.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) simple: $(OBJ) @echo "Compiling in debug mode" cargo rustc --features=c-interface --crate-type cdylib gcc -o $@ $^ $(CFLAGS) -L$(LDIR) $(LIBS) release: $(OBJ) @echo "Compiling in release mode" cargo rustc --features=c-interface --release --crate-type cdylib gcc -o simple $^ $(CFLAGS) -L$(LDIR-RELEASE) $(LIBS) $(ODIR)/%.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) .PHONY: simple clean: @echo "Cleaning mess" rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ sysinfo-0.28.4/README.md000064400000000000000000000155241046102023000126770ustar 00000000000000# sysinfo [![][img_crates]][crates] [![][img_doc]][doc] `sysinfo` is a crate used to get a system's information. ## Supported OSes It currently supports the following OSes (alphabetically sorted): * Android * FreeBSD * iOS * Linux * macOS * Raspberry Pi * Windows You can still use `sysinfo` on non-supported OSes, it'll simply do nothing and always return empty values. You can check in your program directly if an OS is supported by checking the [`SystemExt::IS_SUPPORTED`] constant. The minimum-supported version of `rustc` is **1.59**. ## Usage ⚠️ Before any attempt to read the different structs' information, you need to update them to get up-to-date information because for most of them, it works on diff between the current value and the old one. Which is why, it's much better to keep the same instance of [`System`] around instead of recreating it multiple times. You have an example into the `examples` folder. You can run it with `cargo run --example simple`. Otherwise, here is a little code sample: ```rust use sysinfo::{NetworkExt, NetworksExt, ProcessExt, System, SystemExt}; // Please note that we use "new_all" to ensure that all list of // components, network interfaces, disks and users are already // filled! let mut sys = System::new_all(); // First we update all information of our `System` struct. sys.refresh_all(); // We display all disks' information: println!("=> disks:"); for disk in sys.disks() { println!("{:?}", disk); } // Network interfaces name, data received and data transmitted: println!("=> networks:"); for (interface_name, data) in sys.networks() { println!("{}: {}/{} B", interface_name, data.received(), data.transmitted()); } // Components temperature: println!("=> components:"); for component in sys.components() { println!("{:?}", component); } println!("=> system:"); // RAM and swap information: println!("total memory: {} bytes", sys.total_memory()); println!("used memory : {} bytes", sys.used_memory()); println!("total swap : {} bytes", sys.total_swap()); println!("used swap : {} bytes", sys.used_swap()); // Display system information: println!("System name: {:?}", sys.name()); println!("System kernel version: {:?}", sys.kernel_version()); println!("System OS version: {:?}", sys.os_version()); println!("System host name: {:?}", sys.host_name()); // Number of CPUs: println!("NB CPUs: {}", sys.cpus().len()); // Display processes ID, name na disk usage: for (pid, process) in sys.processes() { println!("[{}] {} {:?}", pid, process.name(), process.disk_usage()); } ``` Please remember that to have some up-to-date information, you need to call the equivalent `refresh` method. For example, for the CPU usage: ```rust,no_run use sysinfo::{CpuExt, System, SystemExt}; let mut sys = System::new(); loop { sys.refresh_cpu(); // Refreshing CPU information. for cpu in sys.cpus() { print!("{}% ", cpu.cpu_usage()); } // Sleeping for 500 ms to let time for the system to run for long // enough to have useful information. std::thread::sleep(std::time::Duration::from_millis(500)); } ``` By default, `sysinfo` uses multiple threads. However, this can increase the memory usage on some platforms (macOS for example). The behavior can be disabled by setting `default-features = false` in `Cargo.toml` (which disables the `multithread` cargo feature). ### Good practice / Performance tips Most of the time, you don't want all information provided by `sysinfo` but just a subset of it. In this case, it's recommended to use `refresh_specifics(...)` methods with only what you need to have much better performance. Another issues frequently encountered: unless you know what you're doing, it's almost all the time better to instantiate the `System` struct once and use this one instance through your program. The reason is because a lot of information needs a previous measure to be computed (the CPU usage for example). Another example why it's much better: in case you want to list all running processes, `sysinfo` needs to allocate all memory for the `Process` struct list, which takes quite some time on the first run. If your program needs to use a lot of file descriptors, you'd better use: ```rust,no_run sysinfo::set_open_files_limit(0); ``` as `sysinfo` keeps a number of file descriptors open to have better performance on some targets when refreshing processes. ### Running on Raspberry Pi It'll be difficult to build on Raspberry Pi. A good way-around is to cross-build, then send the executable to your Raspberry Pi. First install the arm toolchain, for example on Ubuntu: ```bash > sudo apt-get install gcc-multilib-arm-linux-gnueabihf ``` Then configure cargo to use the corresponding toolchain: ```bash cat << EOF > ~/.cargo/config [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" EOF ``` Finally, cross compile: ```bash rustup target add armv7-unknown-linux-gnueabihf cargo build --target=armv7-unknown-linux-gnueabihf ``` ### Linux on Docker & Windows Subsystem for Linux (WSL) Virtual Linux systems, such as those run through Docker and Windows Subsystem for Linux (WSL), do not receive host hardware information via `/sys/class/hwmon` or `/sys/class/thermal`. As such, querying for components may return no results (or unexpected results) when using this library on virtual systems. ### Use in binaries running inside the macOS or iOS Sandbox/stores Apple has restrictions as to which APIs can be linked into binaries that are distributed through the app store. By default, `sysinfo` is not compatible with these restrictions. You can use the `apple-app-store` feature flag to disable the Apple prohibited features. This also enables the `apple-sandbox` feature. In the case of applications using the sandbox outside of the app store, the `apple-sandbox` feature can be used alone to avoid causing policy violations at runtime. ### How it works I wrote a blog post you can find [here][sysinfo-blog] which explains how `sysinfo` extracts information on the different systems. [sysinfo-blog]: https://blog.guillaume-gomez.fr/articles/2021-09-06+sysinfo%3A+how+to+extract+systems%27+information ### C interface It's possible to use this crate directly from C. Take a look at the `Makefile` and at the `examples/simple.c` file. To build the C example, just run: ```bash > make > ./simple # If needed: > LD_LIBRARY_PATH=target/release/ ./simple ``` ### Benchmarks You can run the benchmarks locally with rust **nightly** by doing: ```bash > cargo bench ``` ## Donations If you appreciate my work and want to support me, you can do it with [github sponsors](https://github.com/sponsors/GuillaumeGomez) or with [patreon](https://www.patreon.com/GuillaumeGomez). [img_crates]: https://img.shields.io/crates/v/sysinfo.svg [img_doc]: https://img.shields.io/badge/rust-documentation-blue.svg [crates]: https://crates.io/crates/sysinfo [doc]: https://docs.rs/sysinfo/ sysinfo-0.28.4/benches/basic.rs000064400000000000000000000060601046102023000144510ustar 00000000000000#![feature(test)] extern crate test; use sysinfo::get_current_pid; use sysinfo::{DiskExt, SystemExt}; #[bench] fn bench_new(b: &mut test::Bencher) { b.iter(|| { sysinfo::System::new(); }); } #[bench] fn bench_new_all(b: &mut test::Bencher) { b.iter(|| { sysinfo::System::new_all(); }); } #[bench] fn bench_refresh_all(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_all(); }); } #[bench] fn bench_refresh_system(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); s.refresh_system(); b.iter(move || { s.refresh_system(); }); } #[bench] fn bench_refresh_processes(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_processes(); // to load the whole processes list a first time. b.iter(move || { s.refresh_processes(); }); } #[bench] fn bench_first_refresh_processes(b: &mut test::Bencher) { b.iter(move || { let mut s = sysinfo::System::new(); s.refresh_processes(); }); } #[bench] fn bench_refresh_process(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); s.refresh_all(); // to be sure it'll exist for at least as long as we run let pid = get_current_pid().expect("failed to get current pid"); b.iter(move || { s.refresh_process(pid); }); } #[bench] fn bench_refresh_disk(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); let disks = s.disks_mut(); let disk = &mut disks[0]; b.iter(move || { disk.refresh(); }); } #[bench] fn bench_refresh_disks(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_disks(); }); } #[bench] fn bench_refresh_disks_list(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_disks_list(); }); } #[bench] fn bench_refresh_networks(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_networks(); }); } #[bench] fn bench_refresh_networks_list(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_networks_list(); }); } #[bench] fn bench_refresh_memory(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_memory(); }); } #[bench] fn bench_refresh_cpu(b: &mut test::Bencher) { let mut s = sysinfo::System::new(); b.iter(move || { s.refresh_cpu(); }); } #[bench] fn bench_refresh_components(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_components(); }); } #[bench] fn bench_refresh_components_list(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_components_list(); }); } #[bench] fn bench_refresh_users_list(b: &mut test::Bencher) { let mut s = sysinfo::System::new_all(); b.iter(move || { s.refresh_users_list(); }); } sysinfo-0.28.4/examples/simple.c000064400000000000000000000053221046102023000146660ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #include #include #include #include #include #include "sysinfo.h" void print_process(CProcess process) { RString exe = sysinfo_process_executable_path(process); printf("process[%d]: parent: %d,\n" " cpu_usage: %f,\n" " memory: %ld,\n" " virtual memory: %ld,\n" " executable path: '%s'\n", sysinfo_process_pid(process), sysinfo_process_parent_pid(process), sysinfo_process_cpu_usage(process), sysinfo_process_memory(process), sysinfo_process_virtual_memory(process), exe); sysinfo_rstring_free(exe); } void check_tasks(CSystem system) { #ifdef __linux__ bool task_loop(pid_t pid, CProcess process, void *data) { (void)data; printf(" "); print_process(process); return true; } void *sleeping_func(void *data) { sleep(3); return data; } pthread_t thread; pthread_create(&thread, NULL, sleeping_func, NULL); sysinfo_refresh_system(system); CProcess process = sysinfo_process_by_pid(system, getpid()); printf("\n== Task(s) for current process: ==\n"); print_process(process); printf("Got %ld task(s)\n", sysinfo_process_tasks(process, task_loop, NULL)); #else (void)system; #endif } bool process_loop(pid_t pid, CProcess process, void *data) { unsigned int *i = data; print_process(process); *i += 1; return *i < 10; } int main() { CSystem system = sysinfo_init(); sysinfo_refresh_all(system); printf("total memory: %ld\n", sysinfo_total_memory(system)); printf("free memory: %ld\n", sysinfo_free_memory(system)); printf("used memory: %ld\n", sysinfo_used_memory(system)); printf("total swap: %ld\n", sysinfo_total_swap(system)); printf("free swap: %ld\n", sysinfo_free_swap(system)); printf("used swap: %ld\n", sysinfo_used_swap(system)); printf("networks received: %ld\n", sysinfo_networks_received(system)); printf("networks transmitted: %ld\n", sysinfo_networks_transmitted(system)); unsigned int len = 0, i = 0; float *procs = NULL; sysinfo_cpus_usage(system, &len, &procs); while (i < len) { printf("CPU #%d usage: %f%%\n", i, procs[i]); i += 1; } free(procs); // processes part i = 0; printf("For a total of %ld processes.\n", sysinfo_processes(system, process_loop, &i)); check_tasks(system); // we can now free the CSystem object. sysinfo_destroy(system); return 0; } sysinfo-0.28.4/examples/simple.rs000064400000000000000000000336021046102023000150720ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![crate_type = "bin"] #![allow(unused_must_use, non_upper_case_globals)] #![allow(clippy::manual_range_contains)] use std::io::{self, BufRead, Write}; use std::str::FromStr; use sysinfo::Signal::*; use sysinfo::{ CpuExt, NetworkExt, NetworksExt, Pid, ProcessExt, Signal, System, SystemExt, UserExt, }; const signals: &[Signal] = &[ Hangup, Interrupt, Quit, Illegal, Trap, Abort, Bus, FloatingPointException, Kill, User1, Segv, User2, Pipe, Alarm, Term, Child, Continue, Stop, TSTP, TTIN, TTOU, Urgent, XCPU, XFSZ, VirtualAlarm, Profiling, Winch, IO, Power, Sys, ]; fn print_help() { writeln!(&mut io::stdout(), "== Help menu =="); writeln!(&mut io::stdout(), "help : show this menu"); writeln!( &mut io::stdout(), "signals : show the available signals" ); writeln!( &mut io::stdout(), "refresh : reloads all processes' information" ); writeln!( &mut io::stdout(), "refresh [pid] : reloads corresponding process' information" ); writeln!( &mut io::stdout(), "refresh_disks : reloads only disks' information" ); writeln!( &mut io::stdout(), "refresh_users : reloads only users' information" ); writeln!( &mut io::stdout(), "show [pid | name] : show information of the given process \ corresponding to [pid | name]" ); writeln!( &mut io::stdout(), "kill [pid] [signal]: send [signal] to the process with this \ [pid]. 0 < [signal] < 32" ); writeln!( &mut io::stdout(), "cpus : Displays CPUs state" ); writeln!( &mut io::stdout(), "memory : Displays memory state" ); writeln!( &mut io::stdout(), "temperature : Displays components' temperature" ); writeln!( &mut io::stdout(), "disks : Displays disks' information" ); writeln!( &mut io::stdout(), "network : Displays network' information" ); writeln!( &mut io::stdout(), "all : Displays all process name and pid" ); writeln!( &mut io::stdout(), "uptime : Displays system uptime" ); writeln!( &mut io::stdout(), "boot_time : Displays system boot time" ); writeln!( &mut io::stdout(), "vendor_id : Displays CPU vendor id" ); writeln!(&mut io::stdout(), "brand : Displays CPU brand"); writeln!( &mut io::stdout(), "load_avg : Displays system load average" ); writeln!( &mut io::stdout(), "frequency : Displays CPU frequency" ); writeln!(&mut io::stdout(), "users : Displays all users"); writeln!( &mut io::stdout(), "system : Displays system information (such as name, version and hostname)" ); writeln!( &mut io::stdout(), "pid : Display this example's PID" ); writeln!(&mut io::stdout(), "quit : Exit the program"); } fn interpret_input(input: &str, sys: &mut System) -> bool { match input.trim() { "help" => print_help(), "refresh_disks" => { writeln!(&mut io::stdout(), "Refreshing disk list..."); sys.refresh_disks_list(); writeln!(&mut io::stdout(), "Done."); } "refresh_users" => { writeln!(&mut io::stdout(), "Refreshing user list..."); sys.refresh_users_list(); writeln!(&mut io::stdout(), "Done."); } "signals" => { let mut nb = 1i32; for sig in signals { writeln!(&mut io::stdout(), "{nb:2}:{sig:?}"); nb += 1; } } "cpus" => { // Note: you should refresh a few times before using this, so that usage statistics // can be ascertained writeln!( &mut io::stdout(), "number of physical cores: {}", sys.physical_core_count() .map(|c| c.to_string()) .unwrap_or_else(|| "Unknown".to_owned()), ); writeln!( &mut io::stdout(), "total process usage: {}%", sys.global_cpu_info().cpu_usage() ); for proc_ in sys.cpus() { writeln!(&mut io::stdout(), "{proc_:?}"); } } "memory" => { writeln!( &mut io::stdout(), "total memory: {} KB", sys.total_memory() / 1_000 ); writeln!( &mut io::stdout(), "used memory : {} KB", sys.used_memory() / 1_000 ); writeln!( &mut io::stdout(), "total swap : {} KB", sys.total_swap() / 1_000 ); writeln!( &mut io::stdout(), "used swap : {} KB", sys.used_swap() / 1_000 ); } "quit" | "exit" => return true, "all" => { for (pid, proc_) in sys.processes() { writeln!( &mut io::stdout(), "{}:{} status={:?}", pid, proc_.name(), proc_.status() ); } } "frequency" => { writeln!( &mut io::stdout(), "{} MHz", sys.global_cpu_info().frequency() ); } "vendor_id" => { writeln!( &mut io::stdout(), "vendor ID: {}", sys.cpus()[0].vendor_id() ); } "brand" => { writeln!(&mut io::stdout(), "brand: {}", sys.cpus()[0].brand()); } "load_avg" => { let load_avg = sys.load_average(); writeln!(&mut io::stdout(), "one minute : {}%", load_avg.one); writeln!(&mut io::stdout(), "five minutes : {}%", load_avg.five); writeln!(&mut io::stdout(), "fifteen minutes: {}%", load_avg.fifteen); } e if e.starts_with("show ") => { let tmp: Vec<&str> = e.split(' ').collect(); if tmp.len() != 2 { writeln!( &mut io::stdout(), "show command takes a pid or a name in parameter!" ); writeln!(&mut io::stdout(), "example: show 1254"); } else if let Ok(pid) = Pid::from_str(tmp[1]) { match sys.process(pid) { Some(p) => writeln!(&mut io::stdout(), "{:?}", *p), None => writeln!(&mut io::stdout(), "pid \"{pid:?}\" not found"), }; } else { let proc_name = tmp[1]; for proc_ in sys.processes_by_name(proc_name) { writeln!(&mut io::stdout(), "==== {} ====", proc_.name()); writeln!(&mut io::stdout(), "{proc_:?}"); } } } "temperature" => { for component in sys.components() { writeln!(&mut io::stdout(), "{component:?}"); } } "network" => { for (interface_name, data) in sys.networks().iter() { writeln!( &mut io::stdout(), "{}:\n ether {}\n input data (new / total): {} / {} B\n output data (new / total): {} / {} B", interface_name, data.mac_address(), data.received(), data.total_received(), data.transmitted(), data.total_transmitted(), ); } } "show" => { writeln!( &mut io::stdout(), "'show' command expects a pid number or a process name" ); } e if e.starts_with("kill ") => { let tmp: Vec<&str> = e.split(' ').collect(); if tmp.len() != 3 { writeln!( &mut io::stdout(), "kill command takes the pid and a signal number in parameter!" ); writeln!(&mut io::stdout(), "example: kill 1254 9"); } else { let pid = Pid::from_str(tmp[1]).unwrap(); let signal = i32::from_str(tmp[2]).unwrap(); if signal < 1 || signal > 31 { writeln!( &mut io::stdout(), "Signal must be between 0 and 32 ! See the signals list with the \ signals command" ); } else { match sys.process(pid) { Some(p) => { if let Some(res) = p.kill_with(*signals.get(signal as usize - 1).unwrap()) { writeln!(&mut io::stdout(), "kill: {res}"); } else { writeln!( &mut io::stdout(), "kill: signal not supported on this platform" ); } } None => { writeln!(&mut io::stdout(), "pid not found"); } }; } } } "disks" => { for disk in sys.disks() { writeln!(&mut io::stdout(), "{disk:?}"); } } "users" => { for user in sys.users() { writeln!(&mut io::stdout(), "{:?}", user.name()); } } "boot_time" => { writeln!(&mut io::stdout(), "{} seconds", sys.boot_time()); } "uptime" => { let up = sys.uptime(); let mut uptime = sys.uptime(); let days = uptime / 86400; uptime -= days * 86400; let hours = uptime / 3600; uptime -= hours * 3600; let minutes = uptime / 60; writeln!( &mut io::stdout(), "{days} days {hours} hours {minutes} minutes ({up} seconds in total)", ); } x if x.starts_with("refresh") => { if x == "refresh" { writeln!(&mut io::stdout(), "Getting processes' information..."); sys.refresh_all(); writeln!(&mut io::stdout(), "Done."); } else if x.starts_with("refresh ") { writeln!(&mut io::stdout(), "Getting process' information..."); if let Some(pid) = x .split(' ') .filter_map(|pid| pid.parse().ok()) .take(1) .next() { if sys.refresh_process(pid) { writeln!(&mut io::stdout(), "Process `{pid}` updated successfully"); } else { writeln!(&mut io::stdout(), "Process `{pid}` couldn't be updated..."); } } else { writeln!(&mut io::stdout(), "Invalid [pid] received..."); } } else { writeln!( &mut io::stdout(), "\"{x}\": Unknown command. Enter 'help' if you want to get the commands' \ list.", ); } } "pid" => { writeln!( &mut io::stdout(), "PID: {}", sysinfo::get_current_pid().expect("failed to get PID") ); } "system" => { writeln!( &mut io::stdout(), "System name: {}\n\ System kernel version: {}\n\ System OS version: {}\n\ System OS (long) version: {}\n\ System host name: {}", sys.name().unwrap_or_else(|| "".to_owned()), sys.kernel_version() .unwrap_or_else(|| "".to_owned()), sys.os_version().unwrap_or_else(|| "".to_owned()), sys.long_os_version() .unwrap_or_else(|| "".to_owned()), sys.host_name().unwrap_or_else(|| "".to_owned()), ); } e => { writeln!( &mut io::stdout(), "\"{e}\": Unknown command. Enter 'help' if you want to get the commands' \ list.", ); } } false } fn main() { println!("Getting processes' information..."); let mut t = System::new_all(); println!("Done."); let t_stin = io::stdin(); let mut stin = t_stin.lock(); let mut done = false; println!("To get the commands' list, enter 'help'."); while !done { let mut input = String::new(); write!(&mut io::stdout(), "> "); io::stdout().flush(); stin.read_line(&mut input); if input.is_empty() { // The string is empty, meaning there is no '\n', meaning // that the user used CTRL+D so we can just quit! println!("\nLeaving, bye!"); break; } if (&input as &str).ends_with('\n') { input.pop(); } done = interpret_input(input.as_ref(), &mut t); } } sysinfo-0.28.4/md_doc/component.md000064400000000000000000000010431046102023000151600ustar 00000000000000Struct containing a component information (temperature and name for the moment). ## Linux More information can be found at [kernel.org][k]. Note: these may not be present on virtual Linux systems, such as **Docker** or **Windows Subsystem for Linux**. These hosts do not expose this information and therefore `Component` elements may be missing or not as expected. [k]: https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface ## Windows Please note that on Windows, you need to have Administrator priviledges to get this information. sysinfo-0.28.4/md_doc/cpu.md000064400000000000000000000000501046102023000137420ustar 00000000000000Struct containing information of a CPU. sysinfo-0.28.4/md_doc/disk.md000064400000000000000000000000461046102023000141120ustar 00000000000000Struct containing a disk information. sysinfo-0.28.4/md_doc/network_data.md000064400000000000000000000000501046102023000156350ustar 00000000000000Contains network interface information. sysinfo-0.28.4/md_doc/networks.md000064400000000000000000000002141046102023000150310ustar 00000000000000Networks interfaces. ```no_run use sysinfo::{NetworksExt, System, SystemExt}; let s = System::new_all(); let networks = s.networks(); ``` sysinfo-0.28.4/md_doc/pid.md000064400000000000000000000011641046102023000137360ustar 00000000000000Process ID. Can be used as an integer type by simple casting. For example: ``` use sysinfo::{PidExt, Pid}; // 0's type will be different depending on the platform! let p = Pid::from(0); // For something more "general": let p = Pid::from_u32(0); let i: u32 = p.as_u32(); ``` On glibc systems this is a glibc [`pid_t`](https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html). On Windows systems this is a [`usize` and represents a windows process identifier](https://docs.microsoft.com/en-us/windows/win32/procthread/process-handles-and-identifiers). On unsupported systems, this is also a `usize`. sysinfo-0.28.4/md_doc/process.md000064400000000000000000000003451046102023000146400ustar 00000000000000Struct containing information of a process. ## iOS This information cannot be retrieved on iOS due to sandboxing. ## Apple app store If you are building a macOS Apple app store, it won't be able to retrieve this information. sysinfo-0.28.4/md_doc/serde.md000064400000000000000000000005051046102023000142620ustar 00000000000000 With the `serde` feature enabled, you can then serialize `sysinfo` types. Let's see an example with `serde_json`: ``` use sysinfo::{System, SystemExt}; let mut sys = System::new_all(); // First we update all information of our `System` struct. sys.refresh_all(); println!("{}", serde_json::to_string(&sys).unwrap()); ``` sysinfo-0.28.4/md_doc/sid.md000064400000000000000000000000521046102023000137340ustar 00000000000000Opaque type encapsulating a Windows SID. sysinfo-0.28.4/md_doc/system.md000064400000000000000000000000511046102023000145000ustar 00000000000000Structs containing system's information. sysinfo-0.28.4/src/apple/app_store/component.rs000064400000000000000000000007131046102023000176460ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::ComponentExt; #[doc = include_str!("../../../md_doc/component.md")] pub struct Component {} impl ComponentExt for Component { fn temperature(&self) -> f32 { 0.0 } fn max(&self) -> f32 { 0.0 } fn critical(&self) -> Option { None } fn label(&self) -> &str { "" } fn refresh(&mut self) {} } sysinfo-0.28.4/src/apple/app_store/mod.rs000064400000000000000000000001651046102023000164240ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod process; sysinfo-0.28.4/src/apple/app_store/process.rs000064400000000000000000000026111046102023000173210ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::path::Path; use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessStatus, Signal, Uid}; #[doc = include_str!("../../../md_doc/process.md")] pub struct Process; impl ProcessExt for Process { fn kill_with(&self, _signal: Signal) -> Option { None } fn name(&self) -> &str { "" } fn cmd(&self) -> &[String] { &[] } fn exe(&self) -> &Path { Path::new("/") } fn pid(&self) -> Pid { Pid(0) } fn environ(&self) -> &[String] { &[] } fn cwd(&self) -> &Path { Path::new("/") } fn root(&self) -> &Path { Path::new("/") } fn memory(&self) -> u64 { 0 } fn virtual_memory(&self) -> u64 { 0 } fn parent(&self) -> Option { None } fn status(&self) -> ProcessStatus { ProcessStatus::Unknown(0) } fn start_time(&self) -> u64 { 0 } fn run_time(&self) -> u64 { 0 } fn cpu_usage(&self) -> f32 { 0.0 } fn disk_usage(&self) -> DiskUsage { DiskUsage::default() } fn user_id(&self) -> Option<&Uid> { None } fn group_id(&self) -> Option { None } fn wait(&self) {} fn session_id(&self) -> Option { None } } sysinfo-0.28.4/src/apple/component.rs000064400000000000000000000001721046102023000156510ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub use crate::sys::inner::component::*; sysinfo-0.28.4/src/apple/cpu.rs000064400000000000000000000213301046102023000144350ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::system::get_sys_value; use crate::{CpuExt, CpuRefreshKind}; use libc::{c_char, host_processor_info, mach_task_self}; use std::mem; use std::ops::Deref; use std::sync::Arc; pub(crate) struct UnsafePtr(*mut T); unsafe impl Send for UnsafePtr {} unsafe impl Sync for UnsafePtr {} impl Deref for UnsafePtr { type Target = *mut T; fn deref(&self) -> &*mut T { &self.0 } } pub(crate) struct CpuData { pub cpu_info: UnsafePtr, pub num_cpu_info: u32, } impl CpuData { pub fn new(cpu_info: *mut i32, num_cpu_info: u32) -> CpuData { CpuData { cpu_info: UnsafePtr(cpu_info), num_cpu_info, } } } impl Drop for CpuData { fn drop(&mut self) { if !self.cpu_info.0.is_null() { let prev_cpu_info_size = std::mem::size_of::() as u32 * self.num_cpu_info; unsafe { libc::vm_deallocate( mach_task_self(), self.cpu_info.0 as _, prev_cpu_info_size as _, ); } self.cpu_info.0 = std::ptr::null_mut(); } } } #[doc = include_str!("../../md_doc/cpu.md")] pub struct Cpu { name: String, cpu_usage: f32, cpu_data: Arc, frequency: u64, vendor_id: String, brand: String, } impl Cpu { pub(crate) fn new( name: String, cpu_data: Arc, frequency: u64, vendor_id: String, brand: String, ) -> Cpu { Cpu { name, cpu_usage: 0f32, cpu_data, frequency, vendor_id, brand, } } pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) { self.cpu_usage = cpu_usage; } pub(crate) fn update(&mut self, cpu_usage: f32, cpu_data: Arc) { self.cpu_usage = cpu_usage; self.cpu_data = cpu_data; } pub(crate) fn data(&self) -> Arc { Arc::clone(&self.cpu_data) } pub(crate) fn set_frequency(&mut self, frequency: u64) { self.frequency = frequency; } } impl CpuExt for Cpu { fn cpu_usage(&self) -> f32 { self.cpu_usage } fn name(&self) -> &str { &self.name } /// Returns the CPU frequency in MHz. fn frequency(&self) -> u64 { self.frequency } fn vendor_id(&self) -> &str { &self.vendor_id } fn brand(&self) -> &str { &self.brand } } pub(crate) fn get_cpu_frequency() -> u64 { let mut speed: u64 = 0; let mut len = std::mem::size_of::(); unsafe { libc::sysctlbyname( b"hw.cpufrequency\0".as_ptr() as *const c_char, &mut speed as *mut _ as _, &mut len, std::ptr::null_mut(), 0, ); speed / 1_000_000 } } #[inline] fn get_in_use(cpu_info: *mut i32, offset: isize) -> i64 { unsafe { let user = *cpu_info.offset(offset + libc::CPU_STATE_USER as isize) as i64; let system = *cpu_info.offset(offset + libc::CPU_STATE_SYSTEM as isize) as i64; let nice = *cpu_info.offset(offset + libc::CPU_STATE_NICE as isize) as i64; user.saturating_add(system).saturating_add(nice) } } #[inline] fn get_idle(cpu_info: *mut i32, offset: isize) -> i32 { unsafe { *cpu_info.offset(offset + libc::CPU_STATE_IDLE as isize) } } pub(crate) fn compute_usage_of_cpu(proc_: &Cpu, cpu_info: *mut i32, offset: isize) -> f32 { let old_cpu_info = proc_.data().cpu_info.0; let in_use; let total; // In case we are initializing cpus, there is no "old value" yet. if old_cpu_info == cpu_info { in_use = get_in_use(cpu_info, offset); total = in_use.saturating_add(get_idle(cpu_info, offset) as _); } else { in_use = get_in_use(cpu_info, offset).saturating_sub(get_in_use(old_cpu_info, offset)); total = in_use.saturating_add( get_idle(cpu_info, offset).saturating_sub(get_idle(old_cpu_info, offset)) as _, ); } in_use as f32 / total as f32 * 100. } pub(crate) fn update_cpu_usage, *mut i32) -> (f32, usize)>( port: libc::mach_port_t, global_cpu: &mut Cpu, f: F, ) { let mut num_cpu_u = 0u32; let mut cpu_info: *mut i32 = std::ptr::null_mut(); let mut num_cpu_info = 0u32; let mut total_cpu_usage = 0f32; unsafe { if host_processor_info( port, libc::PROCESSOR_CPU_LOAD_INFO, &mut num_cpu_u as *mut u32, &mut cpu_info as *mut *mut i32, &mut num_cpu_info as *mut u32, ) == libc::KERN_SUCCESS { let (total_percentage, len) = f(Arc::new(CpuData::new(cpu_info, num_cpu_info)), cpu_info); total_cpu_usage = total_percentage / len as f32; } global_cpu.set_cpu_usage(total_cpu_usage); } } pub(crate) fn init_cpus( port: libc::mach_port_t, cpus: &mut Vec, global_cpu: &mut Cpu, refresh_kind: CpuRefreshKind, ) { let mut num_cpu = 0; let mut mib = [0, 0]; let (vendor_id, brand) = get_vendor_id_and_brand(); let frequency = if refresh_kind.frequency() { get_cpu_frequency() } else { global_cpu.frequency }; unsafe { if !get_sys_value( libc::CTL_HW as _, libc::HW_NCPU as _, mem::size_of::(), &mut num_cpu as *mut _ as *mut _, &mut mib, ) { num_cpu = 1; } } update_cpu_usage(port, global_cpu, |proc_data, cpu_info| { let mut percentage = 0f32; let mut offset = 0; for i in 0..num_cpu { let mut p = Cpu::new( format!("{}", i + 1), Arc::clone(&proc_data), frequency, vendor_id.clone(), brand.clone(), ); if refresh_kind.cpu_usage() { let cpu_usage = compute_usage_of_cpu(&p, cpu_info, offset); p.set_cpu_usage(cpu_usage); percentage += p.cpu_usage(); } cpus.push(p); offset += libc::CPU_STATE_MAX as isize; } (percentage, cpus.len()) }); // We didn't set them above to avoid cloning them unnecessarily. global_cpu.brand = brand; global_cpu.vendor_id = vendor_id; global_cpu.frequency = frequency; } fn get_sysctl_str(s: &[u8]) -> String { let mut len = 0; unsafe { libc::sysctlbyname( s.as_ptr() as *const c_char, std::ptr::null_mut(), &mut len, std::ptr::null_mut(), 0, ); if len < 1 { return String::new(); } let mut buf = Vec::with_capacity(len); libc::sysctlbyname( s.as_ptr() as *const c_char, buf.as_mut_ptr() as _, &mut len, std::ptr::null_mut(), 0, ); if len > 0 { buf.set_len(len); while buf.last() == Some(&b'\0') { buf.pop(); } String::from_utf8(buf).unwrap_or_else(|_| String::new()) } else { String::new() } } } pub(crate) fn get_vendor_id_and_brand() -> (String, String) { // On apple M1, `sysctl machdep.cpu.vendor` returns "", so fallback to "Apple" if the result // is empty. let mut vendor = get_sysctl_str(b"machdep.cpu.vendor\0"); if vendor.is_empty() { vendor = "Apple".to_string(); } (vendor, get_sysctl_str(b"machdep.cpu.brand_string\0")) } #[cfg(test)] mod test { use crate::*; use std::process::Command; #[test] fn check_vendor_and_brand() { let child = Command::new("sysctl") .arg("-a") .output() .expect("Failed to start command..."); assert!(child.status.success()); let stdout = String::from_utf8(child.stdout).expect("Not valid UTF8"); let sys = System::new_with_specifics( crate::RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()), ); let cpus = sys.cpus(); assert!(!cpus.is_empty(), "no CPU found"); if let Some(line) = stdout.lines().find(|l| l.contains("machdep.cpu.vendor")) { let sysctl_value = line.split(':').nth(1).unwrap(); assert_eq!(cpus[0].vendor_id(), sysctl_value.trim()); } if let Some(line) = stdout .lines() .find(|l| l.contains("machdep.cpu.brand_string")) { let sysctl_value = line.split(':').nth(1).unwrap(); assert_eq!(cpus[0].brand(), sysctl_value.trim()); } } } sysinfo-0.28.4/src/apple/disk.rs000064400000000000000000000303521046102023000146040ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::{ ffi, utils::{self, CFReleaser}, }; use crate::{DiskExt, DiskType}; use core_foundation_sys::array::CFArrayCreate; use core_foundation_sys::base::kCFAllocatorDefault; use core_foundation_sys::dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef}; use core_foundation_sys::number::{kCFBooleanTrue, CFBooleanRef, CFNumberGetValue}; use core_foundation_sys::string::{self as cfs, CFStringRef}; use libc::c_void; use std::ffi::{CStr, OsStr, OsString}; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::ptr; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { pub(crate) type_: DiskType, pub(crate) name: OsString, pub(crate) file_system: Vec, pub(crate) mount_point: PathBuf, volume_url: RetainedCFURL, pub(crate) total_space: u64, pub(crate) available_space: u64, pub(crate) is_removable: bool, } impl DiskExt for Disk { fn type_(&self) -> DiskType { self.type_ } fn name(&self) -> &OsStr { &self.name } fn file_system(&self) -> &[u8] { &self.file_system } fn mount_point(&self) -> &Path { &self.mount_point } fn total_space(&self) -> u64 { self.total_space } fn available_space(&self) -> u64 { self.available_space } fn is_removable(&self) -> bool { self.is_removable } fn refresh(&mut self) -> bool { unsafe { if let Some(requested_properties) = build_requested_properties(&[ ffi::kCFURLVolumeAvailableCapacityKey, ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, ]) { match get_disk_properties(&self.volume_url, &requested_properties) { Some(disk_props) => { self.available_space = get_available_volume_space(&disk_props); true } None => false, } } else { sysinfo_debug!("failed to create volume key list, skipping refresh"); false } } } } pub(super) unsafe fn get_disks() -> Vec { let raw_disks = { let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT); if count < 1 { return Vec::new(); } let bufsize = count * std::mem::size_of::() as libc::c_int; let mut disks = Vec::with_capacity(count as _); let count = libc::getfsstat(disks.as_mut_ptr(), bufsize, libc::MNT_NOWAIT); if count < 1 { return Vec::new(); } disks.set_len(count as usize); disks }; // Create a list of properties about the disk that we want to fetch. let requested_properties = match build_requested_properties(&[ ffi::kCFURLVolumeIsEjectableKey, ffi::kCFURLVolumeIsRemovableKey, ffi::kCFURLVolumeIsInternalKey, ffi::kCFURLVolumeTotalCapacityKey, ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, ffi::kCFURLVolumeAvailableCapacityKey, ffi::kCFURLVolumeNameKey, ffi::kCFURLVolumeIsBrowsableKey, ffi::kCFURLVolumeIsLocalKey, ]) { Some(properties) => properties, None => { sysinfo_debug!("failed to create volume key list"); return Vec::new(); } }; let mut disks = Vec::with_capacity(raw_disks.len()); for c_disk in raw_disks { let volume_url = match CFReleaser::new( core_foundation_sys::url::CFURLCreateFromFileSystemRepresentation( kCFAllocatorDefault, c_disk.f_mntonname.as_ptr() as *const _, c_disk.f_mntonname.len() as _, false as _, ), ) { Some(url) => url, None => { sysinfo_debug!("getfsstat returned incompatible paths"); continue; } }; let prop_dict = match get_disk_properties(&volume_url, &requested_properties) { Some(props) => props, None => continue, }; // Future note: There is a difference between `kCFURLVolumeIsBrowsableKey` and the // `kCFURLEnumeratorSkipInvisibles` option of `CFURLEnumeratorOptions`. Specifically, // the first one considers the writable `Data`(`/System/Volumes/Data`) partition to be // browsable, while it is classified as "invisible" by CoreFoundation's volume emumerator. let browsable = get_bool_value( prop_dict.inner(), DictKey::Extern(ffi::kCFURLVolumeIsBrowsableKey), ) .unwrap_or_default(); // Do not return invisible "disks". Most of the time, these are APFS snapshots, hidden // system volumes, etc. Browsable is defined to be visible in the system's UI like Finder, // disk utility, system information, etc. // // To avoid seemingly duplicating many disks and creating an inaccurate view of the system's resources, // these are skipped entirely. if !browsable { continue; } let local_only = get_bool_value( prop_dict.inner(), DictKey::Extern(ffi::kCFURLVolumeIsLocalKey), ) .unwrap_or(true); // Skip any drive that is not locally attached to the system. // // This includes items like SMB mounts, and matches the other platform's behavior. if !local_only { continue; } let mount_point = PathBuf::from(OsStr::from_bytes( CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes(), )); disks.extend(new_disk(mount_point, volume_url, c_disk, &prop_dict)) } disks } type RetainedCFArray = CFReleaser; type RetainedCFDictionary = CFReleaser; type RetainedCFURL = CFReleaser; unsafe fn build_requested_properties(properties: &[CFStringRef]) -> Option { CFReleaser::new(CFArrayCreate( ptr::null_mut(), properties.as_ptr() as *const *const c_void, properties.len() as _, &core_foundation_sys::array::kCFTypeArrayCallBacks, )) } fn get_disk_properties( volume_url: &RetainedCFURL, requested_properties: &RetainedCFArray, ) -> Option { CFReleaser::new(unsafe { ffi::CFURLCopyResourcePropertiesForKeys( volume_url.inner(), requested_properties.inner(), ptr::null_mut(), ) }) } fn get_available_volume_space(disk_props: &RetainedCFDictionary) -> u64 { // We prefer `AvailableCapacityForImportantUsage` over `AvailableCapacity` because // it takes more of the system's properties into account, like the trash, system-managed caches, // etc. It generally also returns higher values too, because of the above, so it's a more accurate // representation of what the system _could_ still use. unsafe { get_int_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey), ) .filter(|bytes| *bytes != 0) .or_else(|| { get_int_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityKey), ) }) } .unwrap_or_default() as u64 } pub(super) enum DictKey { Extern(CFStringRef), #[cfg(target_os = "macos")] Defined(&'static str), } unsafe fn get_dict_value Option>( dict: CFDictionaryRef, key: DictKey, callback: F, ) -> Option { #[cfg(target_os = "macos")] let _defined; let key = match key { DictKey::Extern(val) => val, #[cfg(target_os = "macos")] DictKey::Defined(val) => { _defined = CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy( kCFAllocatorDefault, val.as_ptr(), val.len() as _, cfs::kCFStringEncodingUTF8, false as _, core_foundation_sys::base::kCFAllocatorNull, ))?; _defined.inner() } }; let mut value = std::ptr::null(); if CFDictionaryGetValueIfPresent(dict, key.cast(), &mut value) != 0 { callback(value) } else { None } } pub(super) unsafe fn get_str_value(dict: CFDictionaryRef, key: DictKey) -> Option { get_dict_value(dict, key, |v| { let v = v as cfs::CFStringRef; let len_utf16 = cfs::CFStringGetLength(v) as usize; let len_bytes = len_utf16 * 2; // Two bytes per UTF-16 codepoint. let v_ptr = cfs::CFStringGetCStringPtr(v, cfs::kCFStringEncodingUTF8); if v_ptr.is_null() { // Fallback on CFStringGetString to read the underlying bytes from the CFString. let mut buf = vec![0; len_bytes]; let success = cfs::CFStringGetCString( v, buf.as_mut_ptr(), len_bytes as _, cfs::kCFStringEncodingUTF8, ); if success != 0 { utils::vec_to_rust(buf) } else { None } } else { utils::cstr_to_rust_with_size(v_ptr, Some(len_bytes)) } }) } unsafe fn get_bool_value(dict: CFDictionaryRef, key: DictKey) -> Option { get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue)) } unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Option { get_dict_value(dict, key, |v| { let mut val: i64 = 0; if CFNumberGetValue( v.cast(), core_foundation_sys::number::kCFNumberSInt64Type, &mut val as *mut i64 as *mut c_void, ) { Some(val) } else { None } }) } unsafe fn new_disk( mount_point: PathBuf, volume_url: RetainedCFURL, c_disk: libc::statfs, disk_props: &RetainedCFDictionary, ) -> Option { // IOKit is not available on any but the most recent (16+) iOS and iPadOS versions. // Due to this, we can't query the medium type. All iOS devices use flash-based storage // so we just assume the disk type is an SSD until Rust has a way to conditionally link to // IOKit in more recent deployment versions. #[cfg(target_os = "macos")] let type_ = crate::sys::inner::disk::get_disk_type(&c_disk).unwrap_or(DiskType::Unknown(-1)); #[cfg(not(target_os = "macos"))] let type_ = DiskType::SSD; // Note: Since we requested these properties from the system, we don't expect // these property retrievals to fail. let name = get_str_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeNameKey), ) .map(OsString::from)?; let is_removable = { let ejectable = get_bool_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeIsEjectableKey), ) .unwrap_or_default(); let removable = get_bool_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeIsRemovableKey), ) .unwrap_or_default(); let is_removable = ejectable || removable; if is_removable { is_removable } else { // If neither `ejectable` or `removable` return `true`, fallback to checking // if the disk is attached to the internal system. let internal = get_bool_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeIsInternalKey), ) .unwrap_or_default(); !internal } }; let total_space = get_int_value( disk_props.inner(), DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey), )? as u64; let available_space = get_available_volume_space(disk_props); let file_system = IntoIterator::into_iter(c_disk.f_fstypename) .filter_map(|b| if b != 0 { Some(b as u8) } else { None }) .collect(); Some(Disk { type_, name, file_system, mount_point, volume_url, total_space, available_space, is_removable, }) } sysinfo-0.28.4/src/apple/ffi.rs000064400000000000000000000024341046102023000144160ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use core_foundation_sys::{ array::CFArrayRef, dictionary::CFDictionaryRef, error::CFErrorRef, string::CFStringRef, url::CFURLRef, }; // Reexport items defined in either macos or ios ffi module. pub use crate::sys::inner::ffi::*; #[link(name = "CoreFoundation", kind = "framework")] extern "C" { pub fn CFURLCopyResourcePropertiesForKeys( url: CFURLRef, keys: CFArrayRef, error: *mut CFErrorRef, ) -> CFDictionaryRef; pub static kCFURLVolumeIsEjectableKey: CFStringRef; pub static kCFURLVolumeIsRemovableKey: CFStringRef; pub static kCFURLVolumeAvailableCapacityKey: CFStringRef; pub static kCFURLVolumeAvailableCapacityForImportantUsageKey: CFStringRef; pub static kCFURLVolumeTotalCapacityKey: CFStringRef; pub static kCFURLVolumeNameKey: CFStringRef; pub static kCFURLVolumeIsLocalKey: CFStringRef; pub static kCFURLVolumeIsInternalKey: CFStringRef; pub static kCFURLVolumeIsBrowsableKey: CFStringRef; } #[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))] #[derive(Clone)] #[repr(C)] pub struct Val_t { pub key: [i8; 5], pub data_size: u32, pub data_type: [i8; 5], // UInt32Char_t pub bytes: [i8; 32], // SMCBytes_t } sysinfo-0.28.4/src/apple/ios.rs000064400000000000000000000002621046102023000144410ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod ffi {} pub use crate::sys::app_store::component; pub use crate::sys::app_store::process; sysinfo-0.28.4/src/apple/macos/component/arm.rs000064400000000000000000000122361046102023000175360ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::CStr; use core_foundation_sys::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; use core_foundation_sys::base::{kCFAllocatorDefault, CFRetain}; use core_foundation_sys::string::{ kCFStringEncodingUTF8, CFStringCreateWithBytes, CFStringGetCStringPtr, }; use crate::apple::inner::ffi::{ kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, kIOHIDEventTypeTemperature, matching, IOHIDEventFieldBase, IOHIDEventGetFloatValue, IOHIDEventSystemClientCopyServices, IOHIDEventSystemClientCreate, IOHIDEventSystemClientSetMatching, IOHIDServiceClientCopyEvent, IOHIDServiceClientCopyProperty, __IOHIDEventSystemClient, __IOHIDServiceClient, HID_DEVICE_PROPERTY_PRODUCT, }; use crate::sys::utils::CFReleaser; use crate::ComponentExt; pub(crate) struct Components { pub inner: Vec, client: Option>, } impl Components { pub(crate) fn new() -> Self { Self { inner: vec![], client: None, } } pub(crate) fn refresh(&mut self) { self.inner.clear(); unsafe { let matches = match CFReleaser::new(matching( kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, )) { Some(m) => m, None => return, }; if self.client.is_none() { let client = match CFReleaser::new(IOHIDEventSystemClientCreate(kCFAllocatorDefault)) { Some(c) => c, None => return, }; // Without this call, client is freed during the execution of the program. It must be kept! CFRetain(client.inner() as _); self.client = Some(client); } let client = self.client.as_ref().unwrap(); let _ = IOHIDEventSystemClientSetMatching(client.inner(), matches.inner()); let services = match CFReleaser::new(IOHIDEventSystemClientCopyServices(client.inner())) { Some(s) => s, None => return, }; let key_ref = match CFReleaser::new(CFStringCreateWithBytes( kCFAllocatorDefault, HID_DEVICE_PROPERTY_PRODUCT.as_ptr(), HID_DEVICE_PROPERTY_PRODUCT.len() as _, kCFStringEncodingUTF8, false as _, )) { Some(r) => r, None => return, }; let count = CFArrayGetCount(services.inner()); for i in 0..count { let service = match CFReleaser::new( CFArrayGetValueAtIndex(services.inner(), i) as *const _ ) { Some(s) => s, None => continue, }; let name = match CFReleaser::new(IOHIDServiceClientCopyProperty( service.inner(), key_ref.inner(), )) { Some(n) => n, None => continue, }; let name_ptr = CFStringGetCStringPtr(name.inner() as *const _, kCFStringEncodingUTF8); let name_str = CStr::from_ptr(name_ptr).to_string_lossy().to_string(); let mut component = Component::new(name_str, None, None, service); component.refresh(); self.inner.push(component); } } } } unsafe impl Send for Components {} unsafe impl Sync for Components {} #[doc = include_str!("../../../../md_doc/component.md")] pub struct Component { service: CFReleaser<__IOHIDServiceClient>, temperature: f32, label: String, max: f32, critical: Option, } impl Component { pub(crate) fn new( label: String, max: Option, critical: Option, service: CFReleaser<__IOHIDServiceClient>, ) -> Self { Self { service, label, max: max.unwrap_or(0.), critical, temperature: 0., } } } unsafe impl Send for Component {} unsafe impl Sync for Component {} impl ComponentExt for Component { fn temperature(&self) -> f32 { self.temperature } fn max(&self) -> f32 { self.max } fn critical(&self) -> Option { self.critical } fn label(&self) -> &str { &self.label } fn refresh(&mut self) { unsafe { let event = match CFReleaser::new(IOHIDServiceClientCopyEvent( self.service.inner() as *const _, kIOHIDEventTypeTemperature, 0, 0, )) { Some(e) => e, None => return, }; self.temperature = IOHIDEventGetFloatValue( event.inner(), IOHIDEventFieldBase(kIOHIDEventTypeTemperature), ) as _; if self.temperature > self.max { self.max = self.temperature; } } } } sysinfo-0.28.4/src/apple/macos/component/mod.rs000064400000000000000000000005321046102023000175320ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(crate) mod x86; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use self::x86::*; #[cfg(target_arch = "aarch64")] pub(crate) mod arm; #[cfg(target_arch = "aarch64")] pub use self::arm::*; sysinfo-0.28.4/src/apple/macos/component/x86.rs000064400000000000000000000224651046102023000174110ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::{ffi, macos::utils::IOReleaser}; use crate::ComponentExt; use libc::{c_char, c_int, c_void}; use std::mem; const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8]), // PECI CPU "TCXC" ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8]), // PECI CPU "TCXc" ( "CPU Proximity", &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8], ), // CPU Proximity (heat spreader) "TC0P" ("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" ("Battery", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8]), // Battery "TB0T" ]; pub(crate) struct ComponentFFI { input_structure: ffi::KeyData_t, val: ffi::Val_t, /// It is the `System::connection`. We need it to not require an extra argument /// in `ComponentExt::refresh`. connection: ffi::io_connect_t, } impl ComponentFFI { fn new(key: &[i8], connection: ffi::io_connect_t) -> Option { unsafe { get_key_size(connection, key) .ok() .map(|(input_structure, val)| ComponentFFI { input_structure, val, connection, }) } } fn temperature(&self) -> Option { get_temperature_inner(self.connection, &self.input_structure, &self.val) } } /// Used to get CPU information, not supported on iOS, or inside the default macOS sandbox. pub(crate) struct Components { pub inner: Vec, connection: Option, } impl Components { pub(crate) fn new() -> Self { Self { inner: Vec::with_capacity(2), connection: IoService::new_connection(), } } pub(crate) fn refresh(&mut self) { if let Some(ref connection) = self.connection { let connection = connection.inner(); self.inner.clear(); // getting CPU critical temperature let critical_temp = get_temperature(connection, &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]); for (id, v) in COMPONENTS_TEMPERATURE_IDS.iter() { if let Some(c) = Component::new((*id).to_owned(), None, critical_temp, v, connection) { self.inner.push(c); } } } } } #[doc = include_str!("../../../../md_doc/component.md")] pub struct Component { temperature: f32, max: f32, critical: Option, label: String, ffi_part: ComponentFFI, } impl Component { /// Creates a new `Component` with the given information. pub(crate) fn new( label: String, max: Option, critical: Option, key: &[i8], connection: ffi::io_connect_t, ) -> Option { let ffi_part = ComponentFFI::new(key, connection)?; ffi_part.temperature().map(|temperature| Component { temperature, label, max: max.unwrap_or(temperature), critical, ffi_part, }) } } impl ComponentExt for Component { fn temperature(&self) -> f32 { self.temperature } fn max(&self) -> f32 { self.max } fn critical(&self) -> Option { self.critical } fn label(&self) -> &str { &self.label } fn refresh(&mut self) { if let Some(temp) = self.ffi_part.temperature() { self.temperature = temp; if self.temperature > self.max { self.max = self.temperature; } } } } unsafe fn perform_call( conn: ffi::io_connect_t, index: c_int, input_structure: *const ffi::KeyData_t, output_structure: *mut ffi::KeyData_t, ) -> i32 { let mut structure_output_size = mem::size_of::(); ffi::IOConnectCallStructMethod( conn, index as u32, input_structure, mem::size_of::(), output_structure, &mut structure_output_size, ) } // Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28 #[inline] fn strtoul(s: &[i8]) -> u32 { unsafe { ((*s.get_unchecked(0) as u32) << (3u32 << 3)) + ((*s.get_unchecked(1) as u32) << (2u32 << 3)) + ((*s.get_unchecked(2) as u32) << (1u32 << 3)) + (*s.get_unchecked(3) as u32) } } #[inline] unsafe fn ultostr(s: *mut c_char, val: u32) { *s.offset(0) = ((val >> 24) % 128) as i8; *s.offset(1) = ((val >> 16) % 128) as i8; *s.offset(2) = ((val >> 8) % 128) as i8; *s.offset(3) = (val % 128) as i8; *s.offset(4) = 0; } unsafe fn get_key_size( con: ffi::io_connect_t, key: &[i8], ) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { let mut input_structure: ffi::KeyData_t = mem::zeroed::(); let mut output_structure: ffi::KeyData_t = mem::zeroed::(); let mut val: ffi::Val_t = mem::zeroed::(); input_structure.key = strtoul(key); input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; let result = perform_call( con, ffi::KERNEL_INDEX_SMC, &input_structure, &mut output_structure, ); if result != ffi::KIO_RETURN_SUCCESS { return Err(result); } val.data_size = output_structure.key_info.data_size; ultostr( val.data_type.as_mut_ptr(), output_structure.key_info.data_type, ); input_structure.key_info.data_size = val.data_size; input_structure.data8 = ffi::SMC_CMD_READ_BYTES; Ok((input_structure, val)) } unsafe fn read_key( con: ffi::io_connect_t, input_structure: &ffi::KeyData_t, mut val: ffi::Val_t, ) -> Result { let mut output_structure: ffi::KeyData_t = mem::zeroed::(); match perform_call( con, ffi::KERNEL_INDEX_SMC, input_structure, &mut output_structure, ) { ffi::KIO_RETURN_SUCCESS => { libc::memcpy( val.bytes.as_mut_ptr() as *mut c_void, output_structure.bytes.as_mut_ptr() as *mut c_void, mem::size_of::<[u8; 32]>(), ); Ok(val) } result => Err(result), } } fn get_temperature_inner( con: ffi::io_connect_t, input_structure: &ffi::KeyData_t, original_val: &ffi::Val_t, ) -> Option { unsafe { if let Ok(val) = read_key(con, input_structure, (*original_val).clone()) { if val.data_size > 0 && libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) == 0 { // convert sp78 value to temperature let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); return Some(x as f32 / 64f32); } } } None } fn get_temperature(con: ffi::io_connect_t, key: &[i8]) -> Option { unsafe { let (input_structure, val) = get_key_size(con, key).ok()?; get_temperature_inner(con, &input_structure, &val) } } pub(crate) struct IoService(ffi::io_connect_t); impl IoService { fn new(obj: ffi::io_connect_t) -> Option { if obj == 0 { None } else { Some(Self(obj)) } } pub(crate) fn inner(&self) -> ffi::io_connect_t { self.0 } // code from https://github.com/Chris911/iStats // Not supported on iOS, or in the default macOS pub(crate) fn new_connection() -> Option { let mut iterator: ffi::io_iterator_t = 0; unsafe { let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); let result = ffi::IOServiceGetMatchingServices( ffi::kIOMasterPortDefault, matching_dictionary, &mut iterator, ); if result != ffi::KIO_RETURN_SUCCESS { sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result); return None; } let iterator = match IOReleaser::new(iterator) { Some(i) => i, None => { sysinfo_debug!("Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor"); return None; } }; let device = match IOReleaser::new(ffi::IOIteratorNext(iterator.inner())) { Some(d) => d, None => { sysinfo_debug!("Error: no SMC found"); return None; } }; let mut conn = 0; let result = ffi::IOServiceOpen(device.inner(), libc::mach_task_self(), 0, &mut conn); if result != ffi::KIO_RETURN_SUCCESS { sysinfo_debug!("Error: IOServiceOpen() = {}", result); return None; } let conn = IoService::new(conn); if conn.is_none() { sysinfo_debug!( "Error: IOServiceOpen() succeeded but returned invalid descriptor..." ); } conn } } } impl Drop for IoService { fn drop(&mut self) { unsafe { ffi::IOServiceClose(self.0); } } } sysinfo-0.28.4/src/apple/macos/disk.rs000064400000000000000000000110111046102023000156750ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::ffi; use crate::sys::{ disk::{get_str_value, DictKey}, macos::utils::IOReleaser, utils::CFReleaser, }; use crate::DiskType; use core_foundation_sys::base::{kCFAllocatorDefault, kCFAllocatorNull}; use core_foundation_sys::string as cfs; use std::ffi::CStr; pub(crate) fn get_disk_type(disk: &libc::statfs) -> Option { let characteristics_string = unsafe { CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy( kCFAllocatorDefault, ffi::kIOPropertyDeviceCharacteristicsKey.as_ptr(), ffi::kIOPropertyDeviceCharacteristicsKey.len() as _, cfs::kCFStringEncodingUTF8, false as _, kCFAllocatorNull, ))? }; // Removes `/dev/` from the value. let bsd_name = unsafe { CStr::from_ptr(disk.f_mntfromname.as_ptr()) .to_bytes() .strip_prefix(b"/dev/") .or_else(|| { sysinfo_debug!("unknown disk mount path format"); None })? }; // We don't need to wrap this in an auto-releaser because the following call to `IOServiceGetMatchingServices` // will take ownership of one retain reference. let matching = unsafe { ffi::IOBSDNameMatching(ffi::kIOMasterPortDefault, 0, bsd_name.as_ptr().cast()) }; if matching.is_null() { return None; } let mut service_iterator: ffi::io_iterator_t = 0; if unsafe { ffi::IOServiceGetMatchingServices( ffi::kIOMasterPortDefault, matching.cast(), &mut service_iterator, ) } != libc::KERN_SUCCESS { return None; } // Safety: We checked for success, so there is always a valid iterator, even if its empty. let service_iterator = unsafe { IOReleaser::new_unchecked(service_iterator) }; let mut parent_entry: ffi::io_registry_entry_t = 0; while let Some(mut current_service_entry) = IOReleaser::new(unsafe { ffi::IOIteratorNext(service_iterator.inner()) }) { // Note: This loop is required in a non-obvious way. Due to device properties existing as a tree // in IOKit, we may need an arbitrary number of calls to `IORegistryEntryCreateCFProperty` in order to find // the values we are looking for. The function may return nothing if we aren't deep enough into the registry // tree, so we need to continue going from child->parent node until its found. loop { if unsafe { ffi::IORegistryEntryGetParentEntry( current_service_entry.inner(), ffi::kIOServicePlane.as_ptr().cast(), &mut parent_entry, ) } != libc::KERN_SUCCESS { break; } current_service_entry = match IOReleaser::new(parent_entry) { Some(service) => service, // There were no more parents left None => break, }; let properties_result = unsafe { CFReleaser::new(ffi::IORegistryEntryCreateCFProperty( current_service_entry.inner(), characteristics_string.inner(), kCFAllocatorDefault, 0, )) }; if let Some(device_properties) = properties_result { let disk_type = unsafe { super::disk::get_str_value( device_properties.inner(), DictKey::Defined(ffi::kIOPropertyMediumTypeKey), ) }; if let Some(disk_type) = disk_type.and_then(|medium| match medium.as_str() { _ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskType::SSD), _ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskType::HDD), _ => None, }) { return Some(disk_type); } else { // Many external drive vendors do not advertise their device's storage medium. // // In these cases, assuming that there were _any_ properties about them registered, we fallback // to `HDD` when no storage medium is provided by the device instead of `Unknown`. return Some(DiskType::HDD); } } } } None } sysinfo-0.28.4/src/apple/macos/ffi.rs000064400000000000000000000216121046102023000155170ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use core_foundation_sys::base::{mach_port_t, CFAllocatorRef}; use core_foundation_sys::dictionary::{CFDictionaryRef, CFMutableDictionaryRef}; use core_foundation_sys::string::CFStringRef; use libc::{c_char, kern_return_t}; // Note: IOKit is only available on MacOS up until very recent iOS versions: https://developer.apple.com/documentation/iokit #[allow(non_camel_case_types)] pub type io_object_t = mach_port_t; #[allow(non_camel_case_types)] pub type io_iterator_t = io_object_t; #[allow(non_camel_case_types)] pub type io_registry_entry_t = io_object_t; #[allow(non_camel_case_types)] pub type io_name_t = *const c_char; pub type IOOptionBits = u32; #[allow(non_upper_case_globals)] pub const kIOServicePlane: &str = "IOService\0"; #[allow(non_upper_case_globals)] pub const kIOPropertyDeviceCharacteristicsKey: &str = "Device Characteristics"; #[allow(non_upper_case_globals)] pub const kIOPropertyMediumTypeKey: &str = "Medium Type"; #[allow(non_upper_case_globals)] pub const kIOPropertyMediumTypeSolidStateKey: &str = "Solid State"; #[allow(non_upper_case_globals)] pub const kIOPropertyMediumTypeRotationalKey: &str = "Rotational"; // Based on https://github.com/libusb/libusb/blob/bed8d3034eac74a6e1ba123b5c270ea63cb6cf1a/libusb/os/darwin_usb.c#L54-L55, // we can simply set it to 0 (and is the same value as its replacement `kIOMainPortDefault`). #[allow(non_upper_case_globals)] pub const kIOMasterPortDefault: mach_port_t = 0; // Note: Obtaining information about disks using IOKIt is allowed inside the default macOS App Sandbox. #[link(name = "IOKit", kind = "framework")] extern "C" { pub fn IOServiceGetMatchingServices( mainPort: mach_port_t, matching: CFMutableDictionaryRef, existing: *mut io_iterator_t, ) -> kern_return_t; pub fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t; pub fn IOObjectRelease(obj: io_object_t) -> kern_return_t; pub fn IORegistryEntryCreateCFProperty( entry: io_registry_entry_t, key: CFStringRef, allocator: CFAllocatorRef, options: IOOptionBits, ) -> CFDictionaryRef; pub fn IORegistryEntryGetParentEntry( entry: io_registry_entry_t, plane: io_name_t, parent: *mut io_registry_entry_t, ) -> kern_return_t; pub fn IOBSDNameMatching( mainPort: mach_port_t, options: u32, bsdName: *const c_char, ) -> CFMutableDictionaryRef; } #[cfg(all( not(feature = "apple-sandbox"), any(target_arch = "x86", target_arch = "x86_64") ))] mod io_service { use super::{io_object_t, mach_port_t}; use core_foundation_sys::dictionary::CFMutableDictionaryRef; use libc::{c_char, kern_return_t, size_t, task_t}; #[allow(non_camel_case_types)] pub type io_connect_t = io_object_t; #[allow(non_camel_case_types)] pub type io_service_t = io_object_t; #[allow(non_camel_case_types)] pub type task_port_t = task_t; extern "C" { pub fn IOServiceMatching(a: *const c_char) -> CFMutableDictionaryRef; pub fn IOServiceOpen( device: io_service_t, owning_task: task_port_t, type_: u32, connect: *mut io_connect_t, ) -> kern_return_t; pub fn IOServiceClose(a: io_connect_t) -> kern_return_t; #[allow(dead_code)] pub fn IOConnectCallStructMethod( connection: mach_port_t, selector: u32, inputStruct: *const KeyData_t, inputStructCnt: size_t, outputStruct: *mut KeyData_t, outputStructCnt: *mut size_t, ) -> kern_return_t; } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_vers_t { pub major: u8, pub minor: u8, pub build: u8, pub reserved: [u8; 1], pub release: u16, } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_pLimitData_t { pub version: u16, pub length: u16, pub cpu_plimit: u32, pub gpu_plimit: u32, pub mem_plimit: u32, } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_keyInfo_t { pub data_size: u32, pub data_type: u32, pub data_attributes: u8, } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] pub struct KeyData_t { pub key: u32, pub vers: KeyData_vers_t, pub p_limit_data: KeyData_pLimitData_t, pub key_info: KeyData_keyInfo_t, pub result: u8, pub status: u8, pub data8: u8, pub data32: u32, pub bytes: [i8; 32], // SMCBytes_t } #[allow(dead_code)] pub const KERNEL_INDEX_SMC: i32 = 2; #[allow(dead_code)] pub const SMC_CMD_READ_KEYINFO: u8 = 9; #[allow(dead_code)] pub const SMC_CMD_READ_BYTES: u8 = 5; pub const KIO_RETURN_SUCCESS: i32 = 0; } #[cfg(feature = "apple-sandbox")] mod io_service {} #[cfg(all( not(feature = "apple-sandbox"), any(target_arch = "x86", target_arch = "x86_64") ))] pub use io_service::*; #[cfg(all(not(feature = "apple-sandbox"), target_arch = "aarch64"))] mod io_service { use std::ptr::null; use core_foundation_sys::array::CFArrayRef; use core_foundation_sys::base::{CFAllocatorRef, CFRelease}; use core_foundation_sys::dictionary::{ kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionaryCreate, CFDictionaryRef, }; use core_foundation_sys::number::{kCFNumberSInt32Type, CFNumberCreate}; use core_foundation_sys::string::{CFStringCreateWithCString, CFStringRef}; #[repr(C)] pub struct __IOHIDServiceClient(libc::c_void); pub type IOHIDServiceClientRef = *const __IOHIDServiceClient; #[repr(C)] pub struct __IOHIDEventSystemClient(libc::c_void); pub type IOHIDEventSystemClientRef = *const __IOHIDEventSystemClient; #[repr(C)] pub struct __IOHIDEvent(libc::c_void); pub type IOHIDEventRef = *const __IOHIDEvent; #[allow(non_upper_case_globals)] pub const kIOHIDEventTypeTemperature: i64 = 15; #[inline] #[allow(non_snake_case)] pub fn IOHIDEventFieldBase(event_type: i64) -> i64 { event_type << 16 } #[cfg(not(feature = "apple-sandbox"))] extern "C" { pub fn IOHIDEventSystemClientCreate(allocator: CFAllocatorRef) -> IOHIDEventSystemClientRef; pub fn IOHIDEventSystemClientSetMatching( client: IOHIDEventSystemClientRef, matches: CFDictionaryRef, ) -> i32; pub fn IOHIDEventSystemClientCopyServices(client: IOHIDEventSystemClientRef) -> CFArrayRef; pub fn IOHIDServiceClientCopyProperty( service: IOHIDServiceClientRef, key: CFStringRef, ) -> CFStringRef; pub fn IOHIDServiceClientCopyEvent( service: IOHIDServiceClientRef, v0: i64, v1: i32, v2: i64, ) -> IOHIDEventRef; pub fn IOHIDEventGetFloatValue(event: IOHIDEventRef, field: i64) -> f64; } pub(crate) const HID_DEVICE_PROPERTY_PRODUCT: &[u8] = b"Product\0"; pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE: &[u8] = b"PrimaryUsage\0"; pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE: &[u8] = b"PrimaryUsagePage\0"; #[allow(non_upper_case_globals)] pub(crate) const kHIDPage_AppleVendor: i32 = 0xff00; #[allow(non_upper_case_globals)] pub(crate) const kHIDUsage_AppleVendor_TemperatureSensor: i32 = 0x0005; pub(crate) fn matching(page: i32, usage: i32) -> CFDictionaryRef { unsafe { let keys = [ CFStringCreateWithCString( null() as *const _, HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE.as_ptr() as *const _, 0, ), CFStringCreateWithCString( null() as *const _, HID_DEVICE_PROPERTY_PRIMARY_USAGE.as_ptr() as *const _, 0, ), ]; let nums = [ CFNumberCreate(null(), kCFNumberSInt32Type, &page as *const _ as *const _), CFNumberCreate(null(), kCFNumberSInt32Type, &usage as *const _ as *const _), ]; let dict = CFDictionaryCreate( null(), &keys as *const _ as *const _, &nums as *const _ as *const _, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ); for key in keys { CFRelease(key as _); } for num in nums { CFRelease(num as _); } dict } } } #[cfg(all(not(feature = "apple-sandbox"), target_arch = "aarch64"))] pub use io_service::*; sysinfo-0.28.4/src/apple/macos/mod.rs000064400000000000000000000007061046102023000155330ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod disk; pub mod ffi; pub(crate) mod utils; #[cfg(not(feature = "apple-sandbox"))] pub mod system; #[cfg(not(feature = "apple-sandbox"))] pub mod component; #[cfg(not(feature = "apple-sandbox"))] pub mod process; #[cfg(feature = "apple-sandbox")] pub use crate::sys::app_store::component; #[cfg(feature = "apple-sandbox")] pub use crate::sys::app_store::process; sysinfo-0.28.4/src/apple/macos/process.rs000064400000000000000000000476141046102023000164430ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::CStr; use std::mem::{self, MaybeUninit}; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::borrow::Borrow; use libc::{c_int, c_void, kill, size_t}; use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; use crate::sys::process::ThreadStatus; use crate::sys::system::Wrap; #[doc = include_str!("../../../md_doc/process.md")] pub struct Process { pub(crate) name: String, pub(crate) cmd: Vec, pub(crate) exe: PathBuf, pid: Pid, parent: Option, pub(crate) environ: Vec, cwd: PathBuf, pub(crate) root: PathBuf, pub(crate) memory: u64, pub(crate) virtual_memory: u64, old_utime: u64, old_stime: u64, start_time: u64, run_time: u64, pub(crate) updated: bool, cpu_usage: f32, user_id: Option, group_id: Option, pub(crate) process_status: ProcessStatus, /// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have /// enough rights to get this information. /// /// This is very likely this one that you want instead of `process_status`. pub status: Option, pub(crate) old_read_bytes: u64, pub(crate) old_written_bytes: u64, pub(crate) read_bytes: u64, pub(crate) written_bytes: u64, } impl Process { pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Process { Process { name, pid, parent: None, cmd: Vec::new(), environ: Vec::new(), exe, cwd, root: PathBuf::new(), memory: 0, virtual_memory: 0, cpu_usage: 0., old_utime: 0, old_stime: 0, updated: true, start_time: 0, run_time: 0, user_id: None, group_id: None, process_status: ProcessStatus::Unknown(0), status: None, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, } } pub(crate) fn new(pid: Pid, parent: Option, start_time: u64, run_time: u64) -> Process { Process { name: String::new(), pid, parent, cmd: Vec::new(), environ: Vec::new(), exe: PathBuf::new(), cwd: PathBuf::new(), root: PathBuf::new(), memory: 0, virtual_memory: 0, cpu_usage: 0., old_utime: 0, old_stime: 0, updated: true, start_time, run_time, user_id: None, group_id: None, process_status: ProcessStatus::Unknown(0), status: None, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, } } } impl ProcessExt for Process { fn kill_with(&self, signal: Signal) -> Option { let c_signal = crate::sys::system::convert_signal(signal)?; unsafe { Some(kill(self.pid.0, c_signal) == 0) } } fn name(&self) -> &str { &self.name } fn cmd(&self) -> &[String] { &self.cmd } fn exe(&self) -> &Path { self.exe.as_path() } fn pid(&self) -> Pid { self.pid } fn environ(&self) -> &[String] { &self.environ } fn cwd(&self) -> &Path { self.cwd.as_path() } fn root(&self) -> &Path { self.root.as_path() } fn memory(&self) -> u64 { self.memory } fn virtual_memory(&self) -> u64 { self.virtual_memory } fn parent(&self) -> Option { self.parent } fn status(&self) -> ProcessStatus { self.process_status } fn start_time(&self) -> u64 { self.start_time } fn run_time(&self) -> u64 { self.run_time } fn cpu_usage(&self) -> f32 { self.cpu_usage } fn disk_usage(&self) -> DiskUsage { DiskUsage { read_bytes: self.read_bytes - self.old_read_bytes, total_read_bytes: self.read_bytes, written_bytes: self.written_bytes - self.old_written_bytes, total_written_bytes: self.written_bytes, } } fn user_id(&self) -> Option<&Uid> { self.user_id.as_ref() } fn group_id(&self) -> Option { self.group_id } fn wait(&self) { let mut status = 0; // attempt waiting unsafe { if libc::waitpid(self.pid.0, &mut status, 0) < 0 { // attempt failed (non-child process) so loop until process ends let duration = std::time::Duration::from_millis(10); while kill(self.pid.0, 0) == 0 { std::thread::sleep(duration); } } } } fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } } #[allow(deprecated)] // Because of libc::mach_absolute_time. pub(crate) fn compute_cpu_usage( p: &mut Process, task_info: libc::proc_taskinfo, system_time: u64, user_time: u64, time_interval: Option, ) { if let Some(time_interval) = time_interval { let total_existing_time = p.old_stime.saturating_add(p.old_utime); let mut updated_cpu_usage = false; if time_interval > 0.000001 && total_existing_time > 0 { let total_current_time = task_info .pti_total_system .saturating_add(task_info.pti_total_user); let total_time_diff = total_current_time.saturating_sub(total_existing_time); if total_time_diff > 0 { p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; updated_cpu_usage = true; } } if !updated_cpu_usage { p.cpu_usage = 0.; } p.old_stime = task_info.pti_total_system; p.old_utime = task_info.pti_total_user; } else { unsafe { // This is the "backup way" of CPU computation. let time = libc::mach_absolute_time(); let task_time = user_time .saturating_add(system_time) .saturating_add(task_info.pti_total_user) .saturating_add(task_info.pti_total_system); let system_time_delta = if task_time < p.old_utime { task_time } else { task_time.saturating_sub(p.old_utime) }; let time_delta = if time < p.old_stime { time } else { time.saturating_sub(p.old_stime) }; p.old_utime = task_time; p.old_stime = time; p.cpu_usage = if time_delta == 0 { 0f32 } else { (system_time_delta as f64 * 100f64 / time_delta as f64) as f32 }; } } } unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { let mut task_info = mem::zeroed::(); // If it doesn't work, we just don't have memory information for this process // so it's "fine". libc::proc_pidinfo( pid.0, libc::PROC_PIDTASKINFO, 0, &mut task_info as *mut libc::proc_taskinfo as *mut c_void, mem::size_of::() as _, ); task_info } #[inline] fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool { // In case we are iterating all pids we got from `proc_listallpids`, then // there is no point checking if the process is alive since it was returned // from this function. if !check_if_alive { return true; } unsafe { if kill(pid.0, 0) == 0 { return true; } // `kill` failed but it might not be because the process is dead. let errno = libc::__error(); // If errno is equal to ESCHR, it means the process is dead. !errno.is_null() && *errno != libc::ESRCH } } #[inline] fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {} #[inline] fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) { if *check && env.starts_with("PATH=") { *check = false; *root = Path::new(&env[5..]).to_path_buf(); } } unsafe fn get_bsd_info(pid: Pid) -> Option { let mut info = mem::zeroed::(); if libc::proc_pidinfo( pid.0, libc::PROC_PIDTBSDINFO, 0, &mut info as *mut _ as *mut _, mem::size_of::() as _, ) != mem::size_of::() as _ { None } else { Some(info) } } unsafe fn create_new_process( pid: Pid, mut size: size_t, now: u64, refresh_kind: ProcessRefreshKind, info: Option, ) -> Result, ()> { let mut vnodepathinfo = mem::zeroed::(); let result = libc::proc_pidinfo( pid.0, libc::PROC_PIDVNODEPATHINFO, 0, &mut vnodepathinfo as *mut _ as *mut _, mem::size_of::() as _, ); let cwd = if result > 0 { let buffer = vnodepathinfo.pvi_cdir.vip_path; let buffer = CStr::from_ptr(buffer.as_ptr() as _); buffer .to_str() .map(PathBuf::from) .unwrap_or_else(|_| PathBuf::new()) } else { PathBuf::new() }; let info = match info { Some(info) => info, None => { let mut buffer: Vec = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); match libc::proc_pidpath( pid.0, buffer.as_mut_ptr() as *mut _, libc::PROC_PIDPATHINFO_MAXSIZE as _, ) { x if x > 0 => { buffer.set_len(x as _); let tmp = String::from_utf8_unchecked(buffer); let exe = PathBuf::from(tmp); let name = exe .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .to_owned(); return Ok(Some(Process::new_empty(pid, exe, name, cwd))); } _ => {} } return Err(()); } }; let parent = match info.pbi_ppid as i32 { 0 => None, p => Some(Pid(p)), }; let mut proc_args = Vec::with_capacity(size as _); let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid.0 as _]; /* * /---------------\ 0x00000000 * | ::::::::::::: | * |---------------| <-- Beginning of data returned by sysctl() is here. * | argc | * |---------------| * | exec_path | * |---------------| * | 0 | * |---------------| * | arg[0] | * |---------------| * | 0 | * |---------------| * | arg[n] | * |---------------| * | 0 | * |---------------| * | env[0] | * |---------------| * | 0 | * |---------------| * | env[n] | * |---------------| * | ::::::::::::: | * |---------------| <-- Top of stack. * : : * : : * \---------------/ 0xffffffff */ if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, ptr as *mut c_void, &mut size, std::ptr::null_mut(), 0, ) == -1 { return Err(()); // not enough rights I assume? } let mut n_args: c_int = 0; libc::memcpy( (&mut n_args) as *mut c_int as *mut c_void, ptr as *const c_void, mem::size_of::(), ); let mut cp = ptr.add(mem::size_of::()); let mut start = cp; let start_time = info.pbi_start_tvsec; let run_time = now.saturating_sub(start_time); let mut p = if cp < ptr.add(size) { while cp < ptr.add(size) && *cp != 0 { cp = cp.offset(1); } let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); let name = exe .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .to_owned(); while cp < ptr.add(size) && *cp == 0 { cp = cp.offset(1); } start = cp; let mut c = 0; let mut cmd = Vec::with_capacity(n_args as usize); while c < n_args && cp < ptr.add(size) { if *cp == 0 { c += 1; cmd.push(get_unchecked_str(cp, start)); start = cp.offset(1); } cp = cp.offset(1); } #[inline] unsafe fn get_environ( ptr: *mut u8, mut cp: *mut u8, size: size_t, mut root: PathBuf, callback: F, ) -> (Vec, PathBuf) { let mut environ = Vec::with_capacity(10); let mut start = cp; let mut check = true; while cp < ptr.add(size) { if *cp == 0 { if cp == start { break; } let e = get_unchecked_str(cp, start); callback(&e, &mut root, &mut check); environ.push(e); start = cp.offset(1); } cp = cp.offset(1); } (environ, root) } let (environ, root) = if exe.is_absolute() { if let Some(parent_path) = exe.parent() { get_environ( ptr, cp, size, parent_path.to_path_buf(), do_not_get_env_path, ) } else { get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) } } else { get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) }; let mut p = Process::new(pid, parent, start_time, run_time); p.exe = exe; p.name = name; p.cwd = cwd; p.cmd = parse_command_line(&cmd); p.environ = environ; p.root = root; p } else { Process::new(pid, parent, start_time, run_time) }; let task_info = get_task_info(pid); p.memory = task_info.pti_resident_size; p.virtual_memory = task_info.pti_virtual_size; p.user_id = Some(Uid(info.pbi_uid)); p.group_id = Some(Gid(info.pbi_gid)); p.process_status = ProcessStatus::from(info.pbi_status); if refresh_kind.disk_usage() { update_proc_disk_activity(&mut p); } Ok(Some(p)) } pub(crate) fn update_process( wrap: &Wrap, pid: Pid, size: size_t, time_interval: Option, now: u64, refresh_kind: ProcessRefreshKind, check_if_alive: bool, ) -> Result, ()> { unsafe { if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { if p.memory == 0 { // We don't have access to this process' information. return if check_if_pid_is_alive(pid, check_if_alive) { p.updated = true; Ok(None) } else { Err(()) }; } if let Some(info) = get_bsd_info(pid) { if info.pbi_start_tvsec != p.start_time { // We don't it to be removed, just replaced. p.updated = true; // The owner of this PID changed. return create_new_process(pid, size, now, refresh_kind, Some(info)); } } let task_info = get_task_info(pid); let mut thread_info = mem::zeroed::(); let (user_time, system_time, thread_status) = if libc::proc_pidinfo( pid.0, libc::PROC_PIDTHREADINFO, 0, &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, mem::size_of::() as _, ) != 0 { ( thread_info.pth_user_time, thread_info.pth_system_time, Some(ThreadStatus::from(thread_info.pth_run_state)), ) } else { // It very likely means that the process is dead... if check_if_pid_is_alive(pid, check_if_alive) { (0, 0, Some(ThreadStatus::Running)) } else { return Err(()); } }; p.status = thread_status; if refresh_kind.cpu() { compute_cpu_usage(p, task_info, system_time, user_time, time_interval); } p.memory = task_info.pti_resident_size; p.virtual_memory = task_info.pti_virtual_size; if refresh_kind.disk_usage() { update_proc_disk_activity(p); } p.updated = true; return Ok(None); } create_new_process(pid, size, now, refresh_kind, get_bsd_info(pid)) } } fn update_proc_disk_activity(p: &mut Process) { p.old_read_bytes = p.read_bytes; p.old_written_bytes = p.written_bytes; let mut pidrusage = MaybeUninit::::uninit(); unsafe { let retval = libc::proc_pid_rusage( p.pid().0 as _, libc::RUSAGE_INFO_V2, pidrusage.as_mut_ptr() as _, ); if retval < 0 { sysinfo_debug!("proc_pid_rusage failed: {:?}", retval); } else { let pidrusage = pidrusage.assume_init(); p.read_bytes = pidrusage.ri_diskio_bytesread; p.written_bytes = pidrusage.ri_diskio_byteswritten; } } } #[allow(unknown_lints)] #[allow(clippy::uninit_vec)] pub(crate) fn get_proc_list() -> Option> { unsafe { let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); if count < 1 { return None; } let mut pids: Vec = Vec::with_capacity(count as usize); pids.set_len(count as usize); let count = count * mem::size_of::() as i32; let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count); if x < 1 || x as usize >= pids.len() { None } else { pids.set_len(x as usize); Some(pids) } } } unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { let len = cp as usize - start as usize; let part = Vec::from_raw_parts(start, len, len); let tmp = String::from_utf8_unchecked(part.clone()); mem::forget(part); tmp } fn parse_command_line + Borrow>(cmd: &[T]) -> Vec { let mut x = 0; let mut command = Vec::with_capacity(cmd.len()); while x < cmd.len() { let mut y = x; if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; while y < cmd.len() && !cmd[y].ends_with(c) { y += 1; } command.push(cmd[x..y].join(" ")); x = y; } else { command.push(cmd[x].to_owned()); } x += 1; } command } #[cfg(test)] mod test { use super::*; #[test] fn test_get_path() { let mut path = PathBuf::new(); let mut check = true; do_get_env_path("PATH=tadam", &mut path, &mut check); assert!(!check); assert_eq!(path, PathBuf::from("tadam")); } } sysinfo-0.28.4/src/apple/macos/system.rs000064400000000000000000000126671046102023000163110ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::SystemExt; #[allow(deprecated)] use libc::{mach_timebase_info, mach_timebase_info_data_t}; use libc::{ host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info, processor_cpu_load_info_t, sysconf, vm_page_size, PROCESSOR_CPU_LOAD_INFO, _SC_CLK_TCK, }; use std::ptr::null_mut; struct ProcessorCpuLoadInfo { cpu_load: processor_cpu_load_info_t, cpu_count: natural_t, } impl ProcessorCpuLoadInfo { fn new(port: mach_port_t) -> Option { let mut info_size = std::mem::size_of::() as _; let mut cpu_count = 0; let mut cpu_load: processor_cpu_load_info_t = null_mut(); unsafe { if host_processor_info( port, PROCESSOR_CPU_LOAD_INFO, &mut cpu_count, &mut cpu_load as *mut _ as *mut _, &mut info_size, ) != 0 { sysinfo_debug!("host_processor_info failed, not updating CPU ticks usage..."); None } else if cpu_count < 1 || cpu_load.is_null() { None } else { Some(Self { cpu_load, cpu_count, }) } } } } impl Drop for ProcessorCpuLoadInfo { fn drop(&mut self) { unsafe { munmap(self.cpu_load as _, vm_page_size); } } } pub(crate) struct SystemTimeInfo { timebase_to_ns: f64, clock_per_sec: f64, old_cpu_info: ProcessorCpuLoadInfo, } unsafe impl Send for SystemTimeInfo {} unsafe impl Sync for SystemTimeInfo {} impl SystemTimeInfo { #[allow(deprecated)] // Everything related to mach_timebase_info_data_t pub fn new(port: mach_port_t) -> Option { unsafe { let clock_ticks_per_sec = sysconf(_SC_CLK_TCK); // FIXME: Maybe check errno here? Problem is that if errno is not 0 before this call, // we will get an error which isn't related... // if let Some(er) = std::io::Error::last_os_error().raw_os_error() { // if err != 0 { // println!("==> {:?}", er); // sysinfo_debug!("Failed to get _SC_CLK_TCK value, using old CPU tick measure system"); // return None; // } // } let mut info = mach_timebase_info_data_t { numer: 0, denom: 0 }; if mach_timebase_info(&mut info) != libc::KERN_SUCCESS { sysinfo_debug!("mach_timebase_info failed, using default value of 1"); info.numer = 1; info.denom = 1; } let old_cpu_info = match ProcessorCpuLoadInfo::new(port) { Some(cpu_info) => cpu_info, None => { sysinfo_debug!("host_processor_info failed, using old CPU tick measure system"); return None; } }; let nano_per_seconds = 1_000_000_000.; sysinfo_debug!(""); Some(Self { timebase_to_ns: info.numer as f64 / info.denom as f64, clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64, old_cpu_info, }) } } pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { let mut total = 0; let new_cpu_info = match ProcessorCpuLoadInfo::new(port) { Some(cpu_info) => cpu_info, None => return 0., }; let cpu_count = std::cmp::min(self.old_cpu_info.cpu_count, new_cpu_info.cpu_count); unsafe { for i in 0..cpu_count { let new_load: &processor_cpu_load_info = &*new_cpu_info.cpu_load.offset(i as _); let old_load: &processor_cpu_load_info = &*self.old_cpu_info.cpu_load.offset(i as _); for (new, old) in new_load.cpu_ticks.iter().zip(old_load.cpu_ticks.iter()) { if new > old { total += new - old; } } } self.old_cpu_info = new_cpu_info; // Now we convert the ticks to nanoseconds (if the interval is less than // `MINIMUM_CPU_UPDATE_INTERVAL`, we replace it with it instead): let base_interval = total as f64 / cpu_count as f64 * self.clock_per_sec; let smallest = crate::System::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0; if base_interval < smallest { smallest } else { base_interval / self.timebase_to_ns } } } } #[cfg(test)] mod test { use super::*; /// Regression test for . #[test] fn test_getting_time_interval() { if !crate::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let port = unsafe { libc::mach_host_self() }; let mut info = SystemTimeInfo::new(port).unwrap(); info.get_time_interval(port); std::thread::sleep(crate::System::MINIMUM_CPU_UPDATE_INTERVAL.saturating_mul(5)); let val = info.get_time_interval(port); assert_ne!( val, crate::System::MINIMUM_CPU_UPDATE_INTERVAL.as_secs_f64() * 1_000_000_000.0 ); } } sysinfo-0.28.4/src/apple/macos/utils.rs000064400000000000000000000012721046102023000161130ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::num::NonZeroU32; type IoObject = NonZeroU32; pub(crate) struct IOReleaser(IoObject); impl IOReleaser { pub(crate) fn new(obj: u32) -> Option { IoObject::new(obj).map(Self) } pub(crate) unsafe fn new_unchecked(obj: u32) -> Self { // Chance at catching in-development mistakes debug_assert_ne!(obj, 0); Self(IoObject::new_unchecked(obj)) } #[inline] pub(crate) fn inner(&self) -> u32 { self.0.get() } } impl Drop for IOReleaser { fn drop(&mut self) { unsafe { super::ffi::IOObjectRelease(self.0.get() as _) }; } } sysinfo-0.28.4/src/apple/mod.rs000064400000000000000000000013171046102023000144300ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(target_os = "macos")] pub(crate) mod macos; #[cfg(target_os = "macos")] pub(crate) use self::macos as inner; #[cfg(target_os = "ios")] pub(crate) mod ios; #[cfg(target_os = "ios")] pub(crate) use self::ios as inner; #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] pub(crate) mod app_store; pub mod component; pub mod cpu; pub mod disk; mod ffi; pub mod network; pub mod process; pub mod system; pub mod users; mod utils; pub use self::component::Component; pub use self::cpu::Cpu; pub use self::disk::Disk; pub use self::network::{NetworkData, Networks}; pub use self::process::Process; pub use self::system::System; sysinfo-0.28.4/src/apple/network.rs000064400000000000000000000203501046102023000153400ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use libc::{self, c_char, if_msghdr2, CTL_NET, NET_RT_IFLIST2, PF_ROUTE, RTM_IFINFO2}; use std::collections::{hash_map, HashMap}; use std::ptr::null_mut; use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::{NetworkExt, NetworksExt, NetworksIter}; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $new_val; }}; } #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } impl Networks { pub(crate) fn new() -> Self { Networks { interfaces: HashMap::new(), } } #[allow(unknown_lints)] #[allow(clippy::cast_ptr_alignment)] #[allow(clippy::uninit_vec)] fn update_networks(&mut self) { let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; let mut len = 0; unsafe { if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, null_mut(), &mut len, null_mut(), 0, ) < 0 { // TODO: might be nice to put an error in here... return; } let mut buf = Vec::with_capacity(len); buf.set_len(len); if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, buf.as_mut_ptr(), &mut len, null_mut(), 0, ) < 0 { // TODO: might be nice to put an error in here... return; } let buf = buf.as_ptr() as *const c_char; let lim = buf.add(len); let mut next = buf; while next < lim { let ifm = next as *const libc::if_msghdr; next = next.offset((*ifm).ifm_msglen as isize); if (*ifm).ifm_type == RTM_IFINFO2 as u8 { // The interface (line description) name stored at ifname will be returned in // the default coded character set identifier (CCSID) currently in effect for // the job. If this is not a single byte CCSID, then storage greater than // IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs. let mut name = vec![0u8; libc::IFNAMSIZ + 6]; let if2m: *const if_msghdr2 = ifm as *const if_msghdr2; let pname = libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _); if pname.is_null() { continue; } name.set_len(libc::strlen(pname)); let name = String::from_utf8_unchecked(name); match self.interfaces.entry(name) { hash_map::Entry::Occupied(mut e) => { let mut interface = e.get_mut(); old_and_new!( interface, current_out, old_out, (*if2m).ifm_data.ifi_obytes ); old_and_new!( interface, current_in, old_in, (*if2m).ifm_data.ifi_ibytes ); old_and_new!( interface, packets_in, old_packets_in, (*if2m).ifm_data.ifi_ipackets ); old_and_new!( interface, packets_out, old_packets_out, (*if2m).ifm_data.ifi_opackets ); old_and_new!( interface, errors_in, old_errors_in, (*if2m).ifm_data.ifi_ierrors ); old_and_new!( interface, errors_out, old_errors_out, (*if2m).ifm_data.ifi_oerrors ); interface.updated = true; } hash_map::Entry::Vacant(e) => { let current_in = (*if2m).ifm_data.ifi_ibytes; let current_out = (*if2m).ifm_data.ifi_obytes; let packets_in = (*if2m).ifm_data.ifi_ipackets; let packets_out = (*if2m).ifm_data.ifi_opackets; let errors_in = (*if2m).ifm_data.ifi_ierrors; let errors_out = (*if2m).ifm_data.ifi_oerrors; e.insert(NetworkData { current_in, old_in: current_in, current_out, old_out: current_out, packets_in, old_packets_in: packets_in, packets_out, old_packets_out: packets_out, errors_in, old_errors_in: errors_in, errors_out, old_errors_out: errors_out, updated: true, mac_addr: MacAddr::UNSPECIFIED, }); } } } } } } } impl NetworksExt for Networks { #[allow(clippy::needless_lifetimes)] fn iter<'a>(&'a self) -> NetworksIter<'a> { NetworksIter::new(self.interfaces.iter()) } fn refresh_networks_list(&mut self) { for (_, data) in self.interfaces.iter_mut() { data.updated = false; } self.update_networks(); self.interfaces.retain(|_, data| data.updated); refresh_networks_addresses(&mut self.interfaces); } fn refresh(&mut self) { self.update_networks(); } } #[doc = include_str!("../../md_doc/network_data.md")] #[derive(PartialEq, Eq)] pub struct NetworkData { current_in: u64, old_in: u64, current_out: u64, old_out: u64, packets_in: u64, old_packets_in: u64, packets_out: u64, old_packets_out: u64, errors_in: u64, old_errors_in: u64, errors_out: u64, old_errors_out: u64, updated: bool, /// MAC address pub(crate) mac_addr: MacAddr, } impl NetworkExt for NetworkData { fn received(&self) -> u64 { self.current_in.saturating_sub(self.old_in) } fn total_received(&self) -> u64 { self.current_in } fn transmitted(&self) -> u64 { self.current_out.saturating_sub(self.old_out) } fn total_transmitted(&self) -> u64 { self.current_out } fn packets_received(&self) -> u64 { self.packets_in.saturating_sub(self.old_packets_in) } fn total_packets_received(&self) -> u64 { self.packets_in } fn packets_transmitted(&self) -> u64 { self.packets_out.saturating_sub(self.old_packets_out) } fn total_packets_transmitted(&self) -> u64 { self.packets_out } fn errors_on_received(&self) -> u64 { self.errors_in.saturating_sub(self.old_errors_in) } fn total_errors_on_received(&self) -> u64 { self.errors_in } fn errors_on_transmitted(&self) -> u64 { self.errors_out.saturating_sub(self.old_errors_out) } fn total_errors_on_transmitted(&self) -> u64 { self.errors_out } fn mac_address(&self) -> MacAddr { self.mac_addr } } sysinfo-0.28.4/src/apple/process.rs000064400000000000000000000044141046102023000153300ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fmt; pub use crate::sys::inner::process::*; use crate::ProcessStatus; #[doc(hidden)] impl From for ProcessStatus { fn from(status: u32) -> ProcessStatus { match status { 1 => ProcessStatus::Idle, 2 => ProcessStatus::Run, 3 => ProcessStatus::Sleep, 4 => ProcessStatus::Stop, 5 => ProcessStatus::Zombie, x => ProcessStatus::Unknown(x), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", _ => "Unknown", }) } } /// Enum describing the different status of a thread. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ThreadStatus { /// Thread is running normally. Running, /// Thread is stopped. Stopped, /// Thread is waiting normally. Waiting, /// Thread is in an uninterruptible wait Uninterruptible, /// Thread is halted at a clean point. Halted, /// Unknown. Unknown(i32), } impl From for ThreadStatus { fn from(status: i32) -> ThreadStatus { match status { 1 => ThreadStatus::Running, 2 => ThreadStatus::Stopped, 3 => ThreadStatus::Waiting, 4 => ThreadStatus::Uninterruptible, 5 => ThreadStatus::Halted, x => ThreadStatus::Unknown(x), } } } impl ThreadStatus { /// Used to display `ThreadStatus`. pub fn to_string(&self) -> &str { match *self { ThreadStatus::Running => "Running", ThreadStatus::Stopped => "Stopped", ThreadStatus::Waiting => "Waiting", ThreadStatus::Uninterruptible => "Uninterruptible", ThreadStatus::Halted => "Halted", ThreadStatus::Unknown(_) => "Unknown", } } } impl fmt::Display for ThreadStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.to_string()) } } sysinfo-0.28.4/src/apple/system.rs000064400000000000000000000520421046102023000151760ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::component::Component; use crate::sys::cpu::*; use crate::sys::disk::*; use crate::sys::network::Networks; use crate::sys::process::*; use crate::{ CpuExt, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, }; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use crate::ProcessExt; use std::cell::UnsafeCell; use std::collections::HashMap; use std::mem; use std::sync::Arc; use std::time::Duration; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use std::time::SystemTime; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] use libc::size_t; use libc::{ c_char, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, sysctlbyname, timeval, vm_statistics64, _SC_PAGESIZE, }; #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] use super::inner::component::Components; #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] declare_signals! { c_int, Signal::Hangup => libc::SIGHUP, Signal::Interrupt => libc::SIGINT, Signal::Quit => libc::SIGQUIT, Signal::Illegal => libc::SIGILL, Signal::Trap => libc::SIGTRAP, Signal::Abort => libc::SIGABRT, Signal::IOT => libc::SIGIOT, Signal::Bus => libc::SIGBUS, Signal::FloatingPointException => libc::SIGFPE, Signal::Kill => libc::SIGKILL, Signal::User1 => libc::SIGUSR1, Signal::Segv => libc::SIGSEGV, Signal::User2 => libc::SIGUSR2, Signal::Pipe => libc::SIGPIPE, Signal::Alarm => libc::SIGALRM, Signal::Term => libc::SIGTERM, Signal::Child => libc::SIGCHLD, Signal::Continue => libc::SIGCONT, Signal::Stop => libc::SIGSTOP, Signal::TSTP => libc::SIGTSTP, Signal::TTIN => libc::SIGTTIN, Signal::TTOU => libc::SIGTTOU, Signal::Urgent => libc::SIGURG, Signal::XCPU => libc::SIGXCPU, Signal::XFSZ => libc::SIGXFSZ, Signal::VirtualAlarm => libc::SIGVTALRM, Signal::Profiling => libc::SIGPROF, Signal::Winch => libc::SIGWINCH, Signal::IO => libc::SIGIO, // SIGPOLL doesn't exist on apple targets but since it's an equivalent of SIGIO on unix, // we simply use the SIGIO constant. Signal::Poll => libc::SIGIO, Signal::Sys => libc::SIGSYS, _ => None, } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] declare_signals! { c_int, _ => None, } #[doc = include_str!("../../md_doc/system.md")] pub struct System { process_list: HashMap, mem_total: u64, mem_free: u64, mem_used: u64, mem_available: u64, swap_total: u64, swap_free: u64, global_cpu: Cpu, cpus: Vec, page_size_kb: u64, #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] components: Components, disks: Vec, networks: Networks, port: mach_port_t, users: Vec, boot_time: u64, #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] clock_info: Option, got_cpu_frequency: bool, } pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap>); unsafe impl<'a> Send for Wrap<'a> {} unsafe impl<'a> Sync for Wrap<'a> {} fn boot_time() -> u64 { let mut boot_time = timeval { tv_sec: 0, tv_usec: 0, }; let mut len = std::mem::size_of::(); let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; unsafe { if sysctl( mib.as_mut_ptr(), mib.len() as _, &mut boot_time as *mut timeval as *mut _, &mut len, std::ptr::null_mut(), 0, ) < 0 { 0 } else { boot_time.tv_sec as _ } } } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } impl SystemExt for System { const IS_SUPPORTED: bool = true; const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200); fn new_with_specifics(refreshes: RefreshKind) -> System { unsafe { let port = libc::mach_host_self(); let mut s = System { process_list: HashMap::with_capacity(200), mem_total: 0, mem_free: 0, mem_available: 0, mem_used: 0, swap_total: 0, swap_free: 0, global_cpu: Cpu::new( "0".to_owned(), Arc::new(CpuData::new(std::ptr::null_mut(), 0)), 0, String::new(), String::new(), ), cpus: Vec::new(), page_size_kb: sysconf(_SC_PAGESIZE) as _, #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] components: Components::new(), disks: Vec::with_capacity(1), networks: Networks::new(), port, users: Vec::new(), boot_time: boot_time(), #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] clock_info: crate::sys::macos::system::SystemTimeInfo::new(port), got_cpu_frequency: false, }; s.refresh_specifics(refreshes); s } } fn refresh_memory(&mut self) { let mut mib = [0, 0]; unsafe { // get system values // get swap info let mut xs: libc::xsw_usage = mem::zeroed::(); if get_sys_value( libc::CTL_VM as _, libc::VM_SWAPUSAGE as _, mem::size_of::(), &mut xs as *mut _ as *mut c_void, &mut mib, ) { self.swap_total = xs.xsu_total; self.swap_free = xs.xsu_avail; } // get ram info if self.mem_total < 1 { get_sys_value( libc::CTL_HW as _, libc::HW_MEMSIZE as _, mem::size_of::(), &mut self.mem_total as *mut u64 as *mut c_void, &mut mib, ); } let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; let mut stat = mem::zeroed::(); if host_statistics64( self.port, libc::HOST_VM_INFO64, &mut stat as *mut vm_statistics64 as *mut _, &mut count, ) == libc::KERN_SUCCESS { // From the apple documentation: // // /* // * NB: speculative pages are already accounted for in "free_count", // * so "speculative_count" is the number of "free" pages that are // * used to hold data that was read speculatively from disk but // * haven't actually been used by anyone so far. // */ self.mem_available = u64::from(stat.free_count) .saturating_add(u64::from(stat.inactive_count)) .saturating_add(u64::from(stat.purgeable_count)) .saturating_sub(u64::from(stat.compressor_page_count)) .saturating_mul(self.page_size_kb); self.mem_used = u64::from(stat.active_count) .saturating_add(u64::from(stat.wire_count)) .saturating_add(u64::from(stat.compressor_page_count)) .saturating_add(u64::from(stat.speculative_count)) .saturating_mul(self.page_size_kb); self.mem_free = u64::from(stat.free_count) .saturating_sub(u64::from(stat.speculative_count)) .saturating_mul(self.page_size_kb); } } } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] fn refresh_components_list(&mut self) {} #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] fn refresh_components_list(&mut self) { self.components.refresh(); } fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { let cpus = &mut self.cpus; if cpus.is_empty() { init_cpus(self.port, cpus, &mut self.global_cpu, refresh_kind); self.got_cpu_frequency = refresh_kind.frequency(); return; } if refresh_kind.frequency() && !self.got_cpu_frequency { let frequency = get_cpu_frequency(); for proc_ in cpus.iter_mut() { proc_.set_frequency(frequency); } self.got_cpu_frequency = true; } if refresh_kind.cpu_usage() { update_cpu_usage(self.port, &mut self.global_cpu, |proc_data, cpu_info| { let mut percentage = 0f32; let mut offset = 0; for proc_ in cpus.iter_mut() { let cpu_usage = compute_usage_of_cpu(proc_, cpu_info, offset); proc_.update(cpu_usage, Arc::clone(&proc_data)); percentage += proc_.cpu_usage(); offset += libc::CPU_STATE_MAX as isize; } (percentage, cpus.len()) }); } } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { use crate::utils::into_iter; unsafe { let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); if count < 1 { return; } } if let Some(pids) = get_proc_list() { let now = get_now(); let arg_max = get_arg_max(); let port = self.port; let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); let entries: Vec = { let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; into_iter(pids) .flat_map(|pid| { match update_process( wrap, pid, arg_max as size_t, time_interval, now, refresh_kind, false, ) { Ok(x) => x, _ => None, } }) .collect() }; entries.into_iter().for_each(|entry| { self.process_list.insert(entry.pid(), entry); }); self.process_list .retain(|_, proc_| std::mem::replace(&mut proc_.updated, false)); } } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { false } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { let mut time_interval = None; let arg_max = get_arg_max(); let now = get_now(); if refresh_kind.cpu() { let port = self.port; time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); } match { let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); update_process( &wrap, pid, arg_max as size_t, time_interval, now, refresh_kind, true, ) } { Ok(Some(p)) => { self.process_list.insert(p.pid(), p); true } Ok(_) => true, Err(_) => false, } } fn refresh_disks_list(&mut self) { self.disks = unsafe { get_disks() }; } fn refresh_users_list(&mut self) { self.users = crate::apple::users::get_users_list(); } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. fn processes(&self) -> &HashMap { &self.process_list } fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } fn global_cpu_info(&self) -> &Cpu { &self.global_cpu } fn cpus(&self) -> &[Cpu] { &self.cpus } fn physical_core_count(&self) -> Option { let mut physical_core_count = 0; unsafe { if get_sys_value_by_name( b"hw.physicalcpu\0", &mut mem::size_of::(), &mut physical_core_count as *mut usize as *mut c_void, ) { Some(physical_core_count) } else { None } } } fn networks(&self) -> &Networks { &self.networks } fn networks_mut(&mut self) -> &mut Networks { &mut self.networks } fn total_memory(&self) -> u64 { self.mem_total } fn free_memory(&self) -> u64 { self.mem_free } fn available_memory(&self) -> u64 { self.mem_available } fn used_memory(&self) -> u64 { self.mem_used } fn total_swap(&self) -> u64 { self.swap_total } fn free_swap(&self) -> u64 { self.swap_free } // TODO: need to be checked fn used_swap(&self) -> u64 { self.swap_total - self.swap_free } #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] fn components(&self) -> &[Component] { &self.components.inner } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] fn components(&self) -> &[Component] { &[] } #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] fn components_mut(&mut self) -> &mut [Component] { &mut self.components.inner } #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] fn components_mut(&mut self) -> &mut [Component] { &mut [] } fn disks(&self) -> &[Disk] { &self.disks } fn disks_mut(&mut self) -> &mut [Disk] { &mut self.disks } fn sort_disks_by(&mut self, compare: F) where F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, { self.disks.sort_unstable_by(compare); } fn uptime(&self) -> u64 { unsafe { let csec = libc::time(::std::ptr::null_mut()); libc::difftime(csec, self.boot_time as _) as u64 } } fn load_average(&self) -> LoadAvg { let mut loads = vec![0f64; 3]; unsafe { libc::getloadavg(loads.as_mut_ptr(), 3); LoadAvg { one: loads[0], five: loads[1], fifteen: loads[2], } } } fn users(&self) -> &[User] { &self.users } fn boot_time(&self) -> u64 { self.boot_time } fn name(&self) -> Option { get_system_info(libc::KERN_OSTYPE, Some("Darwin")) } fn long_os_version(&self) -> Option { #[cfg(target_os = "macos")] let friendly_name = match self.os_version().unwrap_or_default() { f_n if f_n.starts_with("10.16") | f_n.starts_with("11.0") | f_n.starts_with("11.1") | f_n.starts_with("11.2") => { "Big Sur" } f_n if f_n.starts_with("10.15") => "Catalina", f_n if f_n.starts_with("10.14") => "Mojave", f_n if f_n.starts_with("10.13") => "High Sierra", f_n if f_n.starts_with("10.12") => "Sierra", f_n if f_n.starts_with("10.11") => "El Capitan", f_n if f_n.starts_with("10.10") => "Yosemite", f_n if f_n.starts_with("10.9") => "Mavericks", f_n if f_n.starts_with("10.8") => "Mountain Lion", f_n if f_n.starts_with("10.7") => "Lion", f_n if f_n.starts_with("10.6") => "Snow Leopard", f_n if f_n.starts_with("10.5") => "Leopard", f_n if f_n.starts_with("10.4") => "Tiger", f_n if f_n.starts_with("10.3") => "Panther", f_n if f_n.starts_with("10.2") => "Jaguar", f_n if f_n.starts_with("10.1") => "Puma", f_n if f_n.starts_with("10.0") => "Cheetah", _ => "", }; #[cfg(target_os = "macos")] let long_name = Some(format!( "MacOS {} {}", self.os_version().unwrap_or_default(), friendly_name )); #[cfg(target_os = "ios")] let long_name = Some(format!("iOS {}", self.os_version().unwrap_or_default())); long_name } fn host_name(&self) -> Option { get_system_info(libc::KERN_HOSTNAME, None) } fn kernel_version(&self) -> Option { get_system_info(libc::KERN_OSRELEASE, None) } fn os_version(&self) -> Option { unsafe { // get the size for the buffer first let mut size = 0; if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut()) && size > 0 { // now create a buffer with the size and get the real value let mut buf = vec![0_u8; size as _]; if get_sys_value_by_name( b"kern.osproductversion\0", &mut size, buf.as_mut_ptr() as *mut c_void, ) { if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes buf.resize(pos, 0); } String::from_utf8(buf).ok() } else { // getting the system value failed None } } else { // getting the system value failed, or did not return a buffer size None } } } fn distribution_id(&self) -> String { std::env::consts::OS.to_owned() } } impl Default for System { fn default() -> System { System::new() } } #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] fn get_arg_max() -> usize { let mut mib = [libc::CTL_KERN, libc::KERN_ARGMAX]; let mut arg_max = 0i32; let mut size = mem::size_of::(); unsafe { if sysctl( mib.as_mut_ptr(), mib.len() as _, (&mut arg_max) as *mut i32 as *mut c_void, &mut size, std::ptr::null_mut(), 0, ) == -1 { 4096 // We default to this value } else { arg_max as usize } } } pub(crate) unsafe fn get_sys_value( high: u32, low: u32, mut len: usize, value: *mut c_void, mib: &mut [i32; 2], ) -> bool { mib[0] = high as i32; mib[1] = low as i32; sysctl( mib.as_mut_ptr(), mib.len() as _, value, &mut len as *mut usize, std::ptr::null_mut(), 0, ) == 0 } unsafe fn get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool { sysctlbyname( name.as_ptr() as *const c_char, value, len, std::ptr::null_mut(), 0, ) == 0 } fn get_system_info(value: c_int, default: Option<&str>) -> Option { let mut mib: [c_int; 2] = [libc::CTL_KERN, value]; let mut size = 0; unsafe { // Call first to get size sysctl( mib.as_mut_ptr(), mib.len() as _, std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ); // exit early if we did not update the size if size == 0 { default.map(|s| s.to_owned()) } else { // set the buffer to the correct size let mut buf = vec![0_u8; size as _]; if sysctl( mib.as_mut_ptr(), mib.len() as _, buf.as_mut_ptr() as _, &mut size, std::ptr::null_mut(), 0, ) == -1 { // If command fails return default default.map(|s| s.to_owned()) } else { if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes buf.resize(pos, 0); } String::from_utf8(buf).ok() } } } } sysinfo-0.28.4/src/apple/users.rs000064400000000000000000000125041046102023000150120ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ common::{Gid, Uid}, User, }; use crate::sys::utils; use libc::{c_char, endpwent, getgrgid, getgrouplist, getpwent, gid_t, setpwent, strlen}; fn get_user_groups(name: *const c_char, group_id: gid_t) -> Vec { let mut add = 0; loop { let mut nb_groups = 256 + add; let mut groups = Vec::with_capacity(nb_groups as _); unsafe { if getgrouplist(name, group_id as _, groups.as_mut_ptr(), &mut nb_groups) == -1 { add += 100; continue; } groups.set_len(nb_groups as _); return groups .into_iter() .filter_map(|g| { let group = getgrgid(g as _); if group.is_null() { return None; } utils::cstr_to_rust((*group).gr_name) }) .collect(); } } } fn endswith(s1: *const c_char, s2: &[u8]) -> bool { if s1.is_null() { return false; } unsafe { let mut len = strlen(s1) as isize - 1; let mut i = s2.len() as isize - 1; while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as _ { i -= 1; len -= 1; } i == -1 } } fn users_list(filter: F) -> Vec where F: Fn(*const c_char, u32) -> bool, { let mut users = Vec::new(); unsafe { setpwent(); loop { let pw = getpwent(); if pw.is_null() { break; } if !filter((*pw).pw_shell, (*pw).pw_uid) { // This is not a "real" or "local" user. continue; } let groups = get_user_groups((*pw).pw_name, (*pw).pw_gid); let uid = (*pw).pw_uid; let gid = (*pw).pw_gid; if let Some(name) = utils::cstr_to_rust((*pw).pw_name) { users.push(User { uid: Uid(uid), gid: Gid(gid), name, groups, }); } } endpwent(); } users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); users.dedup_by(|a, b| a.name == b.name); users } pub(crate) fn get_users_list() -> Vec { users_list(|shell, uid| { !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536 }) } // This was the OSX-based solution. It provides enough information, but what a mess! // pub fn get_users_list() -> Vec { // let mut users = Vec::new(); // let node_name = b"/Local/Default\0"; // unsafe { // let node_name = ffi::CFStringCreateWithCStringNoCopy( // std::ptr::null_mut(), // node_name.as_ptr() as *const c_char, // ffi::kCFStringEncodingMacRoman, // ffi::kCFAllocatorNull as *mut c_void, // ); // let node_ref = ffi::ODNodeCreateWithName( // ffi::kCFAllocatorDefault, // ffi::kODSessionDefault, // node_name, // std::ptr::null_mut(), // ); // let query = ffi::ODQueryCreateWithNode( // ffi::kCFAllocatorDefault, // node_ref, // ffi::kODRecordTypeUsers as _, // kODRecordTypeGroups // std::ptr::null(), // 0, // std::ptr::null(), // std::ptr::null(), // 0, // std::ptr::null_mut(), // ); // if query.is_null() { // return users; // } // let results = ffi::ODQueryCopyResults( // query, // false as _, // std::ptr::null_mut(), // ); // let len = ffi::CFArrayGetCount(results); // for i in 0..len { // let name = match get_user_name(ffi::CFArrayGetValueAtIndex(results, i)) { // Some(n) => n, // None => continue, // }; // let groups = get_user_groups(&name); // users.push(User { name }); // } // ffi::CFRelease(results as *const c_void); // ffi::CFRelease(query as *const c_void); // ffi::CFRelease(node_ref as *const c_void); // ffi::CFRelease(node_name as *const c_void); // } // users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); // return users; // } // fn get_user_name(result: *const c_void) -> Option { // let user_name = ffi::ODRecordGetRecordName(result as _); // let ptr = ffi::CFStringGetCharactersPtr(user_name); // String::from_utf16(&if ptr.is_null() { // let len = ffi::CFStringGetLength(user_name); // It returns the len in UTF-16 code pairs. // if len == 0 { // continue; // } // let mut v = Vec::with_capacity(len as _); // for x in 0..len { // v.push(ffi::CFStringGetCharacterAtIndex(user_name, x)); // } // v // } else { // let mut v: Vec = Vec::new(); // let mut x = 0; // loop { // let letter = *ptr.offset(x); // if letter == 0 { // break; // } // v.push(letter); // x += 1; // } // v // }.ok() // } sysinfo-0.28.4/src/apple/utils.rs000064400000000000000000000041331046102023000150100ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use core_foundation_sys::base::CFRelease; use libc::c_char; use std::ptr::NonNull; // A helper using to auto release the resource got from CoreFoundation. // More information about the ownership policy for CoreFoundation pelease refer the link below: // https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-CJBEJBHH #[repr(transparent)] pub(crate) struct CFReleaser(NonNull); impl CFReleaser { pub(crate) fn new(ptr: *const T) -> Option { // This cast is OK because `NonNull` is a transparent wrapper // over a `*const T`. Additionally, mutability doesn't matter with // pointers here. NonNull::new(ptr as *mut T).map(Self) } pub(crate) fn inner(&self) -> *const T { self.0.as_ptr().cast() } } impl Drop for CFReleaser { fn drop(&mut self) { unsafe { CFRelease(self.0.as_ptr().cast()) } } } // Safety: These are safe to implement because we only wrap non-mutable // CoreFoundation types, which are generally threadsafe unless noted // otherwise. unsafe impl Send for CFReleaser {} unsafe impl Sync for CFReleaser {} pub(crate) fn cstr_to_rust(c: *const c_char) -> Option { cstr_to_rust_with_size(c, None) } pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option) -> Option { if c.is_null() { return None; } let mut s = match size { Some(len) => Vec::with_capacity(len), None => Vec::new(), }; let mut i = 0; unsafe { loop { let value = *c.offset(i) as u8; if value == 0 { break; } s.push(value); i += 1; } String::from_utf8(s).ok() } } pub(crate) fn vec_to_rust(buf: Vec) -> Option { String::from_utf8( buf.into_iter() .flat_map(|b| if b > 0 { Some(b as u8) } else { None }) .collect(), ) .ok() } sysinfo-0.28.4/src/c_interface.rs000064400000000000000000000351701046102023000150160ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{CpuExt, NetworkExt, NetworksExt, Pid, Process, ProcessExt, System, SystemExt}; use libc::{self, c_char, c_float, c_uint, c_void, pid_t, size_t}; use std::borrow::BorrowMut; use std::ffi::CString; /// Equivalent of [`System`][crate::System] struct. pub type CSystem = *mut c_void; /// Equivalent of [`Process`][crate::Process] struct. pub type CProcess = *const c_void; /// C string returned from `CString::into_raw`. pub type RString = *const c_char; /// Callback used by [`processes`][crate::System#method.processes]. pub type ProcessLoop = extern "C" fn(pid: pid_t, process: CProcess, data: *mut c_void) -> bool; /// Equivalent of [`System::new()`][crate::System#method.new]. #[no_mangle] pub extern "C" fn sysinfo_init() -> CSystem { let system = Box::new(System::new()); Box::into_raw(system) as CSystem } /// Equivalent of `System::drop()`. Important in C to cleanup memory. #[no_mangle] pub extern "C" fn sysinfo_destroy(system: CSystem) { assert!(!system.is_null()); unsafe { drop(Box::from_raw(system as *mut System)); } } /// Equivalent of [`System::refresh_system()`][crate::System#method.refresh_system]. #[no_mangle] pub extern "C" fn sysinfo_refresh_system(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_system(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_memory()`][crate::System#method.refresh_memory]. #[no_mangle] pub extern "C" fn sysinfo_refresh_memory(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_memory(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_cpu()`][crate::System#method.refresh_cpu]. #[no_mangle] pub extern "C" fn sysinfo_refresh_cpu(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_cpu(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_components()`][crate::System#method.refresh_temperatures]. #[no_mangle] pub extern "C" fn sysinfo_refresh_components(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_components(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_all()`][crate::System#method.refresh_all]. #[no_mangle] pub extern "C" fn sysinfo_refresh_all(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_all(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_processes()`][crate::System#method.refresh_processes]. #[no_mangle] pub extern "C" fn sysinfo_refresh_processes(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_processes(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_process()`][crate::System#method.refresh_process]. #[cfg(target_os = "linux")] #[no_mangle] pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: pid_t) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_process(Pid(pid)); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_disks()`][crate::System#method.refresh_disks]. #[no_mangle] pub extern "C" fn sysinfo_refresh_disks(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_disks(); } Box::into_raw(system); } } /// Equivalent of [`System::refresh_disks_list()`][crate::System#method.refresh_disks_list]. #[no_mangle] pub extern "C" fn sysinfo_refresh_disks_list(system: CSystem) { assert!(!system.is_null()); unsafe { let mut system: Box = Box::from_raw(system as *mut System); { let system: &mut System = system.borrow_mut(); system.refresh_disks_list(); } Box::into_raw(system); } } /// Equivalent of [`System::total_memory()`][crate::System#method.total_memory]. #[no_mangle] pub extern "C" fn sysinfo_total_memory(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.total_memory() as size_t; Box::into_raw(system); ret } } /// Equivalent of [`System::free_memory()`][crate::System#method.free_memory]. #[no_mangle] pub extern "C" fn sysinfo_free_memory(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.free_memory() as size_t; Box::into_raw(system); ret } } /// Equivalent of [`System::used_memory()`][crate::System#method.used_memory]. #[no_mangle] pub extern "C" fn sysinfo_used_memory(system: CSystem) -> size_t { assert!(!system.is_null()); let system: Box = unsafe { Box::from_raw(system as *mut System) }; let ret = system.used_memory() as size_t; Box::into_raw(system); ret } /// Equivalent of [`System::total_swap()`][crate::System#method.total_swap]. #[no_mangle] pub extern "C" fn sysinfo_total_swap(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.total_swap() as size_t; Box::into_raw(system); ret } } /// Equivalent of [`System::free_swap()`][crate::System#method.free_swap]. #[no_mangle] pub extern "C" fn sysinfo_free_swap(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.free_swap() as size_t; Box::into_raw(system); ret } } /// Equivalent of [`System::used_swap()`][crate::System#method.used_swap]. #[no_mangle] pub extern "C" fn sysinfo_used_swap(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.used_swap() as size_t; Box::into_raw(system); ret } } /// Equivalent of /// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.received() as size_t)`. #[no_mangle] pub extern "C" fn sysinfo_networks_received(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.networks().iter().fold(0, |acc: size_t, (_, data)| { acc.saturating_add(data.received() as size_t) }); Box::into_raw(system); ret } } /// Equivalent of /// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.transmitted() as size_t)`. #[no_mangle] pub extern "C" fn sysinfo_networks_transmitted(system: CSystem) -> size_t { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = system.networks().iter().fold(0, |acc: size_t, (_, data)| { acc.saturating_add(data.transmitted() as size_t) }); Box::into_raw(system); ret } } /// Equivalent of [`System::cpus_usage()`][crate::System#method.cpus_usage]. /// /// * `length` will contain the number of CPU usage added into `procs`. /// * `procs` will be allocated if it's null and will contain of CPU usage. #[no_mangle] pub extern "C" fn sysinfo_cpus_usage( system: CSystem, length: *mut c_uint, procs: *mut *mut c_float, ) { assert!(!system.is_null()); if procs.is_null() || length.is_null() { return; } unsafe { let system: Box = Box::from_raw(system as *mut System); { let cpus = system.cpus(); if (*procs).is_null() { (*procs) = libc::malloc(::std::mem::size_of::() * cpus.len()) as *mut c_float; } for (pos, cpu) in cpus.iter().skip(1).enumerate() { (*(*procs).offset(pos as isize)) = cpu.cpu_usage(); } *length = cpus.len() as c_uint - 1; } Box::into_raw(system); } } /// Equivalent of [`System::processes()`][crate::System#method.processes]. Returns an /// array ended by a null pointer. Must be freed. /// /// # ⚠️ WARNING ⚠️ /// /// While having this method returned processes, you should *never* call any refresh method! #[no_mangle] pub extern "C" fn sysinfo_processes( system: CSystem, fn_pointer: Option, data: *mut c_void, ) -> size_t { assert!(!system.is_null()); if let Some(fn_pointer) = fn_pointer { unsafe { let system: Box = Box::from_raw(system as *mut System); let len = { let entries = system.processes(); for (pid, process) in entries { if !fn_pointer(pid.0, process as *const Process as CProcess, data) { break; } } entries.len() as size_t }; Box::into_raw(system); len } } else { 0 } } /// Equivalent of [`System::process()`][crate::System#method.process]. /// /// # ⚠️ WARNING ⚠️ /// /// While having this method returned process, you should *never* call any /// refresh method! #[no_mangle] pub extern "C" fn sysinfo_process_by_pid(system: CSystem, pid: pid_t) -> CProcess { assert!(!system.is_null()); unsafe { let system: Box = Box::from_raw(system as *mut System); let ret = if let Some(process) = system.process(Pid(pid)) { process as *const Process as CProcess } else { std::ptr::null() }; Box::into_raw(system); ret } } /// Equivalent of iterating over [`Process::tasks()`][crate::Process#method.tasks]. /// /// # ⚠️ WARNING ⚠️ /// /// While having this method processes, you should *never* call any refresh method! #[cfg(target_os = "linux")] #[no_mangle] pub extern "C" fn sysinfo_process_tasks( process: CProcess, fn_pointer: Option, data: *mut c_void, ) -> size_t { assert!(!process.is_null()); if let Some(fn_pointer) = fn_pointer { unsafe { let process = process as *const Process; for (pid, process) in (*process).tasks.iter() { if !fn_pointer(pid.0, process as *const Process as CProcess, data) { break; } } (*process).tasks.len() as size_t } } else { 0 } } /// Equivalent of [`Process::pid()`][crate::Process#method.pid]. #[no_mangle] pub extern "C" fn sysinfo_process_pid(process: CProcess) -> pid_t { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).pid().0 } } /// Equivalent of [`Process::parent()`][crate::Process#method.parent]. /// /// In case there is no known parent, it returns `0`. #[no_mangle] pub extern "C" fn sysinfo_process_parent_pid(process: CProcess) -> pid_t { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).parent().unwrap_or(Pid(0)).0 } } /// Equivalent of [`Process::cpu_usage()`][crate::Process#method.cpu_usage]. #[no_mangle] pub extern "C" fn sysinfo_process_cpu_usage(process: CProcess) -> c_float { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).cpu_usage() } } /// Equivalent of [`Process::memory()`][crate::Process#method.memory]. #[no_mangle] pub extern "C" fn sysinfo_process_memory(process: CProcess) -> size_t { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).memory() as usize } } /// Equivalent of [`Process::virtual_memory()`][crate::Process#method.virtual_memory]. #[no_mangle] pub extern "C" fn sysinfo_process_virtual_memory(process: CProcess) -> size_t { assert!(!process.is_null()); let process = process as *const Process; unsafe { (*process).virtual_memory() as usize } } /// Equivalent of [`Process::exe()`][crate::Process#method.exe]. #[no_mangle] pub extern "C" fn sysinfo_process_executable_path(process: CProcess) -> RString { assert!(!process.is_null()); let process = process as *const Process; unsafe { if let Some(p) = (*process).exe().to_str() { if let Ok(c) = CString::new(p) { return c.into_raw() as _; } } std::ptr::null() } } /// Equivalent of [`Process::root()`][crate::Process#method.root]. #[no_mangle] pub extern "C" fn sysinfo_process_root_directory(process: CProcess) -> RString { assert!(!process.is_null()); let process = process as *const Process; unsafe { if let Some(p) = (*process).root().to_str() { if let Ok(c) = CString::new(p) { return c.into_raw() as _; } } std::ptr::null() } } /// Equivalent of [`Process::cwd()`][crate::Process#method.cwd]. #[no_mangle] pub extern "C" fn sysinfo_process_current_directory(process: CProcess) -> RString { assert!(!process.is_null()); let process = process as *const Process; unsafe { if let Some(p) = (*process).cwd().to_str() { if let Ok(c) = CString::new(p) { return c.into_raw() as _; } } std::ptr::null() } } /// Frees a C string created with `CString::into_raw()`. #[no_mangle] pub extern "C" fn sysinfo_rstring_free(s: RString) { if !s.is_null() { unsafe { let _ = CString::from_raw(s as usize as *mut _); } } } sysinfo-0.28.4/src/common.rs000064400000000000000000000703311046102023000140420ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{NetworkData, Networks, NetworksExt, UserExt}; use std::convert::{From, TryFrom}; use std::fmt; use std::str::FromStr; /// Trait to have a common conversions for the [`Pid`][crate::Pid] type. /// /// ``` /// use sysinfo::{Pid, PidExt}; /// /// let p = Pid::from_u32(0); /// let value: u32 = p.as_u32(); /// ``` pub trait PidExt: Copy + From + FromStr + fmt::Display { /// Allows to convert [`Pid`][crate::Pid] into [`u32`]. /// /// ``` /// use sysinfo::{Pid, PidExt}; /// /// let p = Pid::from_u32(0); /// let value: u32 = p.as_u32(); /// ``` fn as_u32(self) -> u32; /// Allows to convert a [`u32`] into [`Pid`][crate::Pid]. /// /// ``` /// use sysinfo::{Pid, PidExt}; /// /// let p = Pid::from_u32(0); /// ``` fn from_u32(v: u32) -> Self; } macro_rules! pid_decl { ($typ:ty) => { #[doc = include_str!("../md_doc/pid.md")] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct Pid(pub(crate) $typ); impl From for Pid { fn from(v: usize) -> Self { Self(v as _) } } impl From for usize { fn from(v: Pid) -> Self { v.0 as _ } } impl PidExt for Pid { fn as_u32(self) -> u32 { self.0 as _ } fn from_u32(v: u32) -> Self { Self(v as _) } } impl FromStr for Pid { type Err = <$typ as FromStr>::Err; fn from_str(s: &str) -> Result { Ok(Self(<$typ>::from_str(s)?)) } } impl fmt::Display for Pid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } }; } cfg_if::cfg_if! { if #[cfg(all( not(feature = "unknown-ci"), any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", ) ))] { use libc::pid_t; pid_decl!(pid_t); } else { pid_decl!(usize); } } macro_rules! impl_get_set { ($ty_name:ident, $name:ident, $with:ident, $without:ident $(, $extra_doc:literal)? $(,)?) => { #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")] $(#[doc = concat!(" ", $extra_doc, " ")])? #[doc = concat!(" ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::new(); assert_eq!(r.", stringify!($name), "(), false); let r = r.with_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), false); ```")] pub fn $name(&self) -> bool { self.$name } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`. ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::new(); assert_eq!(r.", stringify!($name), "(), false); let r = r.with_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), true); ```")] #[must_use] pub fn $with(mut self) -> Self { self.$name = true; self } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`. ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::everything(); assert_eq!(r.", stringify!($name), "(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "(), false); ```")] #[must_use] pub fn $without(mut self) -> Self { self.$name = false; self } }; ($ty_name:ident, $name:ident, $with:ident, $without:ident, $typ:ty $(,)?) => { #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind. ``` use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "}; let r = ", stringify!($ty_name), "::new(); assert_eq!(r.", stringify!($name), "().is_some(), false); let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything()); assert_eq!(r.", stringify!($name), "().is_some(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "().is_some(), false); ```")] pub fn $name(&self) -> Option<$typ> { self.$name } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`. ``` use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "}; let r = ", stringify!($ty_name), "::new(); assert_eq!(r.", stringify!($name), "().is_some(), false); let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything()); assert_eq!(r.", stringify!($name), "().is_some(), true); ```")] #[must_use] pub fn $with(mut self, kind: $typ) -> Self { self.$name = Some(kind); self } #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`. ``` use sysinfo::", stringify!($ty_name), "; let r = ", stringify!($ty_name), "::everything(); assert_eq!(r.", stringify!($name), "().is_some(), true); let r = r.without_", stringify!($name), "(); assert_eq!(r.", stringify!($name), "().is_some(), false); ```")] #[must_use] pub fn $without(mut self) -> Self { self.$name = None; self } }; } /// Used to determine what you want to refresh specifically on the [`Process`] type. /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ``` /// use sysinfo::{ProcessExt, ProcessRefreshKind, System, SystemExt}; /// /// let mut system = System::new(); /// /// // We don't want to update the CPU information. /// system.refresh_processes_specifics(ProcessRefreshKind::everything().without_cpu()); /// /// for (_, proc_) in system.processes() { /// // We use a `==` comparison on float only because we know it's set to 0 here. /// assert_eq!(proc_.cpu_usage(), 0.); /// } /// ``` /// /// [`Process`]: crate::Process #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ProcessRefreshKind { cpu: bool, disk_usage: bool, user: bool, } impl ProcessRefreshKind { /// Creates a new `ProcessRefreshKind` with every refresh set to `false`. /// /// ``` /// use sysinfo::ProcessRefreshKind; /// /// let r = ProcessRefreshKind::new(); /// /// assert_eq!(r.cpu(), false); /// assert_eq!(r.disk_usage(), false); /// ``` pub fn new() -> Self { Self::default() } /// Creates a new `ProcessRefreshKind` with every refresh set to `true`. /// /// ``` /// use sysinfo::ProcessRefreshKind; /// /// let r = ProcessRefreshKind::everything(); /// /// assert_eq!(r.cpu(), true); /// assert_eq!(r.disk_usage(), true); /// ``` pub fn everything() -> Self { Self { cpu: true, disk_usage: true, user: true, } } impl_get_set!(ProcessRefreshKind, cpu, with_cpu, without_cpu); impl_get_set!( ProcessRefreshKind, disk_usage, with_disk_usage, without_disk_usage ); impl_get_set!( ProcessRefreshKind, user, with_user, without_user, r#"This refresh is about `user_id` and `group_id`. Please note that it has an effect mostly on Windows as other platforms get this information alongside the Process information directly."#, ); } /// Used to determine what you want to refresh specifically on the [`Cpu`] type. /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ``` /// use sysinfo::{CpuExt, CpuRefreshKind, System, SystemExt}; /// /// let mut system = System::new(); /// /// // We don't want to update all the CPU information. /// system.refresh_cpu_specifics(CpuRefreshKind::everything().without_frequency()); /// /// for cpu in system.cpus() { /// assert_eq!(cpu.frequency(), 0); /// } /// ``` /// /// [`Cpu`]: crate::Cpu #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct CpuRefreshKind { cpu_usage: bool, frequency: bool, } impl CpuRefreshKind { /// Creates a new `CpuRefreshKind` with every refresh set to `false`. /// /// ``` /// use sysinfo::CpuRefreshKind; /// /// let r = CpuRefreshKind::new(); /// /// assert_eq!(r.frequency(), false); /// ``` pub fn new() -> Self { Self::default() } /// Creates a new `CpuRefreshKind` with every refresh set to `true`. /// /// ``` /// use sysinfo::CpuRefreshKind; /// /// let r = CpuRefreshKind::everything(); /// /// assert_eq!(r.frequency(), true); /// ``` pub fn everything() -> Self { Self { cpu_usage: true, frequency: true, } } impl_get_set!(CpuRefreshKind, cpu_usage, with_cpu_usage, without_cpu_usage); impl_get_set!(CpuRefreshKind, frequency, with_frequency, without_frequency); } /// Used to determine what you want to refresh specifically on the [`System`] type. /// /// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that /// the information won't be retrieved if the information is accessible without needing /// extra computation. /// /// ``` /// use sysinfo::{RefreshKind, System, SystemExt}; /// /// // We want everything except disks. /// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); /// /// assert_eq!(system.disks().len(), 0); /// # if System::IS_SUPPORTED && !cfg!(feature = "apple-sandbox") { /// assert!(system.processes().len() > 0); /// # } /// ``` /// /// [`System`]: crate::System #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct RefreshKind { networks: bool, networks_list: bool, processes: Option, disks_list: bool, disks: bool, memory: bool, cpu: Option, components: bool, components_list: bool, users_list: bool, } impl RefreshKind { /// Creates a new `RefreshKind` with every refresh set to `false`/`None`. /// /// ``` /// use sysinfo::RefreshKind; /// /// let r = RefreshKind::new(); /// /// assert_eq!(r.networks(), false); /// assert_eq!(r.networks_list(), false); /// assert_eq!(r.processes().is_some(), false); /// assert_eq!(r.disks_list(), false); /// assert_eq!(r.disks(), false); /// assert_eq!(r.memory(), false); /// assert_eq!(r.cpu().is_some(), false); /// assert_eq!(r.components(), false); /// assert_eq!(r.components_list(), false); /// assert_eq!(r.users_list(), false); /// ``` pub fn new() -> Self { Self::default() } /// Creates a new `RefreshKind` with every refresh set to `true`/`Some(...)`. /// /// ``` /// use sysinfo::RefreshKind; /// /// let r = RefreshKind::everything(); /// /// assert_eq!(r.networks(), true); /// assert_eq!(r.networks_list(), true); /// assert_eq!(r.processes().is_some(), true); /// assert_eq!(r.disks_list(), true); /// assert_eq!(r.disks(), true); /// assert_eq!(r.memory(), true); /// assert_eq!(r.cpu().is_some(), true); /// assert_eq!(r.components(), true); /// assert_eq!(r.components_list(), true); /// assert_eq!(r.users_list(), true); /// ``` pub fn everything() -> Self { Self { networks: true, networks_list: true, processes: Some(ProcessRefreshKind::everything()), disks: true, disks_list: true, memory: true, cpu: Some(CpuRefreshKind::everything()), components: true, components_list: true, users_list: true, } } impl_get_set!( RefreshKind, processes, with_processes, without_processes, ProcessRefreshKind ); impl_get_set!(RefreshKind, networks, with_networks, without_networks); impl_get_set!( RefreshKind, networks_list, with_networks_list, without_networks_list ); impl_get_set!(RefreshKind, disks, with_disks, without_disks); impl_get_set!(RefreshKind, disks_list, with_disks_list, without_disks_list); impl_get_set!(RefreshKind, memory, with_memory, without_memory); impl_get_set!(RefreshKind, cpu, with_cpu, without_cpu, CpuRefreshKind); impl_get_set!(RefreshKind, components, with_components, without_components); impl_get_set!( RefreshKind, components_list, with_components_list, without_components_list ); impl_get_set!(RefreshKind, users_list, with_users_list, without_users_list); } /// Iterator over network interfaces. /// /// It is returned by [`Networks::iter`][crate::Networks#method.iter]. /// /// ```no_run /// use sysinfo::{System, SystemExt, NetworksExt}; /// /// let system = System::new_all(); /// let networks_iter = system.networks().iter(); /// ``` pub struct NetworksIter<'a> { inner: std::collections::hash_map::Iter<'a, String, NetworkData>, } impl<'a> NetworksIter<'a> { pub(crate) fn new(v: std::collections::hash_map::Iter<'a, String, NetworkData>) -> Self { NetworksIter { inner: v } } } impl<'a> Iterator for NetworksIter<'a> { type Item = (&'a String, &'a NetworkData); fn next(&mut self) -> Option { self.inner.next() } } impl<'a> IntoIterator for &'a Networks { type Item = (&'a String, &'a NetworkData); type IntoIter = NetworksIter<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } /// Enum containing the different supported disks types. /// /// This type is returned by [`Disk::get_type`][crate::Disk#method.type]. /// /// ```no_run /// use sysinfo::{System, SystemExt, DiskExt}; /// /// let system = System::new_all(); /// for disk in system.disks() { /// println!("{:?}: {:?}", disk.name(), disk.type_()); /// } /// ``` #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum DiskType { /// HDD type. HDD, /// SSD type. SSD, /// Unknown type. Unknown(isize), } /// An enum representing signals on UNIX-like systems. /// /// On non-unix systems, this enum is mostly useless and is only there to keep coherency between /// the different OSes. /// /// If you want the list of the supported signals on the current system, use /// [`SystemExt::SUPPORTED_SIGNALS`][crate::SystemExt::SUPPORTED_SIGNALS]. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug)] pub enum Signal { /// Hangup detected on controlling terminal or death of controlling process. Hangup, /// Interrupt from keyboard. Interrupt, /// Quit from keyboard. Quit, /// Illegal instruction. Illegal, /// Trace/breakpoint trap. Trap, /// Abort signal from C abort function. Abort, /// IOT trap. A synonym for SIGABRT. IOT, /// Bus error (bad memory access). Bus, /// Floating point exception. FloatingPointException, /// Kill signal. Kill, /// User-defined signal 1. User1, /// Invalid memory reference. Segv, /// User-defined signal 2. User2, /// Broken pipe: write to pipe with no readers. Pipe, /// Timer signal from C alarm function. Alarm, /// Termination signal. Term, /// Child stopped or terminated. Child, /// Continue if stopped. Continue, /// Stop process. Stop, /// Stop typed at terminal. TSTP, /// Terminal input for background process. TTIN, /// Terminal output for background process. TTOU, /// Urgent condition on socket. Urgent, /// CPU time limit exceeded. XCPU, /// File size limit exceeded. XFSZ, /// Virtual alarm clock. VirtualAlarm, /// Profiling time expired. Profiling, /// Windows resize signal. Winch, /// I/O now possible. IO, /// Pollable event (Sys V). Synonym for IO Poll, /// Power failure (System V). /// /// Doesn't exist on apple systems so will be ignored. Power, /// Bad argument to routine (SVr4). Sys, } impl std::fmt::Display for Signal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match *self { Self::Hangup => "Hangup", Self::Interrupt => "Interrupt", Self::Quit => "Quit", Self::Illegal => "Illegal", Self::Trap => "Trap", Self::Abort => "Abort", Self::IOT => "IOT", Self::Bus => "Bus", Self::FloatingPointException => "FloatingPointException", Self::Kill => "Kill", Self::User1 => "User1", Self::Segv => "Segv", Self::User2 => "User2", Self::Pipe => "Pipe", Self::Alarm => "Alarm", Self::Term => "Term", Self::Child => "Child", Self::Continue => "Continue", Self::Stop => "Stop", Self::TSTP => "TSTP", Self::TTIN => "TTIN", Self::TTOU => "TTOU", Self::Urgent => "Urgent", Self::XCPU => "XCPU", Self::XFSZ => "XFSZ", Self::VirtualAlarm => "VirtualAlarm", Self::Profiling => "Profiling", Self::Winch => "Winch", Self::IO => "IO", Self::Poll => "Poll", Self::Power => "Power", Self::Sys => "Sys", }; f.write_str(s) } } /// A struct representing system load average value. /// /// It is returned by [`SystemExt::load_average`][crate::SystemExt::load_average]. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// let load_avg = s.load_average(); /// println!( /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", /// load_avg.one, /// load_avg.five, /// load_avg.fifteen, /// ); /// ``` #[repr(C)] #[derive(Default, Debug, Clone)] pub struct LoadAvg { /// Average load within one minute. pub one: f64, /// Average load within five minutes. pub five: f64, /// Average load within fifteen minutes. pub fifteen: f64, } macro_rules! xid { ($(#[$outer:meta])+ $name:ident, $type:ty $(, $trait:ty)?) => { $(#[$outer])+ #[repr(transparent)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct $name(pub(crate) $type); impl std::ops::Deref for $name { type Target = $type; fn deref(&self) -> &Self::Target { &self.0 } } $( impl TryFrom for $name { type Error = <$type as TryFrom>::Error; fn try_from(t: usize) -> Result>::Error> { Ok(Self(<$type>::try_from(t)?)) } } impl $trait for $name { type Err = <$type as FromStr>::Err; fn from_str(t: &str) -> Result::Err> { Ok(Self(<$type>::from_str(t)?)) } } )? }; } macro_rules! uid { ($type:ty$(, $trait:ty)?) => { xid!( /// A user id wrapping a platform specific type. Uid, $type $(, $trait)? ); }; } macro_rules! gid { ($type:ty) => { xid!( /// A group id wrapping a platform specific type. #[derive(Copy)] Gid, $type, FromStr ); }; } cfg_if::cfg_if! { if #[cfg(all( not(feature = "unknown-ci"), any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", ) ))] { uid!(libc::uid_t, FromStr); gid!(libc::gid_t); } else if #[cfg(windows)] { uid!(crate::windows::Sid); gid!(u32); // Manual implementation outside of the macro... impl FromStr for Uid { type Err = ::Err; fn from_str(t: &str) -> Result { Ok(Self(t.parse()?)) } } } else { uid!(u32, FromStr); gid!(u32); } } /// Type containing user information. /// /// It is returned by [`SystemExt::users`][crate::SystemExt::users]. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("users: {:?}", s.users()); /// ``` #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct User { pub(crate) uid: Uid, pub(crate) gid: Gid, pub(crate) name: String, pub(crate) groups: Vec, } impl UserExt for User { fn id(&self) -> &Uid { &self.uid } fn group_id(&self) -> Gid { self.gid } fn name(&self) -> &str { &self.name } fn groups(&self) -> &[String] { &self.groups } } /// Type containing read and written bytes. /// /// It is returned by [`ProcessExt::disk_usage`][crate::ProcessExt::disk_usage]. /// /// ```no_run /// use sysinfo::{ProcessExt, System, SystemExt}; /// /// let s = System::new_all(); /// for (pid, process) in s.processes() { /// let disk_usage = process.disk_usage(); /// println!("[{}] read bytes : new/total => {}/{} B", /// pid, /// disk_usage.read_bytes, /// disk_usage.total_read_bytes, /// ); /// println!("[{}] written bytes: new/total => {}/{} B", /// pid, /// disk_usage.written_bytes, /// disk_usage.total_written_bytes, /// ); /// } /// ``` #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd)] pub struct DiskUsage { /// Total number of written bytes. pub total_written_bytes: u64, /// Number of written bytes since the last refresh. pub written_bytes: u64, /// Total number of read bytes. pub total_read_bytes: u64, /// Number of read bytes since the last refresh. pub read_bytes: u64, } /// Enum describing the different status of a process. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ProcessStatus { /// ## Linux /// /// Idle kernel thread. /// /// ## macOs/FreeBSD /// /// Process being created by fork. /// /// ## Other OS /// /// Not available. Idle, /// Running. Run, /// ## Linux /// /// Sleeping in an interruptible waiting. /// /// ## macOS/FreeBSD /// /// Sleeping on an address. /// /// ## Other OS /// /// Not available. Sleep, /// ## Linux /// /// Stopped (on a signal) or (before Linux 2.6.33) trace stopped. /// /// ## macOS/FreeBSD /// /// Process debugging or suspension. /// /// ## Other OS /// /// Not available. Stop, /// ## Linux/FreeBSD/macOS /// /// Zombie process. Terminated but not reaped by its parent. /// /// ## Other OS /// /// Not available. Zombie, /// ## Linux /// /// Tracing stop (Linux 2.6.33 onward). Stopped by debugger during the tracing. /// /// ## Other OS /// /// Not available. Tracing, /// ## Linux /// /// Dead/uninterruptible sleep (usually IO). /// /// ## FreeBSD /// /// A process should never end up in this state. /// /// ## Other OS /// /// Not available. Dead, /// ## Linux /// /// Wakekill (Linux 2.6.33 to 3.13 only). /// /// ## Other OS /// /// Not available. Wakekill, /// ## Linux /// /// Waking (Linux 2.6.33 to 3.13 only). /// /// ## Other OS /// /// Not available. Waking, /// ## Linux /// /// Parked (Linux 3.9 to 3.13 only). /// /// ## Other OS /// /// Not available. Parked, /// ## FreeBSD /// /// Blocked on a lock. /// /// ## Other OS /// /// Not available. LockBlocked, /// ## Linux /// /// Waiting in uninterruptible disk sleep. /// /// ## Other OS /// /// Not available. UninterruptibleDiskSleep, /// Unknown. Unknown(u32), } /// Returns the pid for the current process. /// /// `Err` is returned in case the platform isn't supported. /// /// ```no_run /// use sysinfo::get_current_pid; /// /// match get_current_pid() { /// Ok(pid) => { /// println!("current pid: {}", pid); /// } /// Err(e) => { /// eprintln!("failed to get current pid: {}", e); /// } /// } /// ``` #[allow(clippy::unnecessary_wraps)] pub fn get_current_pid() -> Result { cfg_if::cfg_if! { if #[cfg(feature = "unknown-ci")] { fn inner() -> Result { Err("Unknown platform (CI)") } } else if #[cfg(any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", ))] { fn inner() -> Result { unsafe { Ok(Pid(libc::getpid())) } } } else if #[cfg(windows)] { fn inner() -> Result { use winapi::um::processthreadsapi::GetCurrentProcessId; unsafe { Ok(Pid(GetCurrentProcessId() as _)) } } } else { fn inner() -> Result { Err("Unknown platform") } } } inner() } /// MAC address for network interface. /// /// It is returned by [`NetworkExt::mac_address`][crate::NetworkExt::mac_address]. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct MacAddr(pub [u8; 6]); impl MacAddr { /// A `MacAddr` with all bytes set to `0`. pub const UNSPECIFIED: Self = MacAddr([0; 6]); /// Checks if this `MacAddr` has all bytes equal to `0`. pub fn is_unspecified(&self) -> bool { self == &MacAddr::UNSPECIFIED } } impl fmt::Display for MacAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let data = &self.0; write!( f, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", data[0], data[1], data[2], data[3], data[4], data[5], ) } } #[cfg(test)] mod tests { use super::{MacAddr, ProcessStatus}; // This test only exists to ensure that the `Display` and `Debug` traits are implemented on the // `ProcessStatus` enum on all targets. #[test] fn check_display_impl_process_status() { println!("{} {:?}", ProcessStatus::Parked, ProcessStatus::Idle); } // Ensure that the `Display` and `Debug` traits are implemented on the `MacAddr` struct #[test] fn check_display_impl_mac_address() { println!( "{} {:?}", MacAddr([0x1, 0x2, 0x3, 0x4, 0x5, 0x6]), MacAddr([0xa, 0xb, 0xc, 0xd, 0xe, 0xf]) ); } #[test] fn check_mac_address_is_unspecified_true() { assert!(MacAddr::UNSPECIFIED.is_unspecified()); assert!(MacAddr([0; 6]).is_unspecified()); } #[test] fn check_mac_address_is_unspecified_false() { assert!(!MacAddr([1, 2, 3, 4, 5, 6]).is_unspecified()); } // This test exists to ensure that the `TryFrom` and `FromStr` traits are implemented // on `Uid`, `Gid` and `Pid`. #[test] fn check_uid_gid_from_impls() { use std::convert::TryFrom; use std::str::FromStr; #[cfg(not(windows))] { assert!(crate::Uid::try_from(0usize).is_ok()); assert!(crate::Uid::from_str("0").is_ok()); } #[cfg(windows)] { assert!(crate::Uid::from_str("S-1-5-18").is_ok()); // SECURITY_LOCAL_SYSTEM_RID assert!(crate::Uid::from_str("0").is_err()); } assert!(crate::Gid::try_from(0usize).is_ok()); assert!(crate::Gid::from_str("0").is_ok()); assert!(crate::Pid::try_from(0usize).is_ok()); // If it doesn't panic, it's fine. let _ = crate::Pid::from(0); assert!(crate::Pid::from_str("0").is_ok()); } } sysinfo-0.28.4/src/debug.rs000064400000000000000000000111201046102023000136270ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ Component, ComponentExt, Cpu, CpuExt, Disk, DiskExt, NetworkData, NetworkExt, Networks, NetworksExt, Process, ProcessExt, System, SystemExt, }; use std::fmt; impl fmt::Debug for Cpu { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Cpu") .field("name", &self.name()) .field("CPU usage", &self.cpu_usage()) .field("frequency", &self.frequency()) .field("vendor ID", &self.vendor_id()) .field("brand", &self.brand()) .finish() } } impl fmt::Debug for System { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("System") .field("global CPU usage", &self.global_cpu_info().cpu_usage()) .field("load average", &self.load_average()) .field("total memory", &self.total_memory()) .field("free memory", &self.free_memory()) .field("total swap", &self.total_swap()) .field("free swap", &self.free_swap()) .field("nb CPUs", &self.cpus().len()) .field("nb network interfaces", &self.networks().iter().count()) .field("nb processes", &self.processes().len()) .field("nb disks", &self.disks().len()) .field("nb components", &self.components().len()) .finish() } } impl fmt::Debug for Disk { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!( fmt, "Disk({:?})[FS: {:?}][Type: {:?}][removable: {}] mounted on {:?}: {}/{} B", self.name(), self.file_system() .iter() .map(|c| *c as char) .collect::>(), self.type_(), if self.is_removable() { "yes" } else { "no" }, self.mount_point(), self.available_space(), self.total_space(), ) } } impl fmt::Debug for Process { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Process") .field("pid", &self.pid()) .field("parent", &self.parent()) .field("name", &self.name()) .field("environ", &self.environ()) .field("command", &self.cmd()) .field("executable path", &self.exe()) .field("current working directory", &self.cwd()) .field("memory usage", &self.memory()) .field("virtual memory usage", &self.virtual_memory()) .field("CPU usage", &self.cpu_usage()) .field("status", &self.status()) .field("root", &self.root()) .field("disk_usage", &self.disk_usage()) .finish() } } impl fmt::Debug for Component { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(critical) = self.critical() { write!( f, "{}: {}°C (max: {}°C / critical: {}°C)", self.label(), self.temperature(), self.max(), critical ) } else { write!( f, "{}: {}°C (max: {}°C)", self.label(), self.temperature(), self.max() ) } } } impl fmt::Debug for Networks { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Networks {{ {} }}", self.iter() .map(|x| format!("{x:?}")) .collect::>() .join(", ") ) } } impl fmt::Debug for NetworkData { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NetworkData") .field("income", &self.received()) .field("total income", &self.total_received()) .field("outcome", &self.transmitted()) .field("total outcome", &self.total_transmitted()) .field("packets income", &self.packets_received()) .field("total packets income", &self.total_packets_received()) .field("packets outcome", &self.packets_transmitted()) .field("total packets outcome", &self.total_packets_transmitted()) .field("errors income", &self.errors_on_received()) .field("total errors income", &self.total_errors_on_received()) .field("errors outcome", &self.errors_on_transmitted()) .field("total errors outcome", &self.total_errors_on_transmitted()) .finish() } } sysinfo-0.28.4/src/freebsd/component.rs000064400000000000000000000033131046102023000161620ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::get_sys_value_by_name; use crate::ComponentExt; #[doc = include_str!("../../md_doc/component.md")] pub struct Component { id: Vec, label: String, temperature: f32, max: f32, } impl ComponentExt for Component { fn temperature(&self) -> f32 { self.temperature } fn max(&self) -> f32 { self.max } fn critical(&self) -> Option { None } fn label(&self) -> &str { &self.label } fn refresh(&mut self) { unsafe { if let Some(temperature) = refresh_component(&self.id) { self.temperature = temperature; if self.temperature > self.max { self.max = self.temperature; } } } } } unsafe fn refresh_component(id: &[u8]) -> Option { let mut temperature: libc::c_int = 0; if !get_sys_value_by_name(id, &mut temperature) { None } else { // convert from Kelvin (x 10 -> 273.2 x 10) to Celsius Some((temperature - 2732) as f32 / 10.) } } pub unsafe fn get_components(nb_cpus: usize) -> Vec { // For now, we only have temperature for CPUs... let mut components = Vec::with_capacity(nb_cpus); for core in 0..nb_cpus { let id = format!("dev.cpu.{core}.temperature\0").as_bytes().to_vec(); if let Some(temperature) = refresh_component(&id) { components.push(Component { id, label: format!("CPU {}", core + 1), temperature, max: temperature, }); } } components } sysinfo-0.28.4/src/freebsd/cpu.rs000064400000000000000000000014541046102023000147530ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::CpuExt; #[doc = include_str!("../../md_doc/cpu.md")] pub struct Cpu { pub(crate) cpu_usage: f32, name: String, pub(crate) vendor_id: String, pub(crate) frequency: u64, } impl Cpu { pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Cpu { Cpu { cpu_usage: 0., name, vendor_id, frequency, } } } impl CpuExt for Cpu { fn cpu_usage(&self) -> f32 { self.cpu_usage } fn name(&self) -> &str { &self.name } fn frequency(&self) -> u64 { self.frequency } fn vendor_id(&self) -> &str { &self.vendor_id } fn brand(&self) -> &str { "" } } sysinfo-0.28.4/src/freebsd/disk.rs000064400000000000000000000077271046102023000151270ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskExt, DiskType}; use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use super::utils::c_buf_to_str; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { name: OsString, c_mount_point: Vec, mount_point: PathBuf, total_space: u64, available_space: u64, file_system: Vec, is_removable: bool, } impl DiskExt for Disk { fn type_(&self) -> DiskType { DiskType::Unknown(-1) } fn name(&self) -> &OsStr { &self.name } fn file_system(&self) -> &[u8] { &self.file_system } fn mount_point(&self) -> &Path { &self.mount_point } fn total_space(&self) -> u64 { self.total_space } fn available_space(&self) -> u64 { self.available_space } fn is_removable(&self) -> bool { self.is_removable } fn refresh(&mut self) -> bool { unsafe { let mut vfs: libc::statvfs = std::mem::zeroed(); refresh_disk(self, &mut vfs) } } } // FIXME: if you want to get disk I/O usage: // statfs.[f_syncwrites, f_asyncwrites, f_syncreads, f_asyncreads] unsafe fn refresh_disk(disk: &mut Disk, vfs: &mut libc::statvfs) -> bool { if libc::statvfs(disk.c_mount_point.as_ptr() as *const _, vfs) < 0 { return false; } let f_frsize: u64 = vfs.f_frsize as _; disk.total_space = vfs.f_blocks.saturating_mul(f_frsize); disk.available_space = vfs.f_favail.saturating_mul(f_frsize); true } pub unsafe fn get_all_disks() -> Vec { let mut fs_infos: *mut libc::statfs = std::ptr::null_mut(); let count = libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT); if count < 1 { return Vec::new(); } let mut vfs: libc::statvfs = std::mem::zeroed(); let fs_infos: &[libc::statfs] = std::slice::from_raw_parts(fs_infos as _, count as _); let mut disks = Vec::new(); for fs_info in fs_infos { if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 { // If we have missing information, no need to look any further... continue; } let fs_type: &[libc::c_char] = if let Some(pos) = fs_info.f_fstypename.iter().position(|x| *x == 0) { &fs_info.f_fstypename[..pos] } else { &fs_info.f_fstypename }; let fs_type: &[u8] = std::slice::from_raw_parts(fs_type.as_ptr() as _, fs_type.len()); match fs_type { b"autofs" | b"devfs" | b"linprocfs" | b"procfs" | b"fdesckfs" | b"tmpfs" | b"linsysfs" => { sysinfo_debug!( "Memory filesystem `{:?}`, ignoring it.", c_buf_to_str(&fs_info.f_fstypename).unwrap(), ); continue; } _ => {} } if libc::statvfs(fs_info.f_mntonname.as_ptr(), &mut vfs) != 0 { continue; } let mount_point = match c_buf_to_str(&fs_info.f_mntonname) { Some(m) => m, None => { sysinfo_debug!("Cannot get disk mount point, ignoring it."); continue; } }; let name = if mount_point == "/" { OsString::from("root") } else { OsString::from(mount_point) }; // USB keys and CDs are removable. let is_removable = [b"USB", b"usb"].iter().any(|b| b == &fs_type) || fs_type.starts_with(b"/dev/cd"); let f_frsize: u64 = vfs.f_frsize as _; disks.push(Disk { name, c_mount_point: fs_info.f_mntonname.to_vec(), mount_point: PathBuf::from(mount_point), total_space: vfs.f_blocks.saturating_mul(f_frsize), available_space: vfs.f_favail.saturating_mul(f_frsize), file_system: fs_type.to_vec(), is_removable, }); } disks } sysinfo-0.28.4/src/freebsd/mod.rs000064400000000000000000000006011046102023000147340ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod cpu; pub mod disk; pub mod network; pub mod process; pub mod system; mod utils; pub use self::component::Component; pub use self::cpu::Cpu; pub use self::disk::Disk; pub use self::network::{NetworkData, Networks}; pub use self::process::Process; pub use self::system::System; sysinfo-0.28.4/src/freebsd/network.rs000064400000000000000000000146201046102023000156540ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::{hash_map, HashMap}; use std::mem::MaybeUninit; use super::utils; use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::{NetworkExt, NetworksExt, NetworksIter}; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident, $data:expr) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $data.$name; }}; } #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } impl Networks { pub(crate) fn new() -> Networks { Networks { interfaces: HashMap::new(), } } } impl NetworksExt for Networks { fn iter(&self) -> NetworksIter { NetworksIter::new(self.interfaces.iter()) } fn refresh_networks_list(&mut self) { unsafe { self.refresh_interfaces(true); } // Remove interfaces which are gone. self.interfaces.retain(|_, n| n.updated); refresh_networks_addresses(&mut self.interfaces); } fn refresh(&mut self) { unsafe { self.refresh_interfaces(false); } } } impl Networks { unsafe fn refresh_interfaces(&mut self, refresh_all: bool) { let mut nb_interfaces: libc::c_int = 0; if !utils::get_sys_value( &[ libc::CTL_NET, libc::PF_LINK, libc::NETLINK_GENERIC, libc::IFMIB_SYSTEM, libc::IFMIB_IFCOUNT, ], &mut nb_interfaces, ) { return; } if refresh_all { // We don't need to update this value if we're not updating all interfaces. for interface in self.interfaces.values_mut() { interface.updated = false; } } let mut data: libc::ifmibdata = MaybeUninit::zeroed().assume_init(); for row in 1..nb_interfaces { let mib = [ libc::CTL_NET, libc::PF_LINK, libc::NETLINK_GENERIC, libc::IFMIB_IFDATA, row, libc::IFDATA_GENERAL, ]; if !utils::get_sys_value(&mib, &mut data) { continue; } if let Some(name) = utils::c_buf_to_string(&data.ifmd_name) { let data = &data.ifmd_data; match self.interfaces.entry(name) { hash_map::Entry::Occupied(mut e) => { let mut interface = e.get_mut(); old_and_new!(interface, ifi_ibytes, old_ifi_ibytes, data); old_and_new!(interface, ifi_obytes, old_ifi_obytes, data); old_and_new!(interface, ifi_ipackets, old_ifi_ipackets, data); old_and_new!(interface, ifi_opackets, old_ifi_opackets, data); old_and_new!(interface, ifi_ierrors, old_ifi_ierrors, data); old_and_new!(interface, ifi_oerrors, old_ifi_oerrors, data); interface.updated = true; } hash_map::Entry::Vacant(e) => { if !refresh_all { // This is simply a refresh, we don't want to add new interfaces! continue; } e.insert(NetworkData { ifi_ibytes: data.ifi_ibytes, old_ifi_ibytes: 0, ifi_obytes: data.ifi_obytes, old_ifi_obytes: 0, ifi_ipackets: data.ifi_ipackets, old_ifi_ipackets: 0, ifi_opackets: data.ifi_opackets, old_ifi_opackets: 0, ifi_ierrors: data.ifi_ierrors, old_ifi_ierrors: 0, ifi_oerrors: data.ifi_oerrors, old_ifi_oerrors: 0, updated: true, mac_addr: MacAddr::UNSPECIFIED, }); } } } } } } #[doc = include_str!("../../md_doc/network_data.md")] pub struct NetworkData { /// Total number of bytes received over interface. ifi_ibytes: u64, old_ifi_ibytes: u64, /// Total number of bytes transmitted over interface. ifi_obytes: u64, old_ifi_obytes: u64, /// Total number of packets received. ifi_ipackets: u64, old_ifi_ipackets: u64, /// Total number of packets transmitted. ifi_opackets: u64, old_ifi_opackets: u64, /// Shows the total number of packets received with error. This includes /// too-long-frames errors, ring-buffer overflow errors, CRC errors, /// frame alignment errors, fifo overruns, and missed packets. ifi_ierrors: u64, old_ifi_ierrors: u64, /// similar to `ifi_ierrors` ifi_oerrors: u64, old_ifi_oerrors: u64, /// Whether or not the above data has been updated during refresh updated: bool, /// MAC address pub(crate) mac_addr: MacAddr, } impl NetworkExt for NetworkData { fn received(&self) -> u64 { self.ifi_ibytes.saturating_sub(self.old_ifi_ibytes) } fn total_received(&self) -> u64 { self.ifi_ibytes } fn transmitted(&self) -> u64 { self.ifi_obytes.saturating_sub(self.old_ifi_obytes) } fn total_transmitted(&self) -> u64 { self.ifi_obytes } fn packets_received(&self) -> u64 { self.ifi_ipackets.saturating_sub(self.old_ifi_ipackets) } fn total_packets_received(&self) -> u64 { self.ifi_ipackets } fn packets_transmitted(&self) -> u64 { self.ifi_opackets.saturating_sub(self.old_ifi_opackets) } fn total_packets_transmitted(&self) -> u64 { self.ifi_opackets } fn errors_on_received(&self) -> u64 { self.ifi_ierrors.saturating_sub(self.old_ifi_ierrors) } fn total_errors_on_received(&self) -> u64 { self.ifi_ierrors } fn errors_on_transmitted(&self) -> u64 { self.ifi_oerrors.saturating_sub(self.old_ifi_oerrors) } fn total_errors_on_transmitted(&self) -> u64 { self.ifi_oerrors } fn mac_address(&self) -> MacAddr { self.mac_addr } } sysinfo-0.28.4/src/freebsd/process.rs000064400000000000000000000176001046102023000156420ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; use std::fmt; use std::path::{Path, PathBuf}; use libc::kill; use super::utils::{get_sys_value_str, WrapMap}; #[doc(hidden)] impl From for ProcessStatus { fn from(status: libc::c_char) -> ProcessStatus { match status { libc::SIDL => ProcessStatus::Idle, libc::SRUN => ProcessStatus::Run, libc::SSLEEP => ProcessStatus::Sleep, libc::SSTOP => ProcessStatus::Stop, libc::SZOMB => ProcessStatus::Zombie, libc::SWAIT => ProcessStatus::Dead, libc::SLOCK => ProcessStatus::LockBlocked, x => ProcessStatus::Unknown(x as _), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", ProcessStatus::Dead => "Dead", ProcessStatus::LockBlocked => "LockBlocked", _ => "Unknown", }) } } #[doc = include_str!("../../md_doc/process.md")] pub struct Process { pub(crate) name: String, pub(crate) cmd: Vec, pub(crate) exe: PathBuf, pub(crate) pid: Pid, parent: Option, pub(crate) environ: Vec, pub(crate) cwd: PathBuf, pub(crate) root: PathBuf, pub(crate) memory: u64, pub(crate) virtual_memory: u64, pub(crate) updated: bool, cpu_usage: f32, start_time: u64, run_time: u64, pub(crate) status: ProcessStatus, user_id: Uid, group_id: Gid, read_bytes: u64, old_read_bytes: u64, written_bytes: u64, old_written_bytes: u64, } impl ProcessExt for Process { fn kill_with(&self, signal: Signal) -> Option { let c_signal = super::system::convert_signal(signal)?; unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } } fn name(&self) -> &str { &self.name } fn cmd(&self) -> &[String] { &self.cmd } fn exe(&self) -> &Path { self.exe.as_path() } fn pid(&self) -> Pid { self.pid } fn environ(&self) -> &[String] { &self.environ } fn cwd(&self) -> &Path { self.cwd.as_path() } fn root(&self) -> &Path { self.root.as_path() } fn memory(&self) -> u64 { self.memory } fn virtual_memory(&self) -> u64 { self.virtual_memory } fn parent(&self) -> Option { self.parent } fn status(&self) -> ProcessStatus { self.status } fn start_time(&self) -> u64 { self.start_time } fn run_time(&self) -> u64 { self.run_time } fn cpu_usage(&self) -> f32 { self.cpu_usage } fn disk_usage(&self) -> DiskUsage { DiskUsage { written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, } } fn user_id(&self) -> Option<&Uid> { Some(&self.user_id) } fn group_id(&self) -> Option { Some(self.group_id) } fn wait(&self) { let mut status = 0; // attempt waiting unsafe { if libc::waitpid(self.pid.0, &mut status, 0) < 0 { // attempt failed (non-child process) so loop until process ends let duration = std::time::Duration::from_millis(10); while kill(self.pid.0, 0) == 0 { std::thread::sleep(duration); } } } } fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } } pub(crate) unsafe fn get_process_data( kproc: &libc::kinfo_proc, wrap: &WrapMap, page_size: isize, fscale: f32, now: u64, refresh_kind: ProcessRefreshKind, ) -> Result, ()> { if kproc.ki_pid != 1 && (kproc.ki_flag as libc::c_int & libc::P_SYSTEM) != 0 { // We filter out the kernel threads. return Err(()); } // We now get the values needed for both new and existing process. let cpu_usage = if refresh_kind.cpu() { (100 * kproc.ki_pctcpu) as f32 / fscale } else { 0. }; // Processes can be reparented apparently? let parent = if kproc.ki_ppid != 0 { Some(Pid(kproc.ki_ppid)) } else { None }; let status = ProcessStatus::from(kproc.ki_stat); // from FreeBSD source /src/usr.bin/top/machine.c let virtual_memory = kproc.ki_size as _; let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _); // FIXME: This is to get the "real" run time (in micro-seconds). // let run_time = (kproc.ki_runtime + 5_000) / 10_000; let start_time = kproc.ki_start.tv_sec as u64; if let Some(proc_) = (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) { proc_.updated = true; // If the `start_time` we just got is different from the one stored, it means it's not the // same process. if proc_.start_time == start_time { proc_.cpu_usage = cpu_usage; proc_.parent = parent; proc_.status = status; proc_.virtual_memory = virtual_memory; proc_.memory = memory; proc_.run_time = now.saturating_sub(proc_.start_time); if refresh_kind.disk_usage() { proc_.old_read_bytes = proc_.read_bytes; proc_.read_bytes = kproc.ki_rusage.ru_inblock as _; proc_.old_written_bytes = proc_.written_bytes; proc_.written_bytes = kproc.ki_rusage.ru_oublock as _; } return Ok(None); } } // This is a new process, we need to get more information! let mut buffer = [0; libc::PATH_MAX as usize + 1]; let exe = get_sys_value_str( &[ libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PATHNAME, kproc.ki_pid, ], &mut buffer, ) .unwrap_or_default(); // For some reason, it can return completely invalid path like `p\u{5}`. So we need to use // procstat to get around this problem. // let cwd = get_sys_value_str( // &[ // libc::CTL_KERN, // libc::KERN_PROC, // libc::KERN_PROC_CWD, // kproc.ki_pid, // ], // &mut buffer, // ) // .map(|s| s.into()) // .unwrap_or_else(PathBuf::new); Ok(Some(Process { pid: Pid(kproc.ki_pid), parent, user_id: Uid(kproc.ki_ruid), group_id: Gid(kproc.ki_rgid), start_time, run_time: now.saturating_sub(start_time), cpu_usage, virtual_memory, memory, // procstat_getfiles cwd: PathBuf::new(), exe: exe.into(), // kvm_getargv isn't thread-safe so we get it in the main thread. name: String::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. cmd: Vec::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. root: PathBuf::new(), // kvm_getenvv isn't thread-safe so we get it in the main thread. environ: Vec::new(), status, read_bytes: kproc.ki_rusage.ru_inblock as _, old_read_bytes: 0, written_bytes: kproc.ki_rusage.ru_oublock as _, old_written_bytes: 0, updated: false, })) } sysinfo-0.28.4/src/freebsd/system.rs000064400000000000000000000643661046102023000155230ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ sys::{component::Component, Cpu, Disk, Networks, Process}, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, }; use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::CStr; use std::mem::MaybeUninit; use std::path::{Path, PathBuf}; use std::ptr::NonNull; use std::time::Duration; use super::utils::{ self, boot_time, c_buf_to_string, from_cstr_array, get_frequency_for_cpu, get_sys_value, get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, get_system_info, init_mib, }; use libc::c_int; declare_signals! { c_int, Signal::Hangup => libc::SIGHUP, Signal::Interrupt => libc::SIGINT, Signal::Quit => libc::SIGQUIT, Signal::Illegal => libc::SIGILL, Signal::Trap => libc::SIGTRAP, Signal::Abort => libc::SIGABRT, Signal::IOT => libc::SIGIOT, Signal::Bus => libc::SIGBUS, Signal::FloatingPointException => libc::SIGFPE, Signal::Kill => libc::SIGKILL, Signal::User1 => libc::SIGUSR1, Signal::Segv => libc::SIGSEGV, Signal::User2 => libc::SIGUSR2, Signal::Pipe => libc::SIGPIPE, Signal::Alarm => libc::SIGALRM, Signal::Term => libc::SIGTERM, Signal::Child => libc::SIGCHLD, Signal::Continue => libc::SIGCONT, Signal::Stop => libc::SIGSTOP, Signal::TSTP => libc::SIGTSTP, Signal::TTIN => libc::SIGTTIN, Signal::TTOU => libc::SIGTTOU, Signal::Urgent => libc::SIGURG, Signal::XCPU => libc::SIGXCPU, Signal::XFSZ => libc::SIGXFSZ, Signal::VirtualAlarm => libc::SIGVTALRM, Signal::Profiling => libc::SIGPROF, Signal::Winch => libc::SIGWINCH, Signal::IO => libc::SIGIO, Signal::Sys => libc::SIGSYS, _ => None, } #[doc = include_str!("../../md_doc/system.md")] pub struct System { process_list: HashMap, mem_total: u64, mem_free: u64, mem_used: u64, swap_total: u64, swap_used: u64, global_cpu: Cpu, cpus: Vec, components: Vec, disks: Vec, networks: Networks, users: Vec, boot_time: u64, system_info: SystemInfo, got_cpu_frequency: bool, } impl SystemExt for System { const IS_SUPPORTED: bool = true; const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(100); fn new_with_specifics(refreshes: RefreshKind) -> System { let system_info = SystemInfo::new(); let mut s = System { process_list: HashMap::with_capacity(200), mem_total: 0, mem_free: 0, mem_used: 0, swap_total: 0, swap_used: 0, global_cpu: Cpu::new(String::new(), String::new(), 0), cpus: Vec::with_capacity(system_info.nb_cpus as _), components: Vec::with_capacity(2), disks: Vec::with_capacity(1), networks: Networks::new(), users: Vec::new(), boot_time: boot_time(), system_info, got_cpu_frequency: false, }; s.refresh_specifics(refreshes); s } fn refresh_memory(&mut self) { if self.mem_total == 0 { self.mem_total = self.system_info.get_total_memory(); } self.mem_used = self.system_info.get_used_memory(); self.mem_free = self.system_info.get_free_memory(); let (swap_used, swap_total) = self.system_info.get_swap_info(); self.swap_total = swap_total; self.swap_used = swap_used; } fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { if self.cpus.is_empty() { let mut frequency = 0; // We get the CPU vendor ID in here. let vendor_id = get_sys_value_str_by_name(b"hw.model\0").unwrap_or_else(|| "".to_owned()); for pos in 0..self.system_info.nb_cpus { if refresh_kind.frequency() { unsafe { frequency = get_frequency_for_cpu(pos); } } self.cpus .push(Cpu::new(format!("cpu {pos}"), vendor_id.clone(), frequency)); } self.global_cpu.vendor_id = vendor_id; self.got_cpu_frequency = refresh_kind.frequency(); } else if refresh_kind.frequency() && !self.got_cpu_frequency { for (pos, proc_) in self.cpus.iter_mut().enumerate() { unsafe { proc_.frequency = get_frequency_for_cpu(pos as _); } } self.got_cpu_frequency = true; } if refresh_kind.cpu_usage() { self.system_info .get_cpu_usage(&mut self.global_cpu, &mut self.cpus); } if refresh_kind.frequency() { self.global_cpu.frequency = self.cpus.get(0).map(|cpu| cpu.frequency).unwrap_or(0); } } fn refresh_components_list(&mut self) { if self.cpus.is_empty() { self.refresh_cpu(); } self.components = unsafe { super::component::get_components(self.cpus.len()) }; } fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { unsafe { self.refresh_procs(refresh_kind) } } fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { unsafe { let kd = self.system_info.kd.as_ptr(); let mut count = 0; let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); if count < 1 { sysinfo_debug!("kvm_getprocs returned nothing..."); return false; } let now = super::utils::get_now(); let fscale = self.system_info.fscale; let page_size = self.system_info.page_size as isize; let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); let procs: &mut [utils::KInfoProc] = std::slice::from_raw_parts_mut(procs as _, count as _); #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; macro_rules! multi_iter { ($name:ident, $($iter:tt)+) => { $name = crate::utils::into_iter(procs).$($iter)+; } } let ret; #[cfg(not(feature = "multithread"))] multi_iter!(ret, find(|kproc| kproc.ki_pid == pid.0)); #[cfg(feature = "multithread")] multi_iter!(ret, find_any(|kproc| kproc.ki_pid == pid.0)); let kproc = if let Some(kproc) = ret { kproc } else { return false; }; match super::process::get_process_data( kproc, &proc_list, page_size, fscale, now, refresh_kind, ) { Ok(Some(proc_)) => { self.add_missing_proc_info(self.system_info.kd.as_ptr(), kproc, proc_); true } Ok(None) => true, Err(_) => false, } } } fn refresh_disks_list(&mut self) { self.disks = unsafe { super::disk::get_all_disks() }; } fn refresh_users_list(&mut self) { self.users = crate::users::get_users_list(); } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. fn processes(&self) -> &HashMap { &self.process_list } fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } fn networks(&self) -> &Networks { &self.networks } fn networks_mut(&mut self) -> &mut Networks { &mut self.networks } fn global_cpu_info(&self) -> &Cpu { &self.global_cpu } fn cpus(&self) -> &[Cpu] { &self.cpus } fn physical_core_count(&self) -> Option { let mut physical_core_count: u32 = 0; unsafe { if get_sys_value_by_name(b"hw.ncpu\0", &mut physical_core_count) { Some(physical_core_count as _) } else { None } } } fn total_memory(&self) -> u64 { self.mem_total } fn free_memory(&self) -> u64 { self.mem_free } fn available_memory(&self) -> u64 { self.mem_free } fn used_memory(&self) -> u64 { self.mem_used } fn total_swap(&self) -> u64 { self.swap_total } fn free_swap(&self) -> u64 { self.swap_total - self.swap_used } // TODO: need to be checked fn used_swap(&self) -> u64 { self.swap_used } fn components(&self) -> &[Component] { &self.components } fn components_mut(&mut self) -> &mut [Component] { &mut self.components } fn disks(&self) -> &[Disk] { &self.disks } fn disks_mut(&mut self) -> &mut [Disk] { &mut self.disks } fn sort_disks_by(&mut self, compare: F) where F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, { self.disks.sort_unstable_by(compare); } fn uptime(&self) -> u64 { unsafe { let csec = libc::time(::std::ptr::null_mut()); libc::difftime(csec, self.boot_time as _) as u64 } } fn boot_time(&self) -> u64 { self.boot_time } fn load_average(&self) -> LoadAvg { let mut loads = vec![0f64; 3]; unsafe { libc::getloadavg(loads.as_mut_ptr(), 3); LoadAvg { one: loads[0], five: loads[1], fifteen: loads[2], } } } fn users(&self) -> &[User] { &self.users } fn name(&self) -> Option { self.system_info.get_os_name() } fn long_os_version(&self) -> Option { self.system_info.get_os_release_long() } fn host_name(&self) -> Option { self.system_info.get_hostname() } fn kernel_version(&self) -> Option { self.system_info.get_kernel_version() } fn os_version(&self) -> Option { self.system_info.get_os_release() } fn distribution_id(&self) -> String { std::env::consts::OS.to_owned() } } impl Default for System { fn default() -> Self { Self::new() } } impl System { unsafe fn refresh_procs(&mut self, refresh_kind: ProcessRefreshKind) { let kd = self.system_info.kd.as_ptr(); let procs = { let mut count = 0; let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); if count < 1 { sysinfo_debug!("kvm_getprocs returned nothing..."); return; } #[cfg(feature = "multithread")] use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; #[cfg(not(feature = "multithread"))] use std::iter::Iterator as IterTrait; let fscale = self.system_info.fscale; let page_size = self.system_info.page_size as isize; let now = super::utils::get_now(); let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); let procs: &mut [utils::KInfoProc] = std::slice::from_raw_parts_mut(procs as _, count as _); IterTrait::filter_map(crate::utils::into_iter(procs), |kproc| { super::process::get_process_data( kproc, &proc_list, page_size, fscale, now, refresh_kind, ) .ok() .and_then(|p| p.map(|p| (kproc, p))) }) .collect::>() }; // We remove all processes that don't exist anymore. self.process_list .retain(|_, v| std::mem::replace(&mut v.updated, false)); for (kproc, proc_) in procs { self.add_missing_proc_info(kd, kproc, proc_); } } unsafe fn add_missing_proc_info( &mut self, kd: *mut libc::kvm_t, kproc: &libc::kinfo_proc, mut proc_: Process, ) { proc_.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); self.system_info.get_proc_missing_info(kproc, &mut proc_); if !proc_.cmd.is_empty() { // First, we try to retrieve the name from the command line. let p = Path::new(&proc_.cmd[0]); if let Some(name) = p.file_name().and_then(|s| s.to_str()) { proc_.name = name.to_owned(); } if proc_.root.as_os_str().is_empty() { if let Some(parent) = p.parent() { proc_.root = parent.to_path_buf(); } } } if proc_.name.is_empty() { // The name can be cut short because the `ki_comm` field size is limited, // which is why we prefer to get the name from the command line as much as // possible. proc_.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); } proc_.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); self.process_list.insert(proc_.pid, proc_); } } #[derive(Debug)] struct Zfs { enabled: bool, mib_arcstats_size: [c_int; 5], } impl Zfs { fn new() -> Self { let mut zfs = Self { enabled: false, mib_arcstats_size: Default::default(), }; unsafe { init_mib( b"kstat.zfs.misc.arcstats.size\0", &mut zfs.mib_arcstats_size, ); let mut arc_size: u64 = 0; if get_sys_value(&zfs.mib_arcstats_size, &mut arc_size) { zfs.enabled = arc_size != 0; } } zfs } fn arc_size(&self) -> Option { if self.enabled { let mut arc_size: u64 = 0; unsafe { get_sys_value(&self.mib_arcstats_size, &mut arc_size); Some(arc_size) } } else { None } } } /// This struct is used to get system information more easily. #[derive(Debug)] struct SystemInfo { hw_physical_memory: [c_int; 2], page_size: c_int, virtual_page_count: [c_int; 4], virtual_wire_count: [c_int; 4], virtual_active_count: [c_int; 4], virtual_cache_count: [c_int; 4], virtual_inactive_count: [c_int; 4], virtual_free_count: [c_int; 4], os_type: [c_int; 2], os_release: [c_int; 2], kern_version: [c_int; 2], hostname: [c_int; 2], buf_space: [c_int; 2], nb_cpus: c_int, kd: NonNull, // For these two fields, we could use `kvm_getcptime` but the function isn't very efficient... mib_cp_time: [c_int; 2], mib_cp_times: [c_int; 2], // For the global CPU usage. cp_time: utils::VecSwitcher, // For each CPU usage. cp_times: utils::VecSwitcher, /// From FreeBSD manual: "The kernel fixed-point scale factor". It's used when computing /// processes' CPU usage. fscale: f32, procstat: *mut libc::procstat, zfs: Zfs, } // This is needed because `kd: *mut libc::kvm_t` isn't thread-safe. unsafe impl Send for SystemInfo {} unsafe impl Sync for SystemInfo {} impl SystemInfo { fn new() -> Self { unsafe { let mut errbuf = MaybeUninit::<[libc::c_char; libc::_POSIX2_LINE_MAX as usize]>::uninit(); let kd = NonNull::new(libc::kvm_openfiles( std::ptr::null(), b"/dev/null\0".as_ptr() as *const _, std::ptr::null(), 0, errbuf.as_mut_ptr() as *mut _, )) .expect("kvm_openfiles failed"); let mut smp: c_int = 0; let mut nb_cpus: c_int = 1; if !get_sys_value_by_name(b"kern.smp.active\0", &mut smp) { smp = 0; } #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. if smp != 0 { if !get_sys_value_by_name(b"kern.smp.cpus\0", &mut nb_cpus) || nb_cpus < 1 { nb_cpus = 1; } } let mut si = SystemInfo { hw_physical_memory: Default::default(), page_size: 0, virtual_page_count: Default::default(), virtual_wire_count: Default::default(), virtual_active_count: Default::default(), virtual_cache_count: Default::default(), virtual_inactive_count: Default::default(), virtual_free_count: Default::default(), buf_space: Default::default(), os_type: Default::default(), os_release: Default::default(), kern_version: Default::default(), hostname: Default::default(), nb_cpus, kd, mib_cp_time: Default::default(), mib_cp_times: Default::default(), cp_time: utils::VecSwitcher::new(vec![0; libc::CPUSTATES as usize]), cp_times: utils::VecSwitcher::new(vec![ 0; nb_cpus as usize * libc::CPUSTATES as usize ]), fscale: 0., procstat: std::ptr::null_mut(), zfs: Zfs::new(), }; let mut fscale: c_int = 0; if !get_sys_value_by_name(b"kern.fscale\0", &mut fscale) { // Default value used in htop. fscale = 2048; } si.fscale = fscale as f32; if !get_sys_value_by_name(b"vm.stats.vm.v_page_size\0", &mut si.page_size) { panic!("cannot get page size..."); } init_mib(b"hw.physmem\0", &mut si.hw_physical_memory); init_mib(b"vm.stats.vm.v_page_count\0", &mut si.virtual_page_count); init_mib(b"vm.stats.vm.v_wire_count\0", &mut si.virtual_wire_count); init_mib( b"vm.stats.vm.v_active_count\0", &mut si.virtual_active_count, ); init_mib(b"vm.stats.vm.v_cache_count\0", &mut si.virtual_cache_count); init_mib( b"vm.stats.vm.v_inactive_count\0", &mut si.virtual_inactive_count, ); init_mib(b"vm.stats.vm.v_free_count\0", &mut si.virtual_free_count); init_mib(b"vfs.bufspace\0", &mut si.buf_space); init_mib(b"kern.ostype\0", &mut si.os_type); init_mib(b"kern.osrelease\0", &mut si.os_release); init_mib(b"kern.version\0", &mut si.kern_version); init_mib(b"kern.hostname\0", &mut si.hostname); init_mib(b"kern.cp_time\0", &mut si.mib_cp_time); init_mib(b"kern.cp_times\0", &mut si.mib_cp_times); si } } fn get_os_name(&self) -> Option { get_system_info(&[self.os_type[0], self.os_type[1]], Some("FreeBSD")) } fn get_kernel_version(&self) -> Option { get_system_info(&[self.kern_version[0], self.kern_version[1]], None) } fn get_os_release_long(&self) -> Option { get_system_info(&[self.os_release[0], self.os_release[1]], None) } fn get_os_release(&self) -> Option { // It returns something like "13.0-RELEASE". We want to keep everything until the "-". get_system_info(&[self.os_release[0], self.os_release[1]], None) .and_then(|s| s.split('-').next().map(|s| s.to_owned())) } fn get_hostname(&self) -> Option { get_system_info(&[self.hostname[0], self.hostname[1]], Some("")) } /// Returns (used, total). fn get_swap_info(&self) -> (u64, u64) { // Magic number used in htop. Cannot find how they got it when reading `kvm_getswapinfo` // source code so here we go... const LEN: usize = 16; let mut swap = MaybeUninit::<[libc::kvm_swap; LEN]>::uninit(); unsafe { let nswap = libc::kvm_getswapinfo(self.kd.as_ptr(), swap.as_mut_ptr() as *mut _, LEN as _, 0) as usize; if nswap < 1 { return (0, 0); } let swap = std::slice::from_raw_parts(swap.as_ptr() as *mut libc::kvm_swap, nswap.min(LEN)); let (used, total) = swap.iter().fold((0, 0), |(used, total): (u64, u64), swap| { ( used.saturating_add(swap.ksw_used as _), total.saturating_add(swap.ksw_total as _), ) }); ( used.saturating_mul(self.page_size as _), total.saturating_mul(self.page_size as _), ) } } fn get_total_memory(&self) -> u64 { let mut nb_pages: u64 = 0; unsafe { if get_sys_value(&self.virtual_page_count, &mut nb_pages) { return nb_pages.saturating_mul(self.page_size as _); } // This is a fallback. It includes all the available memory, not just the one available for // the users. let mut total_memory: u64 = 0; get_sys_value(&self.hw_physical_memory, &mut total_memory); total_memory } } fn get_used_memory(&self) -> u64 { let mut mem_active: u64 = 0; let mut mem_wire: u64 = 0; unsafe { get_sys_value(&self.virtual_active_count, &mut mem_active); get_sys_value(&self.virtual_wire_count, &mut mem_wire); let mut mem_wire = mem_wire.saturating_mul(self.page_size as _); // We need to subtract "ZFS ARC" from the "wired memory" because it should belongs to cache // but the kernel reports it as "wired memory" instead... if let Some(arc_size) = self.zfs.arc_size() { mem_wire -= arc_size; } mem_active .saturating_mul(self.page_size as _) .saturating_add(mem_wire) } } fn get_free_memory(&self) -> u64 { let mut buffers_mem: u64 = 0; let mut inactive_mem: u64 = 0; let mut cached_mem: u64 = 0; let mut free_mem: u64 = 0; unsafe { get_sys_value(&self.buf_space, &mut buffers_mem); get_sys_value(&self.virtual_inactive_count, &mut inactive_mem); get_sys_value(&self.virtual_cache_count, &mut cached_mem); get_sys_value(&self.virtual_free_count, &mut free_mem); // For whatever reason, buffers_mem is already the right value... buffers_mem .saturating_add(inactive_mem.saturating_mul(self.page_size as _)) .saturating_add(cached_mem.saturating_mul(self.page_size as _)) .saturating_add(free_mem.saturating_mul(self.page_size as _)) } } fn get_cpu_usage(&mut self, global: &mut Cpu, cpus: &mut [Cpu]) { unsafe { get_sys_value_array(&self.mib_cp_time, self.cp_time.get_mut()); get_sys_value_array(&self.mib_cp_times, self.cp_times.get_mut()); } fn fill_cpu(proc_: &mut Cpu, new_cp_time: &[libc::c_ulong], old_cp_time: &[libc::c_ulong]) { let mut total_new: u64 = 0; let mut total_old: u64 = 0; let mut cp_diff: libc::c_ulong = 0; for i in 0..(libc::CPUSTATES as usize) { // We obviously don't want to get the idle part of the CPU usage, otherwise // we would always be at 100%... if i != libc::CP_IDLE as usize { cp_diff += new_cp_time[i] - old_cp_time[i]; } let mut tmp: u64 = new_cp_time[i] as _; total_new += tmp; tmp = old_cp_time[i] as _; total_old += tmp; } let total_diff = total_new - total_old; if total_diff < 1 { proc_.cpu_usage = 0.; } else { proc_.cpu_usage = cp_diff as f32 / total_diff as f32 * 100.; } } fill_cpu(global, self.cp_time.get_new(), self.cp_time.get_old()); let old_cp_times = self.cp_times.get_old(); let new_cp_times = self.cp_times.get_new(); for (pos, proc_) in cpus.iter_mut().enumerate() { let index = pos * libc::CPUSTATES as usize; fill_cpu(proc_, &new_cp_times[index..], &old_cp_times[index..]); } } #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. unsafe fn get_proc_missing_info(&mut self, kproc: &libc::kinfo_proc, proc_: &mut Process) { if self.procstat.is_null() { self.procstat = libc::procstat_open_sysctl(); } if self.procstat.is_null() { return; } let head = libc::procstat_getfiles(self.procstat, kproc as *const _ as usize as *mut _, 0); if head.is_null() { return; } let mut entry = (*head).stqh_first; let mut done = 0; while !entry.is_null() && done < 2 { { let tmp = &*entry; if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 { if !tmp.fs_path.is_null() { if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { proc_.cwd = PathBuf::from(p); done += 1; } } } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 { if !tmp.fs_path.is_null() { if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { proc_.root = PathBuf::from(p); done += 1; } } } } entry = (*entry).next.stqe_next; } libc::procstat_freefiles(self.procstat, head); } } impl Drop for SystemInfo { fn drop(&mut self) { unsafe { libc::kvm_close(self.kd.as_ptr()); if !self.procstat.is_null() { libc::procstat_close(self.procstat); } } } } sysinfo-0.28.4/src/freebsd/utils.rs000064400000000000000000000165441046102023000153320ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{Pid, Process}; use libc::{c_char, c_int, timeval}; use std::cell::UnsafeCell; use std::collections::HashMap; use std::ffi::CStr; use std::mem; use std::time::SystemTime; /// This struct is used to switch between the "old" and "new" every time you use "get_mut". #[derive(Debug)] pub(crate) struct VecSwitcher { v1: Vec, v2: Vec, first: bool, } impl VecSwitcher { pub fn new(v1: Vec) -> Self { let v2 = v1.clone(); Self { v1, v2, first: true, } } pub fn get_mut(&mut self) -> &mut [T] { self.first = !self.first; if self.first { // It means that `v2` will be the "new". &mut self.v2 } else { // It means that `v1` will be the "new". &mut self.v1 } } pub fn get_old(&self) -> &[T] { if self.first { &self.v1 } else { &self.v2 } } pub fn get_new(&self) -> &[T] { if self.first { &self.v2 } else { &self.v1 } } } #[inline] pub unsafe fn init_mib(name: &[u8], mib: &mut [c_int]) { let mut len = mib.len(); libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len); } pub(crate) fn boot_time() -> u64 { let mut boot_time = timeval { tv_sec: 0, tv_usec: 0, }; let mut len = std::mem::size_of::(); let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; unsafe { if libc::sysctl( mib.as_mut_ptr(), mib.len() as _, &mut boot_time as *mut timeval as *mut _, &mut len, std::ptr::null_mut(), 0, ) < 0 { 0 } else { boot_time.tv_sec as _ } } } pub(crate) unsafe fn get_sys_value(mib: &[c_int], value: &mut T) -> bool { let mut len = mem::size_of::() as libc::size_t; libc::sysctl( mib.as_ptr(), mib.len() as _, value as *mut _ as *mut _, &mut len, std::ptr::null_mut(), 0, ) == 0 } pub(crate) unsafe fn get_sys_value_array(mib: &[c_int], value: &mut [T]) -> bool { let mut len = (mem::size_of::() * value.len()) as libc::size_t; libc::sysctl( mib.as_ptr(), mib.len() as _, value.as_mut_ptr() as *mut _, &mut len as *mut _, std::ptr::null_mut(), 0, ) == 0 } pub(crate) fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> { unsafe { let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes std::str::from_utf8(&buf[..pos]).ok() } else { std::str::from_utf8(buf).ok() } } } pub(crate) fn c_buf_to_string(buf: &[libc::c_char]) -> Option { c_buf_to_str(buf).map(|s| s.to_owned()) } pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) -> Option { let mut len = (mem::size_of::() * buf.len()) as libc::size_t; if libc::sysctl( mib.as_ptr(), mib.len() as _, buf.as_mut_ptr() as *mut _, &mut len, std::ptr::null_mut(), 0, ) != 0 { return None; } c_buf_to_string(&buf[..len / mem::size_of::()]) } pub(crate) unsafe fn get_sys_value_by_name(name: &[u8], value: &mut T) -> bool { let mut len = mem::size_of::() as libc::size_t; let original = len; libc::sysctlbyname( name.as_ptr() as *const c_char, value as *mut _ as *mut _, &mut len, std::ptr::null_mut(), 0, ) == 0 && original == len } pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option { let mut size = 0; unsafe { if libc::sysctlbyname( name.as_ptr() as *const c_char, std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ) == 0 && size > 0 { // now create a buffer with the size and get the real value let mut buf: Vec = vec![0; size as _]; if libc::sysctlbyname( name.as_ptr() as *const c_char, buf.as_mut_ptr() as *mut _, &mut size, std::ptr::null_mut(), 0, ) == 0 && size > 0 { c_buf_to_string(&buf) } else { // getting the system value failed None } } else { None } } } pub(crate) fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option { let mut size = 0; unsafe { // Call first to get size libc::sysctl( mib.as_ptr(), mib.len() as _, std::ptr::null_mut(), &mut size, std::ptr::null_mut(), 0, ); // exit early if we did not update the size if size == 0 { default.map(|s| s.to_owned()) } else { // set the buffer to the correct size let mut buf: Vec = vec![0; size as _]; if libc::sysctl( mib.as_ptr(), mib.len() as _, buf.as_mut_ptr() as _, &mut size, std::ptr::null_mut(), 0, ) == -1 { // If command fails return default default.map(|s| s.to_owned()) } else { c_buf_to_string(&buf) } } } } pub(crate) unsafe fn from_cstr_array(ptr: *const *const c_char) -> Vec { if ptr.is_null() { return Vec::new(); } let mut max = 0; loop { let ptr = ptr.add(max); if (*ptr).is_null() { break; } max += 1; } if max == 0 { return Vec::new(); } let mut ret = Vec::with_capacity(max); for pos in 0..max { let p = ptr.add(pos); if let Ok(s) = CStr::from_ptr(*p).to_str() { ret.push(s.to_owned()); } } ret } pub(crate) fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } // All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers). pub(crate) struct WrapMap<'a>(pub UnsafeCell<&'a mut HashMap>); unsafe impl<'a> Send for WrapMap<'a> {} unsafe impl<'a> Sync for WrapMap<'a> {} #[repr(transparent)] pub(crate) struct KInfoProc(libc::kinfo_proc); unsafe impl Send for KInfoProc {} unsafe impl Sync for KInfoProc {} impl std::ops::Deref for KInfoProc { type Target = libc::kinfo_proc; fn deref(&self) -> &Self::Target { &self.0 } } pub(crate) unsafe fn get_frequency_for_cpu(cpu_nb: c_int) -> u64 { let mut frequency = 0; // The information can be missing if it's running inside a VM. if !get_sys_value_by_name( format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(), &mut frequency, ) { frequency = 0; } frequency as _ } sysinfo-0.28.4/src/lib.rs000064400000000000000000000401051046102023000133140ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![doc = include_str!("../README.md")] #![cfg_attr(feature = "serde", doc = include_str!("../md_doc/serde.md"))] #![allow(unknown_lints)] #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] #![allow(clippy::upper_case_acronyms)] #![allow(clippy::non_send_fields_in_send_ty)] #![allow(renamed_and_removed_lints)] #![allow(clippy::assertions_on_constants)] #![allow(unknown_lints)] #[macro_use] mod macros; cfg_if::cfg_if! { if #[cfg(feature = "unknown-ci")] { // This is used in CI to check that the build for unknown targets is compiling fine. mod unknown; use unknown as sys; #[cfg(test)] pub(crate) const MIN_USERS: usize = 0; } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { mod apple; use apple as sys; mod network_helper_nix; use network_helper_nix as network_helper; mod network; #[cfg(test)] pub(crate) const MIN_USERS: usize = 1; } else if #[cfg(windows)] { mod windows; use windows as sys; mod network_helper_win; use network_helper_win as network_helper; mod network; #[cfg(test)] pub(crate) const MIN_USERS: usize = 1; } else if #[cfg(any(target_os = "linux", target_os = "android"))] { mod linux; use linux as sys; pub(crate) mod users; mod network_helper_nix; use network_helper_nix as network_helper; mod network; #[cfg(test)] pub(crate) const MIN_USERS: usize = 1; } else if #[cfg(target_os = "freebsd")] { mod freebsd; use freebsd as sys; pub(crate) mod users; mod network_helper_nix; use network_helper_nix as network_helper; mod network; #[cfg(test)] pub(crate) const MIN_USERS: usize = 1; } else { mod unknown; use unknown as sys; #[cfg(test)] pub(crate) const MIN_USERS: usize = 0; } } pub use common::{ get_current_pid, CpuRefreshKind, DiskType, DiskUsage, Gid, LoadAvg, MacAddr, NetworksIter, Pid, PidExt, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, Uid, User, }; pub use sys::{Component, Cpu, Disk, NetworkData, Networks, Process, System}; pub use traits::{ ComponentExt, CpuExt, DiskExt, NetworkExt, NetworksExt, ProcessExt, SystemExt, UserExt, }; #[cfg(feature = "c-interface")] pub use c_interface::*; #[cfg(feature = "c-interface")] mod c_interface; mod common; mod debug; #[cfg(feature = "serde")] mod serde; mod system; mod traits; mod utils; /// This function is only used on Linux targets, on the other platforms it does nothing and returns /// `false`. /// /// On Linux, to improve performance, we keep a `/proc` file open for each process we index with /// a maximum number of files open equivalent to half of the system limit. /// /// The problem is that some users might need all the available file descriptors so we need to /// allow them to change this limit. /// /// Note that if you set a limit bigger than the system limit, the system limit will be set. /// /// Returns `true` if the new value has been set. /// /// ```no_run /// use sysinfo::{System, SystemExt, set_open_files_limit}; /// /// // We call the function before any call to the processes update. /// if !set_open_files_limit(10) { /// // It'll always return false on non-linux targets. /// eprintln!("failed to update the open files limit..."); /// } /// let s = System::new_all(); /// ``` pub fn set_open_files_limit(mut _new_limit: isize) -> bool { cfg_if::cfg_if! { if #[cfg(all(not(feature = "unknown-ci"), any(target_os = "linux", target_os = "android")))] { if _new_limit < 0 { _new_limit = 0; } let max = sys::system::get_max_nb_fds(); if _new_limit > max { _new_limit = max; } unsafe { if let Ok(ref mut x) = sys::system::REMAINING_FILES.lock() { // If files are already open, to be sure that the number won't be bigger when those // files are closed, we subtract the current number of opened files to the new // limit. let diff = max.saturating_sub(**x); **x = _new_limit.saturating_sub(diff); true } else { false } } } else { false } } } // FIXME: Can be removed once negative trait bounds are supported. #[cfg(doctest)] mod doctest { /// Check that `Process` doesn't implement `Clone`. /// /// First we check that the "basic" code works: /// /// ```no_run /// use sysinfo::{Process, System, SystemExt}; /// /// let mut s = System::new_all(); /// let p: &Process = s.processes().values().next().unwrap(); /// ``` /// /// And now we check if it fails when we try to clone it: /// /// ```compile_fail /// use sysinfo::{Process, System, SystemExt}; /// /// let mut s = System::new_all(); /// let p: &Process = s.processes().values().next().unwrap(); /// let p = (*p).clone(); /// ``` mod process_clone {} /// Check that `System` doesn't implement `Clone`. /// /// First we check that the "basic" code works: /// /// ```no_run /// use sysinfo::{Process, System, SystemExt}; /// /// let s = System::new(); /// ``` /// /// And now we check if it fails when we try to clone it: /// /// ```compile_fail /// use sysinfo::{Process, System, SystemExt}; /// /// let s = System::new(); /// let s = s.clone(); /// ``` mod system_clone {} } #[cfg(test)] mod test { use crate::*; #[cfg(feature = "unknown-ci")] #[test] fn check_unknown_ci_feature() { assert!(!System::IS_SUPPORTED); } #[test] fn check_process_memory_usage() { let mut s = System::new(); s.refresh_all(); if System::IS_SUPPORTED { // No process should have 0 as memory usage. #[cfg(not(feature = "apple-sandbox"))] assert!(!s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); } else { // There should be no process, but if there is one, its memory usage should be 0. assert!(s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); } } #[test] fn check_memory_usage() { let mut s = System::new(); assert_eq!(s.total_memory(), 0); assert_eq!(s.free_memory(), 0); assert_eq!(s.available_memory(), 0); assert_eq!(s.used_memory(), 0); assert_eq!(s.total_swap(), 0); assert_eq!(s.free_swap(), 0); assert_eq!(s.used_swap(), 0); s.refresh_memory(); if System::IS_SUPPORTED { assert!(s.total_memory() > 0); assert!(s.used_memory() > 0); if s.total_swap() > 0 { // I think it's pretty safe to assume that there is still some swap left... assert!(s.free_swap() > 0); } } else { assert_eq!(s.total_memory(), 0); assert_eq!(s.used_memory(), 0); assert_eq!(s.total_swap(), 0); assert_eq!(s.free_swap(), 0); } } #[cfg(target_os = "linux")] #[test] fn check_processes_cpu_usage() { if !System::IS_SUPPORTED { return; } let mut s = System::new(); s.refresh_processes(); // All CPU usage will start at zero until the second refresh assert!(s .processes() .iter() .all(|(_, proc_)| proc_.cpu_usage() == 0.0)); // Wait a bit to update CPU usage values std::thread::sleep(std::time::Duration::from_millis(100)); s.refresh_processes(); assert!(s .processes() .iter() .all(|(_, proc_)| proc_.cpu_usage() >= 0.0 && proc_.cpu_usage() <= (s.cpus().len() as f32) * 100.0)); assert!(s .processes() .iter() .any(|(_, proc_)| proc_.cpu_usage() > 0.0)); } #[test] fn check_cpu_usage() { if !System::IS_SUPPORTED { return; } let mut s = System::new(); for _ in 0..10 { s.refresh_cpu(); // Wait a bit to update CPU usage values std::thread::sleep(std::time::Duration::from_millis(100)); if s.cpus().iter().any(|c| c.cpu_usage() > 0.0) { // All good! return; } } panic!("CPU usage is always zero..."); } #[test] fn check_users() { let mut s = System::new(); assert!(s.users().is_empty()); s.refresh_users_list(); assert!(s.users().len() >= MIN_USERS); let mut s = System::new(); assert!(s.users().is_empty()); s.refresh_all(); assert!(s.users().is_empty()); let s = System::new_all(); assert!(s.users().len() >= MIN_USERS); } #[test] fn check_uid_gid() { let mut s = System::new(); assert!(s.users().is_empty()); s.refresh_users_list(); let users = s.users(); assert!(users.len() >= MIN_USERS); if System::IS_SUPPORTED { #[cfg(not(target_os = "windows"))] { let user = users .iter() .find(|u| u.name() == "root") .expect("no root user"); assert_eq!(**user.id(), 0); assert_eq!(*user.group_id(), 0); if let Some(user) = users.iter().find(|u| *u.group_id() > 0) { assert!(**user.id() > 0); assert!(*user.group_id() > 0); } assert!(users.iter().filter(|u| **u.id() > 0).count() > 0); } // And now check that our `get_user_by_id` method works. s.refresh_processes(); assert!(s .processes() .iter() .filter_map(|(_, p)| p.user_id()) .any(|uid| s.get_user_by_id(uid).is_some())); } } #[test] fn check_all_process_uids_resolvable() { if System::IS_SUPPORTED { let s = System::new_with_specifics( RefreshKind::new() .with_processes(ProcessRefreshKind::new().with_user()) .with_users_list(), ); // For every process where we can get a user ID, we should also be able // to find that user ID in the global user list for process in s.processes().values() { if let Some(uid) = process.user_id() { assert!(s.get_user_by_id(uid).is_some(), "No UID {:?} found", uid); } } } } #[test] fn check_system_info() { let s = System::new(); // We don't want to test on unsupported systems. if System::IS_SUPPORTED { assert!(!s.name().expect("Failed to get system name").is_empty()); assert!(!s .kernel_version() .expect("Failed to get kernel version") .is_empty()); assert!(!s.os_version().expect("Failed to get os version").is_empty()); assert!(!s .long_os_version() .expect("Failed to get long OS version") .is_empty()); } assert!(!s.distribution_id().is_empty()); } #[test] fn check_host_name() { // We don't want to test on unsupported systems. if System::IS_SUPPORTED { let s = System::new(); assert!(s.host_name().is_some()); } } #[test] fn check_refresh_process_return_value() { // We don't want to test on unsupported systems. if System::IS_SUPPORTED { let _pid = get_current_pid().expect("Failed to get current PID"); #[cfg(not(feature = "apple-sandbox"))] { let mut s = System::new(); // First check what happens in case the process isn't already in our process list. assert!(s.refresh_process(_pid)); // Then check that it still returns true if the process is already in our process list. assert!(s.refresh_process(_pid)); } } } #[test] fn ensure_is_supported_is_set_correctly() { if MIN_USERS > 0 { assert!(System::IS_SUPPORTED); } else { assert!(!System::IS_SUPPORTED); } } #[test] fn check_cpus_number() { let mut s = System::new(); // This information isn't retrieved by default. assert!(s.cpus().is_empty()); if System::IS_SUPPORTED { // The physical cores count is recomputed every time the function is called, so the // information must be relevant even with nothing initialized. let physical_cores_count = s .physical_core_count() .expect("failed to get number of physical cores"); s.refresh_cpu(); // The cpus shouldn't be empty anymore. assert!(!s.cpus().is_empty()); // In case we are running inside a VM, it's possible to not have a physical core, only // logical ones, which is why we don't test `physical_cores_count > 0`. let physical_cores_count2 = s .physical_core_count() .expect("failed to get number of physical cores"); assert!(physical_cores_count2 <= s.cpus().len()); assert_eq!(physical_cores_count, physical_cores_count2); } else { assert_eq!(s.physical_core_count(), None); } assert!(s.physical_core_count().unwrap_or(0) <= s.cpus().len()); } #[test] fn check_nb_supported_signals() { if System::IS_SUPPORTED { assert!( !System::SUPPORTED_SIGNALS.is_empty(), "SUPPORTED_SIGNALS shoudn't be empty on supported systems!" ); } else { assert!( System::SUPPORTED_SIGNALS.is_empty(), "SUPPORTED_SIGNALS should be empty on not support systems!" ); } } // Ensure that the CPUs frequency isn't retrieved until we ask for it. #[test] fn check_cpu_frequency() { if !System::IS_SUPPORTED { return; } let mut s = System::new(); s.refresh_processes(); for proc_ in s.cpus() { assert_eq!(proc_.frequency(), 0); } s.refresh_cpu(); for proc_ in s.cpus() { assert_eq!(proc_.frequency(), 0); } // In a VM, it'll fail. if std::env::var("APPLE_CI").is_err() && std::env::var("FREEBSD_CI").is_err() { s.refresh_cpu_specifics(CpuRefreshKind::everything()); for proc_ in s.cpus() { assert_ne!(proc_.frequency(), 0); } } } // In case `Process::updated` is misused, `System::refresh_processes` might remove them // so this test ensures that it doesn't happen. #[test] fn check_refresh_process_update() { if !System::IS_SUPPORTED { return; } let mut s = System::new_all(); let total = s.processes().len() as isize; s.refresh_processes(); let new_total = s.processes().len() as isize; // There should be almost no difference in the processes count. assert!( (new_total - total).abs() <= 5, "{} <= 5", (new_total - total).abs() ); } // We ensure that the `Process` cmd information is retrieved as expected. #[test] fn check_cmd_line() { if !System::IS_SUPPORTED { return; } let mut sys = System::new(); sys.refresh_processes_specifics(ProcessRefreshKind::new()); assert!(sys .processes() .iter() .any(|(_, process)| !process.cmd().is_empty())); } } sysinfo-0.28.4/src/linux/component.rs000064400000000000000000000311771046102023000157200ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // Information about values readable from `hwmon` sysfs. // // Values in /sys/class/hwmonN are `c_long` or `c_ulong` // transposed to rust we only read `u32` or `i32` values. use crate::ComponentExt; use std::collections::HashMap; use std::fs::{read_dir, File}; use std::io::Read; use std::path::{Path, PathBuf}; #[doc = include_str!("../../md_doc/component.md")] #[derive(Default)] pub struct Component { /// Optional associated device of a `Component`. device_model: Option, /// The chip name. /// /// Kernel documentation extract: /// ```txt /// This should be a short, lowercase string, not containing /// whitespace, dashes, or the wildcard character '*'. /// This attribute represents the chip name. It is the only /// mandatory attribute. /// I2C devices get this attribute created automatically. /// ``` name: String, /// Temperature current value /// - Read in: `temp[1-*]_input`. /// - Unit: read as millidegree Celsius converted to Celsius. temperature: Option, /// Maximum value computed by `sysinfo`. max: Option, /// Max threshold provided by the chip/kernel /// - Read in:`temp[1-*]_max` /// - Unit: read as millidegree Celsius converted to Celsius. threshold_max: Option, /// Min threshold provided by the chip/kernel. /// - Read in:`temp[1-*]_min` /// - Unit: read as millidegree Celsius converted to Celsius. threshold_min: Option, /// Critical threshold provided by the chip/kernel previous user write. /// Read in `temp[1-*]_crit`: /// Typically greater than corresponding temp_max values. /// - Unit: read as millidegree Celsius converted to Celsius. threshold_critical: Option, /// Sensor type, not common but can exist! /// /// Read in: `temp[1-*]_type` Sensor type selection. /// Values integer: /// - 1: CPU embedded diode /// - 2: 3904 transistor /// - 3: thermal diode /// - 4: thermistor /// - 5: AMD AMDSI /// - 6: Intel PECI /// Not all types are supported by all chips sensor_type: Option, /// Component Label /// /// For formatting detail see `Component::label` function docstring. /// /// ## Linux implementation details /// /// read n: `temp[1-*]_label` Suggested temperature channel label. /// Value: Text string /// /// Should only be created if the driver has hints about what /// this temperature channel is being used for, and user-space /// doesn't. In all other cases, the label is provided by user-space. label: String, // TODO: not used now. // Historical minimum temperature // - Read in:`temp[1-*]_lowest // - Unit: millidegree Celsius // // Temperature critical min value, typically lower than // corresponding temp_min values. // - Read in:`temp[1-*]_lcrit` // - Unit: millidegree Celsius // // Temperature emergency max value, for chips supporting more than // two upper temperature limits. Must be equal or greater than // corresponding temp_crit values. // - temp[1-*]_emergency // - Unit: millidegree Celsius /// File to read current temperature shall be `temp[1-*]_input` /// It may be absent but we don't continue if absent. input_file: Option, /// `temp[1-*]_highest file` to read if available highest value. highest_file: Option, } // Read arbitrary data from sysfs. fn get_file_line(file: &Path, capacity: usize) -> Option { let mut reader = String::with_capacity(capacity); let mut f = File::open(file).ok()?; f.read_to_string(&mut reader).ok()?; reader.truncate(reader.trim_end().len()); Some(reader) } /// Designed at first for reading an `i32` or `u32` aka `c_long` /// from a `/sys/class/hwmon` sysfs file. fn read_number_from_file(file: &Path) -> Option where N: std::str::FromStr, { let mut reader = [0u8; 32]; let mut f = File::open(file).ok()?; let n = f.read(&mut reader).ok()?; // parse and trim would complain about `\0`. let number = &reader[..n]; let number = std::str::from_utf8(number).ok()?; let number = number.trim(); // Assert that we cleaned a little bit that string. if cfg!(feature = "debug") { assert!(!number.contains('\n') && !number.contains('\0')); } number.parse().ok() } // Read a temperature from a `tempN_item` sensor form the sysfs. // number returned will be in mili-celsius. // // Don't call it on `label`, `name` or `type` file. #[inline] fn get_temperature_from_file(file: &Path) -> Option { let temp = read_number_from_file(file); convert_temp_celsius(temp) } /// Takes a raw temperature in mili-celsius and convert it to celsius. #[inline] fn convert_temp_celsius(temp: Option) -> Option { temp.map(|n| (n as f32) / 1000f32) } /// Information about thermal sensor. It may be unavailable as it's /// kernel module and chip dependent. enum TermalSensorType { /// 1: CPU embedded diode CPUEmbeddedDiode, /// 2: 3904 transistor Transistor3904, /// 3: thermal diode ThermalDiode, /// 4: thermistor Thermistor, /// 5: AMD AMDSI AMDAMDSI, /// 6: Intel PECI IntelPECI, /// Not all types are supported by all chips so we keep space for /// unknown sensors. Unknown(u8), } impl From for TermalSensorType { fn from(input: u8) -> Self { match input { 0 => Self::CPUEmbeddedDiode, 1 => Self::Transistor3904, 3 => Self::ThermalDiode, 4 => Self::Thermistor, 5 => Self::AMDAMDSI, 6 => Self::IntelPECI, n => Self::Unknown(n), } } } /// Check given `item` dispatch to read the right `file` with the right parsing and store data in /// given `component`. `id` is provided for `label` creation. fn fill_component(component: &mut Component, item: &str, folder: &Path, file: &str) { let hwmon_file = folder.join(file); match item { "type" => { component.sensor_type = read_number_from_file::(&hwmon_file).map(TermalSensorType::from) } "input" => { let temperature = get_temperature_from_file(&hwmon_file); component.input_file = Some(hwmon_file); component.temperature = temperature; // Maximum know try to get it from `highest` if not available // use current temperature if component.max.is_none() { component.max = temperature; } } "label" => component.label = get_file_line(&hwmon_file, 10).unwrap_or_default(), "highest" => { component.max = get_temperature_from_file(&hwmon_file).or(component.temperature); component.highest_file = Some(hwmon_file); } "max" => component.threshold_max = get_temperature_from_file(&hwmon_file), "min" => component.threshold_min = get_temperature_from_file(&hwmon_file), "crit" => component.threshold_critical = get_temperature_from_file(&hwmon_file), _ => { sysinfo_debug!( "This hwmon-temp file is still not supported! Contributions are appreciated.;) {:?}", hwmon_file, ); } } } impl Component { /// Read out `hwmon` info (hardware monitor) from `folder` /// to get values' path to be used on refresh as well as files containing `max`, /// `critical value` and `label`. Then we store everything into `components`. /// /// Note that a thermal [Component] must have a way to read its temperature. /// If not, it will be ignored and not added into `components`. /// /// ## What is read: /// /// - Mandatory: `name` the name of the `hwmon`. /// - Mandatory: `tempN_input` Drop [Component] if missing /// - Optional: sensor `label`, in the general case content of `tempN_label` /// see below for special cases /// - Optional: `label` /// - Optional: `/device/model` /// - Optional: hightest historic value in `tempN_hightest`. /// - Optional: max threshold value defined in `tempN_max` /// - Optional: critical threshold value defined in `tempN_crit` /// /// Where `N` is a `u32` associated to a sensor like `temp1_max`, `temp1_input`. /// /// ## Doc to Linux kernel API. /// /// Kernel hwmon API: https://www.kernel.org/doc/html/latest/hwmon/hwmon-kernel-api.html /// DriveTemp kernel API: https://docs.kernel.org/gpu/amdgpu/thermal.html#hwmon-interfaces /// Amdgpu hwmon interface: https://www.kernel.org/doc/html/latest/hwmon/drivetemp.html fn from_hwmon(components: &mut Vec, folder: &Path) -> Option<()> { let dir = read_dir(folder).ok()?; let mut matchings: HashMap = HashMap::with_capacity(10); for entry in dir.flatten() { let entry = entry.path(); let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or(""); if entry.is_dir() || !filename.starts_with("temp") { continue; } let (id, item) = filename.split_once('_')?; let id = id.get(4..)?.parse::().ok()?; let component = matchings.entry(id).or_insert_with(Component::default); let name = get_file_line(&folder.join("name"), 16); component.name = name.unwrap_or_default(); let device_model = get_file_line(&folder.join("device/model"), 16); component.device_model = device_model; fill_component(component, item, folder, filename); } let compo = matchings .into_iter() .map(|(id, mut c)| { // sysinfo expose a generic interface with a `label`. // Problem: a lot of sensors don't have a label or a device model! ¯\_(ツ)_/¯ // So let's pretend we have a unique label! // See the table in `Component::label` documentation for the table detail. c.label = c.format_label("temp", id); c }) // Remove components without `tempN_input` file termal. `Component` doesn't support this kind of sensors yet .filter(|c| c.input_file.is_some()); components.extend(compo); Some(()) } /// Compute a label out of available information. /// See the table in `Component::label`'s documentation. fn format_label(&self, class: &str, id: u32) -> String { let Component { device_model, name, label, .. } = self; let has_label = !label.is_empty(); match (has_label, device_model) { (true, Some(device_model)) => { format!("{name} {label} {device_model} {class}{id}") } (true, None) => format!("{name} {label}"), (false, Some(device_model)) => format!("{name} {device_model}"), (false, None) => format!("{name} {class}{id}"), } } } impl ComponentExt for Component { fn temperature(&self) -> f32 { self.temperature.unwrap_or(f32::NAN) } fn max(&self) -> f32 { self.max.unwrap_or(f32::NAN) } fn critical(&self) -> Option { self.threshold_critical } fn label(&self) -> &str { &self.label } fn refresh(&mut self) { let current = self .input_file .as_ref() .and_then(|file| get_temperature_from_file(file.as_path())); // tries to read out kernel highest if not compute something from temperature. let max = self .highest_file .as_ref() .and_then(|file| get_temperature_from_file(file.as_path())) .or_else(|| { let last = self.temperature?; let current = current?; Some(last.max(current)) }); self.max = max; self.temperature = current; } } pub(crate) fn get_components() -> Vec { let mut components = Vec::with_capacity(10); if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) { for entry in dir.flatten() { let entry = entry.path(); if !entry.is_dir() || !entry .file_name() .and_then(|x| x.to_str()) .unwrap_or("") .starts_with("hwmon") { continue; } Component::from_hwmon(&mut components, &entry); } components.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase())); } components } sysinfo-0.28.4/src/linux/cpu.rs000064400000000000000000000616061046102023000145050ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #![allow(clippy::too_many_arguments)] use std::collections::HashSet; use std::fs::File; use std::io::{BufRead, BufReader, Read}; use std::time::Instant; use crate::sys::utils::to_u64; use crate::{CpuExt, CpuRefreshKind, SystemExt}; macro_rules! to_str { ($e:expr) => { unsafe { std::str::from_utf8_unchecked($e) } }; } pub(crate) struct CpusWrapper { pub(crate) global_cpu: Cpu, pub(crate) cpus: Vec, /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`. /// /// The reason behind this is to avoid calling the `update_cpus` more than necessary. /// For example when running `refresh_all` or `refresh_specifics`. need_cpus_update: bool, got_cpu_frequency: bool, /// This field is needed to prevent updating when not enough time passed since last update. last_update: Option, } impl CpusWrapper { pub(crate) fn new() -> Self { Self { global_cpu: Cpu::new_with_values( "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, String::new(), String::new(), ), cpus: Vec::with_capacity(4), need_cpus_update: true, got_cpu_frequency: false, last_update: None, } } pub(crate) fn refresh_if_needed( &mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind, ) { if self.need_cpus_update { self.refresh(only_update_global_cpu, refresh_kind); } } pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { let need_cpu_usage_update = self .last_update .map(|last_update| last_update.elapsed() > crate::System::MINIMUM_CPU_UPDATE_INTERVAL) .unwrap_or(true); let first = self.cpus.is_empty(); let (vendor_id, brand) = if first { get_vendor_id_and_brand() } else { (String::new(), String::new()) }; // If the last CPU usage update is too close (less than `MINIMUM_CPU_UPDATE_INTERVAL`), // we don't want to update CPUs times. if need_cpu_usage_update { self.last_update = Some(Instant::now()); let f = match File::open("/proc/stat") { Ok(f) => f, Err(_e) => { sysinfo_debug!("failed to retrieve CPU information: {:?}", _e); return; } }; let buf = BufReader::new(f); self.need_cpus_update = false; let mut i: usize = 0; let mut it = buf.split(b'\n'); if first || refresh_kind.cpu_usage() { if let Some(Ok(line)) = it.next() { if &line[..4] != b"cpu " { return; } let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); if first { self.global_cpu.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); } else { parts.next(); } self.global_cpu.set( parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), ); } if first || !only_update_global_cpu { while let Some(Ok(line)) = it.next() { if &line[..3] != b"cpu" { break; } let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); if first { self.cpus.push(Cpu::new_with_values( to_str!(parts.next().unwrap_or(&[])), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), 0, vendor_id.clone(), brand.clone(), )); } else { parts.next(); // we don't want the name again self.cpus[i].set( parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), parts.next().map(to_u64).unwrap_or(0), ); } i += 1; } } } } if refresh_kind.frequency() { #[cfg(feature = "multithread")] use rayon::iter::{ IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator, }; #[cfg(feature = "multithread")] // This function is voluntarily made generic in case we want to generalize it. fn iter_mut<'a, T>( val: &'a mut T, ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter where &'a mut T: rayon::iter::IntoParallelIterator, { val.par_iter_mut() } #[cfg(not(feature = "multithread"))] fn iter_mut<'a>(val: &'a mut Vec) -> std::slice::IterMut<'a, Cpu> { val.iter_mut() } // `get_cpu_frequency` is very slow, so better run it in parallel. self.global_cpu.frequency = iter_mut(&mut self.cpus) .enumerate() .map(|(pos, proc_)| { proc_.frequency = get_cpu_frequency(pos); proc_.frequency }) .max() .unwrap_or(0); self.got_cpu_frequency = true; } if first { self.global_cpu.vendor_id = vendor_id; self.global_cpu.brand = brand; } } pub(crate) fn get_global_raw_times(&self) -> (u64, u64) { (self.global_cpu.total_time, self.global_cpu.old_total_time) } pub(crate) fn len(&self) -> usize { self.cpus.len() } pub(crate) fn is_empty(&self) -> bool { self.cpus.is_empty() } pub(crate) fn set_need_cpus_update(&mut self) { self.need_cpus_update = true; } } /// Struct containing values to compute a CPU usage. #[derive(Clone, Copy)] pub(crate) struct CpuValues { user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, } impl CpuValues { /// Creates a new instance of `CpuValues` with everything set to `0`. pub fn new() -> CpuValues { CpuValues { user: 0, nice: 0, system: 0, idle: 0, iowait: 0, irq: 0, softirq: 0, steal: 0, guest: 0, guest_nice: 0, } } /// Sets the given argument to the corresponding fields. pub fn set( &mut self, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, ) { // `guest` is already accounted in `user`. self.user = user.saturating_sub(guest); // `guest_nice` is already accounted in `nice`. self.nice = nice.saturating_sub(guest_nice); self.system = system; self.idle = idle; self.iowait = iowait; self.irq = irq; self.softirq = softirq; self.steal = steal; self.guest = guest; self.guest_nice = guest_nice; } /// Returns work time. pub fn work_time(&self) -> u64 { self.user .saturating_add(self.nice) .saturating_add(self.system) .saturating_add(self.irq) .saturating_add(self.softirq) } /// Returns total time. pub fn total_time(&self) -> u64 { self.work_time() .saturating_add(self.idle) .saturating_add(self.iowait) // `steal`, `guest` and `guest_nice` are only used if we want to account the "guest" // into the computation. .saturating_add(self.guest) .saturating_add(self.guest_nice) .saturating_add(self.steal) } } #[doc = include_str!("../../md_doc/cpu.md")] pub struct Cpu { old_values: CpuValues, new_values: CpuValues, pub(crate) name: String, cpu_usage: f32, total_time: u64, old_total_time: u64, pub(crate) frequency: u64, pub(crate) vendor_id: String, pub(crate) brand: String, } impl Cpu { pub(crate) fn new_with_values( name: &str, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, frequency: u64, vendor_id: String, brand: String, ) -> Cpu { let mut new_values = CpuValues::new(); new_values.set( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); Cpu { name: name.to_owned(), old_values: CpuValues::new(), new_values, cpu_usage: 0f32, total_time: 0, old_total_time: 0, frequency, vendor_id, brand, } } pub(crate) fn set( &mut self, user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, ) { macro_rules! min { ($a:expr, $b:expr, $def:expr) => { if $a > $b { ($a - $b) as f32 } else { $def } }; } self.old_values = self.new_values; self.new_values.set( user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, ); self.total_time = self.new_values.total_time(); self.old_total_time = self.old_values.total_time(); self.cpu_usage = min!(self.new_values.work_time(), self.old_values.work_time(), 0.) / min!(self.total_time, self.old_total_time, 1.) * 100.; if self.cpu_usage > 100. { self.cpu_usage = 100.; // to prevent the percentage to go above 100% } } } impl CpuExt for Cpu { fn cpu_usage(&self) -> f32 { self.cpu_usage } fn name(&self) -> &str { &self.name } /// Returns the CPU frequency in MHz. fn frequency(&self) -> u64 { self.frequency } fn vendor_id(&self) -> &str { &self.vendor_id } fn brand(&self) -> &str { &self.brand } } pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 { let mut s = String::new(); if File::open(format!( "/sys/devices/system/cpu/cpu{cpu_core_index}/cpufreq/scaling_cur_freq", )) .and_then(|mut f| f.read_to_string(&mut s)) .is_ok() { let freq_option = s.trim().split('\n').next(); if let Some(freq_string) = freq_option { if let Ok(freq) = freq_string.parse::() { return freq / 1000; } } } s.clear(); if File::open("/proc/cpuinfo") .and_then(|mut f| f.read_to_string(&mut s)) .is_err() { return 0; } let find_cpu_mhz = s.split('\n').find(|line| { line.starts_with("cpu MHz\t") || line.starts_with("BogoMIPS") || line.starts_with("clock\t") || line.starts_with("bogomips per cpu") }); find_cpu_mhz .and_then(|line| line.split(':').last()) .and_then(|val| val.replace("MHz", "").trim().parse::().ok()) .map(|speed| speed as u64) .unwrap_or_default() } #[allow(unused_assignments)] pub(crate) fn get_physical_core_count() -> Option { let mut s = String::new(); if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) { sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e); return None; } macro_rules! add_core { ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{ if !$core_id.is_empty() && !$physical_id.is_empty() { $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id)); } else if !$cpu.is_empty() { // On systems with only physical cores like raspberry, there is no "core id" or // "physical id" fields. So if one of them is missing, we simply use the "CPU" // info and count it as a physical core. $core_ids_and_physical_ids.insert($cpu.to_owned()); } $core_id = ""; $physical_id = ""; $cpu = ""; }}; } let mut core_ids_and_physical_ids: HashSet = HashSet::new(); let mut core_id = ""; let mut physical_id = ""; let mut cpu = ""; for line in s.lines() { if line.is_empty() { add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu); } else if line.starts_with("processor") { cpu = line .splitn(2, ':') .last() .map(|x| x.trim()) .unwrap_or_default(); } else if line.starts_with("core id") { core_id = line .splitn(2, ':') .last() .map(|x| x.trim()) .unwrap_or_default(); } else if line.starts_with("physical id") { physical_id = line .splitn(2, ':') .last() .map(|x| x.trim()) .unwrap_or_default(); } } add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu); Some(core_ids_and_physical_ids.len()) } /// Obtain the implementer of this CPU core. /// /// This has been obtained from util-linux's lscpu implementation, see /// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L240 /// /// This list will have to be updated every time a new vendor appears, please keep it synchronized /// with util-linux and update the link above with the commit you have used. fn get_arm_implementer(implementer: u32) -> Option<&'static str> { Some(match implementer { 0x41 => "ARM", 0x42 => "Broadcom", 0x43 => "Cavium", 0x44 => "DEC", 0x46 => "FUJITSU", 0x48 => "HiSilicon", 0x49 => "Infineon", 0x4d => "Motorola/Freescale", 0x4e => "NVIDIA", 0x50 => "APM", 0x51 => "Qualcomm", 0x53 => "Samsung", 0x56 => "Marvell", 0x61 => "Apple", 0x66 => "Faraday", 0x69 => "Intel", 0x70 => "Phytium", 0xc0 => "Ampere", _ => return None, }) } /// Obtain the part of this CPU core. /// /// This has been obtained from util-linux's lscpu implementation, see /// https://github.com/util-linux/util-linux/blob/7076703b529d255600631306419cca1b48ab850a/sys-utils/lscpu-arm.c#L34 /// /// This list will have to be updated every time a new core appears, please keep it synchronized /// with util-linux and update the link above with the commit you have used. fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> { Some(match (implementer, part) { // ARM (0x41, 0x810) => "ARM810", (0x41, 0x920) => "ARM920", (0x41, 0x922) => "ARM922", (0x41, 0x926) => "ARM926", (0x41, 0x940) => "ARM940", (0x41, 0x946) => "ARM946", (0x41, 0x966) => "ARM966", (0x41, 0xa20) => "ARM1020", (0x41, 0xa22) => "ARM1022", (0x41, 0xa26) => "ARM1026", (0x41, 0xb02) => "ARM11 MPCore", (0x41, 0xb36) => "ARM1136", (0x41, 0xb56) => "ARM1156", (0x41, 0xb76) => "ARM1176", (0x41, 0xc05) => "Cortex-A5", (0x41, 0xc07) => "Cortex-A7", (0x41, 0xc08) => "Cortex-A8", (0x41, 0xc09) => "Cortex-A9", (0x41, 0xc0d) => "Cortex-A17", // Originally A12 (0x41, 0xc0f) => "Cortex-A15", (0x41, 0xc0e) => "Cortex-A17", (0x41, 0xc14) => "Cortex-R4", (0x41, 0xc15) => "Cortex-R5", (0x41, 0xc17) => "Cortex-R7", (0x41, 0xc18) => "Cortex-R8", (0x41, 0xc20) => "Cortex-M0", (0x41, 0xc21) => "Cortex-M1", (0x41, 0xc23) => "Cortex-M3", (0x41, 0xc24) => "Cortex-M4", (0x41, 0xc27) => "Cortex-M7", (0x41, 0xc60) => "Cortex-M0+", (0x41, 0xd01) => "Cortex-A32", (0x41, 0xd02) => "Cortex-A34", (0x41, 0xd03) => "Cortex-A53", (0x41, 0xd04) => "Cortex-A35", (0x41, 0xd05) => "Cortex-A55", (0x41, 0xd06) => "Cortex-A65", (0x41, 0xd07) => "Cortex-A57", (0x41, 0xd08) => "Cortex-A72", (0x41, 0xd09) => "Cortex-A73", (0x41, 0xd0a) => "Cortex-A75", (0x41, 0xd0b) => "Cortex-A76", (0x41, 0xd0c) => "Neoverse-N1", (0x41, 0xd0d) => "Cortex-A77", (0x41, 0xd0e) => "Cortex-A76AE", (0x41, 0xd13) => "Cortex-R52", (0x41, 0xd20) => "Cortex-M23", (0x41, 0xd21) => "Cortex-M33", (0x41, 0xd40) => "Neoverse-V1", (0x41, 0xd41) => "Cortex-A78", (0x41, 0xd42) => "Cortex-A78AE", (0x41, 0xd43) => "Cortex-A65AE", (0x41, 0xd44) => "Cortex-X1", (0x41, 0xd46) => "Cortex-A510", (0x41, 0xd47) => "Cortex-A710", (0x41, 0xd48) => "Cortex-X2", (0x41, 0xd49) => "Neoverse-N2", (0x41, 0xd4a) => "Neoverse-E1", (0x41, 0xd4b) => "Cortex-A78C", (0x41, 0xd4c) => "Cortex-X1C", (0x41, 0xd4d) => "Cortex-A715", (0x41, 0xd4e) => "Cortex-X3", // Broadcom (0x42, 0x00f) => "Brahma-B15", (0x42, 0x100) => "Brahma-B53", (0x42, 0x516) => "ThunderX2", // Cavium (0x43, 0x0a0) => "ThunderX", (0x43, 0x0a1) => "ThunderX-88XX", (0x43, 0x0a2) => "ThunderX-81XX", (0x43, 0x0a3) => "ThunderX-83XX", (0x43, 0x0af) => "ThunderX2-99xx", // DEC (0x44, 0xa10) => "SA110", (0x44, 0xa11) => "SA1100", // Fujitsu (0x46, 0x001) => "A64FX", // HiSilicon (0x48, 0xd01) => "Kunpeng-920", // aka tsv110 // NVIDIA (0x4e, 0x000) => "Denver", (0x4e, 0x003) => "Denver 2", (0x4e, 0x004) => "Carmel", // APM (0x50, 0x000) => "X-Gene", // Qualcomm (0x51, 0x00f) => "Scorpion", (0x51, 0x02d) => "Scorpion", (0x51, 0x04d) => "Krait", (0x51, 0x06f) => "Krait", (0x51, 0x201) => "Kryo", (0x51, 0x205) => "Kryo", (0x51, 0x211) => "Kryo", (0x51, 0x800) => "Falkor-V1/Kryo", (0x51, 0x801) => "Kryo-V2", (0x51, 0x802) => "Kryo-3XX-Gold", (0x51, 0x803) => "Kryo-3XX-Silver", (0x51, 0x804) => "Kryo-4XX-Gold", (0x51, 0x805) => "Kryo-4XX-Silver", (0x51, 0xc00) => "Falkor", (0x51, 0xc01) => "Saphira", // Samsung (0x53, 0x001) => "exynos-m1", // Marvell (0x56, 0x131) => "Feroceon-88FR131", (0x56, 0x581) => "PJ4/PJ4b", (0x56, 0x584) => "PJ4B-MP", // Apple (0x61, 0x020) => "Icestorm-A14", (0x61, 0x021) => "Firestorm-A14", (0x61, 0x022) => "Icestorm-M1", (0x61, 0x023) => "Firestorm-M1", (0x61, 0x024) => "Icestorm-M1-Pro", (0x61, 0x025) => "Firestorm-M1-Pro", (0x61, 0x028) => "Icestorm-M1-Max", (0x61, 0x029) => "Firestorm-M1-Max", (0x61, 0x030) => "Blizzard-A15", (0x61, 0x031) => "Avalanche-A15", (0x61, 0x032) => "Blizzard-M2", (0x61, 0x033) => "Avalanche-M2", // Faraday (0x66, 0x526) => "FA526", (0x66, 0x626) => "FA626", // Intel (0x69, 0x200) => "i80200", (0x69, 0x210) => "PXA250A", (0x69, 0x212) => "PXA210A", (0x69, 0x242) => "i80321-400", (0x69, 0x243) => "i80321-600", (0x69, 0x290) => "PXA250B/PXA26x", (0x69, 0x292) => "PXA210B", (0x69, 0x2c2) => "i80321-400-B0", (0x69, 0x2c3) => "i80321-600-B0", (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x", (0x69, 0x2d2) => "PXA210C", (0x69, 0x411) => "PXA27x", (0x69, 0x41c) => "IPX425-533", (0x69, 0x41d) => "IPX425-400", (0x69, 0x41f) => "IPX425-266", (0x69, 0x682) => "PXA32x", (0x69, 0x683) => "PXA930/PXA935", (0x69, 0x688) => "PXA30x", (0x69, 0x689) => "PXA31x", (0x69, 0xb11) => "SA1110", (0x69, 0xc12) => "IPX1200", // Phytium (0x70, 0x660) => "FTC660", (0x70, 0x661) => "FTC661", (0x70, 0x662) => "FTC662", (0x70, 0x663) => "FTC663", _ => return None, }) } /// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs). pub(crate) fn get_vendor_id_and_brand() -> (String, String) { let mut s = String::new(); if File::open("/proc/cpuinfo") .and_then(|mut f| f.read_to_string(&mut s)) .is_err() { return (String::new(), String::new()); } fn get_value(s: &str) -> String { s.split(':') .last() .map(|x| x.trim().to_owned()) .unwrap_or_default() } fn get_hex_value(s: &str) -> u32 { s.split(':') .last() .map(|x| x.trim()) .filter(|x| x.starts_with("0x")) .map(|x| u32::from_str_radix(&x[2..], 16).unwrap()) .unwrap_or_default() } let mut vendor_id = None; let mut brand = None; let mut implementer = None; let mut part = None; for it in s.split('\n') { if it.starts_with("vendor_id\t") { vendor_id = Some(get_value(it)); } else if it.starts_with("model name\t") { brand = Some(get_value(it)); } else if it.starts_with("CPU implementer\t") { implementer = Some(get_hex_value(it)); } else if it.starts_with("CPU part\t") { part = Some(get_hex_value(it)); } else { continue; } if (brand.is_some() && vendor_id.is_some()) || (implementer.is_some() && part.is_some()) { break; } } if let (Some(implementer), Some(part)) = (implementer, part) { vendor_id = get_arm_implementer(implementer).map(String::from); // It's possible to "model name" even with an ARM CPU, so just in case we can't retrieve // the brand from "CPU part", we will then use the value from "model name". // // Example from raspberry pi 3B+: // // ``` // model name : ARMv7 Processor rev 4 (v7l) // CPU implementer : 0x41 // CPU part : 0xd03 // ``` brand = get_arm_part(implementer, part).map(String::from).or(brand); } (vendor_id.unwrap_or_default(), brand.unwrap_or_default()) } sysinfo-0.28.4/src/linux/disk.rs000064400000000000000000000251111046102023000146370ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::{get_all_data, to_cpath}; use crate::{DiskExt, DiskType}; use libc::statvfs; use std::ffi::{OsStr, OsString}; use std::fs; use std::mem; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; macro_rules! cast { ($x:expr) => { u64::from($x) }; } #[doc = include_str!("../../md_doc/disk.md")] #[derive(PartialEq, Eq)] pub struct Disk { type_: DiskType, device_name: OsString, file_system: Vec, mount_point: PathBuf, total_space: u64, available_space: u64, is_removable: bool, } impl DiskExt for Disk { fn type_(&self) -> DiskType { self.type_ } fn name(&self) -> &OsStr { &self.device_name } fn file_system(&self) -> &[u8] { &self.file_system } fn mount_point(&self) -> &Path { &self.mount_point } fn total_space(&self) -> u64 { self.total_space } fn available_space(&self) -> u64 { self.available_space } fn is_removable(&self) -> bool { self.is_removable } fn refresh(&mut self) -> bool { unsafe { let mut stat: statvfs = mem::zeroed(); let mount_point_cpath = to_cpath(&self.mount_point); if statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat) == 0 { let tmp = cast!(stat.f_bsize).saturating_mul(cast!(stat.f_bavail)); self.available_space = cast!(tmp); true } else { false } } } } fn new_disk( device_name: &OsStr, mount_point: &Path, file_system: &[u8], removable_entries: &[PathBuf], ) -> Option { let mount_point_cpath = to_cpath(mount_point); let type_ = find_type_for_device_name(device_name); let mut total = 0; let mut available = 0; unsafe { let mut stat: statvfs = mem::zeroed(); if statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat) == 0 { let bsize = cast!(stat.f_bsize); let blocks = cast!(stat.f_blocks); let bavail = cast!(stat.f_bavail); total = bsize.saturating_mul(blocks); available = bsize.saturating_mul(bavail); } if total == 0 { return None; } let mount_point = mount_point.to_owned(); let is_removable = removable_entries .iter() .any(|e| e.as_os_str() == device_name); Some(Disk { type_, device_name: device_name.to_owned(), file_system: file_system.to_owned(), mount_point, total_space: cast!(total), available_space: cast!(available), is_removable, }) } } #[allow(clippy::manual_range_contains)] fn find_type_for_device_name(device_name: &OsStr) -> DiskType { // The format of devices are as follows: // - device_name is symbolic link in the case of /dev/mapper/ // and /dev/root, and the target is corresponding device under // /sys/block/ // - In the case of /dev/sd, the format is /dev/sd[a-z][1-9], // corresponding to /sys/block/sd[a-z] // - In the case of /dev/nvme, the format is /dev/nvme[0-9]n[0-9]p[0-9], // corresponding to /sys/block/nvme[0-9]n[0-9] // - In the case of /dev/mmcblk, the format is /dev/mmcblk[0-9]p[0-9], // corresponding to /sys/block/mmcblk[0-9] let device_name_path = device_name.to_str().unwrap_or_default(); let real_path = fs::canonicalize(device_name).unwrap_or_else(|_| PathBuf::from(device_name)); let mut real_path = real_path.to_str().unwrap_or_default(); if device_name_path.starts_with("/dev/mapper/") { // Recursively solve, for example /dev/dm-0 if real_path != device_name_path { return find_type_for_device_name(OsStr::new(&real_path)); } } else if device_name_path.starts_with("/dev/sd") || device_name_path.starts_with("/dev/vd") { // Turn "sda1" into "sda" or "vda1" into "vda" real_path = real_path.trim_start_matches("/dev/"); real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); } else if device_name_path.starts_with("/dev/nvme") { // Turn "nvme0n1p1" into "nvme0n1" real_path = match real_path.find('p') { Some(idx) => &real_path["/dev/".len()..idx], None => &real_path["/dev/".len()..], }; } else if device_name_path.starts_with("/dev/root") { // Recursively solve, for example /dev/mmcblk0p1 if real_path != device_name_path { return find_type_for_device_name(OsStr::new(&real_path)); } } else if device_name_path.starts_with("/dev/mmcblk") { // Turn "mmcblk0p1" into "mmcblk0" real_path = match real_path.find('p') { Some(idx) => &real_path["/dev/".len()..idx], None => &real_path["/dev/".len()..], }; } else { // Default case: remove /dev/ and expects the name presents under /sys/block/ // For example, /dev/dm-0 to dm-0 real_path = real_path.trim_start_matches("/dev/"); } let trimmed: &OsStr = OsStrExt::from_bytes(real_path.as_bytes()); let path = Path::new("/sys/block/") .to_owned() .join(trimmed) .join("queue/rotational"); // Normally, this file only contains '0' or '1' but just in case, we get 8 bytes... match get_all_data(path, 8) .unwrap_or_default() .trim() .parse() .ok() { // The disk is marked as rotational so it's a HDD. Some(1) => DiskType::HDD, // The disk is marked as non-rotational so it's very likely a SSD. Some(0) => DiskType::SSD, // Normally it shouldn't happen but welcome to the wonderful world of IT! :D Some(x) => DiskType::Unknown(x), // The information isn't available... None => DiskType::Unknown(-1), } } fn get_all_disks_inner(content: &str) -> Vec { // The goal of this array is to list all removable devices (the ones whose name starts with // "usb-"). Then we check if let removable_entries = match fs::read_dir("/dev/disk/by-id/") { Ok(r) => r .filter_map(|res| Some(res.ok()?.path())) .filter_map(|e| { if e.file_name() .and_then(|x| Some(x.to_str()?.starts_with("usb-"))) .unwrap_or_default() { e.canonicalize().ok() } else { None } }) .collect::>(), _ => Vec::new(), }; content .lines() .map(|line| { let line = line.trim_start(); // mounts format // http://man7.org/linux/man-pages/man5/fstab.5.html // fs_specfs_filefs_vfstypeother fields let mut fields = line.split_whitespace(); let fs_spec = fields.next().unwrap_or(""); let fs_file = fields .next() .unwrap_or("") .replace("\\134", "\\") .replace("\\040", " ") .replace("\\011", "\t") .replace("\\012", "\n"); let fs_vfstype = fields.next().unwrap_or(""); (fs_spec, fs_file, fs_vfstype) }) .filter(|(fs_spec, fs_file, fs_vfstype)| { // Check if fs_vfstype is one of our 'ignored' file systems. let filtered = matches!( *fs_vfstype, "rootfs" | // https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt "sysfs" | // pseudo file system for kernel objects "proc" | // another pseudo file system "tmpfs" | "devtmpfs" | "cgroup" | "cgroup2" | "pstore" | // https://www.kernel.org/doc/Documentation/ABI/testing/pstore "squashfs" | // squashfs is a compressed read-only file system (for snaps) "rpc_pipefs" | // The pipefs pseudo file system service "iso9660" | // optical media "nfs4" | // calling statvfs on a mounted NFS may hang "nfs" // nfs2 or nfs3 ); !(filtered || fs_file.starts_with("/sys") || // check if fs_file is an 'ignored' mount point fs_file.starts_with("/proc") || (fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) || fs_spec.starts_with("sunrpc")) }) .filter_map(|(fs_spec, fs_file, fs_vfstype)| { new_disk( fs_spec.as_ref(), Path::new(&fs_file), fs_vfstype.as_bytes(), &removable_entries, ) }) .collect() } pub(crate) fn get_all_disks() -> Vec { get_all_disks_inner(&get_all_data("/proc/mounts", 16_385).unwrap_or_default()) } // #[test] // fn check_all_disks() { // let disks = get_all_disks_inner( // r#"tmpfs /proc tmpfs rw,seclabel,relatime 0 0 // proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 // systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=17771 0 0 // tmpfs /sys tmpfs rw,seclabel,relatime 0 0 // sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 // securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 // cgroup2 /sys/fs/cgroup cgroup2 rw,seclabel,nosuid,nodev,noexec,relatime,nsdelegate 0 0 // pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0 // none /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0 // configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0 // selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0 // debugfs /sys/kernel/debug debugfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 // tmpfs /dev/shm tmpfs rw,seclabel,relatime 0 0 // devpts /dev/pts devpts rw,seclabel,relatime,gid=5,mode=620,ptmxmode=666 0 0 // tmpfs /sys/fs/selinux tmpfs rw,seclabel,relatime 0 0 // /dev/vda2 /proc/filesystems xfs rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 // "#, // ); // assert_eq!(disks.len(), 1); // assert_eq!( // disks[0], // Disk { // type_: DiskType::Unknown(-1), // name: OsString::from("devpts"), // file_system: vec![100, 101, 118, 112, 116, 115], // mount_point: PathBuf::from("/dev/pts"), // total_space: 0, // available_space: 0, // } // ); // } sysinfo-0.28.4/src/linux/mod.rs000064400000000000000000000006141046102023000144650ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod cpu; pub mod disk; pub mod network; pub mod process; pub mod system; pub(crate) mod utils; pub use self::component::Component; pub use self::cpu::Cpu; pub use self::disk::Disk; pub use self::network::{NetworkData, Networks}; pub use self::process::Process; pub use self::system::System; sysinfo-0.28.4/src/linux/network.rs000064400000000000000000000245401046102023000154030ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::io::Read; use std::path::Path; use std::{fs::File, u8}; use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::{NetworkExt, NetworksExt, NetworksIter}; use std::collections::{hash_map, HashMap}; #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $name; }}; ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{ let _tmp = $path; $ty_.$old = $ty_.$name; $ty_.$name = _tmp; }}; } #[allow(clippy::ptr_arg)] fn read>(parent: P, path: &str, data: &mut Vec) -> u64 { if let Ok(mut f) = File::open(parent.as_ref().join(path)) { if let Ok(size) = f.read(data) { let mut i = 0; let mut ret = 0; while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' { ret *= 10; ret += (data[i] - b'0') as u64; i += 1; } return ret; } } 0 } impl Networks { pub(crate) fn new() -> Self { Networks { interfaces: HashMap::new(), } } } fn refresh_networks_list_from_sysfs( interfaces: &mut HashMap, sysfs_net: &Path, ) { if let Ok(dir) = std::fs::read_dir(sysfs_net) { let mut data = vec![0; 30]; for stats in interfaces.values_mut() { stats.updated = false; } for entry in dir.flatten() { let parent = &entry.path().join("statistics"); let entry = match entry.file_name().into_string() { Ok(entry) => entry, Err(_) => continue, }; let rx_bytes = read(parent, "rx_bytes", &mut data); let tx_bytes = read(parent, "tx_bytes", &mut data); let rx_packets = read(parent, "rx_packets", &mut data); let tx_packets = read(parent, "tx_packets", &mut data); let rx_errors = read(parent, "rx_errors", &mut data); let tx_errors = read(parent, "tx_errors", &mut data); // let rx_compressed = read(parent, "rx_compressed", &mut data); // let tx_compressed = read(parent, "tx_compressed", &mut data); match interfaces.entry(entry) { hash_map::Entry::Occupied(mut e) => { let mut interface = e.get_mut(); old_and_new!(interface, rx_bytes, old_rx_bytes); old_and_new!(interface, tx_bytes, old_tx_bytes); old_and_new!(interface, rx_packets, old_rx_packets); old_and_new!(interface, tx_packets, old_tx_packets); old_and_new!(interface, rx_errors, old_rx_errors); old_and_new!(interface, tx_errors, old_tx_errors); // old_and_new!(e, rx_compressed, old_rx_compressed); // old_and_new!(e, tx_compressed, old_tx_compressed); interface.updated = true; } hash_map::Entry::Vacant(e) => { e.insert(NetworkData { rx_bytes, old_rx_bytes: rx_bytes, tx_bytes, old_tx_bytes: tx_bytes, rx_packets, old_rx_packets: rx_packets, tx_packets, old_tx_packets: tx_packets, rx_errors, old_rx_errors: rx_errors, tx_errors, old_tx_errors: tx_errors, mac_addr: MacAddr::UNSPECIFIED, // rx_compressed, // old_rx_compressed: rx_compressed, // tx_compressed, // old_tx_compressed: tx_compressed, updated: true, }); } }; } // Remove interfaces which are gone. interfaces.retain(|_, d| d.updated); } } impl NetworksExt for Networks { fn iter(&self) -> NetworksIter { NetworksIter::new(self.interfaces.iter()) } fn refresh(&mut self) { let mut v = vec![0; 30]; for (interface_name, data) in self.interfaces.iter_mut() { data.update(interface_name, &mut v); } } fn refresh_networks_list(&mut self) { refresh_networks_list_from_sysfs(&mut self.interfaces, Path::new("/sys/class/net/")); refresh_networks_addresses(&mut self.interfaces); } } #[doc = include_str!("../../md_doc/network_data.md")] pub struct NetworkData { /// Total number of bytes received over interface. rx_bytes: u64, old_rx_bytes: u64, /// Total number of bytes transmitted over interface. tx_bytes: u64, old_tx_bytes: u64, /// Total number of packets received. rx_packets: u64, old_rx_packets: u64, /// Total number of packets transmitted. tx_packets: u64, old_tx_packets: u64, /// Shows the total number of packets received with error. This includes /// too-long-frames errors, ring-buffer overflow errors, CRC errors, /// frame alignment errors, fifo overruns, and missed packets. rx_errors: u64, old_rx_errors: u64, /// similar to `rx_errors` tx_errors: u64, old_tx_errors: u64, /// MAC address pub(crate) mac_addr: MacAddr, // /// Indicates the number of compressed packets received by this // /// network device. This value might only be relevant for interfaces // /// that support packet compression (e.g: PPP). // rx_compressed: usize, // old_rx_compressed: usize, // /// Indicates the number of transmitted compressed packets. Note // /// this might only be relevant for devices that support // /// compression (e.g: PPP). // tx_compressed: usize, // old_tx_compressed: usize, /// Whether or not the above data has been updated during refresh updated: bool, } impl NetworkData { fn update(&mut self, path: &str, data: &mut Vec) { let path = &Path::new("/sys/class/net/").join(path).join("statistics"); old_and_new!(self, rx_bytes, old_rx_bytes, read(path, "rx_bytes", data)); old_and_new!(self, tx_bytes, old_tx_bytes, read(path, "tx_bytes", data)); old_and_new!( self, rx_packets, old_rx_packets, read(path, "rx_packets", data) ); old_and_new!( self, tx_packets, old_tx_packets, read(path, "tx_packets", data) ); old_and_new!( self, rx_errors, old_rx_errors, read(path, "rx_errors", data) ); old_and_new!( self, tx_errors, old_tx_errors, read(path, "tx_errors", data) ); // old_and_new!( // self, // rx_compressed, // old_rx_compressed, // read(path, "rx_compressed", data) // ); // old_and_new!( // self, // tx_compressed, // old_tx_compressed, // read(path, "tx_compressed", data) // ); } } impl NetworkExt for NetworkData { fn received(&self) -> u64 { self.rx_bytes.saturating_sub(self.old_rx_bytes) } fn total_received(&self) -> u64 { self.rx_bytes } fn transmitted(&self) -> u64 { self.tx_bytes.saturating_sub(self.old_tx_bytes) } fn total_transmitted(&self) -> u64 { self.tx_bytes } fn packets_received(&self) -> u64 { self.rx_packets.saturating_sub(self.old_rx_packets) } fn total_packets_received(&self) -> u64 { self.rx_packets } fn packets_transmitted(&self) -> u64 { self.tx_packets.saturating_sub(self.old_tx_packets) } fn total_packets_transmitted(&self) -> u64 { self.tx_packets } fn errors_on_received(&self) -> u64 { self.rx_errors.saturating_sub(self.old_rx_errors) } fn total_errors_on_received(&self) -> u64 { self.rx_errors } fn errors_on_transmitted(&self) -> u64 { self.tx_errors.saturating_sub(self.old_tx_errors) } fn total_errors_on_transmitted(&self) -> u64 { self.tx_errors } fn mac_address(&self) -> MacAddr { self.mac_addr } } #[cfg(test)] mod test { use super::refresh_networks_list_from_sysfs; use std::collections::HashMap; use std::fs; #[test] fn refresh_networks_list_add_interface() { let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); fs::create_dir(sys_net_dir.path().join("itf1")).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf1"]); fs::create_dir(sys_net_dir.path().join("itf2")).expect("failed to create subdirectory"); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); let mut itf_names: Vec = interfaces.keys().map(|n| n.to_owned()).collect(); itf_names.sort(); assert_eq!(itf_names, ["itf1", "itf2"]); } #[test] fn refresh_networks_list_remove_interface() { let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); let itf1_dir = sys_net_dir.path().join("itf1"); let itf2_dir = sys_net_dir.path().join("itf2"); fs::create_dir(&itf1_dir).expect("failed to create subdirectory"); fs::create_dir(itf2_dir).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); let mut itf_names: Vec = interfaces.keys().map(|n| n.to_owned()).collect(); itf_names.sort(); assert_eq!(itf_names, ["itf1", "itf2"]); fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory"); refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); assert_eq!(interfaces.keys().collect::>(), ["itf2"]); } } sysinfo-0.28.4/src/linux/process.rs000064400000000000000000000520401046102023000153640ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::cell::UnsafeCell; use std::collections::HashMap; use std::fmt; use std::fs::{self, File}; use std::io::Read; use std::mem::MaybeUninit; use std::path::{Path, PathBuf}; use std::str::FromStr; use libc::{gid_t, kill, uid_t}; use crate::sys::system::SystemInfo; use crate::sys::utils::{ get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush, }; use crate::utils::into_iter; use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; #[doc(hidden)] impl From for ProcessStatus { fn from(status: char) -> ProcessStatus { match status { 'R' => ProcessStatus::Run, 'S' => ProcessStatus::Sleep, 'I' => ProcessStatus::Idle, 'D' => ProcessStatus::UninterruptibleDiskSleep, 'Z' => ProcessStatus::Zombie, 'T' => ProcessStatus::Stop, 't' => ProcessStatus::Tracing, 'X' | 'x' => ProcessStatus::Dead, 'K' => ProcessStatus::Wakekill, 'W' => ProcessStatus::Waking, 'P' => ProcessStatus::Parked, x => ProcessStatus::Unknown(x as u32), } } } impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Idle => "Idle", ProcessStatus::Run => "Runnable", ProcessStatus::Sleep => "Sleeping", ProcessStatus::Stop => "Stopped", ProcessStatus::Zombie => "Zombie", ProcessStatus::Tracing => "Tracing", ProcessStatus::Dead => "Dead", ProcessStatus::Wakekill => "Wakekill", ProcessStatus::Waking => "Waking", ProcessStatus::Parked => "Parked", ProcessStatus::UninterruptibleDiskSleep => "UninterruptibleDiskSleep", _ => "Unknown", }) } } #[doc = include_str!("../../md_doc/process.md")] pub struct Process { pub(crate) name: String, pub(crate) cmd: Vec, pub(crate) exe: PathBuf, pub(crate) pid: Pid, parent: Option, pub(crate) environ: Vec, pub(crate) cwd: PathBuf, pub(crate) root: PathBuf, pub(crate) memory: u64, pub(crate) virtual_memory: u64, utime: u64, stime: u64, old_utime: u64, old_stime: u64, start_time_without_boot_time: u64, start_time: u64, run_time: u64, pub(crate) updated: bool, cpu_usage: f32, user_id: Option, group_id: Option, pub(crate) status: ProcessStatus, /// Tasks run by this process. pub tasks: HashMap, pub(crate) stat_file: Option, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, written_bytes: u64, } impl Process { pub(crate) fn new(pid: Pid) -> Process { Process { name: String::with_capacity(20), pid, parent: None, cmd: Vec::with_capacity(2), environ: Vec::with_capacity(10), exe: PathBuf::new(), cwd: PathBuf::new(), root: PathBuf::new(), memory: 0, virtual_memory: 0, cpu_usage: 0., utime: 0, stime: 0, old_utime: 0, old_stime: 0, updated: true, start_time_without_boot_time: 0, start_time: 0, run_time: 0, user_id: None, group_id: None, status: ProcessStatus::Unknown(0), tasks: if pid.0 == 0 { HashMap::with_capacity(1000) } else { HashMap::new() }, stat_file: None, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, } } } impl ProcessExt for Process { fn kill_with(&self, signal: Signal) -> Option { let c_signal = super::system::convert_signal(signal)?; unsafe { Some(kill(self.pid.0, c_signal) == 0) } } fn name(&self) -> &str { &self.name } fn cmd(&self) -> &[String] { &self.cmd } fn exe(&self) -> &Path { self.exe.as_path() } fn pid(&self) -> Pid { self.pid } fn environ(&self) -> &[String] { &self.environ } fn cwd(&self) -> &Path { self.cwd.as_path() } fn root(&self) -> &Path { self.root.as_path() } fn memory(&self) -> u64 { self.memory } fn virtual_memory(&self) -> u64 { self.virtual_memory } fn parent(&self) -> Option { self.parent } fn status(&self) -> ProcessStatus { self.status } fn start_time(&self) -> u64 { self.start_time } fn run_time(&self) -> u64 { self.run_time } fn cpu_usage(&self) -> f32 { self.cpu_usage } fn disk_usage(&self) -> DiskUsage { DiskUsage { written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), total_written_bytes: self.written_bytes, read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), total_read_bytes: self.read_bytes, } } fn user_id(&self) -> Option<&Uid> { self.user_id.as_ref() } fn group_id(&self) -> Option { self.group_id } fn wait(&self) { let mut status = 0; // attempt waiting unsafe { if libc::waitpid(self.pid.0, &mut status, 0) < 0 { // attempt failed (non-child process) so loop until process ends let duration = std::time::Duration::from_millis(10); while kill(self.pid.0, 0) == 0 { std::thread::sleep(duration); } } } } fn session_id(&self) -> Option { unsafe { let session_id = libc::getsid(self.pid.0); if session_id < 0 { None } else { Some(Pid(session_id)) } } } } pub(crate) fn compute_cpu_usage(p: &mut Process, total_time: f32, max_value: f32) { // First time updating the values without reference, wait for a second cycle to update cpu_usage if p.old_utime == 0 && p.old_stime == 0 { return; } // We use `max_value` to ensure that the process CPU usage will never get bigger than: // `"number of CPUs" * 100.` p.cpu_usage = ((p.utime.saturating_sub(p.old_utime) + p.stime.saturating_sub(p.old_stime)) as f32 / total_time * 100.) .min(max_value); for task in p.tasks.values_mut() { compute_cpu_usage(task, total_time, max_value); } } pub(crate) fn unset_updated(p: &mut Process) { p.updated = false; for task in p.tasks.values_mut() { unset_updated(task); } } pub(crate) fn set_time(p: &mut Process, utime: u64, stime: u64) { p.old_utime = p.utime; p.old_stime = p.stime; p.utime = utime; p.stime = stime; p.updated = true; } pub(crate) fn update_process_disk_activity(p: &mut Process, path: &Path) { let data = match get_all_data(path.join("io"), 16_384) { Ok(d) => d, Err(_) => return, }; let mut done = 0; for line in data.split('\n') { let mut parts = line.split(": "); match parts.next() { Some("read_bytes") => { p.old_read_bytes = p.read_bytes; p.read_bytes = parts .next() .and_then(|x| x.parse::().ok()) .unwrap_or(p.old_read_bytes); } Some("write_bytes") => { p.old_written_bytes = p.written_bytes; p.written_bytes = parts .next() .and_then(|x| x.parse::().ok()) .unwrap_or(p.old_written_bytes); } _ => continue, } done += 1; if done > 1 { // No need to continue the reading. break; } } } struct Wrap<'a, T>(UnsafeCell<&'a mut T>); impl<'a, T> Wrap<'a, T> { fn get(&self) -> &'a mut T { unsafe { *(self.0.get()) } } } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl<'a, T> Send for Wrap<'a, T> {} unsafe impl<'a, T> Sync for Wrap<'a, T> {} #[inline(always)] fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 { // To be noted that the start time is invalid here, it still needs to be converted into // "real" time. u64::from_str(parts[21]).unwrap_or(0) / info.clock_cycle } fn _get_stat_data(path: &Path, stat_file: &mut Option) -> Result { let mut file = File::open(path.join("stat")).map_err(|_| ())?; let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; *stat_file = FileCounter::new(file); Ok(data) } #[inline(always)] fn get_status(p: &mut Process, part: &str) { p.status = part .chars() .next() .map(ProcessStatus::from) .unwrap_or_else(|| ProcessStatus::Unknown(0)); } fn refresh_user_group_ids(p: &mut Process, path: &mut P) { if let Some((user_id, group_id)) = get_uid_and_gid(path.join("status")) { p.user_id = Some(Uid(user_id)); p.group_id = Some(Gid(group_id)); } } fn retrieve_all_new_process_info( pid: Pid, proc_list: &Process, parts: &[&str], path: &Path, info: &SystemInfo, refresh_kind: ProcessRefreshKind, uptime: u64, ) -> Process { let mut p = Process::new(pid); let mut tmp = PathHandler::new(path); let name = parts[1]; p.parent = if proc_list.pid.0 != 0 { Some(proc_list.pid) } else { match Pid::from_str(parts[3]) { Ok(p) if p.0 != 0 => Some(p), _ => None, } }; p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info); p.start_time = p .start_time_without_boot_time .saturating_add(info.boot_time); get_status(&mut p, parts[2]); if refresh_kind.user() { refresh_user_group_ids(&mut p, &mut tmp); } p.name = name.into(); match tmp.join("exe").read_link() { Ok(exe_path) => { p.exe = exe_path; } Err(_) => { // Do not use cmd[0] because it is not the same thing. // See https://github.com/GuillaumeGomez/sysinfo/issues/697. p.exe = PathBuf::new() } } p.cmd = copy_from_file(tmp.join("cmdline")); p.environ = copy_from_file(tmp.join("environ")); p.cwd = realpath(tmp.join("cwd")); p.root = realpath(tmp.join("root")); update_time_and_memory( path, &mut p, parts, proc_list.memory, proc_list.virtual_memory, uptime, info, refresh_kind, ); if refresh_kind.disk_usage() { update_process_disk_activity(&mut p, path); } p } pub(crate) fn _get_process_data( path: &Path, proc_list: &mut Process, pid: Pid, uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, ) -> Result<(Option, Pid), ()> { let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { // If `pid` and `nb` are the same, it means the file is linking to itself so we skip it. // // It's because when reading `/proc/[PID]` folder, we then go through the folders inside it. // Then, if we encounter a sub-folder with the same PID as the parent, then it's a link to // the current folder we already did read so no need to do anything. Some(Ok(nb)) if nb != pid => nb, _ => return Err(()), }; let parent_memory = proc_list.memory; let parent_virtual_memory = proc_list.virtual_memory; let data; let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { data = if let Some(mut f) = entry.stat_file.take() { match get_all_data_from_file(&mut f, 1024) { Ok(data) => { // Everything went fine, we put back the file descriptor. entry.stat_file = Some(f); data } Err(_) => { // It's possible that the file descriptor is no longer valid in case the // original process was terminated and another one took its place. _get_stat_data(path, &mut entry.stat_file)? } } } else { _get_stat_data(path, &mut entry.stat_file)? }; let parts = parse_stat_file(&data).ok_or(())?; let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info); // It's possible that a new process took this same PID when the "original one" terminated. // If the start time differs, then it means it's not the same process anymore and that we // need to get all its information, hence why we check it here. if start_time_without_boot_time == entry.start_time_without_boot_time { get_status(entry, parts[2]); update_time_and_memory( path, entry, &parts, parent_memory, parent_virtual_memory, uptime, info, refresh_kind, ); if refresh_kind.disk_usage() { update_process_disk_activity(entry, path); } if refresh_kind.user() && entry.user_id.is_none() { refresh_user_group_ids(entry, &mut PathBuf::from(path)); } return Ok((None, pid)); } parts } else { let mut stat_file = None; let data = _get_stat_data(path, &mut stat_file)?; let parts = parse_stat_file(&data).ok_or(())?; let mut p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); p.stat_file = stat_file; return Ok((Some(p), pid)); }; // If we're here, it means that the PID still exists but it's a different process. let p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); match proc_list.tasks.get_mut(&pid) { Some(ref mut entry) => **entry = p, // If it ever enters this case, it means that the process was removed from the HashMap // in-between with the usage of dark magic. None => unreachable!(), } // Since this PID is already in the HashMap, no need to add it again. Ok((None, pid)) } #[allow(clippy::too_many_arguments)] fn update_time_and_memory( path: &Path, entry: &mut Process, parts: &[&str], parent_memory: u64, parent_virtual_memory: u64, uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, ) { { // rss entry.memory = u64::from_str(parts[23]) .unwrap_or(0) .saturating_mul(info.page_size_kb); if entry.memory >= parent_memory { entry.memory -= parent_memory; } // vsz correspond to the Virtual memory size in bytes. // see: https://man7.org/linux/man-pages/man5/proc.5.html entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0); if entry.virtual_memory >= parent_virtual_memory { entry.virtual_memory -= parent_virtual_memory; } set_time( entry, u64::from_str(parts[13]).unwrap_or(0), u64::from_str(parts[14]).unwrap_or(0), ); entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time); } refresh_procs( entry, &path.join("task"), entry.pid, uptime, info, refresh_kind, ); } pub(crate) fn refresh_procs( proc_list: &mut Process, path: &Path, pid: Pid, uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, ) -> bool { let d = match fs::read_dir(path) { Ok(d) => d, Err(_) => return false, }; let folders = d .filter_map(|entry| { let entry = entry.ok()?; let entry = entry.path(); if entry.is_dir() { Some(entry) } else { None } }) .collect::>(); if pid.0 == 0 { let proc_list = Wrap(UnsafeCell::new(proc_list)); #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; into_iter(folders) .filter_map(|e| { let (p, _) = _get_process_data( e.as_path(), proc_list.get(), pid, uptime, info, refresh_kind, ) .ok()?; p }) .collect::>() } else { let mut updated_pids = Vec::with_capacity(folders.len()); let new_tasks = folders .iter() .filter_map(|e| { let (p, pid) = _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) .ok()?; updated_pids.push(pid); p }) .collect::>(); // Sub-tasks are not cleaned up outside so we do it here directly. proc_list .tasks .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid)); new_tasks } .into_iter() .for_each(|e| { proc_list.tasks.insert(e.pid(), e); }); true } fn copy_from_file(entry: &Path) -> Vec { match File::open(entry) { Ok(mut f) => { let mut data = Vec::with_capacity(16_384); if let Err(_e) = f.read_to_end(&mut data) { sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e); Vec::new() } else { let mut out = Vec::with_capacity(20); let mut start = 0; for (pos, x) in data.iter().enumerate() { if *x == 0 { if pos - start >= 1 { if let Ok(s) = std::str::from_utf8(&data[start..pos]).map(|x| x.trim().to_owned()) { out.push(s); } } start = pos + 1; // to keeping prevent '\0' } } out } } Err(_e) => { sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}", _e); Vec::new() } } } fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { use std::os::unix::ffi::OsStrExt; unsafe { let mut sstat: MaybeUninit = MaybeUninit::uninit(); let mut file_path: Vec = file_path.as_os_str().as_bytes().to_vec(); file_path.push(0); if libc::stat(file_path.as_ptr() as *const _, sstat.as_mut_ptr()) == 0 { let sstat = sstat.assume_init(); return Some((sstat.st_uid, sstat.st_gid)); } } let status_data = get_all_data(file_path, 16_385).ok()?; // We're only interested in the lines starting with Uid: and Gid: // here. From these lines, we're looking at the second entry to get // the effective u/gid. let f = |h: &str, n: &str| -> Option { if h.starts_with(n) { h.split_whitespace().nth(2).unwrap_or("0").parse().ok() } else { None } }; let mut uid = None; let mut gid = None; for line in status_data.lines() { if let Some(u) = f(line, "Uid:") { debug_assert!(uid.is_none()); uid = Some(u); } else if let Some(g) = f(line, "Gid:") { debug_assert!(gid.is_none()); gid = Some(g); } else { continue; } if uid.is_some() && gid.is_some() { break; } } match (uid, gid) { (Some(u), Some(g)) => Some((u, g)), _ => None, } } fn parse_stat_file(data: &str) -> Option> { // The stat file is "interesting" to parse, because spaces cannot // be used as delimiters. The second field stores the command name // surrounded by parentheses. Unfortunately, whitespace and // parentheses are legal parts of the command, so parsing has to // proceed like this: The first field is delimited by the first // whitespace, the second field is everything until the last ')' // in the entire string. All other fields are delimited by // whitespace. let mut parts = Vec::with_capacity(52); let mut data_it = data.splitn(2, ' '); parts.push(data_it.next()?); let mut data_it = data_it.next()?.rsplitn(2, ')'); let data = data_it.next()?; parts.push(data_it.next()?); parts.extend(data.split_whitespace()); // Remove command name '(' if let Some(name) = parts[1].strip_prefix('(') { parts[1] = name; } Some(parts) } sysinfo-0.28.4/src/linux/system.rs000064400000000000000000000545551046102023000152470ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::component::{self, Component}; use crate::sys::cpu::*; use crate::sys::disk; use crate::sys::process::*; use crate::sys::utils::{get_all_data, to_u64}; use crate::{ CpuRefreshKind, Disk, LoadAvg, Networks, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, }; use libc::{self, c_char, c_int, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE}; use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, Read}; use std::path::Path; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::Duration; // This whole thing is to prevent having too many files open at once. It could be problematic // for processes using a lot of files and using sysinfo at the same time. #[allow(clippy::mutex_atomic)] pub(crate) static mut REMAINING_FILES: once_cell::sync::Lazy>> = once_cell::sync::Lazy::new(|| { unsafe { let mut limits = libc::rlimit { rlim_cur: 0, rlim_max: 0, }; if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { // Most Linux system now defaults to 1024. return Arc::new(Mutex::new(1024 / 2)); } // We save the value in case the update fails. let current = limits.rlim_cur; // The set the soft limit to the hard one. limits.rlim_cur = limits.rlim_max; // In this part, we leave minimum 50% of the available file descriptors to the process // using sysinfo. Arc::new(Mutex::new( if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 { limits.rlim_cur / 2 } else { current / 2 } as _, )) } }); pub(crate) fn get_max_nb_fds() -> isize { unsafe { let mut limits = libc::rlimit { rlim_cur: 0, rlim_max: 0, }; if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { // Most Linux system now defaults to 1024. 1024 / 2 } else { limits.rlim_max as isize / 2 } } } fn boot_time() -> u64 { if let Ok(f) = File::open("/proc/stat") { let buf = BufReader::new(f); let line = buf .split(b'\n') .filter_map(|r| r.ok()) .find(|l| l.starts_with(b"btime")); if let Some(line) = line { return line .split(|x| *x == b' ') .filter(|s| !s.is_empty()) .nth(1) .map(to_u64) .unwrap_or(0); } } // Either we didn't find "btime" or "/proc/stat" wasn't available for some reason... unsafe { let mut up: libc::timespec = std::mem::zeroed(); if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 { up.tv_sec as u64 } else { sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve..."); 0 } } } pub(crate) struct SystemInfo { pub(crate) page_size_kb: u64, pub(crate) clock_cycle: u64, pub(crate) boot_time: u64, } impl SystemInfo { fn new() -> Self { unsafe { Self { page_size_kb: sysconf(_SC_PAGESIZE) as _, clock_cycle: sysconf(_SC_CLK_TCK) as _, boot_time: boot_time(), } } } } declare_signals! { c_int, Signal::Hangup => libc::SIGHUP, Signal::Interrupt => libc::SIGINT, Signal::Quit => libc::SIGQUIT, Signal::Illegal => libc::SIGILL, Signal::Trap => libc::SIGTRAP, Signal::Abort => libc::SIGABRT, Signal::IOT => libc::SIGIOT, Signal::Bus => libc::SIGBUS, Signal::FloatingPointException => libc::SIGFPE, Signal::Kill => libc::SIGKILL, Signal::User1 => libc::SIGUSR1, Signal::Segv => libc::SIGSEGV, Signal::User2 => libc::SIGUSR2, Signal::Pipe => libc::SIGPIPE, Signal::Alarm => libc::SIGALRM, Signal::Term => libc::SIGTERM, Signal::Child => libc::SIGCHLD, Signal::Continue => libc::SIGCONT, Signal::Stop => libc::SIGSTOP, Signal::TSTP => libc::SIGTSTP, Signal::TTIN => libc::SIGTTIN, Signal::TTOU => libc::SIGTTOU, Signal::Urgent => libc::SIGURG, Signal::XCPU => libc::SIGXCPU, Signal::XFSZ => libc::SIGXFSZ, Signal::VirtualAlarm => libc::SIGVTALRM, Signal::Profiling => libc::SIGPROF, Signal::Winch => libc::SIGWINCH, Signal::IO => libc::SIGIO, Signal::Poll => libc::SIGPOLL, Signal::Power => libc::SIGPWR, Signal::Sys => libc::SIGSYS, } #[doc = include_str!("../../md_doc/system.md")] pub struct System { process_list: Process, mem_total: u64, mem_free: u64, mem_available: u64, mem_buffers: u64, mem_page_cache: u64, mem_shmem: u64, mem_slab_reclaimable: u64, swap_total: u64, swap_free: u64, components: Vec, disks: Vec, networks: Networks, users: Vec, info: SystemInfo, cpus: CpusWrapper, } impl System { /// It is sometime possible that a CPU usage computation is bigger than /// `"number of CPUs" * 100`. /// /// To prevent that, we compute ahead of time this maximum value and ensure that processes' /// CPU usage don't go over it. fn get_max_process_cpu_usage(&self) -> f32 { self.cpus.len() as f32 * 100. } fn clear_procs(&mut self, refresh_kind: ProcessRefreshKind) { let (total_time, compute_cpu, max_value) = if refresh_kind.cpu() { self.cpus .refresh_if_needed(true, CpuRefreshKind::new().with_cpu_usage()); if self.cpus.is_empty() { sysinfo_debug!("cannot compute processes CPU usage: no CPU found..."); (0., false, 0.) } else { let (new, old) = self.cpus.get_global_raw_times(); let total_time = if old > new { 1 } else { new - old }; ( total_time as f32 / self.cpus.len() as f32, true, self.get_max_process_cpu_usage(), ) } } else { (0., false, 0.) }; self.process_list.tasks.retain(|_, proc_| { if !proc_.updated { return false; } if compute_cpu { compute_cpu_usage(proc_, total_time, max_value); } unset_updated(proc_); true }); } fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { self.cpus.refresh(only_update_global_cpu, refresh_kind); } } impl SystemExt for System { const IS_SUPPORTED: bool = true; const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200); fn new_with_specifics(refreshes: RefreshKind) -> System { let process_list = Process::new(Pid(0)); let mut s = System { process_list, mem_total: 0, mem_free: 0, mem_available: 0, mem_buffers: 0, mem_page_cache: 0, mem_shmem: 0, mem_slab_reclaimable: 0, swap_total: 0, swap_free: 0, cpus: CpusWrapper::new(), components: Vec::new(), disks: Vec::with_capacity(2), networks: Networks::new(), users: Vec::new(), info: SystemInfo::new(), }; s.refresh_specifics(refreshes); s } fn refresh_components_list(&mut self) { self.components = component::get_components(); } fn refresh_memory(&mut self) { if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { let mut mem_available_found = false; for line in data.split('\n') { let mut iter = line.split(':'); let field = match iter.next() { Some("MemTotal") => &mut self.mem_total, Some("MemFree") => &mut self.mem_free, Some("MemAvailable") => { mem_available_found = true; &mut self.mem_available } Some("Buffers") => &mut self.mem_buffers, Some("Cached") => &mut self.mem_page_cache, Some("Shmem") => &mut self.mem_shmem, Some("SReclaimable") => &mut self.mem_slab_reclaimable, Some("SwapTotal") => &mut self.swap_total, Some("SwapFree") => &mut self.swap_free, _ => continue, }; if let Some(val_str) = iter.next().and_then(|s| s.trim_start().split(' ').next()) { if let Ok(value) = u64::from_str(val_str) { // /proc/meminfo reports KiB, though it says "kB". Convert it. *field = value.saturating_mul(1_024); } } } // Linux < 3.14 may not have MemAvailable in /proc/meminfo // So it should fallback to the old way of estimating available memory // https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716 if !mem_available_found { self.mem_available = self.mem_free + self.mem_buffers + self.mem_page_cache + self.mem_slab_reclaimable - self.mem_shmem; } } } fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { self.refresh_cpus(false, refresh_kind); } fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { let uptime = self.uptime(); refresh_procs( &mut self.process_list, Path::new("/proc"), Pid(0), uptime, &self.info, refresh_kind, ); self.clear_procs(refresh_kind); self.cpus.set_need_cpus_update(); } fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { let uptime = self.uptime(); match _get_process_data( &Path::new("/proc/").join(pid.to_string()), &mut self.process_list, Pid(0), uptime, &self.info, refresh_kind, ) { Ok((Some(p), pid)) => { self.process_list.tasks.insert(pid, p); } Ok(_) => {} Err(_e) => { sysinfo_debug!("Cannot get information for PID {:?}: {:?}", pid, _e); return false; } }; if refresh_kind.cpu() { self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage()); if self.cpus.is_empty() { eprintln!("Cannot compute process CPU usage: no cpus found..."); return true; } let (new, old) = self.cpus.get_global_raw_times(); let total_time = (if old >= new { 1 } else { new - old }) as f32; let total_time = total_time / self.cpus.len() as f32; let max_cpu_usage = self.get_max_process_cpu_usage(); if let Some(p) = self.process_list.tasks.get_mut(&pid) { compute_cpu_usage(p, total_time, max_cpu_usage); unset_updated(p); } } else if let Some(p) = self.process_list.tasks.get_mut(&pid) { unset_updated(p); } true } fn refresh_disks_list(&mut self) { self.disks = disk::get_all_disks(); } fn refresh_users_list(&mut self) { self.users = crate::users::get_users_list(); } // COMMON PART // // Need to be moved into a "common" file to avoid duplication. fn processes(&self) -> &HashMap { &self.process_list.tasks } fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.tasks.get(&pid) } fn networks(&self) -> &Networks { &self.networks } fn networks_mut(&mut self) -> &mut Networks { &mut self.networks } fn global_cpu_info(&self) -> &Cpu { &self.cpus.global_cpu } fn cpus(&self) -> &[Cpu] { &self.cpus.cpus } fn physical_core_count(&self) -> Option { get_physical_core_count() } fn total_memory(&self) -> u64 { self.mem_total } fn free_memory(&self) -> u64 { self.mem_free } fn available_memory(&self) -> u64 { self.mem_available } fn used_memory(&self) -> u64 { self.mem_total - self.mem_available } fn total_swap(&self) -> u64 { self.swap_total } fn free_swap(&self) -> u64 { self.swap_free } // need to be checked fn used_swap(&self) -> u64 { self.swap_total - self.swap_free } fn components(&self) -> &[Component] { &self.components } fn components_mut(&mut self) -> &mut [Component] { &mut self.components } fn disks(&self) -> &[Disk] { &self.disks } fn disks_mut(&mut self) -> &mut [Disk] { &mut self.disks } fn sort_disks_by(&mut self, compare: F) where F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, { self.disks.sort_unstable_by(compare); } fn uptime(&self) -> u64 { let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); content .split('.') .next() .and_then(|t| t.parse().ok()) .unwrap_or_default() } fn boot_time(&self) -> u64 { self.info.boot_time } fn load_average(&self) -> LoadAvg { let mut s = String::new(); if File::open("/proc/loadavg") .and_then(|mut f| f.read_to_string(&mut s)) .is_err() { return LoadAvg::default(); } let loads = s .trim() .split(' ') .take(3) .map(|val| val.parse::().unwrap()) .collect::>(); LoadAvg { one: loads[0], five: loads[1], fifteen: loads[2], } } fn users(&self) -> &[User] { &self.users } #[cfg(not(target_os = "android"))] fn name(&self) -> Option { get_system_info_linux( InfoType::Name, Path::new("/etc/os-release"), Path::new("/etc/lsb-release"), ) } #[cfg(target_os = "android")] fn name(&self) -> Option { get_system_info_android(InfoType::Name) } fn long_os_version(&self) -> Option { #[cfg(target_os = "android")] let system_name = "Android"; #[cfg(not(target_os = "android"))] let system_name = "Linux"; Some(format!( "{} {} {}", system_name, self.os_version().unwrap_or_default(), self.name().unwrap_or_default() )) } fn host_name(&self) -> Option { unsafe { let hostname_max = sysconf(_SC_HOST_NAME_MAX); let mut buffer = vec![0_u8; hostname_max as usize]; if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 { if let Some(pos) = buffer.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes buffer.resize(pos, 0); } String::from_utf8(buffer).ok() } else { sysinfo_debug!("gethostname failed: hostname cannot be retrieved..."); None } } } fn kernel_version(&self) -> Option { let mut raw = std::mem::MaybeUninit::::zeroed(); unsafe { if libc::uname(raw.as_mut_ptr()) == 0 { let info = raw.assume_init(); let release = info .release .iter() .filter(|c| **c != 0) .map(|c| *c as u8 as char) .collect::(); Some(release) } else { None } } } #[cfg(not(target_os = "android"))] fn os_version(&self) -> Option { get_system_info_linux( InfoType::OsVersion, Path::new("/etc/os-release"), Path::new("/etc/lsb-release"), ) } #[cfg(target_os = "android")] fn os_version(&self) -> Option { get_system_info_android(InfoType::OsVersion) } #[cfg(not(target_os = "android"))] fn distribution_id(&self) -> String { get_system_info_linux( InfoType::DistributionID, Path::new("/etc/os-release"), Path::new(""), ) .unwrap_or_else(|| std::env::consts::OS.to_owned()) } #[cfg(target_os = "android")] fn distribution_id(&self) -> String { // Currently get_system_info_android doesn't support InfoType::DistributionID and always // returns None. This call is done anyway for consistency with non-Android implementation // and to suppress dead-code warning for DistributionID on Android. get_system_info_android(InfoType::DistributionID) .unwrap_or_else(|| std::env::consts::OS.to_owned()) } } impl Default for System { fn default() -> System { System::new() } } #[derive(PartialEq, Eq)] enum InfoType { /// The end-user friendly name of: /// - Android: The device model /// - Linux: The distributions name Name, OsVersion, /// Machine-parseable ID of a distribution, see /// https://www.freedesktop.org/software/systemd/man/os-release.html#ID= DistributionID, } #[cfg(not(target_os = "android"))] fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option { if let Ok(f) = File::open(path) { let reader = BufReader::new(f); let info_str = match info { InfoType::Name => "NAME=", InfoType::OsVersion => "VERSION_ID=", InfoType::DistributionID => "ID=", }; for line in reader.lines().flatten() { if let Some(stripped) = line.strip_prefix(info_str) { return Some(stripped.replace('"', "")); } } } // Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included. // VERSION_ID is not required in the `/etc/os-release` file // per https://www.linux.org/docs/man5/os-release.html // If this fails for some reason, fallback to None let reader = BufReader::new(File::open(fallback_path).ok()?); let info_str = match info { InfoType::OsVersion => "DISTRIB_RELEASE=", InfoType::Name => "DISTRIB_ID=", InfoType::DistributionID => { // lsb-release is inconsistent with os-release and unsupported. return None; } }; for line in reader.lines().flatten() { if let Some(stripped) = line.strip_prefix(info_str) { return Some(stripped.replace('"', "")); } } None } #[cfg(target_os = "android")] fn get_system_info_android(info: InfoType) -> Option { // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58 let name: &'static [u8] = match info { InfoType::Name => b"ro.product.model\0", InfoType::OsVersion => b"ro.build.version.release\0", InfoType::DistributionID => { // Not supported. return None; } }; let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize]; unsafe { let len = libc::__system_property_get( name.as_ptr() as *const c_char, value_buffer.as_mut_ptr() as *mut c_char, ); if len != 0 { if let Some(pos) = value_buffer.iter().position(|c| *c == 0) { value_buffer.resize(pos, 0); } String::from_utf8(value_buffer).ok() } else { None } } } #[cfg(test)] mod test { #[cfg(target_os = "android")] use super::get_system_info_android; #[cfg(not(target_os = "android"))] use super::get_system_info_linux; use super::InfoType; #[test] #[cfg(target_os = "android")] fn lsb_release_fallback_android() { assert!(get_system_info_android(InfoType::OsVersion).is_some()); assert!(get_system_info_android(InfoType::Name).is_some()); assert!(get_system_info_android(InfoType::DistributionID).is_none()); } #[test] #[cfg(not(target_os = "android"))] fn lsb_release_fallback_not_android() { use std::path::Path; let dir = tempfile::tempdir().expect("failed to create temporary directory"); let tmp1 = dir.path().join("tmp1"); let tmp2 = dir.path().join("tmp2"); // /etc/os-release std::fs::write( &tmp1, r#"NAME="Ubuntu" VERSION="20.10 (Groovy Gorilla)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.10" VERSION_ID="20.10" VERSION_CODENAME=groovy UBUNTU_CODENAME=groovy "#, ) .expect("Failed to create tmp1"); // /etc/lsb-release std::fs::write( &tmp2, r#"DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.10 DISTRIB_CODENAME=groovy DISTRIB_DESCRIPTION="Ubuntu 20.10" "#, ) .expect("Failed to create tmp2"); // Check for the "normal" path: "/etc/os-release" assert_eq!( get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")), Some("20.10".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::Name, &tmp1, Path::new("")), Some("Ubuntu".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")), Some("ubuntu".to_owned()) ); // Check for the "fallback" path: "/etc/lsb-release" assert_eq!( get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2), Some("20.10".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::Name, Path::new(""), &tmp2), Some("Ubuntu".to_owned()) ); assert_eq!( get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2), None ); } } sysinfo-0.28.4/src/linux/utils.rs000064400000000000000000000063101046102023000150450ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fs::File; use std::io::{self, Read, Seek}; use std::path::{Path, PathBuf}; use crate::sys::system::REMAINING_FILES; pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result { let mut buf = String::with_capacity(size); file.rewind()?; file.read_to_string(&mut buf)?; Ok(buf) } pub(crate) fn get_all_data>(file_path: P, size: usize) -> io::Result { let mut file = File::open(file_path.as_ref())?; get_all_data_from_file(&mut file, size) } #[allow(clippy::useless_conversion)] pub(crate) fn realpath(path: &Path) -> std::path::PathBuf { match std::fs::read_link(path) { Ok(f) => f, Err(_e) => { sysinfo_debug!("failed to get real path for {:?}: {:?}", path, _e); PathBuf::new() } } } /// Type used to correctly handle the `REMAINING_FILES` global. pub(crate) struct FileCounter(File); impl FileCounter { pub(crate) fn new(f: File) -> Option { unsafe { if let Ok(ref mut x) = REMAINING_FILES.lock() { if **x > 0 { **x -= 1; return Some(Self(f)); } // All file descriptors we were allowed are being used. } } None } } impl std::ops::Deref for FileCounter { type Target = File; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for FileCounter { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Drop for FileCounter { fn drop(&mut self) { unsafe { if let Ok(ref mut x) = crate::sys::system::REMAINING_FILES.lock() { **x += 1; } } } } /// This type is used in `retrieve_all_new_process_info` because we have a "parent" path and /// from it, we `pop`/`join` every time because it's more memory efficient than using `Path::join`. pub(crate) struct PathHandler(PathBuf); impl PathHandler { pub(crate) fn new(path: &Path) -> Self { // `path` is the "parent" for all paths which will follow so we add a fake element at // the end since every `PathHandler::join` call will first call `pop` internally. Self(path.join("a")) } } pub(crate) trait PathPush { fn join(&mut self, p: &str) -> &Path; } impl PathPush for PathHandler { fn join(&mut self, p: &str) -> &Path { self.0.pop(); self.0.push(p); self.0.as_path() } } // This implementation allows to skip one allocation that is done in `PathHandler`. impl PathPush for PathBuf { fn join(&mut self, p: &str) -> &Path { self.push(p); self.as_path() } } pub(crate) fn to_u64(v: &[u8]) -> u64 { let mut x = 0; for c in v { x *= 10; x += u64::from(c - b'0'); } x } /// Converts a path to a NUL-terminated `Vec` suitable for use with C functions. pub(crate) fn to_cpath(path: &std::path::Path) -> Vec { use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; let path_os: &OsStr = path.as_ref(); let mut cpath = path_os.as_bytes().to_vec(); cpath.push(0); cpath } sysinfo-0.28.4/src/macros.rs000064400000000000000000000025371046102023000140410ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[cfg(feature = "debug")] #[doc(hidden)] #[allow(unused)] macro_rules! sysinfo_debug { ($($x:tt)*) => {{ eprintln!($($x)*); }} } #[cfg(not(feature = "debug"))] #[doc(hidden)] #[allow(unused)] macro_rules! sysinfo_debug { ($($x:tt)*) => {{}}; } macro_rules! declare_signals { ($kind:ty, _ => None,) => ( use crate::Signal; pub(crate) const fn supported_signals() -> &'static [Signal] { &[] } ); ($kind:ty, $(Signal::$signal:ident => $map:expr,)+ _ => None,) => ( use crate::Signal; pub(crate) const fn supported_signals() -> &'static [Signal] { &[$(Signal::$signal,)*] } #[inline] pub(crate) fn convert_signal(s: Signal) -> Option<$kind> { match s { $(Signal::$signal => Some($map),)* _ => None, } } ); ($kind:ty, $(Signal::$signal:ident => $map:expr,)+) => ( use crate::Signal; pub(crate) const fn supported_signals() -> &'static [Signal] { &[$(Signal::$signal,)*] } #[inline] pub(crate) fn convert_signal(s: Signal) -> Option<$kind> { match s { $(Signal::$signal => Some($map),)* } } ) } sysinfo-0.28.4/src/network.rs000064400000000000000000000010561046102023000142410ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use crate::network_helper::get_interface_address; use crate::NetworkData; /// Interface addresses are OS-independent pub(crate) fn refresh_networks_addresses(interfaces: &mut HashMap) { if let Ok(ifa_iterator) = get_interface_address() { for (name, ifa) in ifa_iterator { if let Some(interface) = interfaces.get_mut(&name) { interface.mac_addr = ifa; } } } } sysinfo-0.28.4/src/network_helper_nix.rs000064400000000000000000000070721046102023000164620ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::common::MacAddr; use std::ptr::null_mut; /// This iterator yields an interface name and address. pub(crate) struct InterfaceAddressIterator { /// Pointer to the current `ifaddrs` struct. ifap: *mut libc::ifaddrs, /// Pointer to the first element in linked list. buf: *mut libc::ifaddrs, } impl Iterator for InterfaceAddressIterator { type Item = (String, MacAddr); fn next(&mut self) -> Option { unsafe { while !self.ifap.is_null() { // advance the pointer until a MAC address is found let ifap = self.ifap; self.ifap = (*ifap).ifa_next; if let Some(addr) = parse_interface_address(ifap) { // libc::IFNAMSIZ + 6 // This size refers to ./apple/network.rs:75 let mut name = vec![0u8; libc::IFNAMSIZ + 6]; libc::strcpy(name.as_mut_ptr() as _, (*ifap).ifa_name); name.set_len(libc::strlen((*ifap).ifa_name)); let name = String::from_utf8_unchecked(name); return Some((name, addr)); } } None } } } impl Drop for InterfaceAddressIterator { fn drop(&mut self) { unsafe { libc::freeifaddrs(self.buf); } } } #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))] impl From<&libc::sockaddr_dl> for MacAddr { fn from(value: &libc::sockaddr_dl) -> Self { let sdl_data = value.sdl_data; // interface name length, NO trailing 0 let sdl_nlen = value.sdl_nlen as usize; // make sure that it is never out of bound if sdl_nlen + 5 < 12 { MacAddr([ sdl_data[sdl_nlen] as u8, sdl_data[sdl_nlen + 1] as u8, sdl_data[sdl_nlen + 2] as u8, sdl_data[sdl_nlen + 3] as u8, sdl_data[sdl_nlen + 4] as u8, sdl_data[sdl_nlen + 5] as u8, ]) } else { MacAddr::UNSPECIFIED } } } #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "ios"))] unsafe fn parse_interface_address(ifap: *const libc::ifaddrs) -> Option { let sock_addr = (*ifap).ifa_addr; if sock_addr.is_null() { return None; } match (*sock_addr).sa_family as libc::c_int { libc::AF_LINK => { let addr = sock_addr as *const libc::sockaddr_dl; Some(MacAddr::from(&*addr)) } _ => None, } } #[cfg(any(target_os = "linux", target_os = "android"))] unsafe fn parse_interface_address(ifap: *const libc::ifaddrs) -> Option { use libc::sockaddr_ll; let sock_addr = (*ifap).ifa_addr; if sock_addr.is_null() { return None; } match (*sock_addr).sa_family as libc::c_int { libc::AF_PACKET => { let addr = sock_addr as *const sockaddr_ll; // Take the first 6 bytes let [addr @ .., _, _] = (*addr).sll_addr; Some(MacAddr(addr)) } _ => None, } } /// Return an iterator on (interface_name, address) pairs pub(crate) fn get_interface_address() -> Result { let mut ifap = null_mut(); unsafe { if libc::getifaddrs(&mut ifap) == 0 && !ifap.is_null() { Ok(InterfaceAddressIterator { ifap, buf: ifap }) } else { Err("failed to call getifaddrs()".to_string()) } } } sysinfo-0.28.4/src/network_helper_win.rs000064400000000000000000000074351046102023000164640ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::ffi::OsString; use std::os::windows::prelude::OsStringExt; use std::ptr::null_mut; use winapi::shared::winerror::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS}; use winapi::shared::ws2def::AF_UNSPEC; use winapi::um::iphlpapi::GetAdaptersAddresses; use winapi::um::iptypes::{ GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST, PIP_ADAPTER_ADDRESSES, }; use crate::common::MacAddr; /// this iterator yields an interface name and address pub(crate) struct InterfaceAddressIterator { /// The first item in the linked list buf: PIP_ADAPTER_ADDRESSES, /// The current adapter adapter: PIP_ADAPTER_ADDRESSES, } // Need a function to convert u16 pointer into String // https://stackoverflow.com/a/48587463/8706476 unsafe fn u16_ptr_to_string(ptr: *const u16) -> OsString { let len = (0..).take_while(|&i| *ptr.offset(i) != 0).count(); let slice = std::slice::from_raw_parts(ptr, len); OsString::from_wide(slice) } impl Iterator for InterfaceAddressIterator { type Item = (String, MacAddr); fn next(&mut self) -> Option { if self.adapter.is_null() { return None; } unsafe { let adapter = self.adapter; // Move to the next adapter self.adapter = (*adapter).Next; if let Ok(interface_name) = u16_ptr_to_string((*adapter).FriendlyName).into_string() { // take the first 6 bytes and return the MAC address instead let [mac @ .., _, _] = (*adapter).PhysicalAddress; Some((interface_name, MacAddr(mac))) } else { // Not sure whether error can occur when parsing adapter name. self.next() } } } } impl Drop for InterfaceAddressIterator { fn drop(&mut self) { unsafe { libc::free(self.buf as _); } } } pub(crate) fn get_interface_address() -> Result { unsafe { // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#remarks // A 15k buffer is recommended let mut size: u32 = 15 * 1024; let mut ret = ERROR_SUCCESS; // https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#examples // Try to retrieve adapter information up to 3 times for _ in 0..3 { let buf = libc::malloc(size as _) as PIP_ADAPTER_ADDRESSES; // free memory on drop let iterator = InterfaceAddressIterator { buf, adapter: buf }; if buf.is_null() { // insufficient memory available // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/malloc?view=msvc-170#return-value // malloc is not documented to set the last-error code return Err("failed to allocate memory for IP_ADAPTER_ADDRESSES".to_string()); } ret = GetAdaptersAddresses( AF_UNSPEC as u32, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, null_mut(), buf, &mut size, ); if ret == ERROR_SUCCESS { return Ok(iterator); } else if ret != ERROR_BUFFER_OVERFLOW { break; } // if the given memory size is too small to hold the adapter information, // the SizePointer returned will point to the required size of the buffer, // and we should continue. // Otherwise, break the loop and check the return code again } Err(format!("GetAdaptersAddresses() failed with code {ret}")) } } sysinfo-0.28.4/src/serde.rs000064400000000000000000000311201046102023000136450ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::common::PidExt; use crate::{ ComponentExt, CpuExt, DiskExt, DiskType, DiskUsage, MacAddr, NetworkExt, NetworksExt, ProcessExt, ProcessStatus, Signal, SystemExt, UserExt, }; use serde::{ser::SerializeStruct, Serialize, Serializer}; use std::ops::Deref; impl Serialize for crate::Disk { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("Disk", 7)?; state.serialize_field("DiskType", &self.type_())?; if let Some(s) = self.name().to_str() { state.serialize_field("name", s)?; } state.serialize_field("file_system", &self.file_system())?; state.serialize_field("mount_point", &self.mount_point())?; state.serialize_field("total_space", &self.total_space())?; state.serialize_field("available_space", &self.available_space())?; state.serialize_field("is_removable", &self.is_removable())?; state.end() } } impl Serialize for crate::Process { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("Process", 18)?; state.serialize_field("name", &self.name())?; state.serialize_field("cmd", &self.cmd())?; state.serialize_field("exe", &self.exe())?; state.serialize_field("pid", &self.pid().as_u32())?; state.serialize_field("environ", &self.environ())?; state.serialize_field("cwd", &self.cwd())?; state.serialize_field("root", &self.root())?; state.serialize_field("memory", &self.memory())?; state.serialize_field("virtual_memory", &self.virtual_memory())?; if let Some(pid) = self.parent() { state.serialize_field("parent", &pid.as_u32())?; } state.serialize_field("status", &self.status())?; state.serialize_field("start_time", &self.start_time())?; state.serialize_field("run_time", &self.run_time())?; state.serialize_field("cpu_usage", &self.cpu_usage())?; state.serialize_field("disk_usage", &self.disk_usage())?; if let Some(uid) = self.user_id() { state.serialize_field("user_id", &uid.to_string())?; } if let Some(gid) = self.group_id() { let gid = *gid.deref(); state.serialize_field("group_id", &gid)?; } if let Some(pid) = self.session_id() { state.serialize_field("session_id", &pid.as_u32())?; } state.end() } } impl Serialize for crate::Cpu { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("Cpu", 1)?; state.serialize_field("cpu_usage", &self.cpu_usage())?; state.serialize_field("name", &self.name())?; state.serialize_field("vendor_id", &self.vendor_id())?; state.serialize_field("brand", &self.brand())?; state.serialize_field("frequency", &self.frequency())?; state.end() } } impl serde::Serialize for crate::System { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut state = serializer.serialize_struct("System", 27)?; state.serialize_field("IS_SUPPORTED", &::IS_SUPPORTED)?; state.serialize_field("SUPPORTED_SIGNALS", ::SUPPORTED_SIGNALS)?; state.serialize_field( "MINIMUM_CPU_UPDATE_INTERVAL", &::MINIMUM_CPU_UPDATE_INTERVAL, )?; state.serialize_field("global_cpu_info", &self.global_cpu_info())?; state.serialize_field("cpus", &self.cpus())?; state.serialize_field("physical_core_count", &self.physical_core_count())?; state.serialize_field("total_memory", &self.total_memory())?; state.serialize_field("free_memory", &self.free_memory())?; state.serialize_field("available_memory", &self.available_memory())?; state.serialize_field("used_memory", &self.used_memory())?; state.serialize_field("total_swap", &self.total_swap())?; state.serialize_field("free_swap", &self.free_swap())?; state.serialize_field("used_swap", &self.used_swap())?; state.serialize_field("components", &self.components())?; state.serialize_field("users", &self.users())?; state.serialize_field("disks", &self.disks())?; state.serialize_field("networks", &self.networks())?; state.serialize_field("uptime", &self.uptime())?; state.serialize_field("boot_time", &self.boot_time())?; state.serialize_field("load_average", &self.load_average())?; state.serialize_field("name", &self.name())?; state.serialize_field("kernel_version", &self.kernel_version())?; state.serialize_field("os_version", &self.os_version())?; state.serialize_field("long_os_version", &self.long_os_version())?; state.serialize_field("distribution_id", &self.distribution_id())?; state.serialize_field("host_name", &self.host_name())?; state.end() } } impl Serialize for crate::Networks { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.collect_seq(self.iter()) } } impl Serialize for Signal { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant) = match *self { Signal::Hangup => (0, "Hangup"), Signal::Interrupt => (1, "Interrupt"), Signal::Quit => (2, "Quit"), Signal::Illegal => (3, "Illegal"), Signal::Trap => (4, "Trap"), Signal::Abort => (5, "Abort"), Signal::IOT => (6, "IOT"), Signal::Bus => (7, "Bus"), Signal::FloatingPointException => (8, "FloatingPointException"), Signal::Kill => (9, "Kill"), Signal::User1 => (10, "User1"), Signal::Segv => (11, "Segv"), Signal::User2 => (12, "User2"), Signal::Pipe => (13, "Pipe"), Signal::Alarm => (14, "Alarm"), Signal::Term => (15, "Term"), Signal::Child => (16, "Child"), Signal::Continue => (17, "Continue"), Signal::Stop => (18, "Stop"), Signal::TSTP => (19, "TSTP"), Signal::TTIN => (20, "TTIN"), Signal::TTOU => (21, "TTOU"), Signal::Urgent => (22, "Urgent"), Signal::XCPU => (23, "XCPU"), Signal::XFSZ => (24, "XFSZ"), Signal::VirtualAlarm => (25, "VirtualAlarm"), Signal::Profiling => (26, "Profiling"), Signal::Winch => (27, "Winch"), Signal::IO => (28, "IO"), Signal::Poll => (29, "Poll"), Signal::Power => (30, "Power"), Signal::Sys => (31, "Sys"), }; serializer.serialize_unit_variant("Signal", index, variant) } } impl Serialize for crate::LoadAvg { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("LoadAvg", 1)?; state.serialize_field("one", &self.one)?; state.serialize_field("five", &self.five)?; state.serialize_field("fifteen", &self.fifteen)?; state.end() } } // impl Serialize for crate::NetworkData { // fn serialize(&self, serializer: S) -> Result // where // S: Serializer, // { // let mut state = serializer.serialize_struct("NetworkData", 1)?; // } // } impl Serialize for crate::NetworkData { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("NetworkData", 1)?; state.serialize_field("received", &self.received())?; state.serialize_field("total_received", &self.total_received())?; state.serialize_field("transmitted", &self.transmitted())?; state.serialize_field("total_transmitted", &self.total_transmitted())?; state.serialize_field("packets_received", &self.packets_received())?; state.serialize_field("total_packets_received", &self.total_packets_received())?; state.serialize_field("packets_transmitted", &self.packets_transmitted())?; state.serialize_field( "total_packets_transmitted", &self.total_packets_transmitted(), )?; state.serialize_field("errors_on_received", &self.errors_on_received())?; state.serialize_field("total_errors_on_received", &self.total_errors_on_received())?; state.serialize_field("errors_on_transmitted", &self.errors_on_transmitted())?; state.serialize_field( "total_errors_on_transmitted", &self.total_errors_on_transmitted(), )?; state.serialize_field("mac_address", &self.mac_address())?; state.end() } } impl Serialize for crate::Component { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("Component", 4)?; state.serialize_field("temperature", &self.temperature())?; state.serialize_field("max", &self.max())?; state.serialize_field("critical", &self.critical())?; state.serialize_field("label", &self.label())?; state.end() } } impl Serialize for crate::User { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("User", 1)?; state.serialize_field("id", &self.id().to_string())?; let gid = *self.group_id().deref(); state.serialize_field("group_id", &gid)?; state.serialize_field("name", &self.name())?; state.serialize_field("groups", &self.groups())?; state.end() } } impl Serialize for DiskType { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant, maybe_value) = match *self { DiskType::HDD => (0, "HDD", None), DiskType::SSD => (1, "SSD", None), DiskType::Unknown(ref s) => (2, "Unknown", Some(s)), }; if let Some(ref value) = maybe_value { serializer.serialize_newtype_variant("DiskType", index, variant, value) } else { serializer.serialize_unit_variant("DiskType", index, variant) } } } impl Serialize for ProcessStatus { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let (index, variant, maybe_value) = match *self { ProcessStatus::Idle => (0, "Idle", None), ProcessStatus::Run => (1, "Run", None), ProcessStatus::Sleep => (2, "Sleep", None), ProcessStatus::Stop => (3, "Stop", None), ProcessStatus::Zombie => (4, "Zombie", None), ProcessStatus::Tracing => (5, "Tracing", None), ProcessStatus::Dead => (6, "Dead", None), ProcessStatus::Wakekill => (7, "Wakekill", None), ProcessStatus::Waking => (8, "Waking", None), ProcessStatus::Parked => (9, "Parked", None), ProcessStatus::LockBlocked => (10, "LockBlocked", None), ProcessStatus::UninterruptibleDiskSleep => (11, "UninterruptibleDiskSleep", None), ProcessStatus::Unknown(n) => (12, "Unknown", Some(n)), }; if let Some(ref value) = maybe_value { serializer.serialize_newtype_variant("ProcessStatus", index, variant, value) } else { serializer.serialize_unit_variant("ProcessStatus", index, variant) } } } impl Serialize for DiskUsage { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("DiskUsage", 4)?; state.serialize_field("total_written_bytes", &self.total_written_bytes)?; state.serialize_field("written_bytes", &self.written_bytes)?; state.serialize_field("total_read_bytes", &self.total_read_bytes)?; state.serialize_field("read_bytes", &self.read_bytes)?; state.end() } } impl Serialize for MacAddr { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_newtype_struct("MacAddr", &self.0) } } sysinfo-0.28.4/src/sysinfo.h000064400000000000000000000042021046102023000140410ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #pragma once #include #include typedef void* CSystem; typedef const void* CProcess; typedef const char* RString; CSystem *sysinfo_init(); void sysinfo_destroy(CSystem system); void sysinfo_refresh_system(CSystem system); void sysinfo_refresh_memory(CSystem system); void sysinfo_refresh_cpu(CSystem system); void sysinfo_refresh_components(CSystem system); void sysinfo_refresh_all(CSystem system); void sysinfo_refresh_processes(CSystem system); #ifdef __linux__ void sysinfo_refresh_process(CSystem system, pid_t pid); #endif void sysinfo_refresh_disks(CSystem system); void sysinfo_refresh_disk_list(CSystem system); size_t sysinfo_total_memory(CSystem system); size_t sysinfo_free_memory(CSystem system); size_t sysinfo_used_memory(CSystem system); size_t sysinfo_total_swap(CSystem system); size_t sysinfo_free_swap(CSystem system); size_t sysinfo_used_swap(CSystem system); size_t sysinfo_networks_received(CSystem system); size_t sysinfo_networks_transmitted(CSystem system); void sysinfo_cpus_usage(CSystem system, unsigned int *length, float **cpus); size_t sysinfo_processes(CSystem system, bool (*fn_pointer)(pid_t, CProcess, void*), void *data); #ifdef __linux__ size_t sysinfo_process_tasks(CProcess process, bool (*fn_pointer)(pid_t, CProcess, void*), void *data); #endif CProcess sysinfo_process_by_pid(CSystem system, pid_t pid); pid_t sysinfo_process_pid(CProcess process); pid_t sysinfo_process_parent_pid(CProcess process); float sysinfo_process_cpu_usage(CProcess process); size_t sysinfo_process_memory(CProcess process); size_t sysinfo_process_virtual_memory(CProcess process); RString sysinfo_process_executable_path(CProcess process); RString sysinfo_process_root_directory(CProcess process); RString sysinfo_process_current_directory(CProcess process); void sysinfo_rstring_free(RString str); sysinfo-0.28.4/src/system.rs000064400000000000000000000137201046102023000140750ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // Once https://github.com/rust-lang/rfcs/blob/master/text/1422-pub-restricted.md // feature gets stabilized, we can move common parts in here. #[cfg(test)] mod tests { use crate::{ProcessExt, System, SystemExt}; #[test] fn test_refresh_system() { let mut sys = System::new(); sys.refresh_system(); // We don't want to test on unsupported systems. if System::IS_SUPPORTED { assert!(sys.total_memory() != 0); assert!(sys.free_memory() != 0); } assert!(sys.total_memory() >= sys.free_memory()); assert!(sys.total_swap() >= sys.free_swap()); } #[test] fn test_refresh_process() { let mut sys = System::new(); assert!(sys.processes().is_empty(), "no process should be listed!"); // We don't want to test on unsupported systems. #[cfg(not(feature = "apple-sandbox"))] if System::IS_SUPPORTED { assert!( sys.refresh_process(crate::get_current_pid().expect("failed to get current pid")), "process not listed", ); // Ensure that the process was really added to the list! assert!(sys .process(crate::get_current_pid().expect("failed to get current pid")) .is_some()); } } #[test] fn test_get_process() { let mut sys = System::new(); sys.refresh_processes(); let current_pid = match crate::get_current_pid() { Ok(pid) => pid, _ => { if !System::IS_SUPPORTED { return; } panic!("get_current_pid should work!"); } }; if let Some(p) = sys.process(current_pid) { assert!(p.memory() > 0); } else { #[cfg(not(feature = "apple-sandbox"))] assert!(!System::IS_SUPPORTED); } } #[test] fn check_if_send_and_sync() { trait Foo { fn foo(&self) {} } impl Foo for T where T: Send {} trait Bar { fn bar(&self) {} } impl Bar for T where T: Sync {} let mut sys = System::new(); sys.refresh_processes(); let current_pid = match crate::get_current_pid() { Ok(pid) => pid, _ => { if !System::IS_SUPPORTED { return; } panic!("get_current_pid should work!"); } }; if let Some(p) = sys.process(current_pid) { p.foo(); // If this doesn't compile, it'll simply mean that the Process type // doesn't implement the Send trait. p.bar(); // If this doesn't compile, it'll simply mean that the Process type // doesn't implement the Sync trait. } else { #[cfg(not(feature = "apple-sandbox"))] assert!(!System::IS_SUPPORTED); } } #[test] fn check_hostname_has_no_nuls() { let sys = System::new(); if let Some(hostname) = sys.host_name() { assert!(!hostname.contains('\u{0}')) } } #[test] fn check_uptime() { let sys = System::new(); let uptime = sys.uptime(); if System::IS_SUPPORTED { std::thread::sleep(std::time::Duration::from_millis(1000)); let new_uptime = sys.uptime(); assert!(uptime < new_uptime); } } // This test is used to ensure that the CPU usage computation isn't completely going off // when refreshing it too frequently (ie, multiple times in a row in a very small interval). #[test] #[ignore] // This test MUST be run on its own to prevent wrong CPU usage measurements. fn test_consecutive_cpu_usage_update() { use crate::{PidExt, ProcessExt, ProcessRefreshKind, System, SystemExt}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; if !System::IS_SUPPORTED { return; } let mut sys = System::new_all(); assert!(!sys.cpus().is_empty()); sys.refresh_processes_specifics(ProcessRefreshKind::new().with_cpu()); let stop = Arc::new(AtomicBool::new(false)); // Spawning a few threads to ensure that it will actually have an impact on the CPU usage. for it in 0..sys.cpus().len() / 2 + 1 { let stop_c = Arc::clone(&stop); std::thread::spawn(move || { while !stop_c.load(Ordering::Relaxed) { if it != 0 { // The first thread runs at 100% to be sure it'll be noticeable. std::thread::sleep(Duration::from_millis(1)); } } }); } let mut pids = sys .processes() .iter() .map(|(pid, _)| *pid) .take(2) .collect::>(); let pid = std::process::id(); pids.push(PidExt::from_u32(pid)); assert_eq!(pids.len(), 3); for it in 0..3 { std::thread::sleep( crate::System::MINIMUM_CPU_UPDATE_INTERVAL + Duration::from_millis(1), ); for pid in &pids { sys.refresh_process_specifics(*pid, ProcessRefreshKind::new().with_cpu()); } // To ensure that Linux doesn't give too high numbers. assert!( sys.process(pids[2]).unwrap().cpu_usage() < sys.cpus().len() as f32 * 100., "using ALL CPU: failed at iteration {}", it ); // To ensure it's not 0 either. assert!( sys.process(pids[2]).unwrap().cpu_usage() > 0., "using NO CPU: failed at iteration {}", it ); } stop.store(false, Ordering::Relaxed); } } sysinfo-0.28.4/src/traits.rs000064400000000000000000001517351046102023000140700ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ common::{Gid, MacAddr, Uid}, sys::{Component, Cpu, Disk, Networks, Process}, }; use crate::{ CpuRefreshKind, DiskType, DiskUsage, LoadAvg, NetworksIter, Pid, ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, User, }; use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; use std::path::Path; use std::time::Duration; /// Contains all the methods of the [`Disk`][crate::Disk] struct. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{:?}: {:?}", disk.name(), disk.type_()); /// } /// ``` pub trait DiskExt: Debug { /// Returns the disk type. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{:?}", disk.type_()); /// } /// ``` fn type_(&self) -> DiskType; /// Returns the disk name. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{:?}", disk.name()); /// } /// ``` fn name(&self) -> &OsStr; /// Returns the file system used on this disk (so for example: `EXT4`, `NTFS`, etc...). /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{:?}", disk.file_system()); /// } /// ``` fn file_system(&self) -> &[u8]; /// Returns the mount point of the disk (`/` for example). /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{:?}", disk.mount_point()); /// } /// ``` fn mount_point(&self) -> &Path; /// Returns the total disk size, in bytes. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{}", disk.total_space()); /// } /// ``` fn total_space(&self) -> u64; /// Returns the available disk size, in bytes. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{}", disk.available_space()); /// } /// ``` fn available_space(&self) -> u64; /// Returns `true` if the disk is removable. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new(); /// for disk in s.disks() { /// println!("{}", disk.is_removable()); /// } /// ``` fn is_removable(&self) -> bool; /// Updates the disk' information. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// for disk in s.disks_mut() { /// disk.refresh(); /// } /// ``` fn refresh(&mut self) -> bool; } /// Contains all the methods of the [`Process`][crate::Process] struct. pub trait ProcessExt: Debug { /// Sends [`Signal::Kill`] to the process (which is the only signal supported on all supported /// platforms by this crate). /// /// If you want to send another signal, take a look at [`ProcessExt::kill_with`]. /// /// To get the list of the supported signals on this system, use /// [`SystemExt::SUPPORTED_SIGNALS`]. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// process.kill(); /// } /// ``` fn kill(&self) -> bool { self.kill_with(Signal::Kill).unwrap_or(false) } /// Sends the given `signal` to the process. If the signal doesn't exist on this platform, /// it'll do nothing and will return `None`. Otherwise it'll return if the signal was sent /// successfully. /// /// If you just want to kill the process, use [`ProcessExt::kill`] directly. /// /// To get the list of the supported signals on this system, use /// [`SystemExt::SUPPORTED_SIGNALS`]. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, Signal, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// if process.kill_with(Signal::Kill).is_none() { /// eprintln!("This signal isn't supported on this platform"); /// } /// } /// ``` fn kill_with(&self, signal: Signal) -> Option; /// Returns the name of the process. /// /// **⚠️ Important ⚠️** /// /// On **Linux**, there are two things to know about processes' name: /// 1. It is limited to 15 characters. /// 2. It is not always the exe name. /// /// If you are looking for a specific process, unless you know what you are doing, in most /// cases it's better to use [`ProcessExt::exe`] instead (which can be empty sometimes!). /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.name()); /// } /// ``` fn name(&self) -> &str; /// Returns the command line. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.cmd()); /// } /// ``` fn cmd(&self) -> &[String]; /// Returns the path to the process. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.exe().display()); /// } /// ``` /// /// ### Implementation notes /// /// On Linux, this method will return an empty path if there /// was an error trying to read `/proc//exe`. This can /// happen, for example, if the permission levels or UID namespaces /// between the caller and target processes are different. /// /// It is also the case that `cmd[0]` is _not_ usually a correct /// replacement for this. /// A process [may change its `cmd[0]` value](https://man7.org/linux/man-pages/man5/proc.5.html) /// freely, making this an untrustworthy source of information. fn exe(&self) -> &Path; /// Returns the PID of the process. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.pid()); /// } /// ``` fn pid(&self) -> Pid; /// Returns the environment variables of the process. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.environ()); /// } /// ``` fn environ(&self) -> &[String]; /// Returns the current working directory. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.cwd().display()); /// } /// ``` fn cwd(&self) -> &Path; /// Returns the path of the root directory. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.root().display()); /// } /// ``` fn root(&self) -> &Path; /// Returns the memory usage (in bytes). /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{} bytes", process.memory()); /// } /// ``` fn memory(&self) -> u64; /// Returns the virtual memory usage (in bytes). /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{} bytes", process.virtual_memory()); /// } /// ``` fn virtual_memory(&self) -> u64; /// Returns the parent PID. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.parent()); /// } /// ``` fn parent(&self) -> Option; /// Returns the status of the process. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{:?}", process.status()); /// } /// ``` fn status(&self) -> ProcessStatus; /// Returns the time where the process was started (in seconds) from epoch. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Started at {} seconds", process.start_time()); /// } /// ``` fn start_time(&self) -> u64; /// Returns for how much time the process has been running (in seconds). /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("Running since {} seconds", process.run_time()); /// } /// ``` fn run_time(&self) -> u64; /// Returns the total CPU usage (in %). Notice that it might be bigger than 100 if run on a /// multi-core machine. /// /// If you want a value between 0% and 100%, divide the returned value by the number of CPUs. /// /// ⚠️ To start to have accurate CPU usage, a process needs to be refreshed **twice** because /// CPU usage computation is based on time diff (process time on a given time period divided by /// total system time on the same time period). /// /// ⚠️ If you want accurate CPU usage number, better leave a bit of time /// between two calls of this method (take a look at /// [`SystemExt::MINIMUM_CPU_UPDATE_INTERVAL`] for more information). /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}%", process.cpu_usage()); /// } /// ``` fn cpu_usage(&self) -> f32; /// Returns number of bytes read and written to disk. /// /// ⚠️ On Windows and FreeBSD, this method actually returns **ALL** I/O read and written bytes. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new(); /// if let Some(process) = s.process(Pid::from(1337)) { /// let disk_usage = process.disk_usage(); /// println!("read bytes : new/total => {}/{}", /// disk_usage.read_bytes, /// disk_usage.total_read_bytes, /// ); /// println!("written bytes: new/total => {}/{}", /// disk_usage.written_bytes, /// disk_usage.total_written_bytes, /// ); /// } /// ``` fn disk_usage(&self) -> DiskUsage; /// Returns the ID of the owner user of this process or `None` if this information couldn't /// be retrieved. If you want to get the [`User`] from it, take a look at /// [`SystemExt::get_user_by_id`]. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// eprintln!("User id for process 1337: {:?}", process.user_id()); /// } /// ``` fn user_id(&self) -> Option<&Uid>; /// Returns the process group ID of the process. /// /// ⚠️ It always returns `None` on Windows. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// eprintln!("Group ID for process 1337: {:?}", process.group_id()); /// } /// ``` fn group_id(&self) -> Option; /// Wait for process termination. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// eprintln!("Waiting for pid 1337"); /// process.wait(); /// eprintln!("Pid 1337 exited"); /// } /// ``` fn wait(&self); /// Returns the session ID for the current process or `None` if it couldn't be retrieved. /// /// ⚠️ This information is computed every time this method is called. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// eprintln!("Session ID for process 1337: {:?}", process.session_id()); /// } /// ``` fn session_id(&self) -> Option; } /// Contains all the methods of the [`Cpu`][crate::Cpu] struct. pub trait CpuExt: Debug { /// Returns this CPU's usage. /// /// Note: You'll need to refresh it at least twice (diff between the first and the second is /// how CPU usage is computed) at first if you want to have a non-zero value. /// /// ```no_run /// use sysinfo::{CpuExt, System, SystemExt}; /// /// let s = System::new(); /// for cpu in s.cpus() { /// println!("{}%", cpu.cpu_usage()); /// } /// ``` fn cpu_usage(&self) -> f32; /// Returns this CPU's name. /// /// ```no_run /// use sysinfo::{CpuExt, System, SystemExt}; /// /// let s = System::new(); /// for cpu in s.cpus() { /// println!("{}", cpu.name()); /// } /// ``` fn name(&self) -> &str; /// Returns the CPU's vendor id. /// /// ```no_run /// use sysinfo::{CpuExt, System, SystemExt}; /// /// let s = System::new(); /// for cpu in s.cpus() { /// println!("{}", cpu.vendor_id()); /// } /// ``` fn vendor_id(&self) -> &str; /// Returns the CPU's brand. /// /// ```no_run /// use sysinfo::{CpuExt, System, SystemExt}; /// /// let s = System::new(); /// for cpu in s.cpus() { /// println!("{}", cpu.brand()); /// } /// ``` fn brand(&self) -> &str; /// Returns the CPU's frequency. /// /// ```no_run /// use sysinfo::{CpuExt, System, SystemExt}; /// /// let s = System::new(); /// for cpu in s.cpus() { /// println!("{}", cpu.frequency()); /// } /// ``` fn frequency(&self) -> u64; } /// Contains all the methods of the [`System`][crate::System] type. pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// Returns `true` if this OS is supported. Please refer to the /// [crate-level documentation](index.html) to get the list of supported OSes. /// /// ``` /// use sysinfo::{System, SystemExt}; /// /// if System::IS_SUPPORTED { /// println!("This OS is supported!"); /// } else { /// println!("This OS isn't supported (yet?)."); /// } /// ``` const IS_SUPPORTED: bool; /// Returns the list of the supported signals on this system (used by /// [`ProcessExt::kill_with`]). /// /// ``` /// use sysinfo::{System, SystemExt}; /// /// println!("supported signals: {:?}", System::SUPPORTED_SIGNALS); /// ``` const SUPPORTED_SIGNALS: &'static [Signal]; /// This is the minimum interval time used internally by `sysinfo` to refresh the CPU time. /// /// ⚠️ This value differs from one OS to another. /// /// Why is this constant even needed? /// /// If refreshed too often, the CPU usage of processes will be `0` whereas on Linux it'll /// always be the maximum value (`number of CPUs * 100`). const MINIMUM_CPU_UPDATE_INTERVAL: Duration; /// Creates a new [`System`] instance with nothing loaded. If you want to /// load components, network interfaces or the disks, you'll have to use the /// `refresh_*_list` methods. [`SystemExt::refresh_networks_list`] for /// example. /// /// Use the [`refresh_all`] method to update its internal information (or any of the `refresh_` /// method). /// /// [`System`]: crate::System /// [`refresh_all`]: #method.refresh_all /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// ``` fn new() -> Self { Self::new_with_specifics(RefreshKind::new()) } /// Creates a new [`System`] instance with everything loaded. /// /// It is an equivalent of [`SystemExt::new_with_specifics`]`(`[`RefreshKind::everything`]`())`. /// /// [`System`]: crate::System /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// ``` fn new_all() -> Self { Self::new_with_specifics(RefreshKind::everything()) } /// Creates a new [`System`] instance and refresh the data corresponding to the /// given [`RefreshKind`]. /// /// [`System`]: crate::System /// /// ``` /// use sysinfo::{RefreshKind, System, SystemExt}; /// /// // We want everything except disks. /// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); /// /// assert_eq!(system.disks().len(), 0); /// # if System::IS_SUPPORTED && !cfg!(feature = "apple-sandbox") { /// assert!(system.processes().len() > 0); /// # } /// /// // If you want the disks list afterwards, just call the corresponding /// // "refresh_disks_list": /// system.refresh_disks_list(); /// let disks = system.disks(); /// ``` fn new_with_specifics(refreshes: RefreshKind) -> Self; /// Refreshes according to the given [`RefreshKind`]. It calls the corresponding /// "refresh_" methods. /// /// ``` /// use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt}; /// /// let mut s = System::new_all(); /// /// // Let's just update networks and processes: /// s.refresh_specifics( /// RefreshKind::new().with_networks().with_processes(ProcessRefreshKind::everything()), /// ); /// ``` fn refresh_specifics(&mut self, refreshes: RefreshKind) { if refreshes.memory() { self.refresh_memory(); } if let Some(kind) = refreshes.cpu() { self.refresh_cpu_specifics(kind); } if refreshes.components_list() { self.refresh_components_list(); } else if refreshes.components() { self.refresh_components(); } if refreshes.networks_list() { self.refresh_networks_list(); } else if refreshes.networks() { self.refresh_networks(); } if let Some(kind) = refreshes.processes() { self.refresh_processes_specifics(kind); } if refreshes.disks_list() { self.refresh_disks_list(); } else if refreshes.disks() { self.refresh_disks(); } if refreshes.users_list() { self.refresh_users_list(); } } /// Refreshes all system, processes, disks and network interfaces information. /// /// Please note that it doesn't recompute disks list, components list, network interfaces /// list nor users list. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_all(); /// ``` fn refresh_all(&mut self) { self.refresh_system(); self.refresh_processes(); self.refresh_disks(); self.refresh_networks(); } /// Refreshes system information (RAM, swap, CPU usage and components' temperature). /// /// If you want some more specific refreshes, you might be interested into looking at /// [`refresh_memory`], [`refresh_cpu`] and [`refresh_components`]. /// /// [`refresh_memory`]: SystemExt::refresh_memory /// [`refresh_cpu`]: SystemExt::refresh_memory /// [`refresh_components`]: SystemExt::refresh_components /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_system(); /// ``` fn refresh_system(&mut self) { self.refresh_memory(); self.refresh_cpu(); self.refresh_components(); } /// Refreshes RAM and SWAP usage. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_memory(); /// ``` fn refresh_memory(&mut self); /// Refreshes CPUs information. /// /// ⚠️ Please note that the result will very likely be inaccurate at the first call. /// You need to call this method at least twice (with a bit of time between each call, like /// 200 ms, take a look at [`SystemExt::MINIMUM_CPU_UPDATE_INTERVAL`] for more information) /// to get accurate value as it uses previous results to compute the next value. /// /// Calling this method is the same as calling /// `refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage())`. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_cpu(); /// ``` fn refresh_cpu(&mut self) { self.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()) } /// Refreshes CPUs specific information. /// /// Please note that it doesn't recompute disks list, components list, network interfaces /// list nor users list. /// /// ```no_run /// use sysinfo::{System, SystemExt, CpuRefreshKind}; /// /// let mut s = System::new_all(); /// s.refresh_cpu_specifics(CpuRefreshKind::everything()); /// ``` fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind); /// Refreshes components' temperature. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_components(); /// ``` fn refresh_components(&mut self) { for component in self.components_mut() { component.refresh(); } } /// Refreshes components list. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new(); /// s.refresh_components_list(); /// ``` fn refresh_components_list(&mut self); /// Gets all processes and updates their information. /// /// It does the same as `system.refresh_processes_specifics(ProcessRefreshKind::everything())`. /// /// ⚠️ On Linux, `sysinfo` keeps the `stat` files open by default. You can change this behaviour /// by using [`set_open_files_limit`][crate::set_open_files_limit]. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_processes(); /// ``` fn refresh_processes(&mut self) { self.refresh_processes_specifics(ProcessRefreshKind::everything()); } /// Gets all processes and updates the specified information. /// /// ⚠️ On Linux, `sysinfo` keeps the `stat` files open by default. You can change this behaviour /// by using [`set_open_files_limit`][crate::set_open_files_limit]. /// /// ```no_run /// use sysinfo::{ProcessRefreshKind, System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_processes_specifics(ProcessRefreshKind::new()); /// ``` fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind); /// Refreshes *only* the process corresponding to `pid`. Returns `false` if the process doesn't /// exist (it will **NOT** be removed from the processes if it doesn't exist anymore). If it /// isn't listed yet, it'll be added. /// /// It is the same as calling /// `sys.refresh_process_specifics(pid, ProcessRefreshKind::everything())`. /// /// ```no_run /// use sysinfo::{Pid, System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_process(Pid::from(1337)); /// ``` fn refresh_process(&mut self, pid: Pid) -> bool { self.refresh_process_specifics(pid, ProcessRefreshKind::everything()) } /// Refreshes *only* the process corresponding to `pid`. Returns `false` if the process doesn't /// exist (it will **NOT** be removed from the processes if it doesn't exist anymore). If it /// isn't listed yet, it'll be added. /// /// ```no_run /// use sysinfo::{Pid, ProcessRefreshKind, System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_process_specifics(Pid::from(1337), ProcessRefreshKind::new()); /// ``` fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool; /// Refreshes the listed disks' information. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_disks(); /// ``` fn refresh_disks(&mut self) { for disk in self.disks_mut() { disk.refresh(); } } /// The disk list will be emptied then completely recomputed. /// /// ## Linux /// /// ⚠️ On Linux, the [NFS](https://en.wikipedia.org/wiki/Network_File_System) file /// systems are ignored and the information of a mounted NFS **cannot** be obtained /// via [`SystemExt::refresh_disks_list`]. This is due to the fact that I/O function /// `statvfs` used by [`SystemExt::refresh_disks_list`] is blocking and /// [may hang](https://github.com/GuillaumeGomez/sysinfo/pull/876) in some cases, /// requiring to call `systemctl stop` to terminate the NFS service from the remote /// server in some cases. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_disks_list(); /// ``` fn refresh_disks_list(&mut self); /// Refreshes users list. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_users_list(); /// ``` fn refresh_users_list(&mut self); /// Refreshes networks data. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_networks(); /// ``` /// /// It is a shortcut for: /// /// ```no_run /// use sysinfo::{NetworksExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// let networks = s.networks_mut(); /// networks.refresh(); /// ``` fn refresh_networks(&mut self) { self.networks_mut().refresh(); } /// The network list will be updated: removing not existing anymore interfaces and adding new /// ones. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let mut s = System::new_all(); /// s.refresh_networks_list(); /// ``` /// /// This is a shortcut for: /// /// ```no_run /// use sysinfo::{NetworksExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// let networks = s.networks_mut(); /// networks.refresh_networks_list(); /// ``` fn refresh_networks_list(&mut self) { self.networks_mut().refresh_networks_list(); } /// Returns the process list. /// /// ```no_run /// use sysinfo::{ProcessExt, System, SystemExt}; /// /// let s = System::new_all(); /// for (pid, process) in s.processes() { /// println!("{} {}", pid, process.name()); /// } /// ``` fn processes(&self) -> &HashMap; /// Returns the process corresponding to the given `pid` or `None` if no such process exists. /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { /// println!("{}", process.name()); /// } /// ``` fn process(&self, pid: Pid) -> Option<&Process>; /// Returns an iterator of process containing the given `name`. /// /// If you want only the processes with exactly the given `name`, take a look at /// [`SystemExt::processes_by_exact_name`]. /// /// **⚠️ Important ⚠️** /// /// On **Linux**, there are two things to know about processes' name: /// 1. It is limited to 15 characters. /// 2. It is not always the exe name. /// /// ```no_run /// use sysinfo::{ProcessExt, System, SystemExt}; /// /// let s = System::new_all(); /// for process in s.processes_by_name("htop") { /// println!("{} {}", process.pid(), process.name()); /// } /// ``` // FIXME: replace the returned type with `impl Iterator` when it's supported! fn processes_by_name<'a: 'b, 'b>( &'a self, name: &'b str, ) -> Box + 'b> { Box::new( self.processes() .values() .filter(move |val: &&Process| val.name().contains(name)), ) } /// Returns an iterator of processes with exactly the given `name`. /// /// If you instead want the processes containing `name`, take a look at /// [`SystemExt::processes_by_name`]. /// /// **⚠️ Important ⚠️** /// /// On **Linux**, there are two things to know about processes' name: /// 1. It is limited to 15 characters. /// 2. It is not always the exe name. /// /// ```no_run /// use sysinfo::{ProcessExt, System, SystemExt}; /// /// let s = System::new_all(); /// for process in s.processes_by_exact_name("htop") { /// println!("{} {}", process.pid(), process.name()); /// } /// ``` // FIXME: replace the returned type with `impl Iterator` when it's supported! fn processes_by_exact_name<'a: 'b, 'b>( &'a self, name: &'b str, ) -> Box + 'b> { Box::new( self.processes() .values() .filter(move |val: &&Process| val.name() == name), ) } /// Returns "global" CPUs information (aka the addition of all the CPUs). /// /// To have up-to-date information, you need to call [`SystemExt::refresh_cpu`] or /// [`SystemExt::refresh_specifics`] with `cpu` enabled. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, CpuExt, RefreshKind, System, SystemExt}; /// /// let s = System::new_with_specifics( /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), /// ); /// println!("{}%", s.global_cpu_info().cpu_usage()); /// ``` fn global_cpu_info(&self) -> &Cpu; /// Returns the list of the CPUs. /// /// By default, the list of CPUs is empty until you call [`SystemExt::refresh_cpu`] or /// [`SystemExt::refresh_specifics`] with `cpu` enabled. /// /// ```no_run /// use sysinfo::{CpuRefreshKind, CpuExt, RefreshKind, System, SystemExt}; /// /// let s = System::new_with_specifics( /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), /// ); /// for cpu in s.cpus() { /// println!("{}%", cpu.cpu_usage()); /// } /// ``` fn cpus(&self) -> &[Cpu]; /// Returns the number of physical cores on the CPU or `None` if it couldn't get it. /// /// In case there are multiple CPUs, it will combine the physical core count of all the CPUs. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{CpuExt, System, SystemExt}; /// /// let s = System::new(); /// println!("{:?}", s.physical_core_count()); /// ``` fn physical_core_count(&self) -> Option; /// Returns the RAM size in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.total_memory()); /// ``` fn total_memory(&self) -> u64; /// Returns the amount of free RAM in bytes. /// /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to /// memory that is available for (re)use. /// /// Side note: Windows doesn't report "free" memory so this method returns the same value /// as [`get_available_memory`](#tymethod.available_memory). /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.free_memory()); /// ``` fn free_memory(&self) -> u64; /// Returns the amount of available RAM in bytes. /// /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to /// memory that is available for (re)use. /// /// ⚠️ Windows and FreeBSD don't report "available" memory so [`SystemExt::free_memory`] /// returns the same value as this method. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.available_memory()); /// ``` fn available_memory(&self) -> u64; /// Returns the amount of used RAM in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.used_memory()); /// ``` fn used_memory(&self) -> u64; /// Returns the SWAP size in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.total_swap()); /// ``` fn total_swap(&self) -> u64; /// Returns the amount of free SWAP in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.free_swap()); /// ``` fn free_swap(&self) -> u64; /// Returns the amount of used SWAP in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("{} bytes", s.used_swap()); /// ``` fn used_swap(&self) -> u64; /// Returns the components list. /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let s = System::new_all(); /// for component in s.components() { /// println!("{}: {}°C", component.label(), component.temperature()); /// } /// ``` fn components(&self) -> &[Component]; /// Returns a mutable components list. /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// for component in s.components_mut() { /// component.refresh(); /// } /// ``` fn components_mut(&mut self) -> &mut [Component]; /// Returns the users list. /// /// ```no_run /// use sysinfo::{System, SystemExt, UserExt}; /// /// let mut s = System::new_all(); /// for user in s.users() { /// println!("{} is in {} groups", user.name(), user.groups().len()); /// } /// ``` fn users(&self) -> &[User]; /// Returns the disks list. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let s = System::new_all(); /// for disk in s.disks() { /// println!("{:?}", disk.name()); /// } /// ``` fn disks(&self) -> &[Disk]; /// Returns the disks list. /// /// ```no_run /// use sysinfo::{DiskExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// for disk in s.disks_mut() { /// disk.refresh(); /// } /// ``` fn disks_mut(&mut self) -> &mut [Disk]; /// Sort the disk list with the provided callback. /// /// Internally, it is using the [`slice::sort_unstable_by`] function, so please refer to it /// for implementation details. /// /// ⚠️ If you use [`SystemExt::refresh_disks_list`], you need to use this method before using /// [`SystemExt::disks`] or [`SystemExt::disks_mut`] if you want them to be sorted. fn sort_disks_by(&mut self, compare: F) where F: FnMut(&Disk, &Disk) -> std::cmp::Ordering; /// Returns the network interfaces object. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, data) in networks { /// println!( /// "[{}] in: {}, out: {}", /// interface_name, /// data.received(), /// data.transmitted(), /// ); /// } /// ``` fn networks(&self) -> &Networks; /// Returns a mutable access to network interfaces. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// let networks = s.networks_mut(); /// networks.refresh_networks_list(); /// ``` fn networks_mut(&mut self) -> &mut Networks; /// Returns system uptime (in seconds). /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// println!("System running since {} seconds", s.uptime()); /// ``` fn uptime(&self) -> u64; /// Returns the time (in seconds) when the system booted since UNIX epoch. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("System booted at {} seconds", s.boot_time()); /// ``` fn boot_time(&self) -> u64; /// Returns the system load average value. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); /// let load_avg = s.load_average(); /// println!( /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", /// load_avg.one, /// load_avg.five, /// load_avg.fifteen, /// ); /// ``` fn load_average(&self) -> LoadAvg; /// Returns the system name. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("OS: {:?}", s.name()); /// ``` fn name(&self) -> Option; /// Returns the system's kernel version. /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("kernel version: {:?}", s.kernel_version()); /// ``` fn kernel_version(&self) -> Option; /// Returns the system version (e.g. for MacOS this will return 11.1 rather than the kernel version). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("OS version: {:?}", s.os_version()); /// ``` fn os_version(&self) -> Option; /// Returns the system long os version (e.g "MacOS 11.2 BigSur"). /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("Long OS Version: {:?}", s.long_os_version()); /// ``` fn long_os_version(&self) -> Option; /// Returns the distribution id as defined by os-release, /// or [`std::env::consts::OS`]. /// /// See also /// - /// - /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("Distribution ID: {:?}", s.distribution_id()); /// ``` fn distribution_id(&self) -> String; /// Returns the system hostname based off DNS /// /// **Important**: this information is computed every time this function is called. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new(); /// println!("Hostname: {:?}", s.host_name()); /// ``` fn host_name(&self) -> Option; /// Returns the [`User`] matching the given `user_id`. /// /// **Important**: The user list must be filled before using this method, otherwise it will /// always return `None` (through the `refresh_*` methods). /// /// It is a shorthand for: /// /// ```ignore /// let s = System::new_all(); /// s.users().find(|user| user.id() == user_id); /// ``` /// /// Full example: /// /// ```no_run /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// /// if let Some(process) = s.process(Pid::from(1337)) { /// if let Some(user_id) = process.user_id() { /// eprintln!("User for process 1337: {:?}", s.get_user_by_id(user_id)); /// } /// } /// ``` fn get_user_by_id(&self, user_id: &Uid) -> Option<&User> { self.users().iter().find(|user| user.id() == user_id) } } /// Getting volume of received and transmitted data. pub trait NetworkExt: Debug { /// Returns the number of received bytes since the last refresh. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {} B", network.received()); /// } /// ``` fn received(&self) -> u64; /// Returns the total number of received bytes. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {} B", network.total_received()); /// } /// ``` fn total_received(&self) -> u64; /// Returns the number of transmitted bytes since the last refresh. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("out: {} B", network.transmitted()); /// } /// ``` fn transmitted(&self) -> u64; /// Returns the total number of transmitted bytes. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("out: {} B", network.total_transmitted()); /// } /// ``` fn total_transmitted(&self) -> u64; /// Returns the number of incoming packets since the last refresh. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {}", network.packets_received()); /// } /// ``` fn packets_received(&self) -> u64; /// Returns the total number of incoming packets. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {}", network.total_packets_received()); /// } /// ``` fn total_packets_received(&self) -> u64; /// Returns the number of outcoming packets since the last refresh. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("out: {}", network.packets_transmitted()); /// } /// ``` fn packets_transmitted(&self) -> u64; /// Returns the total number of outcoming packets. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("out: {}", network.total_packets_transmitted()); /// } /// ``` fn total_packets_transmitted(&self) -> u64; /// Returns the number of incoming errors since the last refresh. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {}", network.errors_on_received()); /// } /// ``` fn errors_on_received(&self) -> u64; /// Returns the total number of incoming errors. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {}", network.total_errors_on_received()); /// } /// ``` fn total_errors_on_received(&self) -> u64; /// Returns the number of outcoming errors since the last refresh. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("out: {}", network.errors_on_transmitted()); /// } /// ``` fn errors_on_transmitted(&self) -> u64; /// Returns the total number of outcoming errors. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("out: {}", network.total_errors_on_transmitted()); /// } /// ``` fn total_errors_on_transmitted(&self) -> u64; /// Returns the MAC address associated to current interface. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("MAC address: {}", network.mac_address()); /// } /// ``` fn mac_address(&self) -> MacAddr; } /// Interacting with network interfaces. pub trait NetworksExt: Debug { /// Returns an iterator over the network interfaces. /// /// ```no_run /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; /// /// let s = System::new_all(); /// let networks = s.networks(); /// for (interface_name, network) in networks { /// println!("in: {} B", network.received()); /// } /// ``` fn iter(&self) -> NetworksIter; /// Refreshes the network interfaces list. /// /// ```no_run /// use sysinfo::{NetworksExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// let networks = s.networks_mut(); /// networks.refresh_networks_list(); /// ``` fn refresh_networks_list(&mut self); /// Refreshes the network interfaces' content. /// /// ```no_run /// use sysinfo::{NetworksExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// let networks = s.networks_mut(); /// networks.refresh(); /// ``` fn refresh(&mut self); } /// Getting a component temperature information. pub trait ComponentExt: Debug { /// Returns the temperature of the component (in celsius degree). /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let s = System::new_all(); /// for component in s.components() { /// println!("{}°C", component.temperature()); /// } /// ``` /// /// ## Linux /// /// Returns `f32::NAN` if it failed to retrieve it. fn temperature(&self) -> f32; /// Returns the maximum temperature of the component (in celsius degree). /// /// Note: if `temperature` is higher than the current `max`, /// `max` value will be updated on refresh. /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let s = System::new_all(); /// for component in s.components() { /// println!("{}°C", component.max()); /// } /// ``` /// /// ## Linux /// /// May be computed by `sysinfo` from kernel. /// Returns `f32::NAN` if it failed to retrieve it. fn max(&self) -> f32; /// Returns the highest temperature before the component halts (in celsius degree). /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let s = System::new_all(); /// for component in s.components() { /// println!("{:?}°C", component.critical()); /// } /// ``` /// /// ## Linux /// /// Critical threshold defined by chip or kernel. fn critical(&self) -> Option; /// Returns the label of the component. /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let s = System::new_all(); /// for component in s.components() { /// println!("{}", component.label()); /// } /// ``` /// /// ## Linux /// /// Since components information is retrieved thanks to `hwmon`, /// the labels are generated as follows. /// Note: it may change and it was inspired by `sensors` own formatting. /// /// | name | label | device_model | id_sensor | Computed label by `sysinfo` | /// |---------|--------|------------|----------|----------------------| /// | ✓ | ✓ | ✓ | ✓ | `"{name} {label} {device_model} temp{id}"` | /// | ✓ | ✓ | ✗ | ✓ | `"{name} {label} {id}"` | /// | ✓ | ✗ | ✓ | ✓ | `"{name} {device_model}"` | /// | ✓ | ✗ | ✗ | ✓ | `"{name} temp{id}"` | fn label(&self) -> &str; /// Refreshes component. /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// /// let mut s = System::new_all(); /// for component in s.components_mut() { /// component.refresh(); /// } /// ``` fn refresh(&mut self); } /// Getting information for a user. /// /// It is returned from [`SystemExt::users`]. /// /// ```no_run /// use sysinfo::{System, SystemExt, UserExt}; /// /// let mut s = System::new_all(); /// for user in s.users() { /// println!("{} is in {} groups", user.name(), user.groups().len()); /// } /// ``` pub trait UserExt: Debug { /// Return the user id of the user. /// /// ```no_run /// use sysinfo::{System, SystemExt, UserExt}; /// /// let mut s = System::new_all(); /// for user in s.users() { /// println!("{:?}", *user.id()); /// } /// ``` fn id(&self) -> &Uid; /// Return the group id of the user. /// /// *NOTE* - On Windows, this value defaults to 0. Windows doesn't have a `username` specific group assigned to the user. /// They do however have unique [Security Identifiers](https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers) /// made up of various [Components](https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components). /// Pieces of the SID may be a candidate for this field, but it doesn't map well to a single group id. /// /// ```no_run /// use sysinfo::{System, SystemExt, UserExt}; /// /// let mut s = System::new_all(); /// for user in s.users() { /// println!("{}", *user.group_id()); /// } /// ``` fn group_id(&self) -> Gid; /// Returns the name of the user. /// /// ```no_run /// use sysinfo::{System, SystemExt, UserExt}; /// /// let mut s = System::new_all(); /// for user in s.users() { /// println!("{}", user.name()); /// } /// ``` fn name(&self) -> &str; /// Returns the groups of the user. /// /// ```no_run /// use sysinfo::{System, SystemExt, UserExt}; /// /// let mut s = System::new_all(); /// for user in s.users() { /// println!("{} is in {:?}", user.name(), user.groups()); /// } /// ``` fn groups(&self) -> &[String]; } sysinfo-0.28.4/src/unknown/component.rs000064400000000000000000000007101046102023000162450ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::ComponentExt; #[doc = include_str!("../../md_doc/component.md")] pub struct Component {} impl ComponentExt for Component { fn temperature(&self) -> f32 { 0.0 } fn max(&self) -> f32 { 0.0 } fn critical(&self) -> Option { None } fn label(&self) -> &str { "" } fn refresh(&mut self) {} } sysinfo-0.28.4/src/unknown/cpu.rs000064400000000000000000000007711046102023000150410ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::CpuExt; #[doc = include_str!("../../md_doc/cpu.md")] pub struct Cpu {} impl Cpu { pub(crate) fn new() -> Cpu { Cpu {} } } impl CpuExt for Cpu { fn cpu_usage(&self) -> f32 { 0.0 } fn name(&self) -> &str { "" } fn frequency(&self) -> u64 { 0 } fn vendor_id(&self) -> &str { "" } fn brand(&self) -> &str { "" } } sysinfo-0.28.4/src/unknown/disk.rs000064400000000000000000000013061046102023000151770ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskExt, DiskType}; use std::{ffi::OsStr, path::Path}; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk {} impl DiskExt for Disk { fn type_(&self) -> DiskType { unreachable!() } fn name(&self) -> &OsStr { unreachable!() } fn file_system(&self) -> &[u8] { &[] } fn mount_point(&self) -> &Path { Path::new("") } fn total_space(&self) -> u64 { 0 } fn available_space(&self) -> u64 { 0 } fn is_removable(&self) -> bool { false } fn refresh(&mut self) -> bool { true } } sysinfo-0.28.4/src/unknown/mod.rs000064400000000000000000000005661046102023000150330ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. pub mod component; pub mod cpu; pub mod disk; pub mod network; pub mod process; pub mod system; pub use self::component::Component; pub use self::cpu::Cpu; pub use self::disk::Disk; pub use self::network::{NetworkData, Networks}; pub use self::process::Process; pub use self::system::System; sysinfo-0.28.4/src/unknown/network.rs000064400000000000000000000030201046102023000157310ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::collections::HashMap; use crate::common::MacAddr; use crate::{NetworkExt, NetworksExt, NetworksIter}; #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } impl Networks { pub(crate) fn new() -> Networks { Networks { interfaces: HashMap::new(), } } } impl NetworksExt for Networks { fn iter(&self) -> NetworksIter { NetworksIter::new(self.interfaces.iter()) } fn refresh_networks_list(&mut self) {} fn refresh(&mut self) {} } #[doc = include_str!("../../md_doc/network_data.md")] pub struct NetworkData; impl NetworkExt for NetworkData { fn received(&self) -> u64 { 0 } fn total_received(&self) -> u64 { 0 } fn transmitted(&self) -> u64 { 0 } fn total_transmitted(&self) -> u64 { 0 } fn packets_received(&self) -> u64 { 0 } fn total_packets_received(&self) -> u64 { 0 } fn packets_transmitted(&self) -> u64 { 0 } fn total_packets_transmitted(&self) -> u64 { 0 } fn errors_on_received(&self) -> u64 { 0 } fn total_errors_on_received(&self) -> u64 { 0 } fn errors_on_transmitted(&self) -> u64 { 0 } fn total_errors_on_transmitted(&self) -> u64 { 0 } fn mac_address(&self) -> MacAddr { MacAddr::UNSPECIFIED } } sysinfo-0.28.4/src/unknown/process.rs000064400000000000000000000031151046102023000157230ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessStatus, Signal, Uid}; use std::fmt; use std::path::Path; impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Unknown") } } #[doc = include_str!("../../md_doc/process.md")] pub struct Process { pid: Pid, parent: Option, } impl ProcessExt for Process { fn kill_with(&self, _signal: Signal) -> Option { None } fn name(&self) -> &str { "" } fn cmd(&self) -> &[String] { &[] } fn exe(&self) -> &Path { Path::new("") } fn pid(&self) -> Pid { self.pid } fn environ(&self) -> &[String] { &[] } fn cwd(&self) -> &Path { Path::new("") } fn root(&self) -> &Path { Path::new("") } fn memory(&self) -> u64 { 0 } fn virtual_memory(&self) -> u64 { 0 } fn parent(&self) -> Option { self.parent } fn status(&self) -> ProcessStatus { ProcessStatus::Unknown(0) } fn start_time(&self) -> u64 { 0 } fn run_time(&self) -> u64 { 0 } fn cpu_usage(&self) -> f32 { 0.0 } fn disk_usage(&self) -> DiskUsage { DiskUsage::default() } fn user_id(&self) -> Option<&Uid> { None } fn group_id(&self) -> Option { None } fn wait(&self) {} fn session_id(&self) -> Option { None } } sysinfo-0.28.4/src/unknown/system.rs000064400000000000000000000067361046102023000156050ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ sys::{component::Component, Cpu, Disk, Networks, Process}, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, }; use std::collections::HashMap; use std::time::Duration; declare_signals! { (), _ => None, } #[doc = include_str!("../../md_doc/system.md")] pub struct System { processes_list: HashMap, networks: Networks, global_cpu: Cpu, } impl SystemExt for System { const IS_SUPPORTED: bool = false; const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(0); fn new_with_specifics(_: RefreshKind) -> System { System { processes_list: Default::default(), networks: Networks::new(), global_cpu: Cpu::new(), } } fn refresh_memory(&mut self) {} fn refresh_cpu_specifics(&mut self, _refresh_kind: CpuRefreshKind) {} fn refresh_components_list(&mut self) {} fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { false } fn refresh_disks_list(&mut self) {} fn refresh_users_list(&mut self) {} // COMMON PART // // Need to be moved into a "common" file to avoid duplication. fn processes(&self) -> &HashMap { &self.processes_list } fn process(&self, _pid: Pid) -> Option<&Process> { None } fn networks(&self) -> &Networks { &self.networks } fn networks_mut(&mut self) -> &mut Networks { &mut self.networks } fn global_cpu_info(&self) -> &Cpu { &self.global_cpu } fn cpus(&self) -> &[Cpu] { &[] } fn physical_core_count(&self) -> Option { None } fn total_memory(&self) -> u64 { 0 } fn free_memory(&self) -> u64 { 0 } fn available_memory(&self) -> u64 { 0 } fn used_memory(&self) -> u64 { 0 } fn total_swap(&self) -> u64 { 0 } fn free_swap(&self) -> u64 { 0 } fn used_swap(&self) -> u64 { 0 } fn components(&self) -> &[Component] { &[] } fn components_mut(&mut self) -> &mut [Component] { &mut [] } fn disks(&self) -> &[Disk] { &[] } fn disks_mut(&mut self) -> &mut [Disk] { &mut [] } fn sort_disks_by(&mut self, _compare: F) where F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, { // does nothing. } fn uptime(&self) -> u64 { 0 } fn boot_time(&self) -> u64 { 0 } fn load_average(&self) -> LoadAvg { LoadAvg { one: 0., five: 0., fifteen: 0., } } fn users(&self) -> &[User] { &[] } fn name(&self) -> Option { None } fn long_os_version(&self) -> Option { None } fn kernel_version(&self) -> Option { None } fn os_version(&self) -> Option { None } fn distribution_id(&self) -> String { std::env::consts::OS.to_owned() } fn host_name(&self) -> Option { None } } impl Default for System { fn default() -> System { System::new() } } sysinfo-0.28.4/src/users.rs000064400000000000000000000071551046102023000137170ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ common::{Gid, Uid}, User, }; use libc::{getgrgid, getgrouplist}; use std::fs::File; use std::io::Read; pub fn get_users_list() -> Vec { let mut s = String::new(); let mut ngroups = 100; let mut groups = vec![0; ngroups as usize]; let _ = File::open("/etc/passwd").and_then(|mut f| f.read_to_string(&mut s)); s.lines() .filter_map(|line| { let mut parts = line.split(':'); if let Some(username) = parts.next() { let mut parts = parts.skip(1); // Skip the user if the uid cannot be parsed correctly if let Some(uid) = parts.next().and_then(parse_id) { if let Some(group_id) = parts.next().and_then(parse_id) { let mut c_user = username.as_bytes().to_vec(); c_user.push(0); loop { let mut current = ngroups; unsafe { if getgrouplist( c_user.as_ptr() as *const _, group_id, groups.as_mut_ptr(), &mut current, ) == -1 { if current > ngroups { groups.resize(current as _, 0); ngroups = current; continue; } // It really failed, let's move on... return None; } // Let's get all the group names! return Some(User { uid: Uid(uid), gid: Gid(group_id), name: username.to_owned(), groups: groups[..current as usize] .iter() .filter_map(|id| { let g = getgrgid(*id as _); if g.is_null() { return None; } let mut group_name = Vec::new(); let c_group_name = (*g).gr_name; let mut x = 0; loop { let c = *c_group_name.offset(x); if c == 0 { break; } group_name.push(c as u8); x += 1; } String::from_utf8(group_name).ok() }) .collect(), }); } } } } } None }) .collect() } #[inline] fn parse_id(id: &str) -> Option { id.parse::().ok() } sysinfo-0.28.4/src/utils.rs000064400000000000000000000024221046102023000137060ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. /// Converts the value into a parallel iterator (if the multi-thread feature is enabled). /// Uses the `rayon::iter::IntoParallelIterator` trait. #[cfg(all( all( any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "windows", target_os = "freebsd", ), feature = "multithread" ), not(all(target_os = "macos", feature = "apple-sandbox")), not(feature = "unknown-ci") ))] pub(crate) fn into_iter(val: T) -> T::Iter where T: rayon::iter::IntoParallelIterator, { val.into_par_iter() } /// Converts the value into a sequential iterator (if the multithread feature is disabled). /// Uses the `std::iter::IntoIterator` trait. #[cfg(all( all( any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "windows", target_os = "freebsd", ), not(feature = "multithread") ), not(feature = "unknown-ci"), not(all(target_os = "macos", feature = "apple-sandbox")) ))] pub(crate) fn into_iter(val: T) -> T::IntoIter where T: IntoIterator, { val.into_iter() } sysinfo-0.28.4/src/windows/component.rs000064400000000000000000000254421046102023000162510ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::ComponentExt; use std::ptr::null_mut; use winapi::shared::rpcdce::{ RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, RPC_C_IMP_LEVEL_IMPERSONATE, }; use winapi::shared::winerror::{FAILED, SUCCEEDED, S_FALSE, S_OK}; use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER; use winapi::um::combaseapi::{ CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, CoUninitialize, }; use winapi::um::oaidl::VARIANT; use winapi::um::objidl::EOAC_NONE; use winapi::um::oleauto::{SysAllocString, SysFreeString, VariantClear}; use winapi::um::wbemcli::{ CLSID_WbemLocator, IEnumWbemClassObject, IID_IWbemLocator, IWbemClassObject, IWbemLocator, IWbemServices, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, }; #[doc = include_str!("../../md_doc/component.md")] pub struct Component { temperature: f32, max: f32, critical: Option, label: String, connection: Option, } impl Component { /// Creates a new `Component` with the given information. fn new() -> Option { let mut c = Connection::new() .and_then(|x| x.initialize_security()) .and_then(|x| x.create_instance()) .and_then(|x| x.connect_server()) .and_then(|x| x.set_proxy_blanket()) .and_then(|x| x.exec_query())?; c.temperature(true) .map(|(temperature, critical)| Component { temperature, label: "Computer".to_owned(), max: temperature, critical, connection: Some(c), }) } } impl ComponentExt for Component { fn temperature(&self) -> f32 { self.temperature } fn max(&self) -> f32 { self.max } fn critical(&self) -> Option { self.critical } fn label(&self) -> &str { &self.label } fn refresh(&mut self) { if self.connection.is_none() { self.connection = Connection::new() .and_then(|x| x.initialize_security()) .and_then(|x| x.create_instance()) .and_then(|x| x.connect_server()) .and_then(|x| x.set_proxy_blanket()); } self.connection = if let Some(x) = self.connection.take() { x.exec_query() } else { None }; if let Some(ref mut connection) = self.connection { if let Some((temperature, _)) = connection.temperature(false) { self.temperature = temperature; if self.temperature > self.max { self.max = self.temperature; } } } } } pub(crate) fn get_components() -> Vec { match Component::new() { Some(c) => vec![c], None => Vec::new(), } } struct Instance(*mut IWbemLocator); impl Drop for Instance { fn drop(&mut self) { if !self.0.is_null() { unsafe { (*self.0).Release(); } } } } struct ServerConnection(*mut IWbemServices); impl Drop for ServerConnection { fn drop(&mut self) { if !self.0.is_null() { unsafe { (*self.0).Release(); } } } } struct Enumerator(*mut IEnumWbemClassObject); impl Drop for Enumerator { fn drop(&mut self) { if !self.0.is_null() { unsafe { (*self.0).Release(); } } } } macro_rules! bstr { ($($x:expr),*) => {{ let x: &[u16] = &[$($x as u16),*, 0]; SysAllocString(x.as_ptr()) }} } struct Connection { instance: Option, server_connection: Option, enumerator: Option, initialized: bool, } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for Connection {} unsafe impl Sync for Connection {} impl Connection { #[allow(clippy::unnecessary_wraps)] fn new() -> Option { unsafe { let val = CoInitializeEx(null_mut(), 0); Some(Connection { instance: None, server_connection: None, enumerator: None, initialized: val == S_OK || val == S_FALSE, }) } } fn initialize_security(self) -> Option { unsafe { if FAILED(CoInitializeSecurity( null_mut(), -1, null_mut(), null_mut(), RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, null_mut(), EOAC_NONE, null_mut(), )) { None } else { Some(self) } } } fn create_instance(mut self) -> Option { let mut p_loc = null_mut(); unsafe { if FAILED(CoCreateInstance( &CLSID_WbemLocator as *const _, null_mut(), CLSCTX_INPROC_SERVER, &IID_IWbemLocator as *const _, &mut p_loc as *mut _ as *mut _, )) { None } else { self.instance = Some(Instance(p_loc)); Some(self) } } } fn connect_server(mut self) -> Option { let mut p_svc = null_mut(); if let Some(ref instance) = self.instance { unsafe { // "root\WMI" let s = bstr!('r', 'o', 'o', 't', '\\', 'W', 'M', 'I'); let res = (*instance.0).ConnectServer( s, null_mut(), null_mut(), null_mut(), 0, null_mut(), null_mut(), &mut p_svc as *mut _, ); SysFreeString(s); if FAILED(res) { return None; } } } else { return None; } self.server_connection = Some(ServerConnection(p_svc)); Some(self) } fn set_proxy_blanket(self) -> Option { if let Some(ref server_connection) = self.server_connection { unsafe { if FAILED(CoSetProxyBlanket( server_connection.0 as *mut _, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, null_mut(), RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, null_mut(), EOAC_NONE, )) { return None; } } } else { return None; } Some(self) } fn exec_query(mut self) -> Option { let mut p_enumerator = null_mut(); if let Some(ref server_connection) = self.server_connection { unsafe { // "WQL" let s = bstr!('W', 'Q', 'L'); // query kind // "SELECT * FROM MSAcpi_ThermalZoneTemperature" let query = bstr!( 'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'M', 'S', 'A', 'c', 'p', 'i', '_', 'T', 'h', 'e', 'r', 'm', 'a', 'l', 'Z', 'o', 'n', 'e', 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e' ); let hres = (*server_connection.0).ExecQuery( s, query, (WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY) as _, null_mut(), &mut p_enumerator as *mut _, ); SysFreeString(s); SysFreeString(query); if FAILED(hres) { return None; } } } else { return None; } self.enumerator = Some(Enumerator(p_enumerator)); Some(self) } fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option)> { use winapi::um::wbemcli::WBEM_INFINITE; let p_enum = match self.enumerator.take() { Some(x) => x, None => { return None; } }; let mut p_obj: *mut IWbemClassObject = null_mut(); let mut nb_returned = 0; unsafe { (*p_enum.0).Next( WBEM_INFINITE as _, // Time out 1, // One object &mut p_obj as *mut _, &mut nb_returned, ); if nb_returned == 0 { return None; // not enough rights I suppose... } (*p_obj).BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY as _); let mut p_val = std::mem::MaybeUninit::::uninit(); // "CurrentTemperature" let temp = bstr!( 'C', 'u', 'r', 'r', 'e', 'n', 't', 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e' ); let res = (*p_obj).Get(temp, 0, p_val.as_mut_ptr(), null_mut(), null_mut()); let mut p_val = p_val.assume_init(); SysFreeString(temp); VariantClear(&mut p_val as *mut _ as *mut _); let temp = if SUCCEEDED(res) { // temperature is given in tenth of degrees Kelvin (p_val.n1.decVal().Lo64 / 10) as f32 - 273.15 } else { (*p_obj).Release(); return None; }; let mut critical = None; if get_critical { // "CriticalPoint" let crit = bstr!( 'C', 'r', 'i', 't', 'i', 'c', 'a', 'l', 'T', 'r', 'i', 'p', 'P', 'o', 'i', 'n', 't' ); let res = (*p_obj).Get(crit, 0, &mut p_val, null_mut(), null_mut()); SysFreeString(crit); VariantClear(&mut p_val as *mut _ as *mut _); if SUCCEEDED(res) { // temperature is given in tenth of degrees Kelvin critical = Some((p_val.n1.decVal().Lo64 / 10) as f32 - 273.15); } } (*p_obj).Release(); Some((temp, critical)) } } } impl Drop for Connection { fn drop(&mut self) { // Those three calls are here to enforce that they get dropped in the good order. self.enumerator.take(); self.server_connection.take(); self.instance.take(); if self.initialized { unsafe { CoUninitialize(); } } } } sysinfo-0.28.4/src/windows/cpu.rs000064400000000000000000000420611046102023000150320ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::tools::KeyHandler; use crate::{CpuExt, CpuRefreshKind, LoadAvg}; use std::collections::HashMap; use std::io::Error; use std::mem; use std::ops::DerefMut; use std::ptr::null_mut; use std::sync::Mutex; use ntapi::ntpoapi::PROCESSOR_POWER_INFORMATION; use winapi::shared::minwindef::FALSE; use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}; use winapi::um::handleapi::CloseHandle; use winapi::um::pdh::{ PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData, PdhCollectQueryDataEx, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, }; use winapi::um::powerbase::CallNtPowerInformation; use winapi::um::synchapi::CreateEventA; use winapi::um::sysinfoapi::GetLogicalProcessorInformationEx; use winapi::um::sysinfoapi::SYSTEM_INFO; use winapi::um::winbase::{RegisterWaitForSingleObject, INFINITE}; use winapi::um::winnt::{ ProcessorInformation, RelationAll, RelationProcessorCore, BOOLEAN, HANDLE, PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PVOID, WT_EXECUTEDEFAULT, }; // This formula comes from Linux's include/linux/sched/loadavg.h // https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 #[allow(clippy::excessive_precision)] const LOADAVG_FACTOR_1F: f64 = 0.9200444146293232478931553241; #[allow(clippy::excessive_precision)] const LOADAVG_FACTOR_5F: f64 = 0.9834714538216174894737477501; #[allow(clippy::excessive_precision)] const LOADAVG_FACTOR_15F: f64 = 0.9944598480048967508795473394; // The time interval in seconds between taking load counts, same as Linux const SAMPLING_INTERVAL: usize = 5; // maybe use a read/write lock instead? static LOAD_AVG: once_cell::sync::Lazy>> = once_cell::sync::Lazy::new(|| unsafe { init_load_avg() }); pub(crate) fn get_load_average() -> LoadAvg { if let Ok(avg) = LOAD_AVG.lock() { if let Some(avg) = &*avg { return avg.clone(); } } LoadAvg::default() } unsafe extern "system" fn load_avg_callback(counter: PVOID, _: BOOLEAN) { let mut display_value = mem::MaybeUninit::::uninit(); if PdhGetFormattedCounterValue( counter as _, PDH_FMT_DOUBLE, null_mut(), display_value.as_mut_ptr(), ) != ERROR_SUCCESS as _ { return; } let display_value = display_value.assume_init(); if let Ok(mut avg) = LOAD_AVG.lock() { if let Some(avg) = avg.deref_mut() { let current_load = display_value.u.doubleValue(); avg.one = avg.one * LOADAVG_FACTOR_1F + current_load * (1.0 - LOADAVG_FACTOR_1F); avg.five = avg.five * LOADAVG_FACTOR_5F + current_load * (1.0 - LOADAVG_FACTOR_5F); avg.fifteen = avg.fifteen * LOADAVG_FACTOR_15F + current_load * (1.0 - LOADAVG_FACTOR_15F); } } } unsafe fn init_load_avg() -> Mutex> { // You can see the original implementation here: https://github.com/giampaolo/psutil let mut query = null_mut(); if PdhOpenQueryA(null_mut(), 0, &mut query) != ERROR_SUCCESS as _ { sysinfo_debug!("init_load_avg: PdhOpenQueryA failed"); return Mutex::new(None); } let mut counter: PDH_HCOUNTER = mem::zeroed(); if PdhAddEnglishCounterA( query, b"\\System\\Cpu Queue Length\0".as_ptr() as _, 0, &mut counter, ) != ERROR_SUCCESS as _ { PdhCloseQuery(query); sysinfo_debug!("init_load_avg: failed to get CPU queue length"); return Mutex::new(None); } let event = CreateEventA(null_mut(), FALSE, FALSE, b"LoadUpdateEvent\0".as_ptr() as _); if event.is_null() { PdhCloseQuery(query); sysinfo_debug!("init_load_avg: failed to create event `LoadUpdateEvent`"); return Mutex::new(None); } if PdhCollectQueryDataEx(query, SAMPLING_INTERVAL as _, event) != ERROR_SUCCESS as _ { PdhCloseQuery(query); sysinfo_debug!("init_load_avg: PdhCollectQueryDataEx failed"); return Mutex::new(None); } let mut wait_handle = null_mut(); if RegisterWaitForSingleObject( &mut wait_handle, event, Some(load_avg_callback), counter as _, INFINITE, WT_EXECUTEDEFAULT, ) == 0 { PdhRemoveCounter(counter); PdhCloseQuery(query); sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed"); Mutex::new(None) } else { Mutex::new(Some(LoadAvg::default())) } } struct InternalQuery { query: PDH_HQUERY, event: HANDLE, data: HashMap, } unsafe impl Send for InternalQuery {} unsafe impl Sync for InternalQuery {} impl Drop for InternalQuery { fn drop(&mut self) { unsafe { for (_, counter) in self.data.iter() { PdhRemoveCounter(*counter); } if !self.event.is_null() { CloseHandle(self.event); } if !self.query.is_null() { PdhCloseQuery(self.query); } } } } pub(crate) struct Query { internal: InternalQuery, } impl Query { pub fn new() -> Option { let mut query = null_mut(); unsafe { if PdhOpenQueryA(null_mut(), 0, &mut query) == ERROR_SUCCESS as i32 { let q = InternalQuery { query, event: null_mut(), data: HashMap::new(), }; Some(Query { internal: q }) } else { sysinfo_debug!("Query::new: PdhOpenQueryA failed"); None } } } #[allow(clippy::ptr_arg)] pub fn get(&self, name: &String) -> Option { if let Some(counter) = self.internal.data.get(name) { unsafe { let mut display_value = mem::MaybeUninit::::uninit(); let counter: PDH_HCOUNTER = *counter; let ret = PdhGetFormattedCounterValue( counter, PDH_FMT_DOUBLE, null_mut(), display_value.as_mut_ptr(), ) as u32; let display_value = display_value.assume_init(); return if ret == ERROR_SUCCESS as _ { let data = *display_value.u.doubleValue(); Some(data as f32) } else { sysinfo_debug!("Query::get: PdhGetFormattedCounterValue failed"); Some(0.) }; } } None } #[allow(clippy::ptr_arg)] pub fn add_english_counter(&mut self, name: &String, getter: Vec) -> bool { if self.internal.data.contains_key(name) { sysinfo_debug!("Query::add_english_counter: doesn't have key `{:?}`", name); return false; } unsafe { let mut counter: PDH_HCOUNTER = std::mem::zeroed(); let ret = PdhAddEnglishCounterW(self.internal.query, getter.as_ptr(), 0, &mut counter); if ret == ERROR_SUCCESS as _ { self.internal.data.insert(name.clone(), counter); } else { sysinfo_debug!( "Query::add_english_counter: failed to add counter '{}': {:x}...", name, ret, ); return false; } } true } pub fn refresh(&self) { unsafe { if PdhCollectQueryData(self.internal.query) != ERROR_SUCCESS as _ { sysinfo_debug!("failed to refresh CPU data"); } } } } pub(crate) struct CpusWrapper { global: Cpu, cpus: Vec, got_cpu_frequency: bool, } impl CpusWrapper { pub fn new() -> Self { Self { global: Cpu::new_with_values("Total CPU".to_owned(), String::new(), String::new(), 0), cpus: Vec::new(), got_cpu_frequency: false, } } pub fn global_cpu(&self) -> &Cpu { &self.global } pub fn global_cpu_mut(&mut self) -> &mut Cpu { &mut self.global } pub fn cpus(&self) -> &[Cpu] { &self.cpus } fn init_if_needed(&mut self, refresh_kind: CpuRefreshKind) { if self.cpus.is_empty() { let (cpus, vendor_id, brand) = super::tools::init_cpus(refresh_kind); self.cpus = cpus; self.global.vendor_id = vendor_id; self.global.brand = brand; self.got_cpu_frequency = refresh_kind.frequency(); } } pub fn len(&mut self) -> usize { self.init_if_needed(CpuRefreshKind::new()); self.cpus.len() } pub fn iter_mut(&mut self, refresh_kind: CpuRefreshKind) -> impl Iterator { self.init_if_needed(refresh_kind); self.cpus.iter_mut() } pub fn get_frequencies(&mut self) { if self.got_cpu_frequency { return; } let frequencies = get_frequencies(self.cpus.len()); for (cpu, frequency) in self.cpus.iter_mut().zip(frequencies) { cpu.set_frequency(frequency); } self.global .set_frequency(self.cpus.get(0).map(|cpu| cpu.frequency()).unwrap_or(0)); self.got_cpu_frequency = true; } } #[doc = include_str!("../../md_doc/cpu.md")] pub struct Cpu { name: String, cpu_usage: f32, key_used: Option, vendor_id: String, brand: String, frequency: u64, } impl CpuExt for Cpu { fn cpu_usage(&self) -> f32 { self.cpu_usage } fn name(&self) -> &str { &self.name } fn frequency(&self) -> u64 { self.frequency } fn vendor_id(&self) -> &str { &self.vendor_id } fn brand(&self) -> &str { &self.brand } } impl Cpu { pub(crate) fn new_with_values( name: String, vendor_id: String, brand: String, frequency: u64, ) -> Cpu { Cpu { name, cpu_usage: 0f32, key_used: None, vendor_id, brand, frequency, } } pub(crate) fn set_cpu_usage(&mut self, value: f32) { self.cpu_usage = value; } pub(crate) fn set_frequency(&mut self, value: u64) { self.frequency = value; } } fn get_vendor_id_not_great(info: &SYSTEM_INFO) -> String { use winapi::um::winnt; // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info unsafe { match info.u.s().wProcessorArchitecture { winnt::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86", winnt::PROCESSOR_ARCHITECTURE_MIPS => "MIPS", winnt::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha", winnt::PROCESSOR_ARCHITECTURE_PPC => "PPC", winnt::PROCESSOR_ARCHITECTURE_SHX => "SHX", winnt::PROCESSOR_ARCHITECTURE_ARM => "ARM", winnt::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64", winnt::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64", winnt::PROCESSOR_ARCHITECTURE_MSIL => "MSIL", winnt::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64", winnt::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86", winnt::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown", winnt::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64", winnt::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM", winnt::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 => "Intel Itanium-based x86", _ => "unknown", } .to_owned() } } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { #[cfg(target_arch = "x86")] use std::arch::x86::__cpuid; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::__cpuid; unsafe fn add_u32(v: &mut Vec, i: u32) { let i = &i as *const u32 as *const u8; v.push(*i); v.push(*i.offset(1)); v.push(*i.offset(2)); v.push(*i.offset(3)); } unsafe { // First, we try to get the complete name. let res = __cpuid(0x80000000); let n_ex_ids = res.eax; let brand = if n_ex_ids >= 0x80000004 { let mut extdata = Vec::with_capacity(5); for i in 0x80000000..=n_ex_ids { extdata.push(__cpuid(i)); } // 4 * u32 * nb_entries let mut out = Vec::with_capacity(4 * std::mem::size_of::() * 3); for data in extdata.iter().take(5).skip(2) { add_u32(&mut out, data.eax); add_u32(&mut out, data.ebx); add_u32(&mut out, data.ecx); add_u32(&mut out, data.edx); } let mut pos = 0; for e in out.iter() { if *e == 0 { break; } pos += 1; } match std::str::from_utf8(&out[..pos]) { Ok(s) => s.to_owned(), _ => String::new(), } } else { String::new() }; // Failed to get full name, let's retry for the short version! let res = __cpuid(0); let mut x = Vec::with_capacity(3 * std::mem::size_of::()); add_u32(&mut x, res.ebx); add_u32(&mut x, res.edx); add_u32(&mut x, res.ecx); let mut pos = 0; for e in x.iter() { if *e == 0 { break; } pos += 1; } let vendor_id = match std::str::from_utf8(&x[..pos]) { Ok(s) => s.to_owned(), Err(_) => get_vendor_id_not_great(info), }; (vendor_id, brand) } } #[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { (get_vendor_id_not_great(info), String::new()) } pub(crate) fn get_key_used(p: &mut Cpu) -> &mut Option { &mut p.key_used } // From https://stackoverflow.com/a/43813138: // // If your PC has 64 or fewer logical cpus installed, the above code will work fine. However, // if your PC has more than 64 logical cpus installed, use GetActiveCpuCount() or // GetLogicalCpuInformation() to determine the total number of logical cpus installed. pub(crate) fn get_frequencies(nb_cpus: usize) -> Vec { let size = nb_cpus * mem::size_of::(); let mut infos: Vec = Vec::with_capacity(nb_cpus); unsafe { if CallNtPowerInformation( ProcessorInformation, null_mut(), 0, infos.as_mut_ptr() as _, size as _, ) == 0 { infos.set_len(nb_cpus); // infos.Number return infos .into_iter() .map(|i| i.CurrentMhz as u64) .collect::>(); } } sysinfo_debug!("get_frequencies: CallNtPowerInformation failed"); vec![0; nb_cpus] } pub(crate) fn get_physical_core_count() -> Option { // we cannot use the number of cpus here to pre calculate the buf size // GetLogicalCpuInformationEx with RelationProcessorCore passed to it not only returns // the logical cores but also numa nodes // // GetLogicalCpuInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex let mut needed_size = 0; unsafe { GetLogicalProcessorInformationEx(RelationAll, null_mut(), &mut needed_size); let mut buf: Vec = Vec::with_capacity(needed_size as _); loop { if GetLogicalProcessorInformationEx( RelationAll, buf.as_mut_ptr() as *mut _, &mut needed_size, ) == FALSE { let e = Error::last_os_error(); // For some reasons, the function might return a size not big enough... match e.raw_os_error() { Some(value) if value == ERROR_INSUFFICIENT_BUFFER as _ => {} _ => { sysinfo_debug!( "get_physical_core_count: GetLogicalCpuInformationEx failed" ); return None; } } } else { break; } buf.reserve(needed_size as usize - buf.capacity()); } buf.set_len(needed_size as _); let mut i = 0; let raw_buf = buf.as_ptr(); let mut count = 0; while i < buf.len() { let p = &*(raw_buf.add(i) as PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX); i += p.Size as usize; if p.Relationship == RelationProcessorCore { // Only count the physical cores. count += 1; } } Some(count) } } sysinfo-0.28.4/src/windows/disk.rs000064400000000000000000000164431046102023000152020ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{DiskExt, DiskType}; use std::ffi::{OsStr, OsString}; use std::mem::size_of; use std::path::Path; use winapi::ctypes::c_void; use winapi::shared::minwindef::{DWORD, MAX_PATH}; use winapi::um::fileapi::{ CreateFileW, GetDiskFreeSpaceExW, GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, OPEN_EXISTING, }; use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; use winapi::um::ioapiset::DeviceIoControl; use winapi::um::winbase::{DRIVE_FIXED, DRIVE_REMOVABLE}; use winapi::um::winioctl::{ PropertyStandardQuery, StorageDeviceSeekPenaltyProperty, IOCTL_STORAGE_QUERY_PROPERTY, STORAGE_PROPERTY_QUERY, }; use winapi::um::winnt::{BOOLEAN, FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE, ULARGE_INTEGER}; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { type_: DiskType, name: OsString, file_system: Vec, mount_point: Vec, s_mount_point: String, total_space: u64, available_space: u64, is_removable: bool, } impl DiskExt for Disk { fn type_(&self) -> DiskType { self.type_ } fn name(&self) -> &OsStr { &self.name } fn file_system(&self) -> &[u8] { &self.file_system } fn mount_point(&self) -> &Path { Path::new(&self.s_mount_point) } fn total_space(&self) -> u64 { self.total_space } fn available_space(&self) -> u64 { self.available_space } fn is_removable(&self) -> bool { self.is_removable } fn refresh(&mut self) -> bool { if self.total_space != 0 { unsafe { let mut tmp: ULARGE_INTEGER = std::mem::zeroed(); if GetDiskFreeSpaceExW( self.mount_point.as_ptr(), std::ptr::null_mut(), std::ptr::null_mut(), &mut tmp, ) != 0 { self.available_space = *tmp.QuadPart(); return true; } } } false } } struct HandleWrapper(HANDLE); impl HandleWrapper { unsafe fn new(drive_name: &[u16], open_rights: DWORD) -> Option { let handle = CreateFileW( drive_name.as_ptr(), open_rights, FILE_SHARE_READ | FILE_SHARE_WRITE, std::ptr::null_mut(), OPEN_EXISTING, 0, std::ptr::null_mut(), ); if handle == INVALID_HANDLE_VALUE { CloseHandle(handle); None } else { Some(Self(handle)) } } } impl Drop for HandleWrapper { fn drop(&mut self) { unsafe { CloseHandle(self.0); } } } unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { let mut total_size: ULARGE_INTEGER = std::mem::zeroed(); let mut available_space: ULARGE_INTEGER = std::mem::zeroed(); if GetDiskFreeSpaceExW( mount_point.as_ptr(), std::ptr::null_mut(), &mut total_size, &mut available_space, ) != 0 { Some(( *total_size.QuadPart() as _, *available_space.QuadPart() as _, )) } else { None } } // FIXME: To be removed once has been merged. #[allow(non_snake_case)] #[repr(C)] struct DEVICE_SEEK_PENALTY_DESCRIPTOR { Version: DWORD, Size: DWORD, IncursSeekPenalty: BOOLEAN, } pub(crate) unsafe fn get_disks() -> Vec { let drives = GetLogicalDrives(); if drives == 0 { return Vec::new(); } #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; crate::utils::into_iter(0..DWORD::BITS) .filter_map(|x| { if (drives >> x) & 1 == 0 { return None; } let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0]; let drive_type = GetDriveTypeW(mount_point.as_ptr()); let is_removable = drive_type == DRIVE_REMOVABLE; if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { return None; } let mut name = [0u16; MAX_PATH + 1]; let mut file_system = [0u16; 32]; if GetVolumeInformationW( mount_point.as_ptr(), name.as_mut_ptr(), name.len() as DWORD, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), file_system.as_mut_ptr(), file_system.len() as DWORD, ) == 0 { return None; } let mut pos = 0; for x in name.iter() { if *x == 0 { break; } pos += 1; } let name = String::from_utf16_lossy(&name[..pos]); let name = OsStr::new(&name); pos = 0; for x in file_system.iter() { if *x == 0 { break; } pos += 1; } let file_system: Vec = file_system[..pos].iter().map(|x| *x as u8).collect(); let drive_name = [ b'\\' as u16, b'\\' as u16, b'.' as u16, b'\\' as u16, b'A' as u16 + x as u16, b':' as u16, 0, ]; let handle = HandleWrapper::new(&drive_name, 0)?; let (total_space, available_space) = get_drive_size(&mount_point)?; if total_space == 0 { return None; } let mut spq_trim = STORAGE_PROPERTY_QUERY { PropertyId: StorageDeviceSeekPenaltyProperty, QueryType: PropertyStandardQuery, AdditionalParameters: [0], }; let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = std::mem::zeroed(); let mut dw_size = 0; let type_ = if DeviceIoControl( handle.0, IOCTL_STORAGE_QUERY_PROPERTY, &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void, size_of::() as DWORD, &mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut c_void, size_of::() as DWORD, &mut dw_size, std::ptr::null_mut(), ) == 0 || dw_size != size_of::() as DWORD { DiskType::Unknown(-1) } else { let is_ssd = result.IncursSeekPenalty == 0; if is_ssd { DiskType::SSD } else { DiskType::HDD } }; Some(Disk { type_, name: name.to_owned(), file_system: file_system.to_vec(), mount_point: mount_point.to_vec(), s_mount_point: String::from_utf16_lossy(&mount_point[..mount_point.len() - 1]), total_space, available_space, is_removable, }) }) .collect::>() } sysinfo-0.28.4/src/windows/mod.rs000064400000000000000000000006401046102023000150170ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod component; mod cpu; mod disk; mod network; mod process; mod sid; mod system; mod tools; mod users; mod utils; pub use self::component::Component; pub use self::cpu::Cpu; pub use self::disk::Disk; pub use self::network::{NetworkData, Networks}; pub use self::process::Process; pub use self::sid::Sid; pub use self::system::System; sysinfo-0.28.4/src/windows/network.rs000064400000000000000000000216021046102023000157320ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::common::MacAddr; use crate::network::refresh_networks_addresses; use crate::{NetworkExt, NetworksExt, NetworksIter}; use std::collections::{hash_map, HashMap}; use winapi::shared::ifdef::{MediaConnectStateDisconnected, NET_LUID}; use winapi::shared::netioapi::{ FreeMibTable, GetIfEntry2, GetIfTable2, MIB_IF_ROW2, PMIB_IF_TABLE2, }; use winapi::shared::winerror::NO_ERROR; macro_rules! old_and_new { ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ $ty_.$old = $ty_.$name; $ty_.$name = $new_val; }}; } #[doc = include_str!("../../md_doc/networks.md")] pub struct Networks { interfaces: HashMap, } impl Networks { pub(crate) fn new() -> Networks { Networks { interfaces: HashMap::new(), } } } impl NetworksExt for Networks { #[allow(clippy::needless_lifetimes)] fn iter<'a>(&'a self) -> NetworksIter<'a> { NetworksIter::new(self.interfaces.iter()) } fn refresh_networks_list(&mut self) { let mut table: PMIB_IF_TABLE2 = std::ptr::null_mut(); unsafe { if GetIfTable2(&mut table) != NO_ERROR { return; } for (_, data) in self.interfaces.iter_mut() { data.updated = false; } // In here, this is tricky: we have to filter out the software interfaces to only keep // the hardware ones. To do so, we first check the connection potential speed (if 0, not // interesting), then we check its state: if not open, not interesting either. And finally, // we count the members of a same group: if there is more than 1, then it's software level. let mut groups = HashMap::new(); let mut indexes = Vec::new(); let ptr = (*table).Table.as_ptr(); for i in 0..(*table).NumEntries { let ptr = &*ptr.offset(i as _); if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0) || ptr.MediaConnectState == MediaConnectStateDisconnected || ptr.PhysicalAddressLength == 0 { continue; } let id = vec![ ptr.InterfaceGuid.Data2, ptr.InterfaceGuid.Data3, ptr.InterfaceGuid.Data4[0] as _, ptr.InterfaceGuid.Data4[1] as _, ptr.InterfaceGuid.Data4[2] as _, ptr.InterfaceGuid.Data4[3] as _, ptr.InterfaceGuid.Data4[4] as _, ptr.InterfaceGuid.Data4[5] as _, ptr.InterfaceGuid.Data4[6] as _, ptr.InterfaceGuid.Data4[7] as _, ]; let entry = groups.entry(id.clone()).or_insert(0); *entry += 1; if *entry > 1 { continue; } indexes.push((i, id)); } for (i, id) in indexes { let ptr = &*ptr.offset(i as _); if *groups.get(&id).unwrap_or(&0) > 1 { continue; } let mut pos = 0; for x in ptr.Alias.iter() { if *x == 0 { break; } pos += 1; } let interface_name = match String::from_utf16(&ptr.Alias[..pos]) { Ok(s) => s, _ => continue, }; match self.interfaces.entry(interface_name) { hash_map::Entry::Occupied(mut e) => { let mut interface = e.get_mut(); old_and_new!(interface, current_out, old_out, ptr.OutOctets); old_and_new!(interface, current_in, old_in, ptr.InOctets); old_and_new!( interface, packets_in, old_packets_in, ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts) ); old_and_new!( interface, packets_out, old_packets_out, ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts) ); old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors); old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors); interface.updated = true; } hash_map::Entry::Vacant(e) => { let packets_in = ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts); let packets_out = ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts); e.insert(NetworkData { id: ptr.InterfaceLuid, current_out: ptr.OutOctets, old_out: ptr.OutOctets, current_in: ptr.InOctets, old_in: ptr.InOctets, packets_in, old_packets_in: packets_in, packets_out, old_packets_out: packets_out, errors_in: ptr.InErrors, old_errors_in: ptr.InErrors, errors_out: ptr.OutErrors, old_errors_out: ptr.OutErrors, mac_addr: MacAddr::UNSPECIFIED, updated: true, }); } } } FreeMibTable(table as _); } // Remove interfaces which are gone. self.interfaces.retain(|_, d| d.updated); // Refresh all interfaces' addresses. refresh_networks_addresses(&mut self.interfaces); } fn refresh(&mut self) { let entry = std::mem::MaybeUninit::::zeroed(); unsafe { let mut entry = entry.assume_init(); for (_, interface) in self.interfaces.iter_mut() { entry.InterfaceLuid = interface.id; entry.InterfaceIndex = 0; // to prevent the function to pick this one as index if GetIfEntry2(&mut entry) != NO_ERROR { continue; } old_and_new!(interface, current_out, old_out, entry.OutOctets); old_and_new!(interface, current_in, old_in, entry.InOctets); old_and_new!( interface, packets_in, old_packets_in, entry.InUcastPkts.saturating_add(entry.InNUcastPkts) ); old_and_new!( interface, packets_out, old_packets_out, entry.OutUcastPkts.saturating_add(entry.OutNUcastPkts) ); old_and_new!(interface, errors_in, old_errors_in, entry.InErrors); old_and_new!(interface, errors_out, old_errors_out, entry.OutErrors); } } } } #[doc = include_str!("../../md_doc/network_data.md")] pub struct NetworkData { id: NET_LUID, current_out: u64, old_out: u64, current_in: u64, old_in: u64, packets_in: u64, old_packets_in: u64, packets_out: u64, old_packets_out: u64, errors_in: u64, old_errors_in: u64, errors_out: u64, old_errors_out: u64, updated: bool, pub(crate) mac_addr: MacAddr, } impl NetworkExt for NetworkData { fn received(&self) -> u64 { self.current_in.saturating_sub(self.old_in) } fn total_received(&self) -> u64 { self.current_in } fn transmitted(&self) -> u64 { self.current_out.saturating_sub(self.old_out) } fn total_transmitted(&self) -> u64 { self.current_out } fn packets_received(&self) -> u64 { self.packets_in.saturating_sub(self.old_packets_in) } fn total_packets_received(&self) -> u64 { self.packets_in } fn packets_transmitted(&self) -> u64 { self.packets_out.saturating_sub(self.old_packets_out) } fn total_packets_transmitted(&self) -> u64 { self.packets_out } fn errors_on_received(&self) -> u64 { self.errors_in.saturating_sub(self.old_errors_in) } fn total_errors_on_received(&self) -> u64 { self.errors_in } fn errors_on_transmitted(&self) -> u64 { self.errors_out.saturating_sub(self.old_errors_out) } fn total_errors_on_transmitted(&self) -> u64 { self.errors_out } fn mac_address(&self) -> MacAddr { self.mac_addr } } sysinfo-0.28.4/src/windows/process.rs000064400000000000000000001004261046102023000157210ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::system::is_proc_running; use crate::windows::Sid; use crate::{ DiskUsage, Gid, Pid, PidExt, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid, }; use std::ffi::OsString; use std::fmt; use std::mem::{size_of, zeroed, MaybeUninit}; use std::ops::Deref; use std::os::windows::ffi::OsStringExt; use std::os::windows::process::CommandExt; use std::path::{Path, PathBuf}; use std::process; use std::ptr::null_mut; use std::str; use std::sync::Arc; use libc::{c_void, memcpy}; use ntapi::ntpebteb::PEB; use ntapi::ntwow64::{PEB32, PRTL_USER_PROCESS_PARAMETERS32, RTL_USER_PROCESS_PARAMETERS32}; use once_cell::sync::Lazy; use ntapi::ntpsapi::{ NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation, ProcessWow64Information, PROCESSINFOCLASS, PROCESS_BASIC_INFORMATION, }; use ntapi::ntrtl::{RtlGetVersion, PRTL_USER_PROCESS_PARAMETERS, RTL_USER_PROCESS_PARAMETERS}; use winapi::shared::basetsd::SIZE_T; use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, TRUE, ULONG}; use winapi::shared::ntdef::{NT_SUCCESS, UNICODE_STRING}; use winapi::shared::ntstatus::{ STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, }; use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER; use winapi::um::errhandlingapi::GetLastError; use winapi::um::handleapi::CloseHandle; use winapi::um::heapapi::{GetProcessHeap, HeapAlloc, HeapFree}; use winapi::um::memoryapi::{ReadProcessMemory, VirtualQueryEx}; use winapi::um::processthreadsapi::{ GetProcessTimes, GetSystemTimes, OpenProcess, OpenProcessToken, ProcessIdToSessionId, }; use winapi::um::psapi::{ EnumProcessModulesEx, GetModuleBaseNameW, GetModuleFileNameExW, GetProcessMemoryInfo, LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, }; use winapi::um::securitybaseapi::GetTokenInformation; use winapi::um::winbase::{GetProcessIoCounters, CREATE_NO_WINDOW}; use winapi::um::winnt::{ TokenUser, HANDLE, HEAP_ZERO_MEMORY, IO_COUNTERS, MEMORY_BASIC_INFORMATION, PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ, RTL_OSVERSIONINFOEXW, TOKEN_QUERY, TOKEN_USER, ULARGE_INTEGER, }; impl fmt::Display for ProcessStatus { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { ProcessStatus::Run => "Runnable", _ => "Unknown", }) } } fn get_process_handler(pid: Pid) -> Option { if pid.0 == 0 { return None; } let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; HandleWrapper::new(unsafe { OpenProcess(options, FALSE, pid.0 as DWORD) }) .or_else(|| { sysinfo_debug!("OpenProcess failed, error: {:?}", unsafe { GetLastError() }); HandleWrapper::new(unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid.0 as DWORD) }) }) .or_else(|| { sysinfo_debug!("OpenProcess limited failed, error: {:?}", unsafe { GetLastError() }); None }) } unsafe fn get_process_user_id( handle: &HandleWrapper, refresh_kind: ProcessRefreshKind, ) -> Option { struct HeapWrap(*mut T); impl HeapWrap { unsafe fn new(size: DWORD) -> Option { let ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size as _) as *mut T; if ptr.is_null() { sysinfo_debug!("HeapAlloc failed"); None } else { Some(Self(ptr)) } } } impl Drop for HeapWrap { fn drop(&mut self) { if !self.0.is_null() { unsafe { HeapFree(GetProcessHeap(), 0, self.0 as *mut _); } } } } if !refresh_kind.user() { return None; } let mut token = null_mut(); if OpenProcessToken(**handle, TOKEN_QUERY, &mut token) == 0 { sysinfo_debug!("OpenProcessToken failed"); return None; } let token = HandleWrapper::new(token)?; let mut size = 0; if GetTokenInformation(*token, TokenUser, null_mut(), 0, &mut size) == 0 { let err = GetLastError(); if err != ERROR_INSUFFICIENT_BUFFER { sysinfo_debug!("GetTokenInformation failed, error: {:?}", err); return None; } } let ptu: HeapWrap = HeapWrap::new(size)?; if GetTokenInformation(*token, TokenUser, ptu.0 as *mut _, size, &mut size) == 0 { sysinfo_debug!("GetTokenInformation failed, error: {:?}", GetLastError()); return None; } Sid::from_psid((*ptu.0).User.Sid).map(Uid) } struct HandleWrapper(HANDLE); impl HandleWrapper { fn new(handle: HANDLE) -> Option { if handle.is_null() { None } else { Some(Self(handle)) } } } impl Deref for HandleWrapper { type Target = HANDLE; fn deref(&self) -> &Self::Target { &self.0 } } impl Drop for HandleWrapper { fn drop(&mut self) { unsafe { CloseHandle(self.0); } } } #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for HandleWrapper {} unsafe impl Sync for HandleWrapper {} #[doc = include_str!("../../md_doc/process.md")] pub struct Process { name: String, cmd: Vec, exe: PathBuf, pid: Pid, user_id: Option, environ: Vec, cwd: PathBuf, root: PathBuf, pub(crate) memory: u64, pub(crate) virtual_memory: u64, parent: Option, status: ProcessStatus, handle: Option>, cpu_calc_values: CPUsageCalculationValues, start_time: u64, pub(crate) run_time: u64, cpu_usage: f32, pub(crate) updated: bool, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, written_bytes: u64, } struct CPUsageCalculationValues { old_process_sys_cpu: u64, old_process_user_cpu: u64, old_system_sys_cpu: u64, old_system_user_cpu: u64, } impl CPUsageCalculationValues { fn new() -> Self { CPUsageCalculationValues { old_process_sys_cpu: 0, old_process_user_cpu: 0, old_system_sys_cpu: 0, old_system_user_cpu: 0, } } } static WINDOWS_8_1_OR_NEWER: Lazy = Lazy::new(|| unsafe { let mut version_info: RTL_OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init(); version_info.dwOSVersionInfoSize = std::mem::size_of::() as u32; if !NT_SUCCESS(RtlGetVersion( &mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _, )) { return true; } // Windows 8.1 is 6.3 version_info.dwMajorVersion > 6 || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3 }); unsafe fn get_process_name(process_handler: &HandleWrapper, h_mod: *mut c_void) -> String { let mut process_name = [0u16; MAX_PATH + 1]; GetModuleBaseNameW( **process_handler, h_mod as _, process_name.as_mut_ptr(), MAX_PATH as DWORD + 1, ); null_terminated_wchar_to_string(&process_name) } unsafe fn get_h_mod(process_handler: &HandleWrapper, h_mod: &mut *mut c_void) -> bool { let mut cb_needed = 0; EnumProcessModulesEx( **process_handler, h_mod as *mut *mut c_void as _, size_of::() as DWORD, &mut cb_needed, LIST_MODULES_ALL, ) != 0 } unsafe fn get_exe(process_handler: &HandleWrapper) -> PathBuf { let mut exe_buf = [0u16; MAX_PATH + 1]; GetModuleFileNameExW( **process_handler, std::ptr::null_mut(), exe_buf.as_mut_ptr(), MAX_PATH as DWORD + 1, ); PathBuf::from(null_terminated_wchar_to_string(&exe_buf)) } impl Process { pub(crate) fn new_from_pid( pid: Pid, now: u64, refresh_kind: ProcessRefreshKind, ) -> Option { unsafe { let process_handler = get_process_handler(pid)?; let mut info: MaybeUninit = MaybeUninit::uninit(); if NtQueryInformationProcess( *process_handler, ProcessBasicInformation, info.as_mut_ptr() as *mut _, size_of::() as _, null_mut(), ) != 0 { return None; } let info = info.assume_init(); let mut h_mod = null_mut(); let name = if get_h_mod(&process_handler, &mut h_mod) { get_process_name(&process_handler, h_mod) } else { String::new() }; let exe = get_exe(&process_handler); let mut root = exe.clone(); root.pop(); let (cmd, environ, cwd) = match get_process_params(&process_handler) { Ok(args) => args, Err(_e) => { sysinfo_debug!("Failed to get process parameters: {}", _e); (Vec::new(), Vec::new(), PathBuf::new()) } }; let (start_time, run_time) = get_start_and_run_time(*process_handler, now); let parent = if info.InheritedFromUniqueProcessId as usize != 0 { Some(Pid(info.InheritedFromUniqueProcessId as _)) } else { None }; let user_id = get_process_user_id(&process_handler, refresh_kind); Some(Process { handle: Some(Arc::new(process_handler)), name, pid, parent, user_id, cmd, environ, exe, cwd, root, status: ProcessStatus::Run, memory: 0, virtual_memory: 0, cpu_usage: 0., cpu_calc_values: CPUsageCalculationValues::new(), start_time, run_time, updated: true, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, }) } } pub(crate) fn new_full( pid: Pid, parent: Option, memory: u64, virtual_memory: u64, name: String, now: u64, refresh_kind: ProcessRefreshKind, ) -> Process { if let Some(handle) = get_process_handler(pid) { unsafe { let exe = get_exe(&handle); let mut root = exe.clone(); root.pop(); let (cmd, environ, cwd) = match get_process_params(&handle) { Ok(args) => args, Err(_e) => { sysinfo_debug!("Failed to get process parameters: {}", _e); (Vec::new(), Vec::new(), PathBuf::new()) } }; let (start_time, run_time) = get_start_and_run_time(*handle, now); let user_id = get_process_user_id(&handle, refresh_kind); Process { handle: Some(Arc::new(handle)), name, pid, user_id, parent, cmd, environ, exe, cwd, root, status: ProcessStatus::Run, memory, virtual_memory, cpu_usage: 0., cpu_calc_values: CPUsageCalculationValues::new(), start_time, run_time, updated: true, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, } } } else { Process { handle: None, name, pid, user_id: None, parent, cmd: Vec::new(), environ: Vec::new(), exe: get_executable_path(pid), cwd: PathBuf::new(), root: PathBuf::new(), status: ProcessStatus::Run, memory, virtual_memory, cpu_usage: 0., cpu_calc_values: CPUsageCalculationValues::new(), start_time: 0, run_time: 0, updated: true, old_read_bytes: 0, old_written_bytes: 0, read_bytes: 0, written_bytes: 0, } } } pub(crate) fn update( &mut self, refresh_kind: crate::ProcessRefreshKind, nb_cpus: u64, now: u64, ) { if refresh_kind.cpu() { compute_cpu_usage(self, nb_cpus); } if refresh_kind.disk_usage() { update_disk_usage(self); } self.run_time = now.saturating_sub(self.start_time()); self.updated = true; } pub(crate) fn get_handle(&self) -> Option { self.handle.as_ref().map(|h| ***h) } pub(crate) fn get_start_time(&self) -> Option { self.handle.as_ref().map(|handle| get_start_time(***handle)) } } impl ProcessExt for Process { fn kill_with(&self, signal: Signal) -> Option { super::system::convert_signal(signal)?; let mut kill = process::Command::new("taskkill.exe"); kill.arg("/PID").arg(self.pid.to_string()).arg("/F"); kill.creation_flags(CREATE_NO_WINDOW); match kill.output() { Ok(o) => Some(o.status.success()), Err(_) => Some(false), } } fn name(&self) -> &str { &self.name } fn cmd(&self) -> &[String] { &self.cmd } fn exe(&self) -> &Path { self.exe.as_path() } fn pid(&self) -> Pid { self.pid } fn environ(&self) -> &[String] { &self.environ } fn cwd(&self) -> &Path { self.cwd.as_path() } fn root(&self) -> &Path { self.root.as_path() } fn memory(&self) -> u64 { self.memory } fn virtual_memory(&self) -> u64 { self.virtual_memory } fn parent(&self) -> Option { self.parent } fn status(&self) -> ProcessStatus { self.status } fn start_time(&self) -> u64 { self.start_time } fn run_time(&self) -> u64 { self.run_time } fn cpu_usage(&self) -> f32 { self.cpu_usage } fn disk_usage(&self) -> DiskUsage { DiskUsage { written_bytes: self.written_bytes - self.old_written_bytes, total_written_bytes: self.written_bytes, read_bytes: self.read_bytes - self.old_read_bytes, total_read_bytes: self.read_bytes, } } fn user_id(&self) -> Option<&Uid> { self.user_id.as_ref() } fn group_id(&self) -> Option { None } fn wait(&self) { if let Some(handle) = self.get_handle() { while is_proc_running(handle) { if get_start_time(handle) != self.start_time() { // PID owner changed so the previous process was finished! return; } std::thread::sleep(std::time::Duration::from_millis(10)); } } else { // In this case, we can't do anything so we just return. sysinfo_debug!("can't wait on this process so returning"); } } fn session_id(&self) -> Option { unsafe { let mut out = 0; if ProcessIdToSessionId(self.pid.as_u32(), &mut out) != 0 { return Some(Pid(out as _)); } sysinfo_debug!("ProcessIdToSessionId failed, error: {:?}", GetLastError()); None } } } #[inline] unsafe fn get_process_times(handle: HANDLE) -> u64 { let mut fstart: FILETIME = zeroed(); let mut x = zeroed(); GetProcessTimes( handle, &mut fstart as *mut FILETIME, &mut x as *mut FILETIME, &mut x as *mut FILETIME, &mut x as *mut FILETIME, ); super::utils::filetime_to_u64(fstart) } #[inline] fn compute_start(process_times: u64) -> u64 { // 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and // the Linux epoch (1970-01-01). process_times / 10_000_000 - 11_644_473_600 } fn get_start_and_run_time(handle: HANDLE, now: u64) -> (u64, u64) { unsafe { let process_times = get_process_times(handle); let start = compute_start(process_times); let run_time = check_sub(now, start); (start, run_time) } } #[inline] pub(crate) fn get_start_time(handle: HANDLE) -> u64 { unsafe { let process_times = get_process_times(handle); compute_start(process_times) } } unsafe fn ph_query_process_variable_size( process_handle: &HandleWrapper, process_information_class: PROCESSINFOCLASS, ) -> Option> { let mut return_length = MaybeUninit::::uninit(); let mut status = NtQueryInformationProcess( **process_handle, process_information_class, null_mut(), 0, return_length.as_mut_ptr() as *mut _, ); if status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL && status != STATUS_INFO_LENGTH_MISMATCH { return None; } let mut return_length = return_length.assume_init(); let buf_len = (return_length as usize) / 2; let mut buffer: Vec = Vec::with_capacity(buf_len + 1); status = NtQueryInformationProcess( **process_handle, process_information_class, buffer.as_mut_ptr() as *mut _, return_length, &mut return_length as *mut _, ); if !NT_SUCCESS(status) { return None; } buffer.set_len(buf_len); buffer.push(0); Some(buffer) } unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec { // Get argc and argv from the command line let mut argc = MaybeUninit::::uninit(); let argv_p = winapi::um::shellapi::CommandLineToArgvW(buffer, argc.as_mut_ptr()); if argv_p.is_null() { return Vec::new(); } let argc = argc.assume_init(); let argv = std::slice::from_raw_parts(argv_p, argc as usize); let mut res = Vec::new(); for arg in argv { let len = libc::wcslen(*arg); let str_slice = std::slice::from_raw_parts(*arg, len); res.push(String::from_utf16_lossy(str_slice)); } winapi::um::winbase::LocalFree(argv_p as *mut _); res } unsafe fn get_region_size(handle: &HandleWrapper, ptr: LPVOID) -> Result { let mut meminfo = MaybeUninit::::uninit(); if VirtualQueryEx( **handle, ptr, meminfo.as_mut_ptr() as *mut _, size_of::(), ) == 0 { return Err("Unable to read process memory information"); } let meminfo = meminfo.assume_init(); Ok((meminfo.RegionSize as isize - ptr.offset_from(meminfo.BaseAddress)) as usize) } unsafe fn get_process_data( handle: &HandleWrapper, ptr: LPVOID, size: usize, ) -> Result, &'static str> { let mut buffer: Vec = Vec::with_capacity(size / 2 + 1); let mut bytes_read = 0; if ReadProcessMemory( **handle, ptr as *mut _, buffer.as_mut_ptr() as *mut _, size, &mut bytes_read, ) == FALSE { return Err("Unable to read process data"); } // Documentation states that the function fails if not all data is accessible. if bytes_read != size { return Err("ReadProcessMemory returned unexpected number of bytes read"); } buffer.set_len(size / 2); buffer.push(0); Ok(buffer) } trait RtlUserProcessParameters { fn get_cmdline(&self, handle: &HandleWrapper) -> Result, &'static str>; fn get_cwd(&self, handle: &HandleWrapper) -> Result, &'static str>; fn get_environ(&self, handle: &HandleWrapper) -> Result, &'static str>; } macro_rules! impl_RtlUserProcessParameters { ($t:ty) => { impl RtlUserProcessParameters for $t { fn get_cmdline(&self, handle: &HandleWrapper) -> Result, &'static str> { let ptr = self.CommandLine.Buffer; let size = self.CommandLine.Length; unsafe { get_process_data(handle, ptr as _, size as _) } } fn get_cwd(&self, handle: &HandleWrapper) -> Result, &'static str> { let ptr = self.CurrentDirectory.DosPath.Buffer; let size = self.CurrentDirectory.DosPath.Length; unsafe { get_process_data(handle, ptr as _, size as _) } } fn get_environ(&self, handle: &HandleWrapper) -> Result, &'static str> { let ptr = self.Environment; unsafe { let size = get_region_size(handle, ptr as LPVOID)?; get_process_data(handle, ptr as _, size as _) } } } }; } impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); unsafe fn get_process_params( handle: &HandleWrapper, ) -> Result<(Vec, Vec, PathBuf), &'static str> { if !cfg!(target_pointer_width = "64") { return Err("Non 64 bit targets are not supported"); } // First check if target process is running in wow64 compatibility emulator let mut pwow32info = MaybeUninit::::uninit(); let result = NtQueryInformationProcess( **handle, ProcessWow64Information, pwow32info.as_mut_ptr() as *mut _, size_of::() as u32, null_mut(), ); if !NT_SUCCESS(result) { return Err("Unable to check WOW64 information about the process"); } let pwow32info = pwow32info.assume_init(); if pwow32info.is_null() { // target is a 64 bit process let mut pbasicinfo = MaybeUninit::::uninit(); let result = NtQueryInformationProcess( **handle, ProcessBasicInformation, pbasicinfo.as_mut_ptr() as *mut _, size_of::() as u32, null_mut(), ); if !NT_SUCCESS(result) { return Err("Unable to get basic process information"); } let pinfo = pbasicinfo.assume_init(); let mut peb = MaybeUninit::::uninit(); if ReadProcessMemory( **handle, pinfo.PebBaseAddress as *mut _, peb.as_mut_ptr() as *mut _, size_of::() as SIZE_T, null_mut(), ) != TRUE { return Err("Unable to read process PEB"); } let peb = peb.assume_init(); let mut proc_params = MaybeUninit::::uninit(); if ReadProcessMemory( **handle, peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _, proc_params.as_mut_ptr() as *mut _, size_of::() as SIZE_T, null_mut(), ) != TRUE { return Err("Unable to read process parameters"); } let proc_params = proc_params.assume_init(); return Ok(( get_cmd_line(&proc_params, handle), get_proc_env(&proc_params, handle), get_cwd(&proc_params, handle), )); } // target is a 32 bit process in wow64 mode let mut peb32 = MaybeUninit::::uninit(); if ReadProcessMemory( **handle, pwow32info, peb32.as_mut_ptr() as *mut _, size_of::() as SIZE_T, null_mut(), ) != TRUE { return Err("Unable to read PEB32"); } let peb32 = peb32.assume_init(); let mut proc_params = MaybeUninit::::uninit(); if ReadProcessMemory( **handle, peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _, proc_params.as_mut_ptr() as *mut _, size_of::() as SIZE_T, null_mut(), ) != TRUE { return Err("Unable to read 32 bit process parameters"); } let proc_params = proc_params.assume_init(); Ok(( get_cmd_line(&proc_params, handle), get_proc_env(&proc_params, handle), get_cwd(&proc_params, handle), )) } fn get_cwd(params: &T, handle: &HandleWrapper) -> PathBuf { match params.get_cwd(handle) { Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, Err(_e) => { sysinfo_debug!("get_cwd failed to get data: {}", _e); PathBuf::new() } } } unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String { match slice.iter().position(|&x| x == 0) { Some(pos) => OsString::from_wide(&slice[..pos]) .to_string_lossy() .into_owned(), None => OsString::from_wide(slice).to_string_lossy().into_owned(), } } fn get_cmd_line_old( params: &T, handle: &HandleWrapper, ) -> Vec { match params.get_cmdline(handle) { Ok(buffer) => unsafe { get_cmdline_from_buffer(buffer.as_ptr()) }, Err(_e) => { sysinfo_debug!("get_cmd_line_old failed to get data: {}", _e); Vec::new() } } } #[allow(clippy::cast_ptr_alignment)] fn get_cmd_line_new(handle: &HandleWrapper) -> Vec { unsafe { if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation) { let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer; get_cmdline_from_buffer(buffer) } else { vec![] } } } fn get_cmd_line(params: &T, handle: &HandleWrapper) -> Vec { if *WINDOWS_8_1_OR_NEWER { get_cmd_line_new(handle) } else { get_cmd_line_old(params, handle) } } fn get_proc_env(params: &T, handle: &HandleWrapper) -> Vec { match params.get_environ(handle) { Ok(buffer) => { let equals = "=".encode_utf16().next().unwrap(); let raw_env = buffer; let mut result = Vec::new(); let mut begin = 0; while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) { let end = begin + offset; if raw_env[begin..end].iter().any(|&c| c == equals) { result.push( OsString::from_wide(&raw_env[begin..end]) .to_string_lossy() .into_owned(), ); begin = end + 1; } else { break; } } result } Err(_e) => { sysinfo_debug!("get_proc_env failed to get data: {}", _e); Vec::new() } } } pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { /*let where_req = format!("ProcessId={}", pid); if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { for line in ret.lines() { if line.is_empty() || line == "ExecutablePath" { continue } return line.to_owned(); } }*/ PathBuf::new() } #[inline] fn check_sub(a: u64, b: u64) -> u64 { if a < b { a } else { a - b } } /// Before changing this function, you must consider the following: /// pub(crate) fn compute_cpu_usage(p: &mut Process, nb_cpus: u64) { unsafe { let mut ftime: FILETIME = zeroed(); let mut fsys: FILETIME = zeroed(); let mut fuser: FILETIME = zeroed(); let mut fglobal_idle_time: FILETIME = zeroed(); let mut fglobal_kernel_time: FILETIME = zeroed(); // notice that it includes idle time let mut fglobal_user_time: FILETIME = zeroed(); if let Some(handle) = p.get_handle() { GetProcessTimes( handle, &mut ftime as *mut FILETIME, &mut ftime as *mut FILETIME, &mut fsys as *mut FILETIME, &mut fuser as *mut FILETIME, ); } // FIXME: should these values be stored in one place to make use of // `MINIMUM_CPU_UPDATE_INTERVAL`? GetSystemTimes( &mut fglobal_idle_time as *mut FILETIME, &mut fglobal_kernel_time as *mut FILETIME, &mut fglobal_user_time as *mut FILETIME, ); let mut sys: ULARGE_INTEGER = std::mem::zeroed(); memcpy( &mut sys as *mut ULARGE_INTEGER as *mut c_void, &mut fsys as *mut FILETIME as *mut c_void, size_of::(), ); let mut user: ULARGE_INTEGER = std::mem::zeroed(); memcpy( &mut user as *mut ULARGE_INTEGER as *mut c_void, &mut fuser as *mut FILETIME as *mut c_void, size_of::(), ); let mut global_kernel_time: ULARGE_INTEGER = std::mem::zeroed(); memcpy( &mut global_kernel_time as *mut ULARGE_INTEGER as *mut c_void, &mut fglobal_kernel_time as *mut FILETIME as *mut c_void, size_of::(), ); let mut global_user_time: ULARGE_INTEGER = std::mem::zeroed(); memcpy( &mut global_user_time as *mut ULARGE_INTEGER as *mut c_void, &mut fglobal_user_time as *mut FILETIME as *mut c_void, size_of::(), ); let sys = *sys.QuadPart(); let user = *user.QuadPart(); let global_kernel_time = *global_kernel_time.QuadPart(); let global_user_time = *global_user_time.QuadPart(); let delta_global_kernel_time = check_sub(global_kernel_time, p.cpu_calc_values.old_system_sys_cpu); let delta_global_user_time = check_sub(global_user_time, p.cpu_calc_values.old_system_user_cpu); let delta_user_time = check_sub(user, p.cpu_calc_values.old_process_user_cpu); let delta_sys_time = check_sub(sys, p.cpu_calc_values.old_process_sys_cpu); p.cpu_calc_values.old_process_user_cpu = user; p.cpu_calc_values.old_process_sys_cpu = sys; p.cpu_calc_values.old_system_user_cpu = global_user_time; p.cpu_calc_values.old_system_sys_cpu = global_kernel_time; let denominator = delta_global_user_time.saturating_add(delta_global_kernel_time) as f32; if denominator < 0.00001 { p.cpu_usage = 0.; return; } p.cpu_usage = 100.0 * (delta_user_time.saturating_add(delta_sys_time) as f32 / denominator) * nb_cpus as f32; } } pub(crate) fn update_disk_usage(p: &mut Process) { let mut counters = MaybeUninit::::uninit(); if let Some(handle) = p.get_handle() { unsafe { let ret = GetProcessIoCounters(handle, counters.as_mut_ptr()); if ret == 0 { sysinfo_debug!("GetProcessIoCounters call failed on process {}", p.pid()); } else { let counters = counters.assume_init(); p.old_read_bytes = p.read_bytes; p.old_written_bytes = p.written_bytes; p.read_bytes = counters.ReadTransferCount; p.written_bytes = counters.WriteTransferCount; } } } } pub(crate) fn update_memory(p: &mut Process) { if let Some(handle) = p.get_handle() { unsafe { let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); if GetProcessMemoryInfo( handle, &mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void as *mut PROCESS_MEMORY_COUNTERS, size_of::() as DWORD, ) != 0 { p.memory = pmc.WorkingSetSize as _; p.virtual_memory = pmc.PrivateUsage as _; } } } } sysinfo-0.28.4/src/windows/sid.rs000064400000000000000000000123371046102023000150250ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::{fmt::Display, str::FromStr}; use winapi::{ shared::{ sddl::{ConvertSidToStringSidW, ConvertStringSidToSidW}, winerror::ERROR_INSUFFICIENT_BUFFER, }, um::{ errhandlingapi::GetLastError, securitybaseapi::{CopySid, GetLengthSid, IsValidSid}, winbase::{LocalFree, LookupAccountSidW}, winnt::{SidTypeUnknown, LPWSTR, PSID}, }, }; use crate::sys::utils::to_str; #[doc = include_str!("../../md_doc/sid.md")] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Sid { sid: Vec, } impl Sid { /// Creates an `Sid` by making a copy of the given raw SID. pub(crate) unsafe fn from_psid(psid: PSID) -> Option { if psid.is_null() { return None; } if IsValidSid(psid) == 0 { return None; } let length = GetLengthSid(psid); let mut sid = vec![0; length as usize]; if CopySid(length, sid.as_mut_ptr() as *mut _, psid) == 0 { sysinfo_debug!("CopySid failed: {:?}", GetLastError()); return None; } // We are making assumptions about the SID internal structure, // and these only hold if the revision is 1 // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid // Namely: // 1. SIDs can be compared directly (memcmp). // 2. Following from this, to hash a SID we can just hash its bytes. // These are the basis for deriving PartialEq, Eq, and Hash. // And since we also need PartialOrd and Ord, we might as well derive them // too. The default implementation will be consistent with Eq, // and we don't care about the actual order, just that there is one. // So it should all work out. // Why bother with this? Because it makes the implementation that // much simpler :) assert_eq!(sid[0], 1, "Expected SID revision to be 1"); Some(Self { sid }) } /// Retrieves the account name of this SID. pub(crate) fn account_name(&self) -> Option { unsafe { let mut name_len = 0; let mut domain_len = 0; let mut name_use = SidTypeUnknown; if LookupAccountSidW( std::ptr::null_mut(), self.sid.as_ptr() as *mut _, std::ptr::null_mut(), &mut name_len, std::ptr::null_mut(), &mut domain_len, &mut name_use, ) == 0 { let error = GetLastError(); if error != ERROR_INSUFFICIENT_BUFFER { sysinfo_debug!("LookupAccountSidW failed: {:?}", error); return None; } } let mut name = vec![0; name_len as usize]; // Reset length to 0 since we're still passing a NULL pointer // for the domain. domain_len = 0; if LookupAccountSidW( std::ptr::null_mut(), self.sid.as_ptr() as *mut _, name.as_mut_ptr(), &mut name_len, std::ptr::null_mut(), &mut domain_len, &mut name_use, ) == 0 { sysinfo_debug!("LookupAccountSidW failed: {:?}", GetLastError()); return None; } Some(to_str(name.as_mut_ptr())) } } } impl Display for Sid { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unsafe fn convert_sid_to_string_sid(sid: PSID) -> Option { let mut string_sid: LPWSTR = std::ptr::null_mut(); if ConvertSidToStringSidW(sid, &mut string_sid) == 0 { sysinfo_debug!("ConvertSidToStringSidW failed: {:?}", GetLastError()); return None; } let result = to_str(string_sid); LocalFree(string_sid as *mut _); Some(result) } let string_sid = unsafe { convert_sid_to_string_sid(self.sid.as_ptr() as *mut _) }; let string_sid = string_sid.ok_or(std::fmt::Error)?; write!(f, "{string_sid}") } } impl FromStr for Sid { type Err = String; fn from_str(s: &str) -> Result { unsafe { let mut string_sid: Vec = s.encode_utf16().collect(); string_sid.push(0); let mut psid: PSID = std::ptr::null_mut(); if ConvertStringSidToSidW(string_sid.as_ptr(), &mut psid) == 0 { return Err(format!( "ConvertStringSidToSidW failed: {:?}", GetLastError() )); } let sid = Self::from_psid(psid); LocalFree(psid as *mut _); // Unwrapping because ConvertStringSidToSidW should've performed // all the necessary validations. If it returned an invalid SID, // we better fail fast. Ok(sid.unwrap()) } } } sysinfo-0.28.4/src/windows/system.rs000064400000000000000000000456201046102023000155730ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::{ CpuRefreshKind, LoadAvg, Networks, Pid, ProcessExt, ProcessRefreshKind, RefreshKind, SystemExt, User, }; use winapi::um::winreg::HKEY_LOCAL_MACHINE; use crate::sys::component::{self, Component}; use crate::sys::cpu::*; use crate::sys::disk::{get_disks, Disk}; use crate::sys::process::{get_start_time, update_memory, Process}; use crate::sys::tools::*; use crate::sys::users::get_users; use crate::sys::utils::{get_now, get_reg_string_value, get_reg_value_u32}; use crate::utils::into_iter; use std::cell::UnsafeCell; use std::collections::HashMap; use std::mem::{size_of, zeroed}; use std::time::{Duration, SystemTime}; use ntapi::ntexapi::{ NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION, }; use winapi::ctypes::wchar_t; use winapi::shared::minwindef::{FALSE, TRUE}; use winapi::shared::ntdef::{PVOID, ULONG}; use winapi::shared::ntstatus::STATUS_INFO_LENGTH_MISMATCH; use winapi::um::minwinbase::STILL_ACTIVE; use winapi::um::processthreadsapi::GetExitCodeProcess; use winapi::um::psapi::{GetPerformanceInfo, PERFORMANCE_INFORMATION}; use winapi::um::sysinfoapi::{ ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx, MEMORYSTATUSEX, }; use winapi::um::winnt::HANDLE; declare_signals! { (), Signal::Kill => (), _ => None, } #[doc = include_str!("../../md_doc/system.md")] pub struct System { process_list: HashMap, mem_total: u64, mem_available: u64, swap_total: u64, swap_used: u64, cpus: CpusWrapper, components: Vec, disks: Vec, query: Option, networks: Networks, boot_time: u64, users: Vec, } static WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000; impl System { fn is_windows_eleven(&self) -> bool { WINDOWS_ELEVEN_BUILD_NUMBER <= self .kernel_version() .unwrap_or_default() .parse() .unwrap_or(0) } } // Useful for parallel iterations. struct Wrap(T); #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for Wrap {} unsafe impl Sync for Wrap {} unsafe fn boot_time() -> u64 { match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => n.as_secs().saturating_sub(GetTickCount64() / 1_000), Err(_e) => { sysinfo_debug!("Failed to compute boot time: {:?}", _e); 0 } } } impl SystemExt for System { const IS_SUPPORTED: bool = true; const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); const MINIMUM_CPU_UPDATE_INTERVAL: Duration = Duration::from_millis(200); #[allow(non_snake_case)] fn new_with_specifics(refreshes: RefreshKind) -> System { let mut s = System { process_list: HashMap::with_capacity(500), mem_total: 0, mem_available: 0, swap_total: 0, swap_used: 0, cpus: CpusWrapper::new(), components: Vec::new(), disks: Vec::with_capacity(2), query: None, networks: Networks::new(), boot_time: unsafe { boot_time() }, users: Vec::new(), }; s.refresh_specifics(refreshes); s } fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { if self.query.is_none() { self.query = Query::new(); if let Some(ref mut query) = self.query { add_english_counter( r"\Processor(_Total)\% Processor Time".to_string(), query, get_key_used(self.cpus.global_cpu_mut()), "tot_0".to_owned(), ); for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() { add_english_counter( format!(r"\Processor({pos})\% Processor Time"), query, get_key_used(proc_), format!("{pos}_0"), ); } } } if let Some(ref mut query) = self.query { query.refresh(); let mut used_time = None; if let Some(ref key_used) = *get_key_used(self.cpus.global_cpu_mut()) { used_time = Some( query .get(&key_used.unique_id) .expect("global_key_idle disappeared"), ); } if let Some(used_time) = used_time { self.cpus.global_cpu_mut().set_cpu_usage(used_time); } for p in self.cpus.iter_mut(refresh_kind) { let mut used_time = None; if let Some(ref key_used) = *get_key_used(p) { used_time = Some( query .get(&key_used.unique_id) .expect("key_used disappeared"), ); } if let Some(used_time) = used_time { p.set_cpu_usage(used_time); } } if refresh_kind.frequency() { self.cpus.get_frequencies(); } } } fn refresh_memory(&mut self) { unsafe { let mut mem_info: MEMORYSTATUSEX = zeroed(); mem_info.dwLength = size_of::() as u32; GlobalMemoryStatusEx(&mut mem_info); self.mem_total = mem_info.ullTotalPhys as _; self.mem_available = mem_info.ullAvailPhys as _; let mut perf_info: PERFORMANCE_INFORMATION = zeroed(); if GetPerformanceInfo(&mut perf_info, size_of::() as u32) == TRUE { let swap_total = perf_info.PageSize.saturating_mul( perf_info .CommitLimit .saturating_sub(perf_info.PhysicalTotal), ); let swap_used = perf_info.PageSize.saturating_mul( perf_info .CommitTotal .saturating_sub(perf_info.PhysicalTotal), ); self.swap_total = swap_total as _; self.swap_used = swap_used as _; } } } fn refresh_components_list(&mut self) { self.components = component::get_components(); } #[allow(clippy::map_entry)] fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { let now = get_now(); let nb_cpus = self.cpus.len() as u64; if let Some(proc_) = self.process_list.get_mut(&pid) { if let Some(ret) = refresh_existing_process(proc_, nb_cpus, now, refresh_kind) { return ret; } // We need to re-make the process because the PID owner changed. } if let Some(mut p) = Process::new_from_pid(pid, now, refresh_kind) { p.update(refresh_kind, nb_cpus, now); p.updated = false; self.process_list.insert(pid, p); true } else { false } } #[allow(clippy::cast_ptr_alignment)] fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { // Windows 10 notebook requires at least 512KiB of memory to make it in one go let mut buffer_size: usize = 512 * 1024; let now = get_now(); loop { let mut process_information: Vec = Vec::with_capacity(buffer_size); let mut cb_needed = 0; unsafe { process_information.set_len(buffer_size); let ntstatus = NtQuerySystemInformation( SystemProcessInformation, process_information.as_mut_ptr() as PVOID, buffer_size as ULONG, &mut cb_needed, ); if ntstatus != STATUS_INFO_LENGTH_MISMATCH { if ntstatus < 0 { sysinfo_debug!( "Couldn't get process infos: NtQuerySystemInformation returned {}", ntstatus ); } // Parse the data block to get process information let mut process_ids = Vec::with_capacity(500); let mut process_information_offset = 0; loop { let p = process_information .as_ptr() .offset(process_information_offset) as *const SYSTEM_PROCESS_INFORMATION; let pi = &*p; process_ids.push(Wrap(p)); if pi.NextEntryOffset == 0 { break; } process_information_offset += pi.NextEntryOffset as isize; } let process_list = Wrap(UnsafeCell::new(&mut self.process_list)); let nb_cpus = if refresh_kind.cpu() { self.cpus.len() as u64 } else { 0 }; #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; // TODO: instead of using parallel iterator only here, would be better to be // able to run it over `process_information` directly! let processes = into_iter(process_ids) .filter_map(|pi| { let pi = *pi.0; let pid = Pid(pi.UniqueProcessId as _); if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) { if proc_ .get_start_time() .map(|start| start == proc_.start_time()) .unwrap_or(true) { proc_.memory = pi.WorkingSetSize as _; proc_.virtual_memory = pi.VirtualSize as _; proc_.update(refresh_kind, nb_cpus, now); return None; } // If the PID owner changed, we need to recompute the whole process. sysinfo_debug!("owner changed for PID {}", proc_.pid()); } let name = get_process_name(&pi, pid); let mut p = Process::new_full( pid, if pi.InheritedFromUniqueProcessId as usize != 0 { Some(Pid(pi.InheritedFromUniqueProcessId as _)) } else { None }, pi.WorkingSetSize as _, pi.VirtualSize as _, name, now, refresh_kind, ); p.update(refresh_kind, nb_cpus, now); Some(p) }) .collect::>(); for p in processes.into_iter() { self.process_list.insert(p.pid(), p); } self.process_list.retain(|_, v| { let x = v.updated; v.updated = false; x }); break; } // GetNewBufferSize if cb_needed == 0 { buffer_size *= 2; continue; } // allocating a few more kilo bytes just in case there are some new process // kicked in since new call to NtQuerySystemInformation buffer_size = (cb_needed + (1024 * 10)) as usize; } } } fn refresh_disks_list(&mut self) { self.disks = unsafe { get_disks() }; } fn refresh_users_list(&mut self) { self.users = unsafe { get_users() }; } fn processes(&self) -> &HashMap { &self.process_list } fn process(&self, pid: Pid) -> Option<&Process> { self.process_list.get(&pid) } fn global_cpu_info(&self) -> &Cpu { self.cpus.global_cpu() } fn cpus(&self) -> &[Cpu] { self.cpus.cpus() } fn physical_core_count(&self) -> Option { get_physical_core_count() } fn total_memory(&self) -> u64 { self.mem_total } fn free_memory(&self) -> u64 { // MEMORYSTATUSEX doesn't report free memory self.mem_available } fn available_memory(&self) -> u64 { self.mem_available } fn used_memory(&self) -> u64 { self.mem_total - self.mem_available } fn total_swap(&self) -> u64 { self.swap_total } fn free_swap(&self) -> u64 { self.swap_total - self.swap_used } fn used_swap(&self) -> u64 { self.swap_used } fn components(&self) -> &[Component] { &self.components } fn components_mut(&mut self) -> &mut [Component] { &mut self.components } fn disks(&self) -> &[Disk] { &self.disks } fn disks_mut(&mut self) -> &mut [Disk] { &mut self.disks } fn sort_disks_by(&mut self, compare: F) where F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, { self.disks.sort_unstable_by(compare); } fn users(&self) -> &[User] { &self.users } fn networks(&self) -> &Networks { &self.networks } fn networks_mut(&mut self) -> &mut Networks { &mut self.networks } fn uptime(&self) -> u64 { unsafe { GetTickCount64() / 1_000 } } fn boot_time(&self) -> u64 { self.boot_time } fn load_average(&self) -> LoadAvg { get_load_average() } fn name(&self) -> Option { Some("Windows".to_owned()) } fn long_os_version(&self) -> Option { if self.is_windows_eleven() { return get_reg_string_value( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ProductName", ) .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 ")); } get_reg_string_value( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ProductName", ) } fn host_name(&self) -> Option { get_dns_hostname() } fn kernel_version(&self) -> Option { get_reg_string_value( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber", ) } fn os_version(&self) -> Option { let build_number = get_reg_string_value( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber", ) .unwrap_or_default(); let major = if self.is_windows_eleven() { 11u32 } else { u32::from_le_bytes( get_reg_value_u32( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "CurrentMajorVersionNumber", ) .unwrap_or_default(), ) }; Some(format!("{major} ({build_number})")) } fn distribution_id(&self) -> String { std::env::consts::OS.to_owned() } } impl Default for System { fn default() -> System { System::new() } } pub(crate) fn is_proc_running(handle: HANDLE) -> bool { let mut exit_code = 0; unsafe { let ret = GetExitCodeProcess(handle, &mut exit_code); !(ret == FALSE || exit_code != STILL_ACTIVE) } } /// If it returns `None`, it means that the PID owner changed and that the `Process` must be /// completely recomputed. fn refresh_existing_process( proc_: &mut Process, nb_cpus: u64, now: u64, refresh_kind: ProcessRefreshKind, ) -> Option { if let Some(handle) = proc_.get_handle() { if get_start_time(handle) != proc_.start_time() { sysinfo_debug!("owner changed for PID {}", proc_.pid()); // PID owner changed! return None; } if !is_proc_running(handle) { return Some(false); } } else { return Some(false); } update_memory(proc_); proc_.update(refresh_kind, nb_cpus, now); proc_.updated = false; Some(true) } #[allow(clippy::size_of_in_element_count)] //^ needed for "name.Length as usize / std::mem::size_of::()" pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> String { let name = &process.ImageName; if name.Buffer.is_null() { match process_id.0 { 0 => "Idle".to_owned(), 4 => "System".to_owned(), _ => format!(" Process {process_id}"), } } else { unsafe { let slice = std::slice::from_raw_parts( name.Buffer, // The length is in bytes, not the length of string name.Length as usize / std::mem::size_of::(), ); String::from_utf16_lossy(slice) } } } fn get_dns_hostname() -> Option { let mut buffer_size = 0; // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH // setting the `lpBuffer` to null will return the buffer size // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw unsafe { GetComputerNameExW( ComputerNamePhysicalDnsHostname, std::ptr::null_mut(), &mut buffer_size, ); // Setting the buffer with the new length let mut buffer = vec![0_u16; buffer_size as usize]; // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format if GetComputerNameExW( ComputerNamePhysicalDnsHostname, buffer.as_mut_ptr() as *mut wchar_t, &mut buffer_size, ) == TRUE { if let Some(pos) = buffer.iter().position(|c| *c == 0) { buffer.resize(pos, 0); } return String::from_utf16(&buffer).ok(); } } sysinfo_debug!("Failed to get computer hostname"); None } sysinfo-0.28.4/src/windows/tools.rs000064400000000000000000000030511046102023000153770ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::cpu::{self, Cpu, Query}; use crate::CpuRefreshKind; use std::mem::zeroed; use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO}; pub(crate) struct KeyHandler { pub unique_id: String, } impl KeyHandler { pub fn new(unique_id: String) -> KeyHandler { KeyHandler { unique_id } } } pub(crate) fn init_cpus(refresh_kind: CpuRefreshKind) -> (Vec, String, String) { unsafe { let mut sys_info: SYSTEM_INFO = zeroed(); GetSystemInfo(&mut sys_info); let (vendor_id, brand) = cpu::get_vendor_id_and_brand(&sys_info); let nb_cpus = sys_info.dwNumberOfProcessors as usize; let frequencies = if refresh_kind.frequency() { cpu::get_frequencies(nb_cpus) } else { vec![0; nb_cpus] }; let mut ret = Vec::with_capacity(nb_cpus + 1); for (nb, frequency) in frequencies.iter().enumerate() { ret.push(Cpu::new_with_values( format!("CPU {}", nb + 1), vendor_id.clone(), brand.clone(), *frequency, )); } (ret, vendor_id, brand) } } pub(crate) fn add_english_counter( s: String, query: &mut Query, keys: &mut Option, counter_name: String, ) { let mut full = s.encode_utf16().collect::>(); full.push(0); if query.add_english_counter(&counter_name, full) { *keys = Some(KeyHandler::new(counter_name)); } } sysinfo-0.28.4/src/windows/users.rs000064400000000000000000000163071046102023000154100ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use crate::sys::utils::to_str; use crate::{ common::{Gid, Uid}, windows::sid::Sid, User, }; use std::ptr::null_mut; use winapi::shared::lmcons::{MAX_PREFERRED_LENGTH, NET_API_STATUS}; use winapi::shared::minwindef::{DWORD, LPBYTE}; use winapi::shared::ntstatus::STATUS_SUCCESS; use winapi::shared::winerror::ERROR_MORE_DATA; use winapi::um::lmaccess::{ NetUserEnum, NetUserGetInfo, NetUserGetLocalGroups, LOCALGROUP_USERS_INFO_0, USER_INFO_23, }; use winapi::um::lmaccess::{FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, USER_INFO_0}; use winapi::um::lmapibuf::NetApiBufferFree; use winapi::um::ntlsa::{ LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, SECURITY_LOGON_SESSION_DATA, }; use winapi::um::winnt::{LPWSTR, LUID}; // FIXME: once this is mreged in winapi, it can be removed. #[allow(non_upper_case_globals)] const NERR_Success: NET_API_STATUS = 0; struct NetApiBuffer(*mut T); impl Drop for NetApiBuffer { fn drop(&mut self) { unsafe { if !self.0.is_null() { NetApiBufferFree(self.0 as *mut _); } } } } impl Default for NetApiBuffer { fn default() -> Self { Self(null_mut()) } } impl NetApiBuffer { pub fn inner_mut(&mut self) -> &mut *mut T { assert!(self.0.is_null()); &mut self.0 } pub unsafe fn inner_mut_as_bytes(&mut self) -> &mut LPBYTE { // https://doc.rust-lang.org/std/mem/fn.transmute.html // Turning an &mut T into an &mut U: &mut *(self.inner_mut() as *mut *mut T as *mut LPBYTE) } } struct LsaBuffer(*mut T); impl Drop for LsaBuffer { fn drop(&mut self) { unsafe { if !self.0.is_null() { LsaFreeReturnBuffer(self.0 as *mut _); } } } } impl Default for LsaBuffer { fn default() -> Self { Self(null_mut()) } } impl LsaBuffer { pub fn inner_mut(&mut self) -> &mut *mut T { assert!(self.0.is_null()); &mut self.0 } } unsafe fn get_groups_for_user(username: LPWSTR) -> Vec { let mut buf: NetApiBuffer = Default::default(); let mut nb_entries = 0; let mut total_entries = 0; let mut groups; let status = NetUserGetLocalGroups( [0u16].as_ptr(), username, 0, LG_INCLUDE_INDIRECT, buf.inner_mut_as_bytes(), MAX_PREFERRED_LENGTH, &mut nb_entries, &mut total_entries, ); if status == NERR_Success { groups = Vec::with_capacity(nb_entries as _); if !buf.0.is_null() { let entries = std::slice::from_raw_parts(buf.0, nb_entries as _); groups.extend(entries.iter().map(|entry| to_str(entry.lgrui0_name))); } } else { groups = Vec::new(); sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status); } groups } pub unsafe fn get_users() -> Vec { let mut users = Vec::new(); let mut resume_handle: DWORD = 0; loop { let mut buffer: NetApiBuffer = Default::default(); let mut nb_read = 0; let mut total = 0; let status = NetUserEnum( null_mut(), 0, FILTER_NORMAL_ACCOUNT, buffer.inner_mut_as_bytes(), MAX_PREFERRED_LENGTH, &mut nb_read, &mut total, &mut resume_handle, ); if status == NERR_Success || status == ERROR_MORE_DATA { let entries = std::slice::from_raw_parts(buffer.0, nb_read as _); for entry in entries { if entry.usri0_name.is_null() { continue; } let mut user: NetApiBuffer = Default::default(); if NetUserGetInfo(null_mut(), entry.usri0_name, 23, user.inner_mut_as_bytes()) == NERR_Success { if let Some(sid) = Sid::from_psid((*user.0).usri23_user_sid) { // Get the account name from the SID (because it's usually // a better name), but fall back to the name we were given // if this fails. let name = sid .account_name() .unwrap_or_else(|| to_str(entry.usri0_name)); users.push(User { uid: Uid(sid), gid: Gid(0), name, groups: get_groups_for_user(entry.usri0_name), }); } } } } else { sysinfo_debug!( "NetUserEnum error: {}", if status == winapi::shared::winerror::ERROR_ACCESS_DENIED { "access denied" } else if status == winapi::shared::winerror::ERROR_INVALID_LEVEL { "invalid level" } else { "unknown error" } ); } if status != ERROR_MORE_DATA { break; } } // First part done. Second part now! let mut nb_sessions = 0; let mut uids: LsaBuffer = Default::default(); if LsaEnumerateLogonSessions(&mut nb_sessions, uids.inner_mut()) != STATUS_SUCCESS { sysinfo_debug!("LsaEnumerateLogonSessions failed"); } else { let entries = std::slice::from_raw_parts_mut(uids.0, nb_sessions as _); for entry in entries { let mut data: LsaBuffer = Default::default(); if LsaGetLogonSessionData(entry, data.inner_mut()) == STATUS_SUCCESS && !data.0.is_null() { let data = *data.0; if data.LogonType == winapi::um::ntlsa::Network { continue; } let sid = match Sid::from_psid(data.Sid) { Some(sid) => sid, None => continue, }; if users.iter().any(|u| u.uid.0 == sid) { continue; } // Get the account name from the SID (because it's usually // a better name), but fall back to the name we were given // if this fails. let name = sid.account_name().unwrap_or_else(|| { String::from_utf16(std::slice::from_raw_parts( data.UserName.Buffer, data.UserName.Length as usize / std::mem::size_of::(), )) .unwrap_or_else(|_err| { sysinfo_debug!("Failed to convert from UTF-16 string: {}", _err); String::new() }) }); users.push(User { uid: Uid(sid), gid: Gid(0), name, // There is no local groups for a non-local user. groups: Vec::new(), }); } } } users } sysinfo-0.28.4/src/windows/utils.rs000064400000000000000000000065731046102023000154130ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use winapi::shared::minwindef::FILETIME; use winapi::shared::minwindef::{DWORD, HKEY}; use winapi::shared::winerror; use winapi::um::winnt::{KEY_READ, LPWSTR}; use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW}; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::time::SystemTime; #[inline] pub(crate) fn filetime_to_u64(f: FILETIME) -> u64 { (f.dwHighDateTime as u64) << 32 | (f.dwLowDateTime as u64) } #[inline] pub(crate) fn get_now() -> u64 { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .map(|n| n.as_secs()) .unwrap_or(0) } pub(crate) unsafe fn to_str(p: LPWSTR) -> String { let mut i = 0; loop { let c = *p.offset(i); if c == 0 { break; } i += 1; } let s = std::slice::from_raw_parts(p, i as _); String::from_utf16(s).unwrap_or_else(|_e| { sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e); String::new() }) } fn utf16_str + ?Sized>(text: &S) -> Vec { OsStr::new(text) .encode_wide() .chain(Some(0).into_iter()) .collect::>() } struct RegKey(HKEY); impl RegKey { unsafe fn open(hkey: HKEY, path: &[u16]) -> Option { let mut new_hkey: HKEY = std::ptr::null_mut(); if RegOpenKeyExW(hkey, path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { return None; } Some(Self(new_hkey)) } unsafe fn get_value(&self, field_name: &[u16], buf: &mut [u8], buf_len: &mut DWORD) -> DWORD { let mut buf_type: DWORD = 0; RegQueryValueExW( self.0, field_name.as_ptr(), std::ptr::null_mut(), &mut buf_type, buf.as_mut_ptr() as _, buf_len, ) as DWORD } } impl Drop for RegKey { fn drop(&mut self) { unsafe { RegCloseKey(self.0); } } } pub(crate) fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option { let c_path = utf16_str(path); let c_field_name = utf16_str(field_name); unsafe { let new_key = RegKey::open(hkey, &c_path)?; let mut buf_len: DWORD = 2048; let mut buf: Vec = Vec::with_capacity(buf_len as usize); loop { match new_key.get_value(&c_field_name, &mut buf, &mut buf_len) { winerror::ERROR_SUCCESS => break, winerror::ERROR_MORE_DATA => { buf.reserve(buf_len as _); } _ => return None, } } buf.set_len(buf_len as _); let words = std::slice::from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2); let mut s = String::from_utf16_lossy(words); while s.ends_with('\u{0}') { s.pop(); } Some(s) } } pub(crate) fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> { let c_path = utf16_str(path); let c_field_name = utf16_str(field_name); unsafe { let new_key = RegKey::open(hkey, &c_path)?; let mut buf_len: DWORD = 4; let mut buf = [0u8; 4]; match new_key.get_value(&c_field_name, &mut buf, &mut buf_len) { winerror::ERROR_SUCCESS => Some(buf), _ => None, } } } sysinfo-0.28.4/tests/code_checkers/docs.rs000064400000000000000000000100371046102023000166330ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{show_error, TestResult}; use std::ffi::OsStr; use std::path::Path; fn to_correct_name(s: &str) -> String { let mut out = String::with_capacity(s.len()); for c in s.chars() { if c.is_uppercase() { if !out.is_empty() { out.push('_'); } out.push_str(c.to_lowercase().to_string().as_str()); } else { out.push(c); } } out } fn check_md_doc_path(p: &Path, md_line: &str, ty_line: &str) -> bool { let parts = md_line.split('/').collect::>(); if let Some(md_name) = parts.last().and_then(|n| n.split(".md").next()) { if let Some(name) = ty_line.split_whitespace().filter(|s| !s.is_empty()).nth(2) { if let Some(name) = name .split('<') .next() .and_then(|n| n.split('{').next()) .and_then(|n| n.split('(').next()) .and_then(|n| n.split(';').next()) { let correct = to_correct_name(name); if correct.as_str() == md_name { return true; } show_error( p, &format!( "Invalid markdown file name `{md_name}`, should have been `{correct}`", ), ); return false; } } show_error(p, &format!("Cannot extract type name from `{ty_line}`")); } else { show_error(p, &format!("Cannot extract md name from `{md_line}`")); } false } fn check_doc_comments_before(p: &Path, lines: &[&str], start: usize) -> bool { let mut found_docs = false; for pos in (0..start).rev() { let trimmed = lines[pos].trim(); if trimmed.starts_with("///") { if !lines[start].trim().starts_with("pub enum ThreadStatus {") { show_error( p, &format!( "Types should use common documentation by using `#[doc = include_str!(` \ and by putting the markdown file in the `md_doc` folder instead of `{}`", &lines[pos], ), ); return false; } return true; } else if trimmed.starts_with("#[doc = include_str!(") { found_docs = true; if !check_md_doc_path(p, trimmed, lines[start]) { return false; } } else if !trimmed.starts_with("#[") && !trimmed.starts_with("//") { break; } } if !found_docs { show_error( p, &format!( "Missing documentation for public item: `{}` (if it's not supposed to be a public \ item, use `pub(crate)` instead)", lines[start], ), ); return false; } true } pub fn check_docs(content: &str, p: &Path) -> TestResult { let mut res = TestResult { nb_tests: 0, nb_errors: 0, }; // No need to check if we are in the `src` folder or if we are in a `ffi.rs` file. if p.parent().unwrap().file_name().unwrap() == OsStr::new("src") || p.file_name().unwrap() == OsStr::new("ffi.rs") { return res; } let lines = content.lines().collect::>(); for pos in 1..lines.len() { let line = lines[pos]; let trimmed = line.trim(); if trimmed.starts_with("//!") { show_error(p, "There shouln't be inner doc comments (`//!`)"); res.nb_tests += 1; res.nb_errors += 1; continue; } else if !line.starts_with("pub fn ") && !trimmed.starts_with("pub struct ") && !trimmed.starts_with("pub enum ") { continue; } res.nb_tests += 1; if !check_doc_comments_before(p, &lines, pos) { res.nb_errors += 1; } } res } sysinfo-0.28.4/tests/code_checkers/headers.rs000064400000000000000000000034051046102023000173170ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{show_error, TestResult}; use std::path::Path; pub fn check_license_header(content: &str, p: &Path) -> TestResult { let mut lines = content.lines(); let next = lines.next(); let header = "// Take a look at the license at the top of the repository in the LICENSE file."; match next { Some(s) if s == header => { let next = lines.next(); match next { Some("") => TestResult { nb_tests: 1, nb_errors: 0, }, Some(s) => { show_error( p, &format!("Expected empty line after license header, found `{s}`"), ); TestResult { nb_tests: 1, nb_errors: 1, } } None => { show_error(p, "This file should very likely not exist..."); TestResult { nb_tests: 1, nb_errors: 1, } } } } Some(s) => { show_error( p, &format!( "Expected license header at the top of the file (`{header}`), found: `{s}`", ), ); TestResult { nb_tests: 1, nb_errors: 1, } } None => { show_error(p, "This (empty?) file should very likely not exist..."); TestResult { nb_tests: 1, nb_errors: 1, } } } } sysinfo-0.28.4/tests/code_checkers/mod.rs000064400000000000000000000022641046102023000164650ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod docs; mod headers; mod signals; mod utils; use std::path::Path; use utils::TestResult; #[allow(clippy::type_complexity)] const CHECKS: &[(fn(&str, &Path) -> TestResult, &[&str])] = &[ (headers::check_license_header, &["src", "tests", "examples"]), (signals::check_signals, &["src"]), (docs::check_docs, &["src"]), ]; fn handle_tests(res: &mut [TestResult]) { utils::read_dirs( &["benches", "examples", "src", "tests"], &mut |p: &Path, c: &str| { if let Some(first) = p.iter().next().and_then(|first| first.to_str()) { for (pos, (check, filter)) in CHECKS.iter().enumerate() { if filter.contains(&first) { res[pos] += check(c, p); } } } }, ); } #[test] fn code_checks() { let mut res = Vec::new(); for _ in CHECKS { res.push(TestResult { nb_tests: 0, nb_errors: 0, }); } handle_tests(&mut res); for r in res { assert_eq!(r.nb_errors, 0); assert_ne!(r.nb_tests, 0); } } sysinfo-0.28.4/tests/code_checkers/signals.rs000064400000000000000000000041761046102023000173520ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use super::utils::{show_error, TestResult}; use std::path::Path; fn check_supported_signals_decl<'a>(lines: &mut impl Iterator, p: &Path) -> usize { for line in lines { let trimmed = line.trim(); if trimmed.starts_with("const SUPPORTED_SIGNALS: &'static [Signal]") { if trimmed != "const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals();" { show_error( p, "SystemExt::SUPPORTED_SIGNALS should be declared using `supported_signals()`", ); return 1; } break; } } 0 } fn check_kill_decl<'a>(lines: &mut impl Iterator, p: &Path) -> usize { let mut errors = 0; while let Some(line) = lines.next() { let trimmed = line.trim(); if trimmed.starts_with("fn kill(") { show_error(p, "`ProcessExt::kill` should not be reimplemented!"); errors += 1; } else if trimmed.starts_with("fn kill_with(") { if let Some(line) = lines.next() { let trimmed = line.trim(); if trimmed.ends_with("::system::convert_signal(signal)?;") || trimmed == "None" { continue; } else { show_error(p, "`ProcessExt::kill_with` should use `convert_signal`"); errors += 1; } } } } errors } pub fn check_signals(content: &str, p: &Path) -> TestResult { let mut lines = content.lines(); let mut res = TestResult { nb_tests: 0, nb_errors: 0, }; while let Some(line) = lines.next() { let trimmed = line.trim(); if trimmed.starts_with("impl SystemExt for System {") { res.nb_tests += 1; res.nb_errors += check_supported_signals_decl(&mut lines, p); } else if trimmed.starts_with("impl ProcessExt for Process {") { res.nb_tests += 1; res.nb_errors += check_kill_decl(&mut lines, p); } } res } sysinfo-0.28.4/tests/code_checkers/utils.rs000064400000000000000000000031071046102023000170430ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use std::fs::{self, File}; use std::io::Read; use std::path::Path; pub struct TestResult { pub nb_tests: usize, pub nb_errors: usize, } impl std::ops::AddAssign for TestResult { fn add_assign(&mut self, other: Self) { self.nb_tests += other.nb_tests; self.nb_errors += other.nb_errors; } } pub fn read_dirs, F: FnMut(&Path, &str)>(dirs: &[P], callback: &mut F) { for dir in dirs { read_dir(dir, callback); } } fn read_dir, F: FnMut(&Path, &str)>(dir: P, callback: &mut F) { for entry in fs::read_dir(dir).expect("read_dir failed") { let entry = entry.expect("entry failed"); let path = entry.path(); if path.is_dir() { read_dir(path, callback); } else if path .extension() .map(|ext| ext == "rs" || ext == "c" || ext == "h") .unwrap_or(false) { let content = read_file(&path); callback(&path, &content); } } } fn read_file>(p: P) -> String { let mut f = File::open(&p).expect("read_file::open failed"); let mut content = String::with_capacity(f.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)); if let Err(e) = f.read_to_string(&mut content) { panic!( "read_file::read_to_end failed for `{}: {e:?}", p.as_ref().display() ); } content } pub fn show_error(p: &Path, err: &str) { eprintln!("=> [{}]: {err}", p.display()); } sysinfo-0.28.4/tests/cpu.rs000064400000000000000000000015031046102023000137070ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // This test is used to ensure that the CPUs are not loaded by default. #[test] fn test_cpu() { use sysinfo::{CpuExt, SystemExt}; if sysinfo::System::IS_SUPPORTED { let mut s = sysinfo::System::new(); assert!(s.cpus().is_empty()); s.refresh_cpu(); assert!(!s.cpus().is_empty()); let s = sysinfo::System::new_all(); assert!(!s.cpus().is_empty()); assert!(!s.cpus()[0].brand().chars().any(|c| c == '\0')); } } #[test] fn test_physical_core_numbers() { use sysinfo::SystemExt; if sysinfo::System::IS_SUPPORTED { let s = sysinfo::System::new(); let count = s.physical_core_count(); assert_ne!(count, None); assert!(count.unwrap() > 0); } } sysinfo-0.28.4/tests/disk_list.rs000064400000000000000000000006701046102023000151110ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[test] fn test_disks() { use sysinfo::SystemExt; if sysinfo::System::IS_SUPPORTED { let s = sysinfo::System::new_all(); // If we don't have any physical core present, it's very likely that we're inside a VM... if s.physical_core_count().unwrap_or_default() > 0 { assert!(!s.disks().is_empty()); } } } sysinfo-0.28.4/tests/extras.rs000064400000000000000000000001441046102023000144260ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. mod code_checkers; sysinfo-0.28.4/tests/network.rs000064400000000000000000000007201046102023000146110ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. // This test is used to ensure that the networks are not loaded by default. #[test] fn test_networks() { use sysinfo::{NetworksExt, SystemExt}; if sysinfo::System::IS_SUPPORTED { let s = sysinfo::System::new(); assert_eq!(s.networks().iter().count(), 0); let s = sysinfo::System::new_all(); assert!(s.networks().iter().count() > 0); } } sysinfo-0.28.4/tests/process.rs000064400000000000000000000440311046102023000146010ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. use sysinfo::{Pid, PidExt, ProcessExt, SystemExt}; #[test] fn test_process() { let mut s = sysinfo::System::new(); assert_eq!(s.processes().len(), 0); s.refresh_processes(); if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } assert!(!s.processes().is_empty()); assert!(s .processes() .values() .any(|p| !p.exe().to_str().unwrap_or("").is_empty())); } #[test] fn test_cwd() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("CwdSignal") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); let mut s = sysinfo::System::new(); s.refresh_processes(); p.kill().expect("Unable to kill process."); let processes = s.processes(); let p = processes.get(&pid); if let Some(p) = p { assert_eq!(p.pid(), pid); assert_eq!(p.cwd(), std::env::current_dir().unwrap()); } else { panic!("Process not found!"); } } #[test] fn test_cmd() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("CmdSignal") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; std::thread::sleep(std::time::Duration::from_millis(500)); let mut s = sysinfo::System::new(); assert!(s.processes().is_empty()); s.refresh_processes(); p.kill().expect("Unable to kill process."); assert!(!s.processes().is_empty()); if let Some(process) = s.process(Pid::from_u32(p.id() as _)) { if cfg!(target_os = "windows") { // Sometimes, we get the full path instead for some reasons... So just in case, // we check for the command independently that from the arguments. assert!(process.cmd()[0].contains("waitfor")); assert_eq!(&process.cmd()[1..], &["/t", "3", "CmdSignal"]); } else { assert_eq!(process.cmd(), &["sleep", "3"]); } } else { panic!("Process not found!"); } } #[test] fn test_environ() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("EnvironSignal") .stdout(std::process::Stdio::null()) .env("FOO", "BAR") .env("OTHER", "VALUE") .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .env("FOO", "BAR") .env("OTHER", "VALUE") .spawn() .unwrap() }; let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); let mut s = sysinfo::System::new(); s.refresh_processes(); p.kill().expect("Unable to kill process."); let processes = s.processes(); let p = processes.get(&pid); if let Some(p) = p { assert_eq!(p.pid(), pid); // FIXME: instead of ignoring the test on CI, try to find out what's wrong... if std::env::var("APPLE_CI").is_err() { assert!(p.environ().iter().any(|e| e == "FOO=BAR")); assert!(p.environ().iter().any(|e| e == "OTHER=VALUE")); } } else { panic!("Process not found!"); } } // Test to ensure that a process with a lot of environment variables doesn't get truncated. // More information in . #[test] fn test_big_environ() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } const SIZE: usize = 30_000; let mut big_env = String::with_capacity(SIZE); for _ in 0..SIZE { big_env.push('a'); } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("EnvironSignal") .stdout(std::process::Stdio::null()) .env("FOO", &big_env) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .env("FOO", &big_env) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); let mut s = sysinfo::System::new(); s.refresh_processes(); p.kill().expect("Unable to kill process."); let processes = s.processes(); let p = processes.get(&pid); if let Some(p) = p { assert_eq!(p.pid(), pid); // FIXME: instead of ignoring the test on CI, try to find out what's wrong... if std::env::var("APPLE_CI").is_err() { let env = format!("FOO={big_env}"); assert!(p.environ().iter().any(|e| *e == env)); } } else { panic!("Process not found!"); } } #[test] fn test_process_refresh() { let mut s = sysinfo::System::new(); assert_eq!(s.processes().len(), 0); if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } s.refresh_process(sysinfo::get_current_pid().expect("failed to get current pid")); assert!(s .process(sysinfo::get_current_pid().expect("failed to get current pid")) .is_some(),); } #[test] fn test_process_disk_usage() { use std::fs; use std::fs::File; use std::io::prelude::*; use sysinfo::{get_current_pid, ProcessExt, SystemExt}; if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } if std::env::var("FREEBSD_CI").is_ok() { // For an unknown reason, when running this test on Cirrus CI, it fails. It works perfectly // locally though... Dark magic... return; } fn inner() -> sysinfo::System { { let mut file = File::create("test.txt").expect("failed to create file"); file.write_all(b"This is a test file\nwith test data.\n") .expect("failed to write to file"); } fs::remove_file("test.txt").expect("failed to remove file"); // Waiting a bit just in case... std::thread::sleep(std::time::Duration::from_millis(250)); let mut system = sysinfo::System::new(); assert!(system.processes().is_empty()); system.refresh_processes(); assert!(!system.processes().is_empty()); system } let mut system = inner(); let mut p = system .process(get_current_pid().expect("Failed retrieving current pid.")) .expect("failed to get process"); if cfg!(any(target_os = "macos", target_os = "ios")) && p.disk_usage().total_written_bytes == 0 { // For whatever reason, sometimes, mac doesn't work on the first time when running // `cargo test`. Two solutions, either run with "cargo test -- --test-threads 1", or // check twice... system = inner(); p = system .process(get_current_pid().expect("Failed retrieving current pid.")) .expect("failed to get process"); } assert!( p.disk_usage().total_written_bytes > 0, "found {} total written bytes...", p.disk_usage().total_written_bytes ); assert!( p.disk_usage().written_bytes > 0, "found {} written bytes...", p.disk_usage().written_bytes ); } #[test] fn cpu_usage_is_not_nan() { let mut system = sysinfo::System::new(); system.refresh_processes(); if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } // We need `collect` otherwise we can't have mutable access to `system`. #[allow(clippy::needless_collect)] let first_pids = system .processes() .iter() .take(10) .map(|(&pid, _)| pid) .collect::>(); let mut checked = 0; first_pids.into_iter().for_each(|pid| { system.refresh_process(pid); if let Some(p) = system.process(pid) { assert!(!p.cpu_usage().is_nan()); checked += 1; } }); assert!(checked > 0); } #[test] fn test_process_times() { use std::time::{SystemTime, UNIX_EPOCH}; if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("3") .arg("ProcessTimes") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("3") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); let mut s = sysinfo::System::new(); s.refresh_processes(); p.kill().expect("Unable to kill process."); if let Some(p) = s.process(pid) { assert_eq!(p.pid(), pid); assert!(p.run_time() >= 1); assert!(p.run_time() <= 2); assert!(p.start_time() > p.run_time()); // On linux, for whatever reason, the uptime seems to be older than the boot time, leading // to this weird `+ 3` to ensure the test is passing as it should... assert!( p.start_time() + 3 > SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(), ); assert!(p.start_time() >= s.boot_time()); } else { panic!("Process not found!"); } } // Checks that `session_id` is working. #[test] fn test_process_session_id() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut s = sysinfo::System::new(); s.refresh_processes(); assert!(s.processes().values().any(|p| p.session_id().is_some())); } // Checks that `refresh_processes` is removing dead processes. #[test] fn test_refresh_processes() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("300") .arg("RefreshProcesses") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("300") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); // Checks that the process is listed as it should. let mut s = sysinfo::System::new(); s.refresh_processes(); assert!(s.process(pid).is_some()); // Check that the process name is not empty. assert!(!s.process(pid).unwrap().name().is_empty()); p.kill().expect("Unable to kill process."); // We need this, otherwise the process will still be around as a zombie on linux. let _ = p.wait(); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(1)); s.refresh_processes(); // Checks that the process isn't listed anymore. assert!(s.process(pid).is_none()); } // Checks that `refresh_processes` is adding and removing task. #[test] #[cfg(all( any(target_os = "linux", target_os = "android"), not(feature = "unknown-ci") ))] fn test_refresh_tasks() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let task_name = "task_1_second"; std::thread::Builder::new() .name(task_name.into()) .spawn(|| { std::thread::sleep(std::time::Duration::from_secs(1)); }) .unwrap(); let pid = Pid::from_u32(std::process::id() as _); // Checks that the task is listed as it should. let mut s = sysinfo::System::new(); s.refresh_processes(); assert!(s .process(pid) .unwrap() .tasks .values() .any(|t| t.name() == task_name)); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(2)); s.refresh_processes(); assert!(!s .process(pid) .unwrap() .tasks .values() .any(|t| t.name() == task_name)); } // Checks that `refresh_process` is NOT removing dead processes. #[test] fn test_refresh_process() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("300") .arg("RefreshProcess") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("300") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id() as _); std::thread::sleep(std::time::Duration::from_secs(1)); // Checks that the process is listed as it should. let mut s = sysinfo::System::new(); s.refresh_process(pid); assert!(s.process(pid).is_some()); // Check that the process name is not empty. assert!(!s.process(pid).unwrap().name().is_empty()); p.kill().expect("Unable to kill process."); // We need this, otherwise the process will still be around as a zombie on linux. let _ = p.wait(); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(1)); assert!(!s.refresh_process(pid)); // Checks that the process is still listed. assert!(s.process(pid).is_some()); } #[test] fn test_wait_child() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let p = if cfg!(target_os = "windows") { std::process::Command::new("waitfor") .arg("/t") .arg("300") .arg("RefreshProcess") .stdout(std::process::Stdio::null()) .spawn() .unwrap() } else { std::process::Command::new("sleep") .arg("300") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let before = std::time::Instant::now(); let pid = Pid::from_u32(p.id() as _); let mut s = sysinfo::System::new(); s.refresh_process(pid); let process = s.process(pid).unwrap(); // Kill the child process. process.kill(); // Wait for child process should work. process.wait(); // Child process should not be present. assert!(!s.refresh_process(pid)); assert!(before.elapsed() < std::time::Duration::from_millis(1000)); } #[test] fn test_wait_non_child() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let before = std::time::Instant::now(); // spawn non child process. let p = if !cfg!(target_os = "linux") { return; } else { std::process::Command::new("setsid") .arg("-w") .arg("sleep") .arg("2") .stdout(std::process::Stdio::null()) .spawn() .unwrap() }; let pid = Pid::from_u32(p.id()); let mut s = sysinfo::System::new(); s.refresh_process(pid); let process = s.process(pid).expect("Process not found!"); // Wait for a non child process. process.wait(); // Child process should not be present. assert!(!s.refresh_process(pid)); // should wait for 2s. assert!( before.elapsed() > std::time::Duration::from_millis(1900), "Elapsed time {:?} is not greater than 1900ms", before.elapsed() ); assert!( before.elapsed() < std::time::Duration::from_millis(3000), "Elapsed time {:?} is not less than 3000ms", before.elapsed() ); } #[test] fn test_process_iterator_lifetimes() { if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let s = sysinfo::System::new_with_specifics( sysinfo::RefreshKind::new().with_processes(sysinfo::ProcessRefreshKind::new()), ); let process: Option<&sysinfo::Process>; { let name = String::from(""); // errors before PR #904: name does not live long enough process = s.processes_by_name(&name).next(); } process.unwrap(); let process: Option<&sysinfo::Process>; { // worked fine before and after: &'static str lives longer than System, error couldn't appear process = s.processes_by_name("").next(); } process.unwrap(); } // Regression test for . #[test] fn test_process_cpu_usage() { use sysinfo::{ProcessExt, System, SystemExt}; if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") { return; } let mut sys = System::new_all(); std::thread::sleep(System::MINIMUM_CPU_UPDATE_INTERVAL); sys.refresh_all(); let max_usage = sys.cpus().len() as f32 * 100.; for process in sys.processes().values() { assert!(process.cpu_usage() <= max_usage); } } sysinfo-0.28.4/tests/send_sync.rs000064400000000000000000000004371046102023000151120ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[test] #[allow(clippy::extra_unused_type_parameters)] fn test_send_sync() { fn is_send() {} fn is_sync() {} is_send::(); is_sync::(); } sysinfo-0.28.4/tests/uptime.rs000064400000000000000000000004371046102023000144300ustar 00000000000000// Take a look at the license at the top of the repository in the LICENSE file. #[test] fn test_uptime() { use sysinfo::SystemExt; if sysinfo::System::IS_SUPPORTED { let mut s = sysinfo::System::new(); s.refresh_all(); assert!(s.uptime() != 0); } }