From bb5bfc54bafcd15ece489c9e93a072d98d66208a Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Sun, 10 Oct 2021 08:47:09 -0700 Subject: [PATCH] Workaround cargo audit failure for rust-nix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workaround is to add common/rust-psutil and change it's Cargo.toml to use v0.22.2. And then change common/eth2/Cargo.toml to point to ../common/rust-psutil. Also updated Makefile: - lint: target to Allow needless_borrow to pass the `cargo lint` action. - test-release, test-debug: addded --exclude psutil if Windows_NT The failure is: Compiling cargo-audit v0.15.2 Finished release [optimized] target(s) in 3m 34s Replacing /home/runner/.cargo/bin/cargo-audit Replaced package `cargo-audit v0.15.2` with `cargo-audit v0.15.2` (executable `cargo-audit`) cargo audit Fetching advisory database from `https://github.com/RustSec/advisory-db.git` Loaded 367 security advisories (from /home/runner/.cargo/advisory-db) Updating crates.io index Scanning Cargo.lock for vulnerabilities (652 crate dependencies) Crate: nix error: 2 vulnerabilities found! Version: 0.17.0 Title: Out-of-bounds write in nix::unistd::getgrouplist Date: 2021-09-27 ID: RUSTSEC-2021-0119 URL: https://rustsec.org/advisories/RUSTSEC-2021-0119 Solution: Upgrade to ^0.20.2 OR ^0.21.2 OR ^0.22.2 OR >=0.23.0 Dependency tree: nix 0.17.0 Crate: nix Version: 0.22.0 Title: Out-of-bounds write in nix::unistd::getgrouplist Date: 2021-09-27 ID: RUSTSEC-2021-0119 URL: https://rustsec.org/advisories/RUSTSEC-2021-0119 Solution: Upgrade to ^0.20.2 OR ^0.21.2 OR ^0.22.2 OR >=0.23.0 Dependency tree: nix 0.22.0 Crate: stdweb Version: 0.4.20 Warning: unmaintained Title: stdweb is unmaintained Date: 2020-05-04 ID: RUSTSEC-2020-0056 URL: https://rustsec.org/advisories/RUSTSEC-2020-0056 Dependency tree: stdweb 0.4.20 └── time 0.2.27 warning: 1 allowed warning found make: *** [Makefile:154: audit] Error 1 Error: Process completed with exit code 2. --- Cargo.lock | 47 ++- Makefile | 15 +- common/eth2/Cargo.toml | 3 +- common/rust-psutil/.editorconfig | 21 + .../.github/ISSUE_TEMPLATE/bug_report.md | 19 + .../.github/ISSUE_TEMPLATE/other.md | 4 + .../rust-psutil/.github/workflows/rust-ci.yml | 87 ++++ common/rust-psutil/.gitignore | 14 + common/rust-psutil/.rustfmt.toml | 1 + common/rust-psutil/CHANGELOG.md | 149 +++++++ common/rust-psutil/Cargo.toml | 47 +++ common/rust-psutil/LICENSE | 19 + common/rust-psutil/README.md | 49 +++ common/rust-psutil/examples/all.rs | 65 +++ common/rust-psutil/examples/collectors.rs | 31 ++ common/rust-psutil/examples/kill.rs | 12 + common/rust-psutil/examples/ps.rs | 32 ++ common/rust-psutil/platform-support.md | 112 +++++ common/rust-psutil/src/common.rs | 40 ++ common/rust-psutil/src/cpu/cpu_count.rs | 9 + common/rust-psutil/src/cpu/cpu_freq.rs | 22 + common/rust-psutil/src/cpu/cpu_percent.rs | 49 +++ common/rust-psutil/src/cpu/cpu_stats.rs | 26 ++ common/rust-psutil/src/cpu/cpu_times.rs | 124 ++++++ .../rust-psutil/src/cpu/cpu_times_percent.rs | 182 +++++++++ common/rust-psutil/src/cpu/mod.rs | 16 + common/rust-psutil/src/cpu/os/bsd.rs | 24 ++ common/rust-psutil/src/cpu/os/linux.rs | 104 +++++ common/rust-psutil/src/cpu/os/mod.rs | 13 + common/rust-psutil/src/cpu/os/unix.rs | 28 ++ common/rust-psutil/src/cpu/os/windows.rs | 36 ++ .../rust-psutil/src/cpu/sys/linux/cpu_freq.rs | 11 + .../src/cpu/sys/linux/cpu_stats.rs | 7 + .../src/cpu/sys/linux/cpu_times.rs | 131 ++++++ common/rust-psutil/src/cpu/sys/linux/mod.rs | 7 + .../src/cpu/sys/macos/cpu_times.rs | 199 +++++++++ common/rust-psutil/src/cpu/sys/macos/mod.rs | 3 + common/rust-psutil/src/cpu/sys/mod.rs | 9 + .../rust-psutil/src/disk/disk_io_counters.rs | 171 ++++++++ common/rust-psutil/src/disk/filesystem.rs | 146 +++++++ common/rust-psutil/src/disk/mod.rs | 10 + common/rust-psutil/src/disk/os/freebsd.rs | 25 ++ common/rust-psutil/src/disk/os/linux.rs | 43 ++ common/rust-psutil/src/disk/os/macos.rs | 19 + common/rust-psutil/src/disk/os/mod.rs | 8 + common/rust-psutil/src/disk/os/windows.rs | 19 + common/rust-psutil/src/disk/partition.rs | 44 ++ .../src/disk/sys/linux/disk_io_counters.rs | 107 +++++ common/rust-psutil/src/disk/sys/linux/mod.rs | 6 + .../src/disk/sys/linux/partitions.rs | 41 ++ .../src/disk/sys/macos/disk_io_counters.rs | 8 + common/rust-psutil/src/disk/sys/macos/mod.rs | 4 + common/rust-psutil/src/disk/sys/mod.rs | 17 + .../src/disk/sys/unix/disk_usage.rs | 63 +++ common/rust-psutil/src/disk/sys/unix/mod.rs | 7 + .../src/disk/sys/unix/partitions.rs | 84 ++++ common/rust-psutil/src/errors.rs | 127 ++++++ common/rust-psutil/src/host/info.rs | 38 ++ common/rust-psutil/src/host/loadavg.rs | 18 + common/rust-psutil/src/host/mod.rs | 11 + .../src/host/sys/linux/boot_time.rs | 38 ++ .../rust-psutil/src/host/sys/linux/loadavg.rs | 63 +++ common/rust-psutil/src/host/sys/linux/mod.rs | 9 + .../rust-psutil/src/host/sys/linux/uptime.rs | 47 +++ .../rust-psutil/src/host/sys/linux/users.rs | 7 + common/rust-psutil/src/host/sys/mod.rs | 13 + common/rust-psutil/src/host/sys/unix/info.rs | 26 ++ common/rust-psutil/src/host/sys/unix/mod.rs | 3 + common/rust-psutil/src/host/user.rs | 32 ++ common/rust-psutil/src/lib.rs | 64 +++ common/rust-psutil/src/memory/mod.rs | 8 + common/rust-psutil/src/memory/os/bsd.rs | 30 ++ common/rust-psutil/src/memory/os/linux.rs | 33 ++ common/rust-psutil/src/memory/os/macos.rs | 12 + common/rust-psutil/src/memory/os/mod.rs | 13 + common/rust-psutil/src/memory/os/unix.rs | 20 + common/rust-psutil/src/memory/swap_memory.rs | 50 +++ .../src/memory/sys/linux/common.rs | 34 ++ .../rust-psutil/src/memory/sys/linux/mod.rs | 7 + .../src/memory/sys/linux/swap_memory.rs | 51 +++ .../src/memory/sys/linux/virtual_memory.rs | 51 +++ .../src/memory/sys/macos/common.rs | 95 +++++ .../rust-psutil/src/memory/sys/macos/mod.rs | 7 + .../src/memory/sys/macos/swap_memory.rs | 60 +++ .../src/memory/sys/macos/virtual_memory.rs | 64 +++ common/rust-psutil/src/memory/sys/mod.rs | 9 + .../rust-psutil/src/memory/virtual_memory.rs | 60 +++ common/rust-psutil/src/network/mod.rs | 12 + .../rust-psutil/src/network/net_connection.rs | 46 +++ common/rust-psutil/src/network/net_if_addr.rs | 31 ++ .../rust-psutil/src/network/net_if_stats.rs | 34 ++ .../rust-psutil/src/network/net_io_couters.rs | 163 ++++++++ .../rust-psutil/src/network/sys/linux/mod.rs | 10 + .../src/network/sys/linux/net_connections.rs | 10 + .../src/network/sys/linux/net_if_addrs.rs | 8 + .../src/network/sys/linux/net_if_stats.rs | 8 + .../src/network/sys/linux/net_io_counters.rs | 63 +++ .../rust-psutil/src/network/sys/macos/mod.rs | 4 + .../src/network/sys/macos/net_io_counters.rs | 197 +++++++++ common/rust-psutil/src/network/sys/mod.rs | 10 + common/rust-psutil/src/process/collector.rs | 60 +++ common/rust-psutil/src/process/cpu_times.rs | 69 ++++ common/rust-psutil/src/process/errors.rs | 40 ++ common/rust-psutil/src/process/memory.rs | 71 ++++ common/rust-psutil/src/process/mod.rs | 21 + common/rust-psutil/src/process/open_file.rs | 13 + common/rust-psutil/src/process/os/bsd.rs | 13 + common/rust-psutil/src/process/os/freebsd.rs | 29 ++ .../src/process/os/linux/cpu_times.rs | 13 + .../rust-psutil/src/process/os/linux/mod.rs | 9 + .../src/process/os/linux/oneshot.rs | 46 +++ .../src/process/os/linux/process.rs | 128 ++++++ .../src/process/os/linux/procfs/mod.rs | 7 + .../src/process/os/linux/procfs/stat.rs | 386 ++++++++++++++++++ .../src/process/os/linux/procfs/statm.rs | 66 +++ .../src/process/os/linux/procfs/status.rs | 117 ++++++ .../rust-psutil/src/process/os/macos/kinfo.rs | 257 ++++++++++++ .../rust-psutil/src/process/os/macos/mod.rs | 5 + .../src/process/os/macos/process.rs | 13 + common/rust-psutil/src/process/os/mod.rs | 17 + common/rust-psutil/src/process/os/unix.rs | 88 ++++ common/rust-psutil/src/process/os/windows.rs | 58 +++ common/rust-psutil/src/process/process.rs | 344 ++++++++++++++++ common/rust-psutil/src/process/status.rs | 52 +++ .../rust-psutil/src/process/sys/linux/mod.rs | 7 + .../rust-psutil/src/process/sys/linux/pids.rs | 21 + .../src/process/sys/linux/process.rs | 170 ++++++++ .../src/process/sys/linux/status.rs | 68 +++ .../rust-psutil/src/process/sys/macos/mod.rs | 7 + .../rust-psutil/src/process/sys/macos/pids.rs | 14 + .../src/process/sys/macos/process.rs | 187 +++++++++ .../src/process/sys/macos/status.rs | 40 ++ common/rust-psutil/src/process/sys/mod.rs | 9 + common/rust-psutil/src/sensors/fan_sensor.rs | 11 + common/rust-psutil/src/sensors/mod.rs | 11 + .../rust-psutil/src/sensors/sys/linux/fans.rs | 8 + .../rust-psutil/src/sensors/sys/linux/mod.rs | 5 + .../src/sensors/sys/linux/temperatures.rs | 166 ++++++++ .../rust-psutil/src/sensors/sys/macos/fans.rs | 8 + .../rust-psutil/src/sensors/sys/macos/mod.rs | 5 + .../src/sensors/sys/macos/temperatures.rs | 6 + common/rust-psutil/src/sensors/sys/mod.rs | 9 + .../src/sensors/temperature_sensor.rs | 42 ++ common/rust-psutil/src/types.rs | 37 ++ common/rust-psutil/src/unix.rs | 18 + common/rust-psutil/src/utils.rs | 16 + 146 files changed, 7056 insertions(+), 16 deletions(-) create mode 100644 common/rust-psutil/.editorconfig create mode 100644 common/rust-psutil/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 common/rust-psutil/.github/ISSUE_TEMPLATE/other.md create mode 100644 common/rust-psutil/.github/workflows/rust-ci.yml create mode 100644 common/rust-psutil/.gitignore create mode 100644 common/rust-psutil/.rustfmt.toml create mode 100644 common/rust-psutil/CHANGELOG.md create mode 100644 common/rust-psutil/Cargo.toml create mode 100644 common/rust-psutil/LICENSE create mode 100644 common/rust-psutil/README.md create mode 100644 common/rust-psutil/examples/all.rs create mode 100644 common/rust-psutil/examples/collectors.rs create mode 100644 common/rust-psutil/examples/kill.rs create mode 100644 common/rust-psutil/examples/ps.rs create mode 100644 common/rust-psutil/platform-support.md create mode 100644 common/rust-psutil/src/common.rs create mode 100644 common/rust-psutil/src/cpu/cpu_count.rs create mode 100644 common/rust-psutil/src/cpu/cpu_freq.rs create mode 100644 common/rust-psutil/src/cpu/cpu_percent.rs create mode 100644 common/rust-psutil/src/cpu/cpu_stats.rs create mode 100644 common/rust-psutil/src/cpu/cpu_times.rs create mode 100644 common/rust-psutil/src/cpu/cpu_times_percent.rs create mode 100644 common/rust-psutil/src/cpu/mod.rs create mode 100644 common/rust-psutil/src/cpu/os/bsd.rs create mode 100644 common/rust-psutil/src/cpu/os/linux.rs create mode 100644 common/rust-psutil/src/cpu/os/mod.rs create mode 100644 common/rust-psutil/src/cpu/os/unix.rs create mode 100644 common/rust-psutil/src/cpu/os/windows.rs create mode 100644 common/rust-psutil/src/cpu/sys/linux/cpu_freq.rs create mode 100644 common/rust-psutil/src/cpu/sys/linux/cpu_stats.rs create mode 100644 common/rust-psutil/src/cpu/sys/linux/cpu_times.rs create mode 100644 common/rust-psutil/src/cpu/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/cpu/sys/macos/cpu_times.rs create mode 100644 common/rust-psutil/src/cpu/sys/macos/mod.rs create mode 100644 common/rust-psutil/src/cpu/sys/mod.rs create mode 100644 common/rust-psutil/src/disk/disk_io_counters.rs create mode 100644 common/rust-psutil/src/disk/filesystem.rs create mode 100644 common/rust-psutil/src/disk/mod.rs create mode 100644 common/rust-psutil/src/disk/os/freebsd.rs create mode 100644 common/rust-psutil/src/disk/os/linux.rs create mode 100644 common/rust-psutil/src/disk/os/macos.rs create mode 100644 common/rust-psutil/src/disk/os/mod.rs create mode 100644 common/rust-psutil/src/disk/os/windows.rs create mode 100644 common/rust-psutil/src/disk/partition.rs create mode 100644 common/rust-psutil/src/disk/sys/linux/disk_io_counters.rs create mode 100644 common/rust-psutil/src/disk/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/disk/sys/linux/partitions.rs create mode 100644 common/rust-psutil/src/disk/sys/macos/disk_io_counters.rs create mode 100644 common/rust-psutil/src/disk/sys/macos/mod.rs create mode 100644 common/rust-psutil/src/disk/sys/mod.rs create mode 100644 common/rust-psutil/src/disk/sys/unix/disk_usage.rs create mode 100644 common/rust-psutil/src/disk/sys/unix/mod.rs create mode 100644 common/rust-psutil/src/disk/sys/unix/partitions.rs create mode 100644 common/rust-psutil/src/errors.rs create mode 100644 common/rust-psutil/src/host/info.rs create mode 100644 common/rust-psutil/src/host/loadavg.rs create mode 100644 common/rust-psutil/src/host/mod.rs create mode 100644 common/rust-psutil/src/host/sys/linux/boot_time.rs create mode 100644 common/rust-psutil/src/host/sys/linux/loadavg.rs create mode 100644 common/rust-psutil/src/host/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/host/sys/linux/uptime.rs create mode 100644 common/rust-psutil/src/host/sys/linux/users.rs create mode 100644 common/rust-psutil/src/host/sys/mod.rs create mode 100644 common/rust-psutil/src/host/sys/unix/info.rs create mode 100644 common/rust-psutil/src/host/sys/unix/mod.rs create mode 100644 common/rust-psutil/src/host/user.rs create mode 100644 common/rust-psutil/src/lib.rs create mode 100644 common/rust-psutil/src/memory/mod.rs create mode 100644 common/rust-psutil/src/memory/os/bsd.rs create mode 100644 common/rust-psutil/src/memory/os/linux.rs create mode 100644 common/rust-psutil/src/memory/os/macos.rs create mode 100644 common/rust-psutil/src/memory/os/mod.rs create mode 100644 common/rust-psutil/src/memory/os/unix.rs create mode 100644 common/rust-psutil/src/memory/swap_memory.rs create mode 100644 common/rust-psutil/src/memory/sys/linux/common.rs create mode 100644 common/rust-psutil/src/memory/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/memory/sys/linux/swap_memory.rs create mode 100644 common/rust-psutil/src/memory/sys/linux/virtual_memory.rs create mode 100644 common/rust-psutil/src/memory/sys/macos/common.rs create mode 100644 common/rust-psutil/src/memory/sys/macos/mod.rs create mode 100644 common/rust-psutil/src/memory/sys/macos/swap_memory.rs create mode 100644 common/rust-psutil/src/memory/sys/macos/virtual_memory.rs create mode 100644 common/rust-psutil/src/memory/sys/mod.rs create mode 100644 common/rust-psutil/src/memory/virtual_memory.rs create mode 100644 common/rust-psutil/src/network/mod.rs create mode 100644 common/rust-psutil/src/network/net_connection.rs create mode 100644 common/rust-psutil/src/network/net_if_addr.rs create mode 100644 common/rust-psutil/src/network/net_if_stats.rs create mode 100644 common/rust-psutil/src/network/net_io_couters.rs create mode 100644 common/rust-psutil/src/network/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/network/sys/linux/net_connections.rs create mode 100644 common/rust-psutil/src/network/sys/linux/net_if_addrs.rs create mode 100644 common/rust-psutil/src/network/sys/linux/net_if_stats.rs create mode 100644 common/rust-psutil/src/network/sys/linux/net_io_counters.rs create mode 100644 common/rust-psutil/src/network/sys/macos/mod.rs create mode 100644 common/rust-psutil/src/network/sys/macos/net_io_counters.rs create mode 100644 common/rust-psutil/src/network/sys/mod.rs create mode 100644 common/rust-psutil/src/process/collector.rs create mode 100644 common/rust-psutil/src/process/cpu_times.rs create mode 100644 common/rust-psutil/src/process/errors.rs create mode 100644 common/rust-psutil/src/process/memory.rs create mode 100644 common/rust-psutil/src/process/mod.rs create mode 100644 common/rust-psutil/src/process/open_file.rs create mode 100644 common/rust-psutil/src/process/os/bsd.rs create mode 100644 common/rust-psutil/src/process/os/freebsd.rs create mode 100644 common/rust-psutil/src/process/os/linux/cpu_times.rs create mode 100644 common/rust-psutil/src/process/os/linux/mod.rs create mode 100644 common/rust-psutil/src/process/os/linux/oneshot.rs create mode 100644 common/rust-psutil/src/process/os/linux/process.rs create mode 100644 common/rust-psutil/src/process/os/linux/procfs/mod.rs create mode 100644 common/rust-psutil/src/process/os/linux/procfs/stat.rs create mode 100644 common/rust-psutil/src/process/os/linux/procfs/statm.rs create mode 100644 common/rust-psutil/src/process/os/linux/procfs/status.rs create mode 100644 common/rust-psutil/src/process/os/macos/kinfo.rs create mode 100644 common/rust-psutil/src/process/os/macos/mod.rs create mode 100644 common/rust-psutil/src/process/os/macos/process.rs create mode 100644 common/rust-psutil/src/process/os/mod.rs create mode 100644 common/rust-psutil/src/process/os/unix.rs create mode 100644 common/rust-psutil/src/process/os/windows.rs create mode 100644 common/rust-psutil/src/process/process.rs create mode 100644 common/rust-psutil/src/process/status.rs create mode 100644 common/rust-psutil/src/process/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/process/sys/linux/pids.rs create mode 100644 common/rust-psutil/src/process/sys/linux/process.rs create mode 100644 common/rust-psutil/src/process/sys/linux/status.rs create mode 100644 common/rust-psutil/src/process/sys/macos/mod.rs create mode 100644 common/rust-psutil/src/process/sys/macos/pids.rs create mode 100644 common/rust-psutil/src/process/sys/macos/process.rs create mode 100644 common/rust-psutil/src/process/sys/macos/status.rs create mode 100644 common/rust-psutil/src/process/sys/mod.rs create mode 100644 common/rust-psutil/src/sensors/fan_sensor.rs create mode 100644 common/rust-psutil/src/sensors/mod.rs create mode 100644 common/rust-psutil/src/sensors/sys/linux/fans.rs create mode 100644 common/rust-psutil/src/sensors/sys/linux/mod.rs create mode 100644 common/rust-psutil/src/sensors/sys/linux/temperatures.rs create mode 100644 common/rust-psutil/src/sensors/sys/macos/fans.rs create mode 100644 common/rust-psutil/src/sensors/sys/macos/mod.rs create mode 100644 common/rust-psutil/src/sensors/sys/macos/temperatures.rs create mode 100644 common/rust-psutil/src/sensors/sys/mod.rs create mode 100644 common/rust-psutil/src/sensors/temperature_sensor.rs create mode 100644 common/rust-psutil/src/types.rs create mode 100644 common/rust-psutil/src/unix.rs create mode 100644 common/rust-psutil/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index a6ae8075d45..125aa10e726 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,9 +519,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitvec" @@ -1149,7 +1149,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1" dependencies = [ - "nix 0.22.0", + "nix 0.22.2", "winapi", ] @@ -2122,6 +2122,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da62c4f1b81918835a8c6a484a397775fff5953fe83529afd51b05f5c6a6617d" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -4075,9 +4084,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.17.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" dependencies = [ "bitflags", "cc", @@ -4088,9 +4097,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba" dependencies = [ "bitflags", "cc", @@ -4497,6 +4506,9 @@ name = "platforms" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feb3b2b1033b8a60b4da6ee470325f887758c95d5320f52f9ce0df055a55940e" +dependencies = [ + "serde", +] [[package]] name = "plotters" @@ -4748,18 +4760,19 @@ checksum = "23129d50f2c9355ced935fce8a08bd706ee2e7ce2b3b33bf61dace0e379ac63a" [[package]] name = "psutil" version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e780a52bf9358cb8257cac630b130dc603901f7488f8eef13e2d512cead10739" dependencies = [ "cfg-if 0.1.10", "darwin-libproc", "derive_more", + "float-cmp", "glob", "mach", - "nix 0.17.0", + "nix 0.22.2", "num_cpus", "once_cell", "platforms", + "serde", + "signal", "thiserror", "unescape", ] @@ -5313,9 +5326,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.4.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" dependencies = [ "bitflags", "core-foundation", @@ -5510,6 +5523,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f6ce83b159ab6984d2419f495134972b48754d13ff2e3f8c998339942b56ed9" +dependencies = [ + "libc", + "nix 0.14.1", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" diff --git a/Makefile b/Makefile index 6ff132bda29..530a69cdd01 100644 --- a/Makefile +++ b/Makefile @@ -80,12 +80,20 @@ build-release-tarballs: # Runs the full workspace tests in **release**, without downloading any additional # test vectors. test-release: - cargo test --workspace --release --exclude ef_tests --exclude beacon_chain +ifeq ($(OS),Windows_NT) + cargo test --workspace --release --exclude ef_tests --exclude beacon_chain --exclude psutil +else + cargo test --workspace --release --exclude ef_tests --exclude beacon_chain --exclude psutil +endif # Runs the full workspace tests in **debug**, without downloading any additional test # vectors. test-debug: - cargo test --workspace --exclude ef_tests --exclude beacon_chain +ifeq ($(OS),Windows_NT) + cargo test --workspace --exclude ef_tests --exclude beacon_chain --exclude psutil +else + cargo test --workspace --exclude ef_tests --exclude beacon_chain --exclude psutil +endif # Runs cargo-fmt (linter). cargo-fmt: @@ -134,7 +142,8 @@ lint: -D warnings \ -A clippy::from-over-into \ -A clippy::upper-case-acronyms \ - -A clippy::vec-init-then-push + -A clippy::vec-init-then-push \ + -A clippy::needless_borrow # Runs the makefile in the `ef_tests` repo. # diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index 19c12fd0d4e..643f2b67668 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -27,7 +27,8 @@ futures = "0.3.8" store = { path = "../../beacon_node/store", optional = true } [target.'cfg(target_os = "linux")'.dependencies] -psutil = { version = "3.2.0", optional = true } +#psutil = { version = "3.2.0", optional = true } +psutil = { path = "../../common/rust-psutil", optional = true } procinfo = { version = "0.4.2", optional = true } [features] diff --git a/common/rust-psutil/.editorconfig b/common/rust-psutil/.editorconfig new file mode 100644 index 00000000000..fb0704d0bde --- /dev/null +++ b/common/rust-psutil/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +insert_final_newline = true +indent_style = tab +indent_size = 4 +trim_trailing_whitespace = true + +[*.{js,jsx,ts,tsx,html,php,vim,toml,yml,json}] +indent_size = 2 + +[*.yml] +indent_style = space + +[Makefile,*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/common/rust-psutil/.github/ISSUE_TEMPLATE/bug_report.md b/common/rust-psutil/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..cb4b13d2072 --- /dev/null +++ b/common/rust-psutil/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,19 @@ +--- +name: Bug report +about: Template to report bugs. +--- + + + +Please provide any of the following information if relevant: + +- rust-psutil version: +- target platform info + - kernel version: + - architecture: + - (if linux) distro: +- any relevant hardware info: diff --git a/common/rust-psutil/.github/ISSUE_TEMPLATE/other.md b/common/rust-psutil/.github/ISSUE_TEMPLATE/other.md new file mode 100644 index 00000000000..3edfbb4a72d --- /dev/null +++ b/common/rust-psutil/.github/ISSUE_TEMPLATE/other.md @@ -0,0 +1,4 @@ +--- +name: Other +about: No template. +--- diff --git a/common/rust-psutil/.github/workflows/rust-ci.yml b/common/rust-psutil/.github/workflows/rust-ci.yml new file mode 100644 index 00000000000..e938cd939f4 --- /dev/null +++ b/common/rust-psutil/.github/workflows/rust-ci.yml @@ -0,0 +1,87 @@ +# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md + +name: Rust CI + +on: [push, pull_request] + +jobs: + check: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macOS-latest + steps: + - name: Checkout sources + uses: actions/checkout@v1 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + + test: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v1 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + + format: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v1 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v1 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: clippy + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings diff --git a/common/rust-psutil/.gitignore b/common/rust-psutil/.gitignore new file mode 100644 index 00000000000..9172acb8dff --- /dev/null +++ b/common/rust-psutil/.gitignore @@ -0,0 +1,14 @@ +# Compiled files +*.o +*.so +*.rlib +*.dll + +# Executables +*.exe + +# Generated by Cargo +/target/ + +# This is a library +Cargo.lock diff --git a/common/rust-psutil/.rustfmt.toml b/common/rust-psutil/.rustfmt.toml new file mode 100644 index 00000000000..218e203215e --- /dev/null +++ b/common/rust-psutil/.rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/common/rust-psutil/CHANGELOG.md b/common/rust-psutil/CHANGELOG.md new file mode 100644 index 00000000000..e31420d53e5 --- /dev/null +++ b/common/rust-psutil/CHANGELOG.md @@ -0,0 +1,149 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +> **Types of changes**: +> +> - **Added**: for new features. +> - **Changed**: for changes in existing functionality. +> - **Deprecated**: for soon-to-be removed features. +> - **Removed**: for now removed features. +> - **Fixed**: for any bug fixes. +> - **Security**: in case of vulnerabilities. + +## [Unreleased] + +## [v3.2.1] - 2021-04-11 + +### Fix + +- [disk] add missing `pub mod os;` to `disk/mod.rs` + +## [v3.2.0] - 2020-09-26 + +### Added + +- Make all public types serde Serializable and Deserializable + +### Fix + +- significantly reduce compile times by switching from snafu to thiserror +- [process][macos] fix macos process kinfo "Cannot allocate memory" errors + +## [v3.1.0] - 2020-05-10 + +### Added + +- [process][linux] implement some oneshot functions + +### Fix + +- [process] fix process CPU percent calculation when using the ProcessCollector + +## [v3.0.1] - 2020-02-12 + +### Fix + +- fix compilation if the sensors feature is not enabled + +## [v3.0.0] - 2020-02-10 + +### Added + +- [disk] implement DiskIoCountersCollector::disk_io_counters +- [process] make `ProcessCollector` more efficient +- [process][linux] implement `cpu_times.iowait` +- [sensors][linux] implement `thermal_zone` temperatures + +### Changed + +- Switch from io::Error to a custom error type +- [cpu][linux] change `cpu_times{_percent}.{steal,guest,guest_nice}` to `Option`s +- [process] status parsing now returns a ParseStatusError +- [process][linux] change `cpu_times.iowait` from `Duration` to `Option` +- [process][linux] change `process.environ` return type from `io::Result` to `ProcessResult` + +### Fix + +- fix several 'overflow when subtracting durations' panics +- [cpu][linux] fix calculation of cpu_percent, CpuTimes.total, and CpuTimesPercent.total +- [disk][linux] unescape partition mountpoint escape sequences + +### Removed + +- [host] remove runnable, total_runnable, and last_pid from LoadAvg + +## [v2.0.0] - 2020-02-04 + +### Added + +- [macos] get macos to compile +- [cpu][all] implement cpu_count and cpu_count_physical +- [cpu][macos] implement cpu_times, cpu_times_percent, and cpu_percent +- [disk][unix] implement disk_usage +- [disk][unix] implement partitions +- [host][linux] implement boot_time +- [host] add Info +- [host][unix] implement Info +- [memory][macos] implement virtual_memory and swap_memory +- [network][macos] implement io counters +- [process] add ProcessCollector +- [process][unix] implement all signal methods +- [process][macos] implement Process::new +- [process][macos] implement process.name +- [process][macos] implement processes and pids +- [process][macos] implement Process.cpu_percent +- [process][macos] implement Process.cpu_times +- [process][macos] implement Process.memory_percent +- [process][macos] implement Process.memory_info +- [process][linux] implement pids +- [process][linux] implement pid_exists +- [process][linux] implement Process.cpu_percent +- [process][linux] implement Process.cpu_times +- [process][linux] implement Process.memory_percent +- [process][linux] implement Process.memory_info +- [process][linux] implement Process.uids +- [process][linux] implement Process.gids +- [process][linux] implement Process.send_signal +- [process][linux] implement Process.is_replaced +- [process][linux] implement Process.replace +- [process][linux] implement Process.parent +- [sensors][linux] implement temperatures + +### Changed + +- Overhaul the API +- [cpu] replace cpu_percent functions with CpuPercentCollector +- [disk] rename disk_io_counters_{perdisk,per_partition} + +### Removed + +- Remove interval duration argument from various cpu percent functions +- Remove nowrap argument from collectors +- Remove reset method from collectors +- Remove inodes from DiskUsage +- Remove standalone CpuTimesPercent functions in favor of CpuTimesPercentCollector + +### Fixed + +- [memory][linux] fix swap percent calculation + +## [v1.7.0] - 2019-08-01 + +### Changed + +- Remove `psutil::system` and replace with `psutil::{cpu, memory, host}` + +### Removed + +- Remove `getpid()`, `getppid()`, `Process.from_pidfile()`, `write_pidfile()`, and `read_pidfile()` + +[Unreleased]: https://github.com/rust-psutil/rust-psutil/compare/v3.2.0...HEAD +[v3.2.0]: https://github.com/rust-psutil/rust-psutil/compare/v3.1.0...v3.2.0 +[v3.1.0]: https://github.com/rust-psutil/rust-psutil/compare/v3.0.1...v3.1.0 +[v3.0.1]: https://github.com/rust-psutil/rust-psutil/compare/v3.0.0...v3.0.1 +[v3.0.0]: https://github.com/rust-psutil/rust-psutil/compare/v2.0.0...v3.0.0 +[v2.0.0]: https://github.com/rust-psutil/rust-psutil/compare/v1.7.0...v2.0.0 +[v1.7.0]: https://github.com/rust-psutil/rust-psutil/compare/v1.6.0...v1.7.0 diff --git a/common/rust-psutil/Cargo.toml b/common/rust-psutil/Cargo.toml new file mode 100644 index 00000000000..f269635f17f --- /dev/null +++ b/common/rust-psutil/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "psutil" +version = "3.2.1" +authors = [ + "Caleb Bassi ", + "Rob Day ", + "Sam Clements ", +] +description = "Process and system monitoring library" +repository = "https://github.com/rust-psutil/rust-psutil" +readme = "README.md" +license = "MIT" +edition = "2018" + +[dependencies] +cfg-if = "0.1.10" +nix = "0.22.2" +once_cell = "1.2.0" +thiserror = "1.0.20" + +derive_more = { version = "0.99.2", optional = true, default-features = false, features = ["add", "sum"]} +glob = { version = "0.3.0", optional = true } +num_cpus = { version = "1.11.1", optional = true } +platforms = { version = "0.2.1", optional = true } +renamed_serde = { version = "1.0", optional = true, package = "serde", features = ["derive"] } +signal = { version = "0.7.0", optional = true } +unescape = { version = "0.1.0", optional = true } + +[target.'cfg(target_os = "macos")'.dependencies] +darwin-libproc = { version = "0.1.1", optional = true } +mach = { version = "0.3.2", optional = true } + +[features] +default = ["cpu", "disk", "host", "memory", "network", "process", "sensors"] +serde = ["renamed_serde", "platforms/serde"] + +# Modules +cpu = ["mach", "num_cpus"] +disk = ["derive_more", "unescape"] +host = ["platforms"] +memory = ["mach"] +network = ["derive_more"] +process = ["darwin-libproc", "mach", "memory"] +sensors = ["glob"] + +[dev-dependencies] +float-cmp = "0.6.0" diff --git a/common/rust-psutil/LICENSE b/common/rust-psutil/LICENSE new file mode 100644 index 00000000000..53127447d33 --- /dev/null +++ b/common/rust-psutil/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Sam Clements + +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. diff --git a/common/rust-psutil/README.md b/common/rust-psutil/README.md new file mode 100644 index 00000000000..b669a0c78df --- /dev/null +++ b/common/rust-psutil/README.md @@ -0,0 +1,49 @@ +# rust-psutil + +[![crates.io](https://img.shields.io/crates/v/psutil.svg)](https://crates.io/crates/psutil) +[![docs.rs](https://docs.rs/psutil/badge.svg)](https://docs.rs/psutil) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.39+-green.svg) +[![Matrix](https://img.shields.io/badge/matrix-%23rust--psutil-blue.svg)](https://matrix.to/#/#rust-psutil:matrix.org) + +A process and system monitoring library for Rust, heavily inspired by the [psutil] module for Python. + +Note about versioning: rust-psutil prematurely hit version 1.0, so even though it has passed 1.0, it is still going through a lot of changes and the API may be relatively unstable. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +psutil = "3.2.1" +``` + +Or to only use certain submodules: + +```toml +[dependencies] +psutil = { version = "3.2.1", default-features = false, features = ["cpu", "process"] } +``` + +## Platform Support + +Currently, only Linux and macOS are supported, but support is planned for all major platforms. + +[platform-support.md](./platform-support.md) details the implementation level of each platform. + +## Apps using rust-psutil + +- [procrec](https://github.com/gh0st42/procrec) + +## Related projects + +- Rust + - [hiem](https://github.com/heim-rs/heim) + - [rust-battery](https://github.com/svartalf/rust-battery) + - [sys-info-rs](https://github.com/FillZpp/sys-info-rs) + - [sysinfo](https://github.com/GuillaumeGomez/sysinfo) + - [systemstat](https://github.com/myfreeweb/systemstat) +- [gopsutil](https://github.com/shirou/gopsutil) +- [psutil] + +[psutil]: https://github.com/giampaolo/psutil diff --git a/common/rust-psutil/examples/all.rs b/common/rust-psutil/examples/all.rs new file mode 100644 index 00000000000..c2a36d895a8 --- /dev/null +++ b/common/rust-psutil/examples/all.rs @@ -0,0 +1,65 @@ +use std::thread; +use std::time::Duration; + +use psutil::*; + +fn main() { + let block_time = Duration::from_millis(1000); + + let mut cpu_percent_collector = cpu::CpuPercentCollector::new().unwrap(); + let mut cpu_times_percent_collector = cpu::CpuTimesPercentCollector::new().unwrap(); + + let mut disk_io_counters_collector = disk::DiskIoCountersCollector::default(); + + let mut net_io_counters_collector = network::NetIoCountersCollector::default(); + + thread::sleep(block_time); + + let cpu_percents_percpu = cpu_percent_collector.cpu_percent_percpu().unwrap(); + let cpu_times_percpu = cpu::cpu_times_percpu().unwrap(); + let cpu_times_percent_percpu = cpu_times_percent_collector + .cpu_times_percent_percpu() + .unwrap(); + + let disk_io_counters_per_partition = disk_io_counters_collector + .disk_io_counters_per_partition() + .unwrap(); + let partitions = disk::partitions_physical().unwrap(); + let disk_usage = disk::disk_usage("/").unwrap(); + + let uptime = host::uptime().unwrap(); + let boot_time = host::boot_time().unwrap(); + let loadavg = host::loadavg().unwrap(); + + let virtual_memory = memory::virtual_memory().unwrap(); + let swap_memory = memory::swap_memory().unwrap(); + + let net_io_counters = net_io_counters_collector.net_io_counters().unwrap(); + + let pids = process::pids().unwrap(); + let processes = process::processes().unwrap(); + + let temperatures = sensors::temperatures(); + + dbg!(cpu_percents_percpu); + dbg!(cpu_times_percpu); + dbg!(cpu_times_percent_percpu); + + dbg!(disk_io_counters_per_partition); + dbg!(partitions); + dbg!(disk_usage); + + dbg!(uptime); + dbg!(boot_time); + dbg!(loadavg); + + dbg!(virtual_memory); + dbg!(swap_memory); + + dbg!(net_io_counters); + + dbg!(pids); + dbg!(processes); + + dbg!(temperatures); +} diff --git a/common/rust-psutil/examples/collectors.rs b/common/rust-psutil/examples/collectors.rs new file mode 100644 index 00000000000..ac1d76a17f6 --- /dev/null +++ b/common/rust-psutil/examples/collectors.rs @@ -0,0 +1,31 @@ +use std::thread; +use std::time::Duration; + +use psutil::{cpu, disk, network}; + +fn main() { + let block_time = Duration::from_millis(1000); + + let mut disk_io_counters_collector = disk::DiskIoCountersCollector::default(); + let mut prev_disk_io_counters = disk_io_counters_collector.disk_io_counters().unwrap(); + + let mut net_io_counters_collector = network::NetIoCountersCollector::default(); + let mut prev_net_io_counters = net_io_counters_collector.net_io_counters().unwrap(); + + let mut cpu_percent_collector = cpu::CpuPercentCollector::new().unwrap(); + + loop { + thread::sleep(block_time); + + let current_disk_io_counters = disk_io_counters_collector.disk_io_counters().unwrap(); + let current_net_io_counters = net_io_counters_collector.net_io_counters().unwrap(); + let cpu_percents = cpu_percent_collector.cpu_percent_percpu().unwrap(); + + dbg!(current_disk_io_counters.clone() - prev_disk_io_counters); + dbg!(current_net_io_counters.clone() - prev_net_io_counters); + dbg!(cpu_percents); + + prev_disk_io_counters = current_disk_io_counters; + prev_net_io_counters = current_net_io_counters; + } +} diff --git a/common/rust-psutil/examples/kill.rs b/common/rust-psutil/examples/kill.rs new file mode 100644 index 00000000000..be9cbd49da6 --- /dev/null +++ b/common/rust-psutil/examples/kill.rs @@ -0,0 +1,12 @@ +//! Kill a process, reading it's PID as a cli argument. + +use psutil::process::Process; + +fn main() { + let args: Vec = std::env::args().collect(); + let process = Process::new(args[1].parse().unwrap()).unwrap(); + + if let Err(error) = process.kill() { + println!("Failed to kill process: {}.", error); + }; +} diff --git a/common/rust-psutil/examples/ps.rs b/common/rust-psutil/examples/ps.rs new file mode 100644 index 00000000000..5574143d85c --- /dev/null +++ b/common/rust-psutil/examples/ps.rs @@ -0,0 +1,32 @@ +use std::thread; +use std::time::Duration; + +use psutil::process::processes; + +// TODO: update to actually match the output of `ps aux` + +fn main() { + let processes = processes().unwrap(); + + thread::sleep(Duration::from_secs(1)); + + println!( + "{:>6} {:>4} {:>4} {:.100}", + "PID", "%CPU", "%MEM", "COMMAND" + ); + + for p in processes { + let mut p = p.unwrap(); + + // TODO the percent formatting is not working + println!( + "{:>6} {:>2.1} {:>2.1} {:.100}", + p.pid(), + p.cpu_percent().unwrap(), + p.memory_percent().unwrap(), + p.cmdline() + .unwrap() + .unwrap_or_else(|| format!("[{}]", p.name().unwrap())), + ); + } +} diff --git a/common/rust-psutil/platform-support.md b/common/rust-psutil/platform-support.md new file mode 100644 index 00000000000..96b9ed851f4 --- /dev/null +++ b/common/rust-psutil/platform-support.md @@ -0,0 +1,112 @@ +## CPU + +| | Linux | macOS | Windows | +|----------------------------------------------------------------------------------------|--------------------|--------------------|--------------------| +| [cpu_times](https://psutil.readthedocs.io/en/latest/#psutil.cpu_times) | :heavy_check_mark: | :heavy_check_mark: | | +| [cpu_percent](https://psutil.readthedocs.io/en/latest/#psutil.cpu_percent) | :heavy_check_mark: | :heavy_check_mark: | | +| [cpu_times_percent](https://psutil.readthedocs.io/en/latest/#psutil.cpu_times_percent) | :heavy_check_mark: | :heavy_check_mark: | | +| [cpu_count](https://psutil.readthedocs.io/en/latest/#psutil.cpu_count) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| [cpu_stats](https://psutil.readthedocs.io/en/latest/#psutil.cpu_stats) | | | | +| [cpu_freq](https://psutil.readthedocs.io/en/latest/#psutil.cpu_freq) | | | | + +## Disk + +| | Linux | macOS | Windows | +|--------------------------------------------------------------------------------------|--------------------|--------------------|---------| +| [disk_partitions](https://psutil.readthedocs.io/en/latest/#psutil.disk_partitions) | :heavy_check_mark: | :heavy_check_mark: | | +| [disk_usage](https://psutil.readthedocs.io/en/latest/#psutil.disk_usage) | :heavy_check_mark: | :heavy_check_mark: | | +| [disk_io_counters](https://psutil.readthedocs.io/en/latest/#psutil.disk_io_counters) | :heavy_check_mark: | | | + +## Host + +| | Linux | macOS | Windows | +|------------------------------------------------------------------------------------|--------------------|-------|---------| +| [loadavg](https://psutil.readthedocs.io/en/latest/?badge=latest#psutil.getloadavg) | :heavy_check_mark: | | | +| [boot_time](https://psutil.readthedocs.io/en/latest/#psutil.boot_time) | :heavy_check_mark: | | | +| [users](https://psutil.readthedocs.io/en/latest/#psutil.users) | | | | + +## Memory + +| | Linux | macOS | Windows | +|----------------------------------------------------------------------------------|--------------------|--------------------|---------| +| [virtual_memory](https://psutil.readthedocs.io/en/latest/#psutil.virtual_memory) | :heavy_check_mark: | :heavy_check_mark: | | +| [swap_memory](https://psutil.readthedocs.io/en/latest/#psutil.swap_memory) | :heavy_check_mark: | :heavy_check_mark: | | + +## Network + +| | Linux | macOS | Windows | +|------------------------------------------------------------------------------------|--------------------|--------------------|---------| +| [net_io_counters](https://psutil.readthedocs.io/en/latest/#psutil.net_io_counters) | :heavy_check_mark: | :heavy_check_mark: | | +| [net_connections](https://psutil.readthedocs.io/en/latest/#psutil.net_connections) | | | | +| [net_if_addrs](https://psutil.readthedocs.io/en/latest/#psutil.net_if_addrs) | | | | +| [net_if_stats](https://psutil.readthedocs.io/en/latest/#psutil.net_if_stats) | | | | + +## Processes + +| | Linux | macOS | Windows | +|------------------------------------------------------------------------------|--------------------|--------------------|---------| +| [pids](https://psutil.readthedocs.io/en/latest/#psutil.pids) | :heavy_check_mark: | :heavy_check_mark: | | +| [process_iter](https://psutil.readthedocs.io/en/latest/#psutil.process_iter) | :heavy_check_mark: | :heavy_check_mark: | | +| [pid_exists](https://psutil.readthedocs.io/en/latest/#psutil.pid_exists) | :heavy_check_mark: | | | +| [wait_procs](https://psutil.readthedocs.io/en/latest/#psutil.wait_procs) | | | | + +### Per-process + +| | Linux | macOS | Windows | +|----------------------------------------------------------------------------------------------|--------------------|--------------------|---------| +| [pid](https://psutil.readthedocs.io/en/latest/#psutil.Process.pid) | :heavy_check_mark: | :heavy_check_mark: | | +| [ppid](https://psutil.readthedocs.io/en/latest/#psutil.Process.ppid) | :heavy_check_mark: | | | +| [name](https://psutil.readthedocs.io/en/latest/#psutil.Process.name) | :heavy_check_mark: | :heavy_check_mark: | | +| [exe](https://psutil.readthedocs.io/en/latest/#psutil.Process.exe) | :heavy_check_mark: | | | +| [cmdline](https://psutil.readthedocs.io/en/latest/#psutil.Process.cmdline) | :heavy_check_mark: | | | +| [environ](https://psutil.readthedocs.io/en/latest/#psutil.Process.environ) | :heavy_check_mark: | | | +| [create_time](https://psutil.readthedocs.io/en/latest/#psutil.Process.create_time) | :heavy_check_mark: | :heavy_check_mark: | | +| [as_dict](https://psutil.readthedocs.io/en/latest/#psutil.Process.as_dict) | | | | +| [parent](https://psutil.readthedocs.io/en/latest/#psutil.Process.parent) | :heavy_check_mark: | | | +| [parents](https://psutil.readthedocs.io/en/latest/#psutil.Process.parents) | | | | +| [status](https://psutil.readthedocs.io/en/latest/#psutil.Process.status) | :heavy_check_mark: | | | +| [cwd](https://psutil.readthedocs.io/en/latest/#psutil.Process.cwd) | :heavy_check_mark: | | | +| [username](https://psutil.readthedocs.io/en/latest/#psutil.Process.username) | | | | +| [uids](https://psutil.readthedocs.io/en/latest/#psutil.Process.uids) | :heavy_check_mark: | | | +| [gids](https://psutil.readthedocs.io/en/latest/#psutil.Process.gids) | :heavy_check_mark: | | | +| [terminal](https://psutil.readthedocs.io/en/latest/#psutil.Process.terminal) | | | | +| [nice](https://psutil.readthedocs.io/en/latest/#psutil.Process.nice) | | | | +| [ionice](https://psutil.readthedocs.io/en/latest/#psutil.Process.ionice) | | | | +| [rlimit](https://psutil.readthedocs.io/en/latest/#psutil.Process.rlimit) | | | | +| [io_counters](https://psutil.readthedocs.io/en/latest/#psutil.Process.io_counters) | | | | +| [num_ctx_switches](https://psutil.readthedocs.io/en/latest/#psutil.Process.num_ctx_switches) | | | | +| [num_fds](https://psutil.readthedocs.io/en/latest/#psutil.Process.num_fds) | | | | +| [num_threads](https://psutil.readthedocs.io/en/latest/#psutil.Process.num_threads) | | | | +| [threads](https://psutil.readthedocs.io/en/latest/#psutil.Process.threads) | | | | +| [cpu_times](https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_times) | :heavy_check_mark: | :heavy_check_mark: | | +| [cpu_percent](https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_percent) | :heavy_check_mark: | :heavy_check_mark: | | +| [cpu_affinity](https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_affinity) | | | | +| [cpu_num](https://psutil.readthedocs.io/en/latest/#psutil.Process.cpu_num) | | | | +| [memory_info](https://psutil.readthedocs.io/en/latest/#psutil.Process.memory_info) | :heavy_check_mark: | :heavy_check_mark: | | +| [memory_info_full](https://psutil.readthedocs.io/en/latest/#psutil.Process.memory_info_full) | | | | +| [memory_percent](https://psutil.readthedocs.io/en/latest/#psutil.Process.memory_percent) | :heavy_check_mark: | :heavy_check_mark: | | +| [memory_maps](https://psutil.readthedocs.io/en/latest/#psutil.Process.memory_maps) | | | | +| [children](https://psutil.readthedocs.io/en/latest/#psutil.Process.children) | | | | +| [open_files](https://psutil.readthedocs.io/en/latest/#psutil.Process.open_files) | :heavy_check_mark: | | | +| [connections](https://psutil.readthedocs.io/en/latest/#psutil.Process.connections) | | | | +| [is_running](https://psutil.readthedocs.io/en/latest/#psutil.Process.is_running) | :heavy_check_mark: | :heavy_check_mark: | | +| [send_signal](https://psutil.readthedocs.io/en/latest/#psutil.Process.send_signal) | :heavy_check_mark: | :heavy_check_mark: | | +| [suspend](https://psutil.readthedocs.io/en/latest/#psutil.Process.suspend) | :heavy_check_mark: | :heavy_check_mark: | | +| [resume](https://psutil.readthedocs.io/en/latest/#psutil.Process.resume) | :heavy_check_mark: | :heavy_check_mark: | | +| [terminate](https://psutil.readthedocs.io/en/latest/#psutil.Process.terminate) | :heavy_check_mark: | :heavy_check_mark: | | +| [kill](https://psutil.readthedocs.io/en/latest/#psutil.Process.kill) | :heavy_check_mark: | :heavy_check_mark: | | +| [wait](https://psutil.readthedocs.io/en/latest/#psutil.Process.wait) | | | | + +## Sensors + +| | Linux | macOS | Windows | +|----------------------------------------------------------------------------------------------|--------------------|-------|---------| +| [sensors_temperatures](https://psutil.readthedocs.io/en/latest/#psutil.sensors_temperatures) | :heavy_check_mark: | | | +| [sensors_fans](https://psutil.readthedocs.io/en/latest/#psutil.sensors_fans) | | | | + +## New functionality + +| | Linux | macOS | Windows | +|--------|--------------------|--------------------|---------| +| Info | :heavy_check_mark: | :heavy_check_mark: | | +| uptime | :heavy_check_mark: | | | diff --git a/common/rust-psutil/src/common.rs b/common/rust-psutil/src/common.rs new file mode 100644 index 00000000000..292ecfa3cb2 --- /dev/null +++ b/common/rust-psutil/src/common.rs @@ -0,0 +1,40 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TcpConnectionStatus { + Established, + SynSent, + SynRecv, + FinWait1, + FinWait2, + TimeWait, + Close, + CloseWait, + LastAck, + Listen, + Closing, + /// Windows only + DeleteTcb, + /// Solaris only + Idle, + /// Solaris only + Bound, +} + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NetConnectionType { + Inet, + Inet4, + Inet6, + Tcp, + Tcp4, + Tcp6, + Udp, + Udp4, + Udp6, + Unix, + All, +} diff --git a/common/rust-psutil/src/cpu/cpu_count.rs b/common/rust-psutil/src/cpu/cpu_count.rs new file mode 100644 index 00000000000..e0fe955f221 --- /dev/null +++ b/common/rust-psutil/src/cpu/cpu_count.rs @@ -0,0 +1,9 @@ +use crate::Count; + +pub fn cpu_count() -> Count { + num_cpus::get() as Count +} + +pub fn cpu_count_physical() -> Count { + num_cpus::get_physical() as Count +} diff --git a/common/rust-psutil/src/cpu/cpu_freq.rs b/common/rust-psutil/src/cpu/cpu_freq.rs new file mode 100644 index 00000000000..44a283d6107 --- /dev/null +++ b/common/rust-psutil/src/cpu/cpu_freq.rs @@ -0,0 +1,22 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Mhz; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CpuFreq {} + +impl CpuFreq { + pub fn current(&self) -> Mhz { + todo!() + } + + pub fn min(&self) -> Mhz { + todo!() + } + + pub fn max(&self) -> Mhz { + todo!() + } +} diff --git a/common/rust-psutil/src/cpu/cpu_percent.rs b/common/rust-psutil/src/cpu/cpu_percent.rs new file mode 100644 index 00000000000..d1b630317d4 --- /dev/null +++ b/common/rust-psutil/src/cpu/cpu_percent.rs @@ -0,0 +1,49 @@ +use crate::cpu::CpuTimesPercentCollector; +use crate::{Percent, Result}; + +/// Get cpu percents in non-blocking mode. +/// +/// Example: +/// +/// ``` +/// let mut cpu_percent_collector = psutil::cpu::CpuPercentCollector::new().unwrap(); +/// +/// let cpu_percent = cpu_percent_collector.cpu_percent().unwrap(); +/// let cpu_percents_percpu = cpu_percent_collector.cpu_percent_percpu().unwrap(); +/// ``` +#[derive(Debug, Clone)] +pub struct CpuPercentCollector { + cpu_times_percent_collector: CpuTimesPercentCollector, +} + +impl CpuPercentCollector { + /// Initialize the `CpuPercentCollector` so the method calls are ready to be used. + pub fn new() -> Result { + let cpu_times_percent_collector = CpuTimesPercentCollector::new()?; + + Ok(CpuPercentCollector { + cpu_times_percent_collector, + }) + } + + /// Returns a cpu percent since the last time this was called or since + /// `CpuPercentCollector::new()` was called. + pub fn cpu_percent(&mut self) -> Result { + let percent = self.cpu_times_percent_collector.cpu_times_percent()?.busy(); + + Ok(percent) + } + + /// Returns a cpu percent for each cpu since the last time this was called or since + /// `CpuPercentCollector::new()` was called. + pub fn cpu_percent_percpu(&mut self) -> Result> { + let percents = self + .cpu_times_percent_collector + .cpu_times_percent_percpu()? + .into_iter() + .map(|cpu_times_percent| cpu_times_percent.busy()) + .collect(); + + Ok(percents) + } +} diff --git a/common/rust-psutil/src/cpu/cpu_stats.rs b/common/rust-psutil/src/cpu/cpu_stats.rs new file mode 100644 index 00000000000..3399ab0005c --- /dev/null +++ b/common/rust-psutil/src/cpu/cpu_stats.rs @@ -0,0 +1,26 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Count; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CpuStats {} + +impl CpuStats { + pub fn ctx_switches(&self) -> Count { + todo!() + } + + pub fn interrupts(&self) -> Count { + todo!() + } + + pub fn soft_interrupts(&self) -> Count { + todo!() + } + + pub fn syscalls(&self) -> Count { + todo!() + } +} diff --git a/common/rust-psutil/src/cpu/cpu_times.rs b/common/rust-psutil/src/cpu/cpu_times.rs new file mode 100644 index 00000000000..124f797f151 --- /dev/null +++ b/common/rust-psutil/src/cpu/cpu_times.rs @@ -0,0 +1,124 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::ops::Sub; +use std::time::Duration; + +/// Every attribute represents the seconds the CPU has spent in the given mode. +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CpuTimes { + pub(crate) user: Duration, + pub(crate) system: Duration, + pub(crate) idle: Duration, + pub(crate) nice: Duration, + + #[cfg(target_os = "linux")] + pub(crate) iowait: Duration, + #[cfg(target_os = "linux")] + pub(crate) irq: Duration, + #[cfg(target_os = "linux")] + pub(crate) softirq: Duration, + #[cfg(target_os = "linux")] + pub(crate) steal: Option, + #[cfg(target_os = "linux")] + pub(crate) guest: Option, + #[cfg(target_os = "linux")] + pub(crate) guest_nice: Option, +} + +impl CpuTimes { + /// Time spent by normal processes executing in user mode; + /// on Linux this also includes guest time. + pub fn user(&self) -> Duration { + self.user + } + + /// Time spent by processes executing in kernel mode. + pub fn system(&self) -> Duration { + self.system + } + + /// Time spent doing nothing. + pub fn idle(&self) -> Duration { + #[cfg(target_os = "linux")] + { + self.idle + self.iowait + } + #[cfg(target_os = "macos")] + { + self.idle + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + todo!() + } + } + + /// New method, not in Python psutil. + pub fn busy(&self) -> Duration { + #[cfg(target_os = "linux")] + { + // On Linux guest times are already accounted in "user" or "nice" times. + // https://github.com/giampaolo/psutil/blob/e65cc95de72828caed74c7916530dd74fca351e3/psutil/__init__.py#L1653 + self.user + + self.system + self.nice + + self.irq + self.softirq + + self.steal.unwrap_or_default() + } + #[cfg(target_os = "macos")] + { + self.user + self.system + self.nice + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + todo!() + } + } + + /// New method, not in Python psutil. + pub fn total(&self) -> Duration { + self.busy() + self.idle() + } +} + +impl Sub for &CpuTimes { + type Output = CpuTimes; + + fn sub(self, other: Self) -> Self::Output { + CpuTimes { + // have to use `checked_sub` since CPU times can decrease over time on some platforms + // https://github.com/giampaolo/psutil/blob/e65cc95de72828caed74c7916530dd74fca351e3/psutil/__init__.py#L1687 + user: self.user.checked_sub(other.user).unwrap_or_default(), + system: self.system.checked_sub(other.system).unwrap_or_default(), + idle: self.idle.checked_sub(other.idle).unwrap_or_default(), + nice: self.nice.checked_sub(other.nice).unwrap_or_default(), + + #[cfg(target_os = "linux")] + iowait: self.iowait.checked_sub(other.iowait).unwrap_or_default(), + #[cfg(target_os = "linux")] + irq: self.irq.checked_sub(other.irq).unwrap_or_default(), + #[cfg(target_os = "linux")] + softirq: self.softirq.checked_sub(other.softirq).unwrap_or_default(), + #[cfg(target_os = "linux")] + steal: self.steal.and_then(|first| { + other + .steal + .map(|second| first.checked_sub(second).unwrap_or_default()) + }), + #[cfg(target_os = "linux")] + guest: self.guest.and_then(|first| { + other + .guest + .map(|second| first.checked_sub(second).unwrap_or_default()) + }), + #[cfg(target_os = "linux")] + guest_nice: self.guest_nice.and_then(|first| { + other + .guest_nice + .map(|second| first.checked_sub(second).unwrap_or_default()) + }), + } + } +} diff --git a/common/rust-psutil/src/cpu/cpu_times_percent.rs b/common/rust-psutil/src/cpu/cpu_times_percent.rs new file mode 100644 index 00000000000..523d8b5d0cd --- /dev/null +++ b/common/rust-psutil/src/cpu/cpu_times_percent.rs @@ -0,0 +1,182 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::time::Duration; + +use crate::cpu::{cpu_times, cpu_times_percpu, CpuTimes}; +use crate::utils::duration_percent; +use crate::{Percent, Result}; + +/// Every attribute represents the percentage of time the CPU has spent in the given mode. +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Default)] +pub struct CpuTimesPercent { + pub(crate) user: Percent, + pub(crate) system: Percent, + pub(crate) idle: Percent, + pub(crate) nice: Percent, + + #[cfg(target_os = "linux")] + pub(crate) iowait: Percent, + #[cfg(target_os = "linux")] + pub(crate) irq: Percent, + #[cfg(target_os = "linux")] + pub(crate) softirq: Percent, + #[cfg(target_os = "linux")] + pub(crate) steal: Option, + #[cfg(target_os = "linux")] + pub(crate) guest: Option, + #[cfg(target_os = "linux")] + pub(crate) guest_nice: Option, +} + +impl CpuTimesPercent { + /// Time spent by normal processes executing in user mode; + /// on Linux this also includes guest time. + pub fn user(&self) -> Percent { + self.user + } + + /// Time spent by processes executing in kernel mode. + pub fn system(&self) -> Percent { + self.system + } + + /// Time spent doing nothing. + pub fn idle(&self) -> Percent { + #[cfg(target_os = "linux")] + { + self.idle + self.iowait + } + #[cfg(target_os = "macos")] + { + self.idle + } + } + + /// New method, not in Python psutil. + pub fn busy(&self) -> Percent { + #[cfg(target_os = "linux")] + { + // On Linux guest times are already accounted in "user" or "nice" times. + // https://github.com/giampaolo/psutil/blob/e65cc95de72828caed74c7916530dd74fca351e3/psutil/__init__.py#L1653 + self.user + + self.system + self.nice + + self.irq + self.softirq + + self.steal.unwrap_or_default() + } + #[cfg(target_os = "macos")] + { + self.user + self.system + self.nice + } + } +} + +impl From for CpuTimesPercent { + fn from(cpu_times: CpuTimes) -> Self { + let total = cpu_times.total(); + + // total can be zero if cpu_times_percent is called consecutively without allowing enough + // time to pass + // or also when CPU times decrease over time and we reset the calculated value to zero + if total == Duration::default() { + return CpuTimesPercent::default(); + } + + let user = duration_percent(cpu_times.user, total); + let system = duration_percent(cpu_times.system, total); + let idle = duration_percent(cpu_times.idle, total); + let nice = duration_percent(cpu_times.nice, total); + + #[cfg(target_os = "linux")] + let iowait = duration_percent(cpu_times.iowait, total); + #[cfg(target_os = "linux")] + let irq = duration_percent(cpu_times.irq, total); + #[cfg(target_os = "linux")] + let softirq = duration_percent(cpu_times.softirq, total); + + #[cfg(target_os = "linux")] + let steal = cpu_times.steal.map(|steal| duration_percent(steal, total)); + #[cfg(target_os = "linux")] + let guest = cpu_times.guest.map(|guest| duration_percent(guest, total)); + #[cfg(target_os = "linux")] + let guest_nice = cpu_times + .guest_nice + .map(|guest_nice| duration_percent(guest_nice, total)); + + CpuTimesPercent { + user, + system, + idle, + nice, + + #[cfg(target_os = "linux")] + iowait, + #[cfg(target_os = "linux")] + irq, + #[cfg(target_os = "linux")] + softirq, + #[cfg(target_os = "linux")] + steal, + #[cfg(target_os = "linux")] + guest, + #[cfg(target_os = "linux")] + guest_nice, + } + } +} + +/// Get `CpuTimesPercent`s in non-blocking mode. +/// +/// Example: +/// +/// ``` +/// let mut cpu_times_percent_collector = psutil::cpu::CpuTimesPercentCollector::new().unwrap(); +/// +/// let cpu_times_percent = cpu_times_percent_collector.cpu_times_percent().unwrap(); +/// let cpu_times_percent_percpu = cpu_times_percent_collector.cpu_times_percent_percpu().unwrap(); +/// ``` +#[derive(Clone, Debug)] +pub struct CpuTimesPercentCollector { + cpu_times: CpuTimes, + cpu_times_percpu: Vec, +} + +impl CpuTimesPercentCollector { + /// Initialize the `CpuTimesPercentCollector` so the method calls are ready to be used. + pub fn new() -> Result { + let cpu_times = cpu_times()?; + let cpu_times_percpu = cpu_times_percpu()?; + + Ok(CpuTimesPercentCollector { + cpu_times, + cpu_times_percpu, + }) + } + + /// Returns a `CpuTimesPercent` since the last time this was called or since + /// `CpuTimesPercentCollector::new()` was called. + pub fn cpu_times_percent(&mut self) -> Result { + let current_cpu_times = cpu_times()?; + let cpu_percent_since = CpuTimesPercent::from(¤t_cpu_times - &self.cpu_times); + self.cpu_times = current_cpu_times; + + Ok(cpu_percent_since) + } + + /// Returns a `CpuTimesPercent` for each cpu since the last time this was called or since + /// `CpuTimesPercentCollector::new()` was called. + pub fn cpu_times_percent_percpu(&mut self) -> Result> { + let current_cpu_times_percpu = cpu_times_percpu()?; + let vec = self + .cpu_times_percpu + .iter() + .zip(current_cpu_times_percpu.iter()) + .map(|(prev, cur)| CpuTimesPercent::from(cur - prev)) + .collect(); + self.cpu_times_percpu = current_cpu_times_percpu; + + Ok(vec) + } +} diff --git a/common/rust-psutil/src/cpu/mod.rs b/common/rust-psutil/src/cpu/mod.rs new file mode 100644 index 00000000000..130dc1c8f94 --- /dev/null +++ b/common/rust-psutil/src/cpu/mod.rs @@ -0,0 +1,16 @@ +mod cpu_count; +mod cpu_freq; +mod cpu_percent; +mod cpu_stats; +mod cpu_times; +mod cpu_times_percent; +pub mod os; +mod sys; + +pub use cpu_count::*; +pub use cpu_freq::*; +pub use cpu_percent::*; +pub use cpu_stats::*; +pub use cpu_times::*; +pub use cpu_times_percent::*; +pub use sys::*; diff --git a/common/rust-psutil/src/cpu/os/bsd.rs b/common/rust-psutil/src/cpu/os/bsd.rs new file mode 100644 index 00000000000..7cbc1aecc7d --- /dev/null +++ b/common/rust-psutil/src/cpu/os/bsd.rs @@ -0,0 +1,24 @@ +use std::time::Duration; + +use crate::cpu::{CpuTimes, CpuTimesPercent}; +use crate::Percent; + +pub trait CpuTimesExt { + fn irq(&self) -> Duration; +} + +impl CpuTimesExt for CpuTimes { + fn irq(&self) -> Duration { + todo!() + } +} + +pub trait CpuTimesPercentExt { + fn irq(&self) -> Percent; +} + +impl CpuTimesPercentExt for CpuTimesPercent { + fn irq(&self) -> Percent { + todo!() + } +} diff --git a/common/rust-psutil/src/cpu/os/linux.rs b/common/rust-psutil/src/cpu/os/linux.rs new file mode 100644 index 00000000000..db8b863eb7d --- /dev/null +++ b/common/rust-psutil/src/cpu/os/linux.rs @@ -0,0 +1,104 @@ +use std::time::Duration; + +use crate::cpu::{CpuTimes, CpuTimesPercent}; +use crate::Percent; + +pub trait CpuTimesExt { + /// Time spent waiting for I/O to complete. + /// This is *not* accounted in idle time counter. + fn iowait(&self) -> Duration; + + /// Time spent for servicing hardware interrupts. + fn irq(&self) -> Duration; + + /// Time spent for servicing software interrupts. + fn softirq(&self) -> Duration; + + /// Time spent by other operating systems running in a virtualized environment. + fn steal(&self) -> Option; + + /// Time spent running a virtual CPU for guest operating systems + /// under the control of the Linux kernel. + fn guest(&self) -> Option; + + /// Time spent running a niced guest + /// (virtual CPU for guest operating systems + /// under the control of the Linux kernel). + fn guest_nice(&self) -> Option; +} + +impl CpuTimesExt for CpuTimes { + fn iowait(&self) -> Duration { + self.iowait + } + + fn irq(&self) -> Duration { + self.irq + } + + fn softirq(&self) -> Duration { + self.softirq + } + + fn steal(&self) -> Option { + self.steal + } + + fn guest(&self) -> Option { + self.guest + } + + fn guest_nice(&self) -> Option { + self.guest_nice + } +} + +pub trait CpuTimesPercentExt { + /// Time spent waiting for I/O to complete. + /// This is *not* accounted in idle time counter. + fn iowait(&self) -> Percent; + + /// Time spent for servicing hardware interrupts. + fn irq(&self) -> Percent; + + /// Time spent for servicing software interrupts. + fn softirq(&self) -> Percent; + + /// Time spent by other operating systems running in a virtualized environment. + fn steal(&self) -> Option; + + /// Time spent running a virtual CPU for guest operating systems + /// under the control of the Linux kernel. + fn guest(&self) -> Option; + + /// Time spent running a niced guest + /// (virtual CPU for guest operating systems + /// under the control of the Linux kernel). + fn guest_nice(&self) -> Option; +} + +impl CpuTimesPercentExt for CpuTimesPercent { + fn iowait(&self) -> Percent { + self.iowait + } + + fn irq(&self) -> Percent { + self.irq + } + + fn softirq(&self) -> Percent { + self.softirq + } + + fn steal(&self) -> Option { + self.steal + } + + fn guest(&self) -> Option { + self.guest + } + + fn guest_nice(&self) -> Option { + self.guest_nice + } +} diff --git a/common/rust-psutil/src/cpu/os/mod.rs b/common/rust-psutil/src/cpu/os/mod.rs new file mode 100644 index 00000000000..c1ec20dea1d --- /dev/null +++ b/common/rust-psutil/src/cpu/os/mod.rs @@ -0,0 +1,13 @@ +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub mod bsd; +#[cfg(target_os = "linux")] +pub mod linux; +#[cfg(target_family = "unix")] +pub mod unix; +#[cfg(target_os = "windows")] +pub mod windows; diff --git a/common/rust-psutil/src/cpu/os/unix.rs b/common/rust-psutil/src/cpu/os/unix.rs new file mode 100644 index 00000000000..c4c686921b9 --- /dev/null +++ b/common/rust-psutil/src/cpu/os/unix.rs @@ -0,0 +1,28 @@ +use std::time::Duration; + +use crate::cpu::{CpuTimes, CpuTimesPercent}; +use crate::Percent; + +pub trait CpuTimesExt { + /// Time spent by niced (prioritized) processes executing in user mode; + /// on Linux this also includes guest_nice time. + fn nice(&self) -> Duration; +} + +impl CpuTimesExt for CpuTimes { + fn nice(&self) -> Duration { + self.nice + } +} + +pub trait CpuTimesPercentExt { + /// Time spent by niced (prioritized) processes executing in user mode; + /// on Linux this also includes guest_nice time. + fn nice(&self) -> Percent; +} + +impl CpuTimesPercentExt for CpuTimesPercent { + fn nice(&self) -> Percent { + self.nice + } +} diff --git a/common/rust-psutil/src/cpu/os/windows.rs b/common/rust-psutil/src/cpu/os/windows.rs new file mode 100644 index 00000000000..76982ba8cd2 --- /dev/null +++ b/common/rust-psutil/src/cpu/os/windows.rs @@ -0,0 +1,36 @@ +use std::time::Duration; + +use crate::cpu::{CpuTimes, CpuTimesPercent}; +use crate::Percent; + +pub trait CpuTimesExt { + fn interrupt(&self) -> Duration; + + fn dpc(&self) -> Duration; +} + +impl CpuTimesExt for CpuTimes { + fn interrupt(&self) -> Duration { + todo!() + } + + fn dpc(&self) -> Duration { + todo!() + } +} + +pub trait CpuTimesPercentExt { + fn interrupt(&self) -> Percent; + + fn dpc(&self) -> Percent; +} + +impl CpuTimesPercentExt for CpuTimesPercent { + fn interrupt(&self) -> Percent { + todo!() + } + + fn dpc(&self) -> Percent { + todo!() + } +} diff --git a/common/rust-psutil/src/cpu/sys/linux/cpu_freq.rs b/common/rust-psutil/src/cpu/sys/linux/cpu_freq.rs new file mode 100644 index 00000000000..3182f3c9a75 --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/linux/cpu_freq.rs @@ -0,0 +1,11 @@ +use std::io; + +use crate::cpu::CpuFreq; + +pub fn cpu_freq() -> io::Result { + todo!() +} + +pub fn cpu_freq_percpu() -> io::Result> { + todo!() +} diff --git a/common/rust-psutil/src/cpu/sys/linux/cpu_stats.rs b/common/rust-psutil/src/cpu/sys/linux/cpu_stats.rs new file mode 100644 index 00000000000..e05e92e6a5a --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/linux/cpu_stats.rs @@ -0,0 +1,7 @@ +use std::io; + +use crate::cpu::CpuStats; + +pub fn cpu_stats() -> io::Result { + todo!() +} diff --git a/common/rust-psutil/src/cpu/sys/linux/cpu_times.rs b/common/rust-psutil/src/cpu/sys/linux/cpu_times.rs new file mode 100644 index 00000000000..fd62fc15fbb --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/linux/cpu_times.rs @@ -0,0 +1,131 @@ +use std::str::FromStr; +use std::time::Duration; + +use crate::cpu::CpuTimes; +use crate::{read_file, Count, Error, Result, TICKS_PER_SECOND}; + +const PROC_STAT: &str = "/proc/stat"; + +impl FromStr for CpuTimes { + type Err = Error; + + fn from_str(line: &str) -> Result { + let fields = line + .split_whitespace() + .skip(1) + .map(|entry| { + entry.parse().map_err(|err| Error::ParseInt { + path: PROC_STAT.into(), + contents: line.to_string(), + source: err, + }) + }) + .collect::>>()? + .into_iter() + .map(|entry| Duration::from_secs_f64(entry as f64 / *TICKS_PER_SECOND)) + .collect::>(); + + if fields.len() < 7 { + return Err(Error::MissingData { + path: PROC_STAT.into(), + contents: line.to_string(), + }); + } + + let user = fields[0]; + let nice = fields[1]; + let system = fields[2]; + let idle = fields[3]; + let iowait = fields[4]; + let irq = fields[5]; + let softirq = fields[6]; + + // since kernel 2.6.11 + let steal = if fields.len() >= 8 { + Some(fields[7]) + } else { + None + }; + // since kernel 2.6.24 + let guest = if fields.len() >= 9 { + Some(fields[8]) + } else { + None + }; + // since kernel 2.6.33 + let guest_nice = if fields.len() >= 10 { + Some(fields[9]) + } else { + None + }; + + Ok(CpuTimes { + user, + system, + idle, + nice, + iowait, + irq, + softirq, + steal, + guest, + guest_nice, + }) + } +} + +pub fn cpu_times() -> Result { + let contents = read_file(PROC_STAT)?; + let lines: Vec<_> = contents.lines().collect(); + + if lines.is_empty() { + return Err(Error::MissingData { + path: PROC_STAT.into(), + contents, + }); + } + + CpuTimes::from_str(lines[0]) +} + +pub fn cpu_times_percpu() -> Result> { + let contents = read_file(PROC_STAT)?; + let lines: Vec<_> = contents + .lines() + .skip(1) + .take_while(|line| line.starts_with("cpu")) + .collect(); + + if lines.is_empty() { + return Err(Error::MissingData { + path: PROC_STAT.into(), + contents, + }); + } + + lines.into_iter().map(CpuTimes::from_str).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_cpu_times() { + let line = "cpu 11867200 6935 2978038 19104017 85955 502109 144021 0 0 0"; + let result = CpuTimes::from_str(line).unwrap(); + let expected = CpuTimes { + user: Duration::from_secs_f64(11_867_200_f64 / *TICKS_PER_SECOND), + nice: Duration::from_secs_f64(6935_f64 / *TICKS_PER_SECOND), + system: Duration::from_secs_f64(2_978_038_f64 / *TICKS_PER_SECOND), + idle: Duration::from_secs_f64(19_104_017_f64 / *TICKS_PER_SECOND), + iowait: Duration::from_secs_f64(85955_f64 / *TICKS_PER_SECOND), + irq: Duration::from_secs_f64(502_109_f64 / *TICKS_PER_SECOND), + softirq: Duration::from_secs_f64(144_021_f64 / *TICKS_PER_SECOND), + steal: Some(Duration::default()), + guest: Some(Duration::default()), + guest_nice: Some(Duration::default()), + }; + assert_eq!(result, expected); + } +} diff --git a/common/rust-psutil/src/cpu/sys/linux/mod.rs b/common/rust-psutil/src/cpu/sys/linux/mod.rs new file mode 100644 index 00000000000..395a0e14eea --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/linux/mod.rs @@ -0,0 +1,7 @@ +mod cpu_freq; +mod cpu_stats; +mod cpu_times; + +pub use cpu_freq::*; +pub use cpu_stats::*; +pub use cpu_times::*; diff --git a/common/rust-psutil/src/cpu/sys/macos/cpu_times.rs b/common/rust-psutil/src/cpu/sys/macos/cpu_times.rs new file mode 100644 index 00000000000..048f13167b3 --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/macos/cpu_times.rs @@ -0,0 +1,199 @@ +// https://github.com/heim-rs/heim/blob/master/heim-cpu/src/sys/macos/times.rs +// https://github.com/heim-rs/heim/blob/master/heim-cpu/src/sys/macos/bindings.rs +// https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/mod.rs + +use std::io; +use std::mem; +use std::ptr; +use std::slice; +use std::time::Duration; + +use mach::kern_return::{self, kern_return_t}; +use mach::mach_port; +use mach::mach_types::{host_name_port_t, host_t}; +use mach::message::mach_msg_type_number_t; +use mach::traps::mach_task_self; +use mach::vm_types::{integer_t, natural_t, vm_address_t, vm_map_t, vm_size_t}; +use nix::libc; + +use crate::cpu::CpuTimes; +use crate::{Result, TICKS_PER_SECOND}; + +const PROCESSOR_CPU_LOAD_INFO: libc::c_int = 2; +const HOST_CPU_LOAD_INFO: libc::c_int = 3; +const CPU_STATE_USER: usize = 0; +const CPU_STATE_SYSTEM: usize = 1; +const CPU_STATE_IDLE: usize = 2; +const CPU_STATE_NICE: usize = 3; + +#[allow(non_camel_case_types)] +type processor_flavor_t = libc::c_int; +#[allow(non_camel_case_types)] +type processor_info_array_t = *mut integer_t; +/// https://developer.apple.com/documentation/kernel/host_flavor_t?language=objc +#[allow(non_camel_case_types)] +type host_flavor_t = integer_t; +/// https://developer.apple.com/documentation/kernel/host_info64_t?language=objc +#[allow(non_camel_case_types)] +type host_info64_t = *mut integer_t; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] +struct host_cpu_load_info { + user: natural_t, + system: natural_t, + idle: natural_t, + nice: natural_t, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] +struct processor_cpu_load_info { + user: natural_t, + system: natural_t, + idle: natural_t, + nice: natural_t, +} + +extern "C" { + fn host_processor_info( + host: host_t, + flavor: processor_flavor_t, + out_processor_count: *mut natural_t, + out_processor_info: *mut processor_info_array_t, + out_processor_infoCnt: *mut mach_msg_type_number_t, + ) -> kern_return_t; + + fn vm_deallocate( + target_task: vm_map_t, + address: vm_address_t, + size: vm_size_t, + ) -> kern_return_t; + + fn mach_host_self() -> host_name_port_t; + + /// https://developer.apple.com/documentation/kernel/1502863-host_statistics64?language=objc + fn host_statistics64( + host_priv: host_t, + flavor: host_flavor_t, + host_info_out: host_info64_t, + host_info_outCnt: *const mach_msg_type_number_t, + ) -> kern_return_t; +} + +#[allow(trivial_casts)] +unsafe fn processor_load_info() -> io::Result> { + let port = mach_host_self(); + let mut cpu_count = 0; + let mut processor_info: processor_info_array_t = ptr::null_mut(); + let mut cpu_info_count = 0; + + let result = host_processor_info( + port, + PROCESSOR_CPU_LOAD_INFO, + &mut cpu_count, + &mut processor_info, + &mut cpu_info_count, + ); + + let port_result = mach_port::mach_port_deallocate(mach_task_self(), port); + if port_result != kern_return::KERN_SUCCESS { + return Err(io::Error::last_os_error()); + } + + if result != kern_return::KERN_SUCCESS { + Err(io::Error::last_os_error()) + } else { + let cpu_info = slice::from_raw_parts(processor_info, cpu_info_count as usize); + // Could use a `::std::mem::transmute` probably, but this is okay too + let mut stats = Vec::with_capacity(cpu_count as usize); + for chunk in cpu_info.chunks(4) { + stats.push(processor_cpu_load_info { + user: chunk[CPU_STATE_USER] as natural_t, + system: chunk[CPU_STATE_SYSTEM] as natural_t, + idle: chunk[CPU_STATE_IDLE] as natural_t, + nice: chunk[CPU_STATE_NICE] as natural_t, + }) + } + + let result = vm_deallocate( + mach_task_self(), + processor_info as vm_address_t, + cpu_info_count as vm_size_t * std::mem::size_of::(), + ); + if result != kern_return::KERN_SUCCESS { + return Err(io::Error::last_os_error()); + } + + Ok(stats) + } +} + +#[allow(trivial_casts)] +unsafe fn cpu_load_info() -> io::Result { + let port = mach_host_self(); + let mut stats = host_cpu_load_info::default(); + // TODO: Move to const + let count = mem::size_of::() / mem::size_of::(); + + let result = host_statistics64( + port, + HOST_CPU_LOAD_INFO, + &mut stats as *mut _ as host_info64_t, + &count as *const _ as *const mach_msg_type_number_t, + ); + + let port_result = mach_port::mach_port_deallocate(mach_task_self(), port); + // Technically it is a programming bug and we are should panic probably, + // but it is okay as is + if port_result != kern_return::KERN_SUCCESS { + return Err(io::Error::last_os_error()); + } + + if result != kern_return::KERN_SUCCESS { + Err(io::Error::last_os_error()) + } else { + Ok(stats) + } +} + +impl From for CpuTimes { + fn from(info: host_cpu_load_info) -> CpuTimes { + let ticks = *TICKS_PER_SECOND; + + CpuTimes { + user: Duration::from_secs_f64(f64::from(info.user) / ticks), + system: Duration::from_secs_f64(f64::from(info.system) / ticks), + idle: Duration::from_secs_f64(f64::from(info.idle) / ticks), + nice: Duration::from_secs_f64(f64::from(info.nice) / ticks), + } + } +} + +impl From for CpuTimes { + fn from(info: processor_cpu_load_info) -> CpuTimes { + let ticks = *TICKS_PER_SECOND; + + CpuTimes { + user: Duration::from_secs_f64(f64::from(info.user) / ticks), + system: Duration::from_secs_f64(f64::from(info.system) / ticks), + idle: Duration::from_secs_f64(f64::from(info.idle) / ticks), + nice: Duration::from_secs_f64(f64::from(info.nice) / ticks), + } + } +} + +pub fn cpu_times() -> Result { + let info = unsafe { cpu_load_info()? }; + + Ok(info.into()) +} + +pub fn cpu_times_percpu() -> Result> { + let processors = unsafe { processor_load_info()? }; + + Ok(processors + .into_iter() + .map(|processor| processor.into()) + .collect()) +} diff --git a/common/rust-psutil/src/cpu/sys/macos/mod.rs b/common/rust-psutil/src/cpu/sys/macos/mod.rs new file mode 100644 index 00000000000..e608fea4050 --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/macos/mod.rs @@ -0,0 +1,3 @@ +mod cpu_times; + +pub use cpu_times::*; diff --git a/common/rust-psutil/src/cpu/sys/mod.rs b/common/rust-psutil/src/cpu/sys/mod.rs new file mode 100644 index 00000000000..691f8ce1721 --- /dev/null +++ b/common/rust-psutil/src/cpu/sys/mod.rs @@ -0,0 +1,9 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "macos")] { + mod macos; + pub use macos::*; + } +} diff --git a/common/rust-psutil/src/disk/disk_io_counters.rs b/common/rust-psutil/src/disk/disk_io_counters.rs new file mode 100644 index 00000000000..72a45d68f63 --- /dev/null +++ b/common/rust-psutil/src/disk/disk_io_counters.rs @@ -0,0 +1,171 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::collections::HashMap; +use std::time::Duration; + +use derive_more::{Add, Sub, Sum}; + +use crate::disk::disk_io_counters_per_partition; +use crate::{Bytes, Count, Result}; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Add, Sum, Default, Sub)] +pub struct DiskIoCounters { + pub(crate) read_count: Count, + pub(crate) write_count: Count, + pub(crate) read_bytes: Bytes, + pub(crate) write_bytes: Bytes, + + #[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))] + pub(crate) read_time: Duration, + #[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))] + pub(crate) write_time: Duration, + + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + pub(crate) busy_time: Duration, + + #[cfg(target_os = "linux")] + pub(crate) read_merged_count: Count, + #[cfg(target_os = "linux")] + pub(crate) write_merged_count: Count, +} + +impl DiskIoCounters { + /// Number of reads. + pub fn read_count(&self) -> Count { + self.read_count + } + + /// Number of writes. + pub fn write_count(&self) -> Count { + self.write_count + } + + /// Number of bytes read. + pub fn read_bytes(&self) -> Bytes { + self.read_bytes + } + + /// Number of bytes written. + pub fn write_bytes(&self) -> Bytes { + self.write_bytes + } +} + +fn nowrap(prev: u64, current: u64, corrected: u64) -> u64 { + if current >= prev { + corrected + (current - prev) + } else { + corrected + current + ((std::u32::MAX as u64) - prev) + } +} + +fn nowrap_struct( + prev: &DiskIoCounters, + current: &DiskIoCounters, + corrected: &DiskIoCounters, +) -> DiskIoCounters { + DiskIoCounters { + read_count: nowrap(prev.read_count, current.read_count, corrected.read_count), + write_count: nowrap(prev.write_count, current.write_count, corrected.write_count), + read_bytes: nowrap(prev.read_bytes, current.read_bytes, corrected.read_bytes), + write_bytes: nowrap(prev.write_bytes, current.write_bytes, corrected.write_bytes), + + #[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))] + read_time: Duration::from_millis(nowrap( + prev.read_time.as_millis() as u64, + current.read_time.as_millis() as u64, + corrected.read_time.as_millis() as u64, + )), + #[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))] + write_time: Duration::from_millis(nowrap( + prev.write_time.as_millis() as u64, + current.write_time.as_millis() as u64, + corrected.write_time.as_millis() as u64, + )), + + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + busy_time: Duration::from_millis(nowrap( + prev.busy_time.as_millis() as u64, + current.busy_time.as_millis() as u64, + corrected.busy_time.as_millis() as u64, + )), + + #[cfg(target_os = "linux")] + read_merged_count: nowrap( + prev.read_merged_count, + current.read_merged_count, + corrected.read_merged_count, + ), + #[cfg(target_os = "linux")] + write_merged_count: nowrap( + prev.write_merged_count, + current.write_merged_count, + corrected.write_merged_count, + ), + } +} + +fn fix_io_counter_overflow( + prev: &HashMap, + current: &HashMap, + corrected: &HashMap, +) -> HashMap { + current + .iter() + .map(|(name, current_counters)| { + if !prev.contains_key(name) || !corrected.contains_key(name) { + (name.clone(), current_counters.clone()) + } else { + let prev_counters = &prev[name]; + let corrected_counters = &corrected[name]; + + ( + name.clone(), + nowrap_struct(prev_counters, current_counters, corrected_counters), + ) + } + }) + .collect() +} + +/// Used to persist data between calls to detect data overflow by the kernel and fix the result. +/// Requires a minimum kernel version of 2.5.69 due to the usage of `/proc/diskstats`. +#[derive(Clone, Debug, Default)] +pub struct DiskIoCountersCollector { + prev_disk_io_counters_per_partition: Option>, + corrected_disk_io_counters_per_partition: Option>, +} + +impl DiskIoCountersCollector { + pub fn disk_io_counters(&mut self) -> Result { + let sum = self + .disk_io_counters_per_partition()? + .into_iter() + .map(|(_key, val)| val) + .sum(); + + Ok(sum) + } + + pub fn disk_io_counters_per_partition(&mut self) -> Result> { + let io_counters = disk_io_counters_per_partition()?; + + let corrected_counters = match ( + &self.prev_disk_io_counters_per_partition, + &self.corrected_disk_io_counters_per_partition, + ) { + (Some(prev), Some(corrected)) => { + fix_io_counter_overflow(&prev, &io_counters, &corrected) + } + _ => io_counters.clone(), + }; + + self.prev_disk_io_counters_per_partition = Some(io_counters); + self.corrected_disk_io_counters_per_partition = Some(corrected_counters.clone()); + + Ok(corrected_counters) + } +} diff --git a/common/rust-psutil/src/disk/filesystem.rs b/common/rust-psutil/src/disk/filesystem.rs new file mode 100644 index 00000000000..0f86f5349c7 --- /dev/null +++ b/common/rust-psutil/src/disk/filesystem.rs @@ -0,0 +1,146 @@ +// https://github.com/heim-rs/heim/blob/master/heim-disk/src/filesystem.rs + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::str::FromStr; + +/// Known filesystems. +/// +/// All physical filesystems should have their own enum element +/// and all virtual filesystems will go into the `Other` element. +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[non_exhaustive] +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +pub enum FileSystem { + /// ext2 (https://en.wikipedia.org/wiki/Ext2) + Ext2, + + /// ext3 (https://en.wikipedia.org/wiki/Ext3) + Ext3, + + /// ext4 (https://en.wikipedia.org/wiki/Ext4) + Ext4, + + /// FAT (https://en.wikipedia.org/wiki/File_Allocation_Table) + VFat, + + /// exFAT (https://en.wikipedia.org/wiki/ExFAT) + ExFat, + + /// F2FS (https://en.wikipedia.org/wiki/F2FS) + F2fs, + + /// NTFS (https://en.wikipedia.org/wiki/NTFS) + Ntfs, + + /// ZFS (https://en.wikipedia.org/wiki/ZFS) + Zfs, + + /// HFS (https://en.wikipedia.org/wiki/Hierarchical_File_System) + Hfs, + + /// HFS+ (https://en.wikipedia.org/wiki/HFS_Plus) + HfsPlus, + + /// JFS (https://en.wikipedia.org/wiki/JFS_(file_system)) + Jfs, + + /// ReiserFS 3 (https://en.wikipedia.org/wiki/ReiserFS) + Reiser3, + + /// ReiserFS 4 (https://en.wikipedia.org/wiki/Reiser4) + Reiser4, + + /// Btrfs (https://en.wikipedia.org/wiki/Btrfs) + Btrfs, + + /// MINIX FS (https://en.wikipedia.org/wiki/MINIX_file_system) + Minix, + + /// NILFS (https://en.wikipedia.org/wiki/NILFS) + Nilfs, + + /// XFS (https://en.wikipedia.org/wiki/XFS) + Xfs, + + /// APFS (https://en.wikipedia.org/wiki/Apple_File_System) + Apfs, + + // TODO: Should it be considered as a physical FS? + /// FUSE (https://en.wikipedia.org/wiki/Filesystem_in_Userspace) + FuseBlk, + + // TODO: Extend list + /// Some unspecified filesystem. + Other(String), +} + +impl FileSystem { + /// Checks if filesystem is used for a physical devices + pub fn is_physical(&self) -> bool { + !matches!(self, FileSystem::Other(..)) + } + + /// Checks if filesystem is used for a virtual devices (such as `tmpfs` or `smb` mounts) + pub fn is_virtual(&self) -> bool { + !self.is_physical() + } + + /// Returns a string identifying this filesystem. + pub fn as_str(&self) -> &str { + match self { + FileSystem::Ext2 => "ext2", + FileSystem::Ext3 => "ext3", + FileSystem::Ext4 => "ext4", + FileSystem::VFat => "vfat", + FileSystem::Ntfs => "ntfs", + FileSystem::Zfs => "zfs", + FileSystem::Hfs => "hfs", + FileSystem::Reiser3 => "reiserfs", + FileSystem::Reiser4 => "reiser4", + FileSystem::FuseBlk => "fuseblk", + FileSystem::ExFat => "exfat", + FileSystem::F2fs => "f2fs", + FileSystem::HfsPlus => "hfs+", + FileSystem::Jfs => "jfs", + FileSystem::Btrfs => "btrfs", + FileSystem::Minix => "minix", + FileSystem::Nilfs => "nilfs", + FileSystem::Xfs => "xfs", + FileSystem::Apfs => "apfs", + FileSystem::Other(string) => string.as_str(), + } + } +} + +impl FromStr for FileSystem { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result { + match () { + _ if s.eq_ignore_ascii_case("ext2") => Ok(FileSystem::Ext2), + _ if s.eq_ignore_ascii_case("ext3") => Ok(FileSystem::Ext3), + _ if s.eq_ignore_ascii_case("ext4") => Ok(FileSystem::Ext4), + _ if s.eq_ignore_ascii_case("vfat") => Ok(FileSystem::VFat), + _ if s.eq_ignore_ascii_case("ntfs") => Ok(FileSystem::Ntfs), + _ if s.eq_ignore_ascii_case("zfs") => Ok(FileSystem::Zfs), + _ if s.eq_ignore_ascii_case("hfs") => Ok(FileSystem::Hfs), + _ if s.eq_ignore_ascii_case("reiserfs") => Ok(FileSystem::Reiser3), + _ if s.eq_ignore_ascii_case("reiser4") => Ok(FileSystem::Reiser4), + _ if s.eq_ignore_ascii_case("exfat") => Ok(FileSystem::ExFat), + _ if s.eq_ignore_ascii_case("f2fs") => Ok(FileSystem::F2fs), + _ if s.eq_ignore_ascii_case("hfsplus") => Ok(FileSystem::HfsPlus), + _ if s.eq_ignore_ascii_case("jfs") => Ok(FileSystem::Jfs), + _ if s.eq_ignore_ascii_case("btrfs") => Ok(FileSystem::Btrfs), + _ if s.eq_ignore_ascii_case("minix") => Ok(FileSystem::Minix), + _ if s.eq_ignore_ascii_case("nilfs") => Ok(FileSystem::Nilfs), + _ if s.eq_ignore_ascii_case("xfs") => Ok(FileSystem::Xfs), + _ if s.eq_ignore_ascii_case("apfs") => Ok(FileSystem::Apfs), + + _ if s.eq_ignore_ascii_case("fuseblk") => Ok(FileSystem::FuseBlk), + _ => Ok(FileSystem::Other(s.to_string())), + } + } +} diff --git a/common/rust-psutil/src/disk/mod.rs b/common/rust-psutil/src/disk/mod.rs new file mode 100644 index 00000000000..20a24805dca --- /dev/null +++ b/common/rust-psutil/src/disk/mod.rs @@ -0,0 +1,10 @@ +mod disk_io_counters; +mod filesystem; +pub mod os; +mod partition; +mod sys; + +pub use disk_io_counters::*; +pub use filesystem::*; +pub use partition::*; +pub use sys::*; diff --git a/common/rust-psutil/src/disk/os/freebsd.rs b/common/rust-psutil/src/disk/os/freebsd.rs new file mode 100644 index 00000000000..b3f3b424cb9 --- /dev/null +++ b/common/rust-psutil/src/disk/os/freebsd.rs @@ -0,0 +1,25 @@ +use std::time::Duration; + +use crate::disk::DiskIoCounters; + +pub trait DiskIoCountersExt { + fn read_time(&self) -> Duration; + + fn write_time(&self) -> Duration; + + fn busy_time(&self) -> Duration; +} + +impl DiskIoCountersExt for DiskIoCounters { + fn read_time(&self) -> Duration { + todo!() + } + + fn write_time(&self) -> Duration { + todo!() + } + + fn busy_time(&self) -> Duration { + todo!() + } +} diff --git a/common/rust-psutil/src/disk/os/linux.rs b/common/rust-psutil/src/disk/os/linux.rs new file mode 100644 index 00000000000..e2891005a57 --- /dev/null +++ b/common/rust-psutil/src/disk/os/linux.rs @@ -0,0 +1,43 @@ +use std::time::Duration; + +use crate::disk::DiskIoCounters; +use crate::Count; + +pub trait DiskIoCountersExt { + /// Time spent reading from disk. + fn read_time(&self) -> Duration; + + /// Time spent writing to disk. + fn write_time(&self) -> Duration; + + /// Time spent doing actual I/Os. + fn busy_time(&self) -> Duration; + + /// Number of merged reads. + fn read_merged_count(&self) -> Count; + + /// Number of merged writes. + fn write_merged_count(&self) -> Count; +} + +impl DiskIoCountersExt for DiskIoCounters { + fn read_time(&self) -> Duration { + self.read_time + } + + fn write_time(&self) -> Duration { + self.write_time + } + + fn busy_time(&self) -> Duration { + self.busy_time + } + + fn read_merged_count(&self) -> Count { + self.read_merged_count + } + + fn write_merged_count(&self) -> Count { + self.write_merged_count + } +} diff --git a/common/rust-psutil/src/disk/os/macos.rs b/common/rust-psutil/src/disk/os/macos.rs new file mode 100644 index 00000000000..46f5d3385ce --- /dev/null +++ b/common/rust-psutil/src/disk/os/macos.rs @@ -0,0 +1,19 @@ +use std::time::Duration; + +use crate::disk::DiskIoCounters; + +pub trait DiskIoCountersExt { + fn read_time(&self) -> Duration; + + fn write_time(&self) -> Duration; +} + +impl DiskIoCountersExt for DiskIoCounters { + fn read_time(&self) -> Duration { + todo!() + } + + fn write_time(&self) -> Duration { + todo!() + } +} diff --git a/common/rust-psutil/src/disk/os/mod.rs b/common/rust-psutil/src/disk/os/mod.rs new file mode 100644 index 00000000000..b264981f782 --- /dev/null +++ b/common/rust-psutil/src/disk/os/mod.rs @@ -0,0 +1,8 @@ +#[cfg(target_os = "freebsd")] +pub mod freebsd; +#[cfg(target_os = "linux")] +pub mod linux; +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_os = "windows")] +pub mod windows; diff --git a/common/rust-psutil/src/disk/os/windows.rs b/common/rust-psutil/src/disk/os/windows.rs new file mode 100644 index 00000000000..46f5d3385ce --- /dev/null +++ b/common/rust-psutil/src/disk/os/windows.rs @@ -0,0 +1,19 @@ +use std::time::Duration; + +use crate::disk::DiskIoCounters; + +pub trait DiskIoCountersExt { + fn read_time(&self) -> Duration; + + fn write_time(&self) -> Duration; +} + +impl DiskIoCountersExt for DiskIoCounters { + fn read_time(&self) -> Duration { + todo!() + } + + fn write_time(&self) -> Duration { + todo!() + } +} diff --git a/common/rust-psutil/src/disk/partition.rs b/common/rust-psutil/src/disk/partition.rs new file mode 100644 index 00000000000..daec83f1af6 --- /dev/null +++ b/common/rust-psutil/src/disk/partition.rs @@ -0,0 +1,44 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::path::{Path, PathBuf}; + +use crate::disk::{partitions, FileSystem}; +use crate::Result; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +pub struct Partition { + pub(crate) device: String, + pub(crate) mountpoint: PathBuf, + pub(crate) filesystem: FileSystem, + pub(crate) mount_options: String, +} + +impl Partition { + pub fn device(&self) -> &str { + &self.device + } + + pub fn mountpoint(&self) -> &Path { + &self.mountpoint + } + + /// Renamed from `fstype` in Python psutil. + pub fn filesystem(&self) -> &FileSystem { + &self.filesystem + } + + /// Renamed from `opts` in Python psutil. + pub fn mount_options(&self) -> &str { + &self.mount_options + } +} + +pub fn partitions_physical() -> Result> { + Ok(partitions()? + .into_iter() + .filter(|partition| partition.filesystem.is_physical()) + .collect()) +} diff --git a/common/rust-psutil/src/disk/sys/linux/disk_io_counters.rs b/common/rust-psutil/src/disk/sys/linux/disk_io_counters.rs new file mode 100644 index 00000000000..be3bed834fe --- /dev/null +++ b/common/rust-psutil/src/disk/sys/linux/disk_io_counters.rs @@ -0,0 +1,107 @@ +// https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/linux/counters.rs + +use std::collections::HashMap; +use std::str::FromStr; +use std::time::Duration; + +use crate::disk::DiskIoCounters; +use crate::{read_file, Error, Result}; + +// Copied from the `psutil` sources: +// +// "man iostat" states that sectors are equivalent with blocks and have +// a size of 512 bytes. Despite this value can be queried at runtime +// via /sys/block/{DISK}/queue/hw_sector_size and results may vary +// between 1k, 2k, or 4k... 512 appears to be a magic constant used +// throughout Linux source code: +// * https://stackoverflow.com/a/38136179/376587 +// * https://lists.gt.net/linux/kernel/2241060 +// * https://github.com/giampaolo/psutil/issues/1305 +// * https://github.com/torvalds/linux/blob/4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99 +// * https://lkml.org/lkml/2015/8/17/234 +const DISK_SECTOR_SIZE: u64 = 512; + +const PROC_DISKSTATS: &str = "/proc/diskstats"; +const PROC_PARTITIONS: &str = "/proc/partitions"; + +impl FromStr for DiskIoCounters { + type Err = Error; + + // At the moment supports format used in Linux 2.6+, + // except ignoring discard values introduced in Linux 4.18. + // + // https://www.kernel.org/doc/Documentation/iostats.txt + // https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats + fn from_str(line: &str) -> Result { + let fields = match line.split_whitespace().collect::>() { + fields if fields.len() >= 14 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_DISKSTATS.into(), + contents: line.to_string(), + }), + }?; + + let parse = |s: &str| -> Result { + s.parse().map_err(|err| Error::ParseInt { + path: PROC_DISKSTATS.into(), + contents: line.to_string(), + source: err, + }) + }; + + Ok(DiskIoCounters { + read_count: parse(fields[3])?, + write_count: parse(fields[7])?, + read_bytes: parse(fields[5])? * DISK_SECTOR_SIZE, + write_bytes: parse(fields[9])? * DISK_SECTOR_SIZE, + read_time: Duration::from_millis(parse(fields[6])?), + write_time: Duration::from_millis(parse(fields[10])?), + busy_time: Duration::from_millis(parse(fields[12])?), + read_merged_count: parse(fields[4])?, + write_merged_count: parse(fields[8])?, + }) + } +} + +/// Determine partitions we want to look for. +fn get_partitions(contents: &str) -> Result> { + contents + .lines() + .skip(2) + .map(|line| { + let fields = match line.split_whitespace().collect::>() { + fields if fields.len() >= 4 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_PARTITIONS.into(), + contents: line.to_string(), + }), + }?; + + Ok(fields[3]) + }) + .collect() +} + +pub(crate) fn disk_io_counters_per_partition() -> Result> { + let contents = read_file(PROC_PARTITIONS)?; + let partitions = get_partitions(&contents)?; + let contents = read_file(PROC_DISKSTATS)?; + let mut io_counters: HashMap = HashMap::new(); + + for line in contents.lines() { + let fields = match line.split_whitespace().collect::>() { + fields if fields.len() >= 14 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_DISKSTATS.into(), + contents: line.to_string(), + }), + }?; + + let name = fields[2]; + if partitions.contains(&name) { + io_counters.insert(String::from(name), DiskIoCounters::from_str(line)?); + } + } + + Ok(io_counters) +} diff --git a/common/rust-psutil/src/disk/sys/linux/mod.rs b/common/rust-psutil/src/disk/sys/linux/mod.rs new file mode 100644 index 00000000000..61a7299883a --- /dev/null +++ b/common/rust-psutil/src/disk/sys/linux/mod.rs @@ -0,0 +1,6 @@ +mod disk_io_counters; +mod partitions; + +#[allow(unused_imports)] +pub use disk_io_counters::*; +pub use partitions::*; diff --git a/common/rust-psutil/src/disk/sys/linux/partitions.rs b/common/rust-psutil/src/disk/sys/linux/partitions.rs new file mode 100644 index 00000000000..f294262d743 --- /dev/null +++ b/common/rust-psutil/src/disk/sys/linux/partitions.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; +use std::str::FromStr; + +use unescape::unescape; + +use crate::{read_file, Error, Result}; + +use crate::disk::{FileSystem, Partition}; + +const PROC_MOUNTS: &str = "/proc/mounts"; + +impl FromStr for Partition { + type Err = Error; + + fn from_str(line: &str) -> Result { + // Example: `/dev/sda3 /home ext4 rw,relatime,data=ordered 0 0` + match line.split_whitespace().collect::>() { + fields if fields.len() >= 4 => { + Ok(Partition { + device: String::from(fields[0]), + // need to unescape since some characters are escaped by default like the space character + // https://github.com/cjbassi/ytop/issues/29 + mountpoint: PathBuf::from(unescape(fields[1]).unwrap()), // TODO: can this unwrap fail? + filesystem: FileSystem::from_str(fields[2]).unwrap(), // infallible unwrap + mount_options: String::from(fields[3]), + }) + } + _ => Err(Error::MissingData { + path: PROC_MOUNTS.into(), + contents: line.to_string(), + }), + } + } +} + +pub fn partitions() -> Result> { + read_file(PROC_MOUNTS)? + .lines() + .map(|line| Partition::from_str(line)) + .collect() +} diff --git a/common/rust-psutil/src/disk/sys/macos/disk_io_counters.rs b/common/rust-psutil/src/disk/sys/macos/disk_io_counters.rs new file mode 100644 index 00000000000..6e2ce98d075 --- /dev/null +++ b/common/rust-psutil/src/disk/sys/macos/disk_io_counters.rs @@ -0,0 +1,8 @@ +use std::collections::HashMap; + +use crate::disk::DiskIoCounters; +use crate::Result; + +pub(crate) fn disk_io_counters_per_partition() -> Result> { + todo!() +} diff --git a/common/rust-psutil/src/disk/sys/macos/mod.rs b/common/rust-psutil/src/disk/sys/macos/mod.rs new file mode 100644 index 00000000000..70f76dab3df --- /dev/null +++ b/common/rust-psutil/src/disk/sys/macos/mod.rs @@ -0,0 +1,4 @@ +mod disk_io_counters; + +#[allow(unused_imports)] +pub use disk_io_counters::*; diff --git a/common/rust-psutil/src/disk/sys/mod.rs b/common/rust-psutil/src/disk/sys/mod.rs new file mode 100644 index 00000000000..f0f92fc4c83 --- /dev/null +++ b/common/rust-psutil/src/disk/sys/mod.rs @@ -0,0 +1,17 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "macos")] { + mod macos; + #[allow(unused_imports)] + pub use macos::*; + } +} + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod unix; + pub use unix::*; + } +} diff --git a/common/rust-psutil/src/disk/sys/unix/disk_usage.rs b/common/rust-psutil/src/disk/sys/unix/disk_usage.rs new file mode 100644 index 00000000000..08157181534 --- /dev/null +++ b/common/rust-psutil/src/disk/sys/unix/disk_usage.rs @@ -0,0 +1,63 @@ +use std::path::Path; + +use nix::sys; + +use crate::{Bytes, Percent, Result}; + +#[derive(Clone, Debug, Default)] +pub struct DiskUsage { + pub(crate) total: Bytes, + pub(crate) used: Bytes, + pub(crate) free: Bytes, + pub(crate) percent: Percent, +} + +impl DiskUsage { + /// Total disk size in bytes. + pub fn total(&self) -> Bytes { + self.total + } + + /// Number of bytes used. + pub fn used(&self) -> Bytes { + self.used + } + + /// Number of bytes free. + pub fn free(&self) -> Bytes { + self.free + } + + /// Percentage of disk used. + pub fn percent(&self) -> Percent { + self.percent + } +} + +pub fn disk_usage

(path: P) -> Result +where + P: AsRef, +{ + let statvfs = sys::statvfs::statvfs(path.as_ref())?; + + // need to do the u64 casts since on some platforms the types are aliased to u32 + // https://github.com/rust-psutil/rust-psutil/issues/64 + // https://github.com/rust-psutil/rust-psutil/pull/39 + + let total = statvfs.blocks() as u64 * statvfs.fragment_size() as u64; + + let avail_to_root = statvfs.blocks_free() as u64 * statvfs.fragment_size() as u64; + let used = total - avail_to_root; + + let free = statvfs.blocks_available() as u64 * statvfs.fragment_size() as u64; + + let total_user = used + free; + let percent = ((used as f64 / total_user as f64) * 100.0) as f32; + + Ok(DiskUsage { + total, + used, + free, + percent, + }) +} diff --git a/common/rust-psutil/src/disk/sys/unix/mod.rs b/common/rust-psutil/src/disk/sys/unix/mod.rs new file mode 100644 index 00000000000..1f1919efe3f --- /dev/null +++ b/common/rust-psutil/src/disk/sys/unix/mod.rs @@ -0,0 +1,7 @@ +mod disk_usage; +#[cfg(not(target_os = "linux"))] +mod partitions; + +pub use disk_usage::*; +#[cfg(not(target_os = "linux"))] +pub use partitions::*; diff --git a/common/rust-psutil/src/disk/sys/unix/partitions.rs b/common/rust-psutil/src/disk/sys/unix/partitions.rs new file mode 100644 index 00000000000..366b35b4373 --- /dev/null +++ b/common/rust-psutil/src/disk/sys/unix/partitions.rs @@ -0,0 +1,84 @@ +// https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/unix/bindings/mod.rs +// https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/unix/partitions.rs + +use std::ffi::CStr; +use std::io; +use std::mem; +use std::path::PathBuf; +use std::ptr; +use std::str::FromStr; + +use nix::libc; + +use crate::disk::{FileSystem, Partition}; + +#[allow(unused)] +pub const MNT_WAIT: libc::c_int = 1; +pub const MNT_NOWAIT: libc::c_int = 2; + +extern "C" { + fn getfsstat64(buf: *mut libc::statfs, bufsize: libc::c_int, flags: libc::c_int) + -> libc::c_int; +} + +// TODO: Since `MNT_NOWAIT` might return inconsistent data (see `getfsstat(2)`) +// it might be a good idea (maybe?) to wrap it into a `blocking` call +// and switch to the `MNT_WAIT` mode? +// Should be considered later. +fn mounts() -> io::Result> { + let expected_len = unsafe { getfsstat64(ptr::null_mut(), 0, MNT_NOWAIT) }; + let mut mounts: Vec = Vec::with_capacity(expected_len as usize); + let result = unsafe { + getfsstat64( + mounts.as_mut_ptr(), + mem::size_of::() as libc::c_int * expected_len, + MNT_NOWAIT, + ) + }; + if result == -1 { + return Err(io::Error::last_os_error()); + } else { + debug_assert!( + expected_len == result, + "Expected {} statfs entries, but got {}", + expected_len, + result + ); + unsafe { + mounts.set_len(result as usize); + } + } + + Ok(mounts) +} + +impl From for Partition { + fn from(stat: libc::statfs) -> Partition { + let device = unsafe { + CStr::from_ptr(stat.f_mntfromname.as_ptr()) + .to_string_lossy() + .to_string() + }; + let filesystem = FileSystem::from_str(unsafe { + &CStr::from_ptr(stat.f_fstypename.as_ptr()).to_string_lossy() + }) + .unwrap(); + let mountpoint = PathBuf::from(unsafe { + CStr::from_ptr(stat.f_mntonname.as_ptr()) + .to_string_lossy() + .to_string() + }); + let flags = stat.f_flags; + + Partition { + device, + mountpoint, + filesystem, + mount_options: "".to_owned(), // TODO + } + } +} + +pub fn partitions() -> io::Result> { + Ok(mounts()?.into_iter().map(Partition::from).collect()) +} diff --git a/common/rust-psutil/src/errors.rs b/common/rust-psutil/src/errors.rs new file mode 100644 index 00000000000..6749695d2fc --- /dev/null +++ b/common/rust-psutil/src/errors.rs @@ -0,0 +1,127 @@ +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +#[cfg(feature = "sensors")] +use glob::glob as other_glob; + +#[derive(Debug, thiserror::Error)] +pub enum ParseStatusError { + /// Linux only. + #[error("Length is not 1. Contents: '{}'", contents)] + IncorrectLength { contents: String }, + + /// Linux and macOS. + #[error("Incorrect char. Contents: '{}'", contents)] + IncorrectChar { contents: String }, +} + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Linux only. + #[error("Failed to read file '{}': {}", path.display(), source)] + ReadFile { path: PathBuf, source: io::Error }, + + /// Linux only. + #[error("File '{}' is missing data. Contents: '{}'", path.display(), contents)] + MissingData { path: PathBuf, contents: String }, + + /// Linux only. + #[error("Parse error for file '{}'. Contents: '{}'. {}", path.display(), contents, source)] + ParseInt { + path: PathBuf, + contents: String, + source: std::num::ParseIntError, + }, + + /// Linux only. + #[error("Parse error for file '{}'. Contents: '{}'. {}", path.display(), contents, source)] + ParseFloat { + path: PathBuf, + contents: String, + source: std::num::ParseFloatError, + }, + + /// Linux and macOS. + #[error("Failed to parse status. {}", source)] + ParseStatus { source: ParseStatusError }, + + // Unix only. + #[error("nix error: {}", source)] + NixError { source: nix::Error }, + + /// macOS only. + #[error("OS error: {}", source)] + OsError { source: io::Error }, +} + +impl From for Error { + fn from(error: nix::Error) -> Self { + Error::NixError { source: error } + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Error::OsError { source: error } + } +} + +impl From for Error { + fn from(error: ParseStatusError) -> Self { + Error::ParseStatus { source: error } + } +} + +pub(crate) fn read_file

(path: P) -> Result +where + P: AsRef, +{ + fs::read_to_string(&path).map_err(|err| Error::ReadFile { + path: path.as_ref().into(), + source: err, + }) +} + +pub(crate) fn read_dir

(path: P) -> Result> +where + P: AsRef, +{ + fs::read_dir(&path) + .map_err(|err| Error::ReadFile { + path: path.as_ref().into(), + source: err, + })? + .map(|entry| { + entry.map_err(|err| Error::ReadFile { + path: path.as_ref().into(), + source: err, + }) + }) + .collect() +} + +pub(crate) fn read_link

(path: P) -> Result +where + P: AsRef, +{ + fs::read_link(&path).map_err(|err| Error::ReadFile { + path: path.as_ref().into(), + source: err, + }) +} + +#[cfg(feature = "sensors")] +pub(crate) fn glob(path: &str) -> Vec> { + other_glob(path) + .unwrap() // only errors on invalid pattern + .map(|result| { + result.map_err(|err| Error::ReadFile { + path: path.into(), + source: err.into_error(), + }) + }) + .collect() +} diff --git a/common/rust-psutil/src/host/info.rs b/common/rust-psutil/src/host/info.rs new file mode 100644 index 00000000000..1e2f542a632 --- /dev/null +++ b/common/rust-psutil/src/host/info.rs @@ -0,0 +1,38 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use platforms::target::{Arch, OS}; + +/// Not found in Python psutil. +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +pub struct Info { + pub(crate) operating_system: OS, + pub(crate) release: String, + pub(crate) version: String, + pub(crate) hostname: String, + pub(crate) architecture: Arch, +} + +impl Info { + pub fn operating_system(&self) -> OS { + self.operating_system + } + + pub fn release(&self) -> &str { + &self.release + } + + pub fn version(&self) -> &str { + &self.version + } + + pub fn hostname(&self) -> &str { + &self.hostname + } + + pub fn architecture(&self) -> Arch { + self.architecture + } +} diff --git a/common/rust-psutil/src/host/loadavg.rs b/common/rust-psutil/src/host/loadavg.rs new file mode 100644 index 00000000000..26ee1d3e8ea --- /dev/null +++ b/common/rust-psutil/src/host/loadavg.rs @@ -0,0 +1,18 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::FloatCount; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug)] +pub struct LoadAvg { + /// Number of jobs in the run queue averaged over 1 minute. + pub one: FloatCount, + + /// Number of jobs in the run queue averaged over 5 minute. + pub five: FloatCount, + + /// Number of jobs in the run queue averaged over 15 minute. + pub fifteen: FloatCount, +} diff --git a/common/rust-psutil/src/host/mod.rs b/common/rust-psutil/src/host/mod.rs new file mode 100644 index 00000000000..2d69418a809 --- /dev/null +++ b/common/rust-psutil/src/host/mod.rs @@ -0,0 +1,11 @@ +mod info; +mod loadavg; +mod sys; +mod user; + +pub use platforms::target::{Arch, OS}; + +pub use info::*; +pub use loadavg::*; +pub use sys::*; +pub use user::*; diff --git a/common/rust-psutil/src/host/sys/linux/boot_time.rs b/common/rust-psutil/src/host/sys/linux/boot_time.rs new file mode 100644 index 00000000000..4bc94008733 --- /dev/null +++ b/common/rust-psutil/src/host/sys/linux/boot_time.rs @@ -0,0 +1,38 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use crate::{read_file, Error, Result}; + +const PROC_STAT: &str = "/proc/stat"; + +fn parse_boot_time(line: &str) -> Result { + let fields = match line.split_whitespace().collect::>() { + fields if fields.len() >= 2 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_STAT.into(), + contents: line.to_string(), + }), + }?; + + let parsed = fields[1].parse().map_err(|err| Error::ParseInt { + path: PROC_STAT.into(), + contents: line.to_string(), + source: err, + })?; + let boot_time = UNIX_EPOCH + Duration::from_secs(parsed); + + Ok(boot_time) +} + +// TODO: cache with https://github.com/jaemk/cached once `pub fn` is supported +pub fn boot_time() -> Result { + let contents = read_file(PROC_STAT)?; + let line = contents + .lines() + .find(|line| line.starts_with("btime ")) + .ok_or(Error::MissingData { + path: PROC_STAT.into(), + contents: contents.clone(), + })?; + + parse_boot_time(line) +} diff --git a/common/rust-psutil/src/host/sys/linux/loadavg.rs b/common/rust-psutil/src/host/sys/linux/loadavg.rs new file mode 100644 index 00000000000..22d8a49faed --- /dev/null +++ b/common/rust-psutil/src/host/sys/linux/loadavg.rs @@ -0,0 +1,63 @@ +use std::str::FromStr; + +use crate::host::LoadAvg; +use crate::{read_file, Error, Result}; + +const PROC_LOADAVG: &str = "/proc/loadavg"; + +impl FromStr for LoadAvg { + type Err = Error; + + fn from_str(s: &str) -> Result { + let fields = match s.split_whitespace().collect::>() { + fields if fields.len() >= 3 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_LOADAVG.into(), + contents: s.to_string(), + }), + }?; + + let parse = |s: &str| -> Result { + s.parse().map_err(|err| Error::ParseFloat { + path: PROC_LOADAVG.into(), + contents: s.to_string(), + source: err, + }) + }; + + let one = parse(fields[0])?; + let five = parse(fields[1])?; + let fifteen = parse(fields[2])?; + + Ok(LoadAvg { one, five, fifteen }) + } +} + +pub fn loadavg() -> Result { + LoadAvg::from_str(&read_file(PROC_LOADAVG)?) +} + +#[cfg(test)] +mod unit_tests { + use super::*; + use crate::FloatCount; + use float_cmp::approx_eq; + + #[test] + fn test_loadaverage() { + let loadavg = loadavg().unwrap(); + // shouldn't be negative + assert!(loadavg.one >= 0.0); + assert!(loadavg.five >= 0.0); + assert!(loadavg.fifteen >= 0.0); + } + + #[test] + fn test_parse_loadavg() { + let input = "0.49 0.70 0.84 2/519 1454\n"; + let loadavg = LoadAvg::from_str(input).unwrap(); + assert!(approx_eq!(FloatCount, loadavg.one, 0.49)); + assert!(approx_eq!(FloatCount, loadavg.five, 0.70)); + assert!(approx_eq!(FloatCount, loadavg.fifteen, 0.84)); + } +} diff --git a/common/rust-psutil/src/host/sys/linux/mod.rs b/common/rust-psutil/src/host/sys/linux/mod.rs new file mode 100644 index 00000000000..cdeaaaa7e09 --- /dev/null +++ b/common/rust-psutil/src/host/sys/linux/mod.rs @@ -0,0 +1,9 @@ +mod boot_time; +mod loadavg; +mod uptime; +mod users; + +pub use boot_time::*; +pub use loadavg::*; +pub use uptime::*; +pub use users::*; diff --git a/common/rust-psutil/src/host/sys/linux/uptime.rs b/common/rust-psutil/src/host/sys/linux/uptime.rs new file mode 100644 index 00000000000..5ce1fb550d1 --- /dev/null +++ b/common/rust-psutil/src/host/sys/linux/uptime.rs @@ -0,0 +1,47 @@ +use std::time::Duration; + +use crate::{read_file, Error, Result}; + +const PROC_UPTIME: &str = "/proc/uptime"; + +fn parse_uptime(contents: &str) -> Result { + let fields = match contents.split_whitespace().collect::>() { + fields if fields.len() >= 2 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_UPTIME.into(), + contents: contents.to_string(), + }), + }?; + + let parsed = fields[0].parse().map_err(|err| Error::ParseFloat { + path: PROC_UPTIME.into(), + contents: contents.to_string(), + source: err, + })?; + let uptime = Duration::from_secs_f64(parsed); + + Ok(uptime) +} + +/// New function, not in Python psutil. +pub fn uptime() -> Result { + parse_uptime(&read_file(PROC_UPTIME)?) +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_uptime() { + assert!(uptime().unwrap().as_secs() > 0); + } + + #[test] + fn test_parse_uptime() { + assert_eq!( + parse_uptime("12489513.08 22906637.29\n").unwrap(), + Duration::from_secs_f64(12_489_513.08) + ); + } +} diff --git a/common/rust-psutil/src/host/sys/linux/users.rs b/common/rust-psutil/src/host/sys/linux/users.rs new file mode 100644 index 00000000000..322d46ee0c4 --- /dev/null +++ b/common/rust-psutil/src/host/sys/linux/users.rs @@ -0,0 +1,7 @@ +use std::io; + +use crate::host::User; + +pub fn users() -> io::Result> { + todo!() +} diff --git a/common/rust-psutil/src/host/sys/mod.rs b/common/rust-psutil/src/host/sys/mod.rs new file mode 100644 index 00000000000..3e2a9816994 --- /dev/null +++ b/common/rust-psutil/src/host/sys/mod.rs @@ -0,0 +1,13 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } +} + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod unix; + pub use unix::*; + } +} diff --git a/common/rust-psutil/src/host/sys/unix/info.rs b/common/rust-psutil/src/host/sys/unix/info.rs new file mode 100644 index 00000000000..ba71005bd80 --- /dev/null +++ b/common/rust-psutil/src/host/sys/unix/info.rs @@ -0,0 +1,26 @@ +// https://github.com/heim-rs/heim/blob/master/heim-host/src/platform.rs + +use std::str::FromStr; + +use nix::sys; +use platforms::target::{Arch, OS}; + +use crate::host::Info; + +pub fn info() -> Info { + let utsname = sys::utsname::uname(); + + let operating_system = OS::from_str(utsname.sysname()).unwrap_or(OS::Unknown); + let release = utsname.release().to_string(); + let version = utsname.version().to_string(); + let hostname = utsname.nodename().to_string(); + let architecture = Arch::from_str(utsname.machine()).unwrap_or(Arch::Unknown); + + Info { + operating_system, + release, + version, + hostname, + architecture, + } +} diff --git a/common/rust-psutil/src/host/sys/unix/mod.rs b/common/rust-psutil/src/host/sys/unix/mod.rs new file mode 100644 index 00000000000..022d4b4804b --- /dev/null +++ b/common/rust-psutil/src/host/sys/unix/mod.rs @@ -0,0 +1,3 @@ +mod info; + +pub use info::*; diff --git a/common/rust-psutil/src/host/user.rs b/common/rust-psutil/src/host/user.rs new file mode 100644 index 00000000000..6b695a56ec5 --- /dev/null +++ b/common/rust-psutil/src/host/user.rs @@ -0,0 +1,32 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::time::SystemTime; + +use crate::Pid; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct User {} + +impl User { + pub fn user(&self) -> &str { + todo!() + } + + pub fn terminal(&self) -> Option<&str> { + todo!() + } + + pub fn host(&self) -> Option<&str> { + todo!() + } + + pub fn started(&self) -> SystemTime { + todo!() + } + + pub fn pid(&self) -> Option { + todo!() + } +} diff --git a/common/rust-psutil/src/lib.rs b/common/rust-psutil/src/lib.rs new file mode 100644 index 00000000000..2fe2c5aa09a --- /dev/null +++ b/common/rust-psutil/src/lib.rs @@ -0,0 +1,64 @@ +//! A process and system monitoring library for Rust, heavily inspired by the [`psutil`](https://psutil.readthedocs.io/en/latest/#) module for Python. +//! +//! # Minimum versions supported +//! +//! - Linux: 2.6.0 (2003-12-17) +//! +//! # Note about the API +//! +//! `rust-psutil` implements the same API as `psutil` with some exceptions: +//! +//! - some things have been slightly renamed +//! - the crate is namespaced based on subsystem, e.g. `cpu::cpu_percent()` +//! - users can opt into which subsystems to use based on cargo feature flags +//! - some functions have been refactored +//! - e.g. `cpu_count(bool)` into `cpu_count()` and `cpu_count_physical()` +//! - functions that need to persist data between calls are implemented as methods on 'collectors' +//! - e.g. `cpu_percent()` -> `CpuPercentCollector::cpu_percent()` +//! - platform specific functionality is hidden behind traits that need to be imported before used +//! - e.g. import `cpu::os::linux::ProcessExt` to use Linux specific process functionality +//! - some types are different, for example: +//! - structs instead of named tuples +//! - `std::time::Duration` instead of float for seconds +//! - enums instead of constants +//! - most struct fields have been replaced with getter methods to better enable platform based extensions + +#[cfg(feature = "serde")] +extern crate renamed_serde as serde; + +#[macro_use] +mod utils; +pub mod common; +mod errors; +mod types; + +pub use errors::*; +pub use types::*; + +#[cfg(feature = "cpu")] +pub mod cpu; + +#[cfg(feature = "disk")] +pub mod disk; + +#[cfg(feature = "host")] +pub mod host; + +#[cfg(feature = "memory")] +pub mod memory; + +#[cfg(feature = "network")] +pub mod network; + +#[cfg(feature = "process")] +pub mod process; + +#[cfg(feature = "sensors")] +pub mod sensors; + +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod unix; + use unix::*; + } +} diff --git a/common/rust-psutil/src/memory/mod.rs b/common/rust-psutil/src/memory/mod.rs new file mode 100644 index 00000000000..eb90b8b97a8 --- /dev/null +++ b/common/rust-psutil/src/memory/mod.rs @@ -0,0 +1,8 @@ +pub mod os; +mod swap_memory; +mod sys; +mod virtual_memory; + +pub use swap_memory::*; +pub use sys::*; +pub use virtual_memory::*; diff --git a/common/rust-psutil/src/memory/os/bsd.rs b/common/rust-psutil/src/memory/os/bsd.rs new file mode 100644 index 00000000000..2acf43091d9 --- /dev/null +++ b/common/rust-psutil/src/memory/os/bsd.rs @@ -0,0 +1,30 @@ +use crate::memory::VirtualMemory; +use crate::Bytes; + +pub trait VirtualMemoryExt { + fn buffers(&self) -> Bytes; + + fn cached(&self) -> Bytes; + + fn shared(&self) -> Bytes; + + fn wired(&self) -> Bytes; +} + +impl VirtualMemoryExt for VirtualMemory { + fn buffers(&self) -> Bytes { + todo!() + } + + fn cached(&self) -> Bytes { + todo!() + } + + fn shared(&self) -> Bytes { + todo!() + } + + fn wired(&self) -> Bytes { + todo!() + } +} diff --git a/common/rust-psutil/src/memory/os/linux.rs b/common/rust-psutil/src/memory/os/linux.rs new file mode 100644 index 00000000000..10e0b515206 --- /dev/null +++ b/common/rust-psutil/src/memory/os/linux.rs @@ -0,0 +1,33 @@ +use crate::memory::VirtualMemory; +use crate::Bytes; + +pub trait VirtualMemoryExt { + /// Temporary storage for raw disk blocks. + fn buffers(&self) -> Bytes; + + /// Memory used by the page cache. + fn cached(&self) -> Bytes; + + /// Amount of memory consumed by tmpfs filesystems. + fn shared(&self) -> Bytes; + + fn slab(&self) -> Bytes; +} + +impl VirtualMemoryExt for VirtualMemory { + fn buffers(&self) -> Bytes { + self.buffers + } + + fn cached(&self) -> Bytes { + self.cached + } + + fn shared(&self) -> Bytes { + self.shared + } + + fn slab(&self) -> Bytes { + self.slab + } +} diff --git a/common/rust-psutil/src/memory/os/macos.rs b/common/rust-psutil/src/memory/os/macos.rs new file mode 100644 index 00000000000..585d16fabe8 --- /dev/null +++ b/common/rust-psutil/src/memory/os/macos.rs @@ -0,0 +1,12 @@ +use crate::memory::VirtualMemory; +use crate::Bytes; + +pub trait VirtualMemoryExt { + fn wired(&self) -> Bytes; +} + +impl VirtualMemoryExt for VirtualMemory { + fn wired(&self) -> Bytes { + self.wired + } +} diff --git a/common/rust-psutil/src/memory/os/mod.rs b/common/rust-psutil/src/memory/os/mod.rs new file mode 100644 index 00000000000..cab2c381173 --- /dev/null +++ b/common/rust-psutil/src/memory/os/mod.rs @@ -0,0 +1,13 @@ +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub mod bsd; +#[cfg(target_os = "linux")] +pub mod linux; +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_family = "unix")] +pub mod unix; diff --git a/common/rust-psutil/src/memory/os/unix.rs b/common/rust-psutil/src/memory/os/unix.rs new file mode 100644 index 00000000000..4654adec88e --- /dev/null +++ b/common/rust-psutil/src/memory/os/unix.rs @@ -0,0 +1,20 @@ +use crate::memory::VirtualMemory; +use crate::Bytes; + +pub trait VirtualMemoryExt { + /// Memory currently in use. + fn active(&self) -> Bytes; + + /// Memory that is not in use. + fn inactive(&self) -> Bytes; +} + +impl VirtualMemoryExt for VirtualMemory { + fn active(&self) -> Bytes { + self.active + } + + fn inactive(&self) -> Bytes { + self.inactive + } +} diff --git a/common/rust-psutil/src/memory/swap_memory.rs b/common/rust-psutil/src/memory/swap_memory.rs new file mode 100644 index 00000000000..3dc3d6b6548 --- /dev/null +++ b/common/rust-psutil/src/memory/swap_memory.rs @@ -0,0 +1,50 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::{Bytes, Percent}; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct SwapMemory { + pub(crate) total: Bytes, + pub(crate) used: Bytes, + pub(crate) free: Bytes, + pub(crate) percent: Percent, + pub(crate) swapped_in: Bytes, + pub(crate) swapped_out: Bytes, +} + +impl SwapMemory { + /// Amount of total swap memory. + pub fn total(&self) -> Bytes { + self.total + } + + /// Amount of used swap memory. + pub fn used(&self) -> Bytes { + self.used + } + + /// Amount of free swap memory. + pub fn free(&self) -> Bytes { + self.free + } + + /// Percent of swap memory used. + pub fn percent(&self) -> Percent { + self.percent + } + + /// Amount of memory swapped in from disk. + /// Renamed from `sin` in Python psutil. + pub fn swapped_in(&self) -> Bytes { + self.swapped_in + } + + /// Amount of memory swapped to disk. + /// Renamed from `sout` in Python psutil. + pub fn swapped_out(&self) -> Bytes { + self.swapped_out + } +} diff --git a/common/rust-psutil/src/memory/sys/linux/common.rs b/common/rust-psutil/src/memory/sys/linux/common.rs new file mode 100644 index 00000000000..3e33123e09b --- /dev/null +++ b/common/rust-psutil/src/memory/sys/linux/common.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +use crate::{Error, Result}; + +// TODO: should we only parse the ints that we need? +pub(crate) fn make_map<'a>(content: &'a str, path: &str) -> Result> { + content + .lines() + .map(|line| { + let fields = match line.split_whitespace().collect::>() { + fields if fields.len() >= 2 => Ok(fields), + _ => Err(Error::MissingData { + path: path.into(), + contents: line.to_string(), + }), + }?; + + let mut parsed = fields[1].parse().map_err(|err| Error::ParseInt { + path: path.into(), + contents: line.to_string(), + source: err, + })?; + // only needed for `/proc/meminfo` + if fields.len() >= 3 && fields[2] == "kB" { + parsed *= 1024; + } + + // only needed for `/proc/meminfo` + let name = fields[0].trim_end_matches(':'); + + Ok((name, parsed)) + }) + .collect() +} diff --git a/common/rust-psutil/src/memory/sys/linux/mod.rs b/common/rust-psutil/src/memory/sys/linux/mod.rs new file mode 100644 index 00000000000..5e0de44065e --- /dev/null +++ b/common/rust-psutil/src/memory/sys/linux/mod.rs @@ -0,0 +1,7 @@ +mod common; +mod swap_memory; +mod virtual_memory; + +pub(crate) use common::*; +pub use swap_memory::*; +pub use virtual_memory::*; diff --git a/common/rust-psutil/src/memory/sys/linux/swap_memory.rs b/common/rust-psutil/src/memory/sys/linux/swap_memory.rs new file mode 100644 index 00000000000..48e8f4833ee --- /dev/null +++ b/common/rust-psutil/src/memory/sys/linux/swap_memory.rs @@ -0,0 +1,51 @@ +use crate::memory::{make_map, SwapMemory}; +use crate::utils::u64_percent; +use crate::{read_file, Error, Result}; + +const PROC_MEMINFO: &str = "/proc/meminfo"; +const PROC_VMSTAT: &str = "/proc/vmstat"; + +// TODO: return an option for when swap is disabled? +pub fn swap_memory() -> Result { + let meminfo_contents = read_file(PROC_MEMINFO)?; + let meminfo = make_map(&meminfo_contents, PROC_MEMINFO)?; + + let vmstat_contents = read_file(PROC_VMSTAT)?; + let vmstat = make_map(&vmstat_contents, PROC_VMSTAT)?; + + let meminfo_get = |key: &str| -> Result { + meminfo.get(key).copied().ok_or(Error::MissingData { + path: PROC_MEMINFO.into(), + contents: meminfo_contents.clone(), + }) + }; + let vmstat_get = |key: &str| -> Result { + vmstat.get(key).copied().ok_or(Error::MissingData { + path: PROC_VMSTAT.into(), + contents: vmstat_contents.clone(), + }) + }; + + let total = meminfo_get("SwapTotal")?; + let free = meminfo_get("SwapFree")?; + + let swapped_in = vmstat_get("pswpin")?; + let swapped_out = vmstat_get("pswpout")?; + + let used = total - free; + // total will be 0 if swap is disabled + let percent = if total == 0 { + 0.0 + } else { + u64_percent(used, total) + }; + + Ok(SwapMemory { + total, + used, + free, + percent, + swapped_in, + swapped_out, + }) +} diff --git a/common/rust-psutil/src/memory/sys/linux/virtual_memory.rs b/common/rust-psutil/src/memory/sys/linux/virtual_memory.rs new file mode 100644 index 00000000000..066a940868a --- /dev/null +++ b/common/rust-psutil/src/memory/sys/linux/virtual_memory.rs @@ -0,0 +1,51 @@ +use crate::memory::{make_map, VirtualMemory}; +use crate::{read_file, Error, Result}; + +const PROC_MEMINFO: &str = "/proc/meminfo"; + +// TODO: some of this stuff relies on a kernel version greater than 2.6 +pub fn virtual_memory() -> Result { + let contents = read_file(PROC_MEMINFO)?; + let meminfo = make_map(&contents, PROC_MEMINFO)?; + + let get = |key: &str| -> Result { + meminfo.get(key).copied().ok_or(Error::MissingData { + path: PROC_MEMINFO.into(), + contents: contents.clone(), + }) + }; + + let total = get("MemTotal")?; + // since Linux 3.14 + let available = get("MemAvailable")?; + let free = get("MemFree")?; + let active = get("Active")?; + let inactive = get("Inactive")?; + let buffers = get("Buffers")?; + // "free" cmdline utility sums reclaimable to cached. + // Older versions of procps used to add slab memory instead. + // This got changed in: + // https://gitlab.com/procps-ng/procps/commit/05d751c4f076a2f0118b914c5e51cfbb4762ad8e + // SReclaimable available since Linux 2.6.19 + let cached = get("Cached")? + get("SReclaimable")?; + // since Linux 2.6.32 + let shared = get("Shmem")?; + let slab = 0; // TODO + + let used = total - free - cached - buffers; + let percent = (((total as f64 - available as f64) / total as f64) * 100.0) as f32; + + Ok(VirtualMemory { + total, + available, + used, + free, + percent, + active, + inactive, + buffers, + cached, + shared, + slab, + }) +} diff --git a/common/rust-psutil/src/memory/sys/macos/common.rs b/common/rust-psutil/src/memory/sys/macos/common.rs new file mode 100644 index 00000000000..ff2f5527f3d --- /dev/null +++ b/common/rust-psutil/src/memory/sys/macos/common.rs @@ -0,0 +1,95 @@ +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/memory.rs +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/swap.rs +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/bindings.rs +// https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/mod.rs + +use std::io; + +use mach::kern_return::{self, kern_return_t}; +use mach::mach_port; +use mach::mach_types::{host_name_port_t, host_t}; +use mach::message::mach_msg_type_number_t; +use mach::traps::mach_task_self; +use mach::vm_types::{integer_t, natural_t}; +use nix::libc; + +const HOST_VM_INFO64: libc::c_int = 4; +const HOST_VM_INFO64_COUNT: libc::c_uint = 38; + +/// https://developer.apple.com/documentation/kernel/host_flavor_t?language=objc +#[allow(non_camel_case_types)] +type host_flavor_t = integer_t; +/// https://developer.apple.com/documentation/kernel/host_info64_t?language=objc +#[allow(non_camel_case_types)] +type host_info64_t = *mut integer_t; + +extern "C" { + fn mach_host_self() -> host_name_port_t; + + /// https://developer.apple.com/documentation/kernel/1502863-host_statistics64?language=objc + fn host_statistics64( + host_priv: host_t, + flavor: host_flavor_t, + host_info_out: host_info64_t, + host_info_outCnt: *const mach_msg_type_number_t, + ) -> kern_return_t; +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] +pub struct vm_statistics64 { + pub free_count: natural_t, + pub active_count: natural_t, + pub inactive_count: natural_t, + pub wire_count: natural_t, + pub zero_fill_count: u64, + pub reactivations: u64, + pub pageins: u64, + pub pageouts: u64, + pub faults: u64, + pub cow_faults: u64, + pub lookups: u64, + pub hits: u64, + pub purges: u64, + pub purgeable_count: natural_t, + pub speculative_count: natural_t, + pub decompressions: u64, + pub compressions: u64, + pub swapins: u64, + pub swapouts: u64, + pub compressor_page_count: natural_t, + pub throttled_count: natural_t, + pub external_page_count: natural_t, + pub internal_page_count: natural_t, + pub total_uncompressed_pages_in_compressor: u64, +} + +#[allow(trivial_casts)] +pub unsafe fn host_vm_info() -> io::Result { + let port = mach_host_self(); + let mut stats = vm_statistics64::default(); + let count = HOST_VM_INFO64_COUNT; + + let result = host_statistics64( + port, + HOST_VM_INFO64, + &mut stats as *mut _ as host_info64_t, + // We can't pass the reference to const here, + // it leads to `EXC_BAD_ACCESS` for some reasons, + // so we are copying it to a stack and passing a reference to a local copy + &count, + ); + + let port_result = mach_port::mach_port_deallocate(mach_task_self(), port); + // Technically it is a programming bug and we are should panic probably, + // but it is okay as is + if port_result != kern_return::KERN_SUCCESS { + return Err(io::Error::last_os_error()); + } + + if result != kern_return::KERN_SUCCESS { + Err(io::Error::last_os_error()) + } else { + Ok(stats) + } +} diff --git a/common/rust-psutil/src/memory/sys/macos/mod.rs b/common/rust-psutil/src/memory/sys/macos/mod.rs new file mode 100644 index 00000000000..5e0de44065e --- /dev/null +++ b/common/rust-psutil/src/memory/sys/macos/mod.rs @@ -0,0 +1,7 @@ +mod common; +mod swap_memory; +mod virtual_memory; + +pub(crate) use common::*; +pub use swap_memory::*; +pub use virtual_memory::*; diff --git a/common/rust-psutil/src/memory/sys/macos/swap_memory.rs b/common/rust-psutil/src/memory/sys/macos/swap_memory.rs new file mode 100644 index 00000000000..c5066f36b34 --- /dev/null +++ b/common/rust-psutil/src/memory/sys/macos/swap_memory.rs @@ -0,0 +1,60 @@ +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/swap.rs +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/bindings.rs +// https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/mod.rs + +use std::io; +use std::mem; +use std::ptr; + +use nix::libc; + +use crate::memory::{host_vm_info, SwapMemory}; +use crate::{Result, PAGE_SIZE}; + +const CTL_VM: libc::c_int = 2; +const VM_SWAPUSAGE: libc::c_int = 5; + +unsafe fn vm_swapusage() -> io::Result { + let mut name: [i32; 2] = [CTL_VM, VM_SWAPUSAGE]; + let mut value = mem::MaybeUninit::::uninit(); + let mut length = mem::size_of::(); + + let result = libc::sysctl( + name.as_mut_ptr(), + 2, + value.as_mut_ptr() as *mut libc::c_void, + &mut length, + ptr::null_mut(), + 0, + ); + + if result == 0 { + let value = value.assume_init(); + Ok(value) + } else { + Err(io::Error::last_os_error()) + } +} + +pub fn swap_memory() -> Result { + let xsw_usage = unsafe { vm_swapusage()? }; + let vm_stats = unsafe { host_vm_info()? }; + let page_size = *PAGE_SIZE; + + let total = u64::from(xsw_usage.xsu_total); + let used = u64::from(xsw_usage.xsu_used); + let free = u64::from(xsw_usage.xsu_avail); + let swapped_in = u64::from(vm_stats.pageins) * page_size; + let swapped_out = u64::from(vm_stats.pageouts) * page_size; + + let percent = ((used as f64 / total as f64) * 100.0) as f32; + + Ok(SwapMemory { + total, + used, + free, + percent, + swapped_in, + swapped_out, + }) +} diff --git a/common/rust-psutil/src/memory/sys/macos/virtual_memory.rs b/common/rust-psutil/src/memory/sys/macos/virtual_memory.rs new file mode 100644 index 00000000000..f8b6fa99953 --- /dev/null +++ b/common/rust-psutil/src/memory/sys/macos/virtual_memory.rs @@ -0,0 +1,64 @@ +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/memory.rs +// https://github.com/heim-rs/heim/blob/master/heim-memory/src/sys/macos/bindings.rs +// https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/mod.rs + +use std::io; +use std::mem; +use std::ptr; + +use nix::libc; + +use crate::memory::{host_vm_info, VirtualMemory}; +use crate::{Result, PAGE_SIZE}; + +const CTL_HW: libc::c_int = 6; +const HW_MEMSIZE: libc::c_int = 24; + +#[allow(trivial_casts)] +unsafe fn hw_memsize() -> io::Result { + let mut name: [i32; 2] = [CTL_HW, HW_MEMSIZE]; + let mut value = 0u64; + let mut length = mem::size_of::(); + + let result = libc::sysctl( + name.as_mut_ptr(), + 2, + &mut value as *mut u64 as *mut libc::c_void, + &mut length, + ptr::null_mut(), + 0, + ); + + if result == 0 { + Ok(value) + } else { + Err(io::Error::last_os_error()) + } +} + +pub fn virtual_memory() -> Result { + let total = unsafe { hw_memsize()? }; + let vm_stats = unsafe { host_vm_info()? }; + let page_size = *PAGE_SIZE; + + let available = u64::from(vm_stats.active_count + vm_stats.free_count) * page_size; + let used = u64::from(vm_stats.active_count + vm_stats.wire_count) * page_size; + let free = u64::from(vm_stats.free_count - vm_stats.speculative_count) * page_size; + let active = u64::from(vm_stats.active_count) * page_size; + let inactive = u64::from(vm_stats.inactive_count) * page_size; + + let wired = u64::from(vm_stats.wire_count) * page_size; + + let percent = (((total as f64 - available as f64) / total as f64) * 100.0) as f32; + + Ok(VirtualMemory { + total, + available, + used, + free, + percent, + active, + inactive, + wired, + }) +} diff --git a/common/rust-psutil/src/memory/sys/mod.rs b/common/rust-psutil/src/memory/sys/mod.rs new file mode 100644 index 00000000000..691f8ce1721 --- /dev/null +++ b/common/rust-psutil/src/memory/sys/mod.rs @@ -0,0 +1,9 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "macos")] { + mod macos; + pub use macos::*; + } +} diff --git a/common/rust-psutil/src/memory/virtual_memory.rs b/common/rust-psutil/src/memory/virtual_memory.rs new file mode 100644 index 00000000000..49e269c27f3 --- /dev/null +++ b/common/rust-psutil/src/memory/virtual_memory.rs @@ -0,0 +1,60 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::{Bytes, Percent}; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct VirtualMemory { + pub(crate) total: Bytes, + pub(crate) available: Bytes, + pub(crate) used: Bytes, + pub(crate) free: Bytes, + pub(crate) percent: Percent, + + #[cfg(target_family = "unix")] + pub(crate) active: Bytes, + #[cfg(target_family = "unix")] + pub(crate) inactive: Bytes, + + #[cfg(target_os = "linux")] + pub(crate) buffers: Bytes, + #[cfg(target_os = "linux")] + pub(crate) cached: Bytes, + #[cfg(target_os = "linux")] + pub(crate) shared: Bytes, + #[cfg(target_os = "linux")] + pub(crate) slab: Bytes, + + #[cfg(target_os = "macos")] + pub(crate) wired: Bytes, +} + +impl VirtualMemory { + /// Amount of total memory. + pub fn total(&self) -> Bytes { + self.total + } + + /// Amount of memory available for new processes. + pub fn available(&self) -> Bytes { + self.available + } + + /// Memory currently in use. + pub fn used(&self) -> Bytes { + self.used + } + + /// Memory not being used. + pub fn free(&self) -> Bytes { + self.free + } + + /// New method, not in Python psutil. + /// Percent of memory used. + pub fn percent(&self) -> Percent { + self.percent + } +} diff --git a/common/rust-psutil/src/network/mod.rs b/common/rust-psutil/src/network/mod.rs new file mode 100644 index 00000000000..66f295b8e4a --- /dev/null +++ b/common/rust-psutil/src/network/mod.rs @@ -0,0 +1,12 @@ +mod net_connection; +mod net_if_addr; +mod net_if_stats; +mod net_io_couters; +mod sys; + +pub use net_connection::*; +pub use net_if_addr::*; +pub use net_if_stats::*; +pub use net_io_couters::*; +#[allow(unused_imports)] +pub use sys::*; diff --git a/common/rust-psutil/src/network/net_connection.rs b/common/rust-psutil/src/network/net_connection.rs new file mode 100644 index 00000000000..c14ccd34fe2 --- /dev/null +++ b/common/rust-psutil/src/network/net_connection.rs @@ -0,0 +1,46 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::common::TcpConnectionStatus; +use crate::{Fd, Pid}; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct NetConnection {} + +impl NetConnection { + pub fn fd(&self) -> Option { + todo!() + } + + // TODO: return type + pub fn family(&self) { + todo!() + } + + // TODO: return type + /// Renamed from `type` in Python psutil. + pub fn address_type(&self) { + todo!() + } + + // TODO: return type + /// Renamed from `laddr` in Python psutil. + pub fn local_addr(&self) { + todo!() + } + + // TODO: return type + /// Renamed from `raddr` in Python psutil. + pub fn remote_addr(&self) { + todo!() + } + + pub fn status(&self) -> Option { + todo!() + } + + pub fn pid(&self) -> Option { + todo!() + } +} diff --git a/common/rust-psutil/src/network/net_if_addr.rs b/common/rust-psutil/src/network/net_if_addr.rs new file mode 100644 index 00000000000..3befd306f83 --- /dev/null +++ b/common/rust-psutil/src/network/net_if_addr.rs @@ -0,0 +1,31 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::net::IpAddr; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct NetIfAddr {} + +impl NetIfAddr { + // TODO: return type + pub fn family(&self) { + todo!() + } + + pub fn address(&self) -> IpAddr { + todo!() + } + + pub fn netmask(&self) -> Option { + todo!() + } + + pub fn broadcast(&self) -> Option { + todo!() + } + + pub fn ptp(&self) -> Option { + todo!() + } +} diff --git a/common/rust-psutil/src/network/net_if_stats.rs b/common/rust-psutil/src/network/net_if_stats.rs new file mode 100644 index 00000000000..77f9d4b2f9c --- /dev/null +++ b/common/rust-psutil/src/network/net_if_stats.rs @@ -0,0 +1,34 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Bytes; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Duplex { + Full, + Half, + Unknown, +} + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct NetIfStats {} + +impl NetIfStats { + pub fn is_up(&self) -> bool { + todo!() + } + + pub fn duplex(&self) -> Duplex { + todo!() + } + + pub fn speed(&self) -> Bytes { + todo!() + } + + pub fn mtu(&self) -> Bytes { + todo!() + } +} diff --git a/common/rust-psutil/src/network/net_io_couters.rs b/common/rust-psutil/src/network/net_io_couters.rs new file mode 100644 index 00000000000..0d065eed643 --- /dev/null +++ b/common/rust-psutil/src/network/net_io_couters.rs @@ -0,0 +1,163 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::collections::HashMap; + +use derive_more::{Add, Sub, Sum}; + +use crate::network::net_io_counters_pernic; +use crate::{Bytes, Count, Result}; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Add, Sum, Sub)] +pub struct NetIoCounters { + pub(crate) bytes_sent: Bytes, + pub(crate) bytes_recv: Bytes, + pub(crate) packets_sent: Count, + pub(crate) packets_recv: Count, + pub(crate) err_in: Count, + pub(crate) err_out: Count, + pub(crate) drop_in: Count, + pub(crate) drop_out: Count, +} + +impl NetIoCounters { + /// Number of bytes sent. + pub fn bytes_sent(&self) -> Bytes { + self.bytes_sent + } + + /// Number of bytes received. + pub fn bytes_recv(&self) -> Bytes { + self.bytes_recv + } + + /// Number of packets sent. + pub fn packets_sent(&self) -> Count { + self.packets_sent + } + + /// Number of packets received. + pub fn packets_recv(&self) -> Count { + self.packets_recv + } + + /// Total number of errors while receiving. + /// Renamed from `errin` in Python psutil. + pub fn err_in(&self) -> Count { + self.err_in + } + + /// Total number of errors while sending. + /// Renamed from `errout` in Python psutil. + pub fn err_out(&self) -> Count { + self.err_out + } + + /// Total number of incoming packets which were dropped. + /// Renamed from `dropin` in Python psutil. + pub fn drop_in(&self) -> Count { + self.drop_in + } + + /// Total number of outgoing packets which were dropped (always 0 on macOS and BSD). + /// Renamed from `dropout` in Python psutil. + pub fn drop_out(&self) -> Count { + self.drop_out + } +} + +fn nowrap(prev: u64, current: u64, corrected: u64) -> u64 { + if current >= prev { + corrected + (current - prev) + } else { + corrected + current + ((std::u32::MAX as u64) - prev) + } +} + +fn nowrap_struct( + prev: &NetIoCounters, + current: &NetIoCounters, + corrected: &NetIoCounters, +) -> NetIoCounters { + NetIoCounters { + bytes_sent: nowrap(prev.bytes_sent, current.bytes_sent, corrected.bytes_sent), + bytes_recv: nowrap(prev.bytes_recv, current.bytes_recv, corrected.bytes_recv), + packets_sent: nowrap( + prev.packets_sent, + current.packets_sent, + corrected.packets_sent, + ), + packets_recv: nowrap( + prev.packets_recv, + current.packets_recv, + corrected.packets_recv, + ), + err_in: nowrap(prev.err_in, current.err_in, corrected.err_in), + err_out: nowrap(prev.err_out, current.err_out, corrected.err_out), + drop_in: nowrap(prev.drop_in, current.drop_in, corrected.drop_in), + drop_out: nowrap(prev.drop_out, current.drop_out, corrected.drop_out), + } +} + +fn fix_io_counter_overflow( + prev: &HashMap, + current: &HashMap, + corrected: &HashMap, +) -> HashMap { + current + .iter() + .map(|(name, current_counters)| { + if !prev.contains_key(name) || !corrected.contains_key(name) { + (name.clone(), current_counters.clone()) + } else { + let prev_counters = &prev[name]; + let corrected_counters = &corrected[name]; + + ( + name.clone(), + nowrap_struct(prev_counters, current_counters, corrected_counters), + ) + } + }) + .collect() +} + +/// Used to persist data between calls to detect data overflow by the kernel and fix the result. +#[derive(Debug, Clone, Default)] +pub struct NetIoCountersCollector { + prev_net_io_counters_pernic: Option>, + corrected_net_io_counters_pernic: Option>, +} + +impl NetIoCountersCollector { + pub fn net_io_counters(&mut self) -> Result { + let sum = self + .net_io_counters_pernic()? + .into_iter() + .map(|(_key, val)| val) + .sum(); + + Ok(sum) + } + + pub fn net_io_counters_pernic(&mut self) -> Result> { + let io_counters = net_io_counters_pernic()?; + + let corrected_counters = match ( + &self.prev_net_io_counters_pernic, + &self.corrected_net_io_counters_pernic, + ) { + (Some(prev), Some(corrected)) => { + fix_io_counter_overflow(&prev, &io_counters, &corrected) + } + _ => io_counters.clone(), + }; + + self.prev_net_io_counters_pernic = Some(io_counters); + self.corrected_net_io_counters_pernic = Some(corrected_counters.clone()); + + Ok(corrected_counters) + } +} diff --git a/common/rust-psutil/src/network/sys/linux/mod.rs b/common/rust-psutil/src/network/sys/linux/mod.rs new file mode 100644 index 00000000000..d23bbfeb667 --- /dev/null +++ b/common/rust-psutil/src/network/sys/linux/mod.rs @@ -0,0 +1,10 @@ +mod net_connections; +mod net_if_addrs; +mod net_if_stats; +mod net_io_counters; + +pub use net_connections::*; +pub use net_if_addrs::*; +pub use net_if_stats::*; +#[allow(unused_imports)] +pub use net_io_counters::*; diff --git a/common/rust-psutil/src/network/sys/linux/net_connections.rs b/common/rust-psutil/src/network/sys/linux/net_connections.rs new file mode 100644 index 00000000000..1a3cdacb8cd --- /dev/null +++ b/common/rust-psutil/src/network/sys/linux/net_connections.rs @@ -0,0 +1,10 @@ +use crate::common::NetConnectionType; +use crate::network::NetConnection; + +pub fn net_connections() -> Vec { + todo!() +} + +pub fn net_connections_with_type(_type: NetConnectionType) -> Vec { + todo!() +} diff --git a/common/rust-psutil/src/network/sys/linux/net_if_addrs.rs b/common/rust-psutil/src/network/sys/linux/net_if_addrs.rs new file mode 100644 index 00000000000..ee257a30def --- /dev/null +++ b/common/rust-psutil/src/network/sys/linux/net_if_addrs.rs @@ -0,0 +1,8 @@ +use std::collections::HashMap; +use std::io; + +use crate::network::NetIfAddr; + +pub fn net_if_addrs() -> io::Result>> { + todo!() +} diff --git a/common/rust-psutil/src/network/sys/linux/net_if_stats.rs b/common/rust-psutil/src/network/sys/linux/net_if_stats.rs new file mode 100644 index 00000000000..1ee3bcdfede --- /dev/null +++ b/common/rust-psutil/src/network/sys/linux/net_if_stats.rs @@ -0,0 +1,8 @@ +use std::collections::HashMap; +use std::io; + +use crate::network::NetIfStats; + +pub fn net_if_stats() -> io::Result> { + todo!() +} diff --git a/common/rust-psutil/src/network/sys/linux/net_io_counters.rs b/common/rust-psutil/src/network/sys/linux/net_io_counters.rs new file mode 100644 index 00000000000..1d6ecdfcf03 --- /dev/null +++ b/common/rust-psutil/src/network/sys/linux/net_io_counters.rs @@ -0,0 +1,63 @@ +use std::collections::HashMap; +use std::str::FromStr; + +use crate::network::NetIoCounters; +use crate::{read_file, Error, Result}; + +const PROC_NET_DEV: &str = "/proc/net/dev"; + +impl FromStr for NetIoCounters { + type Err = Error; + + fn from_str(line: &str) -> Result { + let fields = match line.split_whitespace().collect::>() { + fields if fields.len() >= 17 => Ok(fields), + _ => Err(Error::MissingData { + path: PROC_NET_DEV.into(), + contents: line.to_string(), + }), + }?; + + let parse = |s: &str| -> Result { + s.parse().map_err(|err| Error::ParseInt { + path: PROC_NET_DEV.into(), + contents: line.to_string(), + source: err, + }) + }; + + Ok(NetIoCounters { + bytes_sent: parse(fields[9])?, + bytes_recv: parse(fields[1])?, + packets_sent: parse(fields[10])?, + packets_recv: parse(fields[2])?, + err_in: parse(fields[3])?, + err_out: parse(fields[11])?, + drop_in: parse(fields[4])?, + drop_out: parse(fields[12])?, + }) + } +} + +pub(crate) fn net_io_counters_pernic() -> Result> { + read_file(PROC_NET_DEV)? + .lines() + .skip(2) + .map(|line| { + let fields: Vec<&str> = line.split_whitespace().collect(); + + if fields.len() < 17 { + return Err(Error::MissingData { + path: PROC_NET_DEV.into(), + contents: line.to_string(), + }); + } + + let mut net_name = String::from(fields[0]); + // remove the trailing colon + net_name.pop(); + + Ok((net_name, NetIoCounters::from_str(&line)?)) + }) + .collect() +} diff --git a/common/rust-psutil/src/network/sys/macos/mod.rs b/common/rust-psutil/src/network/sys/macos/mod.rs new file mode 100644 index 00000000000..365a6af67f3 --- /dev/null +++ b/common/rust-psutil/src/network/sys/macos/mod.rs @@ -0,0 +1,4 @@ +mod net_io_counters; + +#[allow(unused_imports)] +pub use net_io_counters::*; diff --git a/common/rust-psutil/src/network/sys/macos/net_io_counters.rs b/common/rust-psutil/src/network/sys/macos/net_io_counters.rs new file mode 100644 index 00000000000..b829c5c722e --- /dev/null +++ b/common/rust-psutil/src/network/sys/macos/net_io_counters.rs @@ -0,0 +1,197 @@ +// https://github.com/heim-rs/heim/blob/master/heim-net/src/sys/macos/bindings.rs +// https://github.com/heim-rs/heim/blob/master/heim-net/src/sys/macos/counters.rs + +use std::collections::HashMap; +use std::io; +use std::mem; +use std::ptr; + +use nix::libc; + +use crate::network::NetIoCounters; +use crate::{Error, Result}; + +#[derive(Debug)] +struct Routes { + position: usize, + data: Vec, +} + +impl Iterator for Routes { + type Item = if_msghdr2; + + fn next(&mut self) -> Option { + loop { + if self.position == self.data.len() { + return None; + } + + let data_ptr = unsafe { self.data.as_ptr().add(self.position) }; + + // In order not to read uninitialized memory (leading to heap-buffer-overflow), + // which might happen if the whole `libc::if_msghdr` struct would be used here, + // we are going to read as small as possible bytes amount + // and see if that would be enough to determine the `ifm_type` + assert!( + self.position + mem::size_of::() < self.data.len(), + "Not enough data to read the `if_msghdr` header, need at least {} bytes, got {}", + mem::size_of::(), + self.data.len() - self.position, + ); + + let hdr = unsafe { + let mut maybe_hdr = mem::MaybeUninit::::uninit(); + ptr::copy_nonoverlapping( + data_ptr, + maybe_hdr.as_mut_ptr() as *mut u8, + mem::size_of::(), + ); + maybe_hdr.assume_init() + }; + debug_assert!(hdr.ifm_msglen as usize <= self.data.len() + self.position); + + self.position += hdr.ifm_msglen as usize; + + if libc::c_int::from(hdr.ifm_type) == libc::RTM_IFINFO2 { + let hdr = unsafe { + let mut maybe_hdr = mem::MaybeUninit::::uninit(); + ptr::copy_nonoverlapping( + data_ptr, + maybe_hdr.as_mut_ptr() as *mut u8, + mem::size_of::(), + ); + maybe_hdr.assume_init() + }; + + // Just in case to be sure that copying worked properly + debug_assert!(libc::c_int::from(hdr.ifm_type) == libc::RTM_IFINFO2); + + return Some(hdr); + } else { + continue; + } + } + } +} + +#[repr(C)] +struct if_data64 { + pub ifi_type: libc::c_uchar, + pub ifi_typelen: libc::c_uchar, + pub ifi_physical: libc::c_uchar, + pub ifi_addrlen: libc::c_uchar, + pub ifi_hdrlen: libc::c_uchar, + pub ifi_recvquota: libc::c_uchar, + pub ifi_xmitquota: libc::c_uchar, + pub ifi_unused1: libc::c_uchar, + pub ifi_mtu: u32, + pub ifi_metric: u32, + pub ifi_baudrate: u64, + pub ifi_ipackets: u64, + pub ifi_ierrors: u64, + pub ifi_opackets: u64, + pub ifi_oerrors: u64, + pub ifi_collisions: u64, + pub ifi_ibytes: u64, + pub ifi_obytes: u64, + pub ifi_imcasts: u64, + pub ifi_omcasts: u64, + pub ifi_iqdrops: u64, + pub ifi_noproto: u64, + pub ifi_recvtiming: u32, + pub ifi_xmittiming: u32, + pub ifi_lastchange: libc::timeval, +} + +#[repr(C)] +struct if_msghdr2 { + pub ifm_msglen: libc::c_ushort, + pub ifm_version: libc::c_uchar, + pub ifm_type: libc::c_uchar, + pub ifm_addrs: libc::c_int, + pub ifm_flags: libc::c_int, + pub ifm_index: libc::c_ushort, + pub ifm_snd_len: libc::c_int, + pub ifm_snd_maxlen: libc::c_int, + pub ifm_snd_drops: libc::c_int, + pub ifm_timer: libc::c_int, + pub ifm_data: if_data64, +} + +#[repr(C)] +struct if_msghdr_partial { + pub ifm_msglen: libc::c_ushort, + pub ifm_version: libc::c_uchar, + pub ifm_type: libc::c_uchar, +} + +unsafe fn net_pf_route() -> io::Result { + let mut name: [libc::c_int; 6] = [libc::CTL_NET, libc::PF_ROUTE, 0, 0, libc::NET_RT_IFLIST2, 0]; + let mut length: libc::size_t = 0; + + let result = libc::sysctl( + name.as_mut_ptr(), + 6, + ptr::null_mut(), + &mut length, + ptr::null_mut(), + 0, + ); + + if result != 0 { + return Err(io::Error::last_os_error()); + } + + let mut data: Vec = Vec::with_capacity(length); + let result = libc::sysctl( + name.as_mut_ptr(), + 6, + data.as_mut_ptr() as *mut libc::c_void, + &mut length, + ptr::null_mut(), + 0, + ); + + if result == 0 { + data.set_len(length); + Ok(Routes { position: 0, data }) + } else { + Err(io::Error::last_os_error()) + } +} + +impl From for NetIoCounters { + fn from(data: if_msghdr2) -> Self { + NetIoCounters { + bytes_sent: data.ifm_data.ifi_obytes, + bytes_recv: data.ifm_data.ifi_ibytes, + packets_sent: data.ifm_data.ifi_opackets, + packets_recv: data.ifm_data.ifi_ipackets, + err_in: data.ifm_data.ifi_ierrors, + err_out: data.ifm_data.ifi_oerrors, + drop_in: data.ifm_data.ifi_iqdrops, + drop_out: 0, // TODO + } + } +} + +pub(crate) fn net_io_counters_pernic() -> Result> { + let interfaces = unsafe { net_pf_route() }; + interfaces? + .into_iter() + .map(|msg: if_msghdr2| { + let mut name: [u8; libc::IF_NAMESIZE] = [0; libc::IF_NAMESIZE]; + let result = unsafe { + libc::if_indextoname(msg.ifm_index.into(), name.as_mut_ptr() as *mut libc::c_char) + }; + if result.is_null() { + return Err(io::Error::last_os_error()); + } + let first_nul = name.iter().position(|c| *c == b'\0').unwrap_or(0); + let name = String::from_utf8_lossy(&name[..first_nul]).to_string(); + + Ok((name, NetIoCounters::from(msg))) + }) + .map(|result| result.map_err(Error::from)) + .collect() +} diff --git a/common/rust-psutil/src/network/sys/mod.rs b/common/rust-psutil/src/network/sys/mod.rs new file mode 100644 index 00000000000..f4e4b032951 --- /dev/null +++ b/common/rust-psutil/src/network/sys/mod.rs @@ -0,0 +1,10 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "macos")] { + mod macos; + #[allow(unused_imports)] + pub use macos::*; + } +} diff --git a/common/rust-psutil/src/process/collector.rs b/common/rust-psutil/src/process/collector.rs new file mode 100644 index 00000000000..89aa6bbd3fd --- /dev/null +++ b/common/rust-psutil/src/process/collector.rs @@ -0,0 +1,60 @@ +// #[cfg(feature = "serde")] +// use serde::{Deserialize, Serialize}; + +use std::collections::BTreeMap; + +use crate::process::{self, Process}; +use crate::{Pid, Result}; +// FIXME: Process cannot be serialized/deserialize, as a result, +// neither this can be. +// #[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct ProcessCollector { + pub processes: BTreeMap, +} + +/// Used to maintain a list of up-to-date processes while persisting cached data within the process +/// struct between each update. For example, processes cache CPU busy times in order to calculate +/// CPU percent. +impl ProcessCollector { + pub fn new() -> Result { + let processes = process::processes()? + .into_iter() + .filter_map(|process| process.ok()) + .map(|process| (process.pid(), process)) + .collect(); + + Ok(ProcessCollector { processes }) + } + + pub fn update(&mut self) -> Result<()> { + let new = ProcessCollector::new()?.processes; + + // remove processes with a PID that is no longer in use + let to_remove: Vec = self + .processes + .iter() + .filter(|(pid, _process)| !new.contains_key(pid)) + .map(|(pid, _process)| *pid) + .collect(); + for id in to_remove { + self.processes.remove(&id); + } + + new.into_iter().for_each(|(pid, process)| { + // add new processes and replace processes with reused PIDs + if !self.processes.contains_key(&pid) || self.processes[&pid] != process { + self.processes.insert(pid, process); + } else { + // Update data used for oneshot. + #[cfg(target_os = "linux")] + { + self.processes.get_mut(&pid).unwrap().procfs_stat = process.procfs_stat; + } + } + }); + + Ok(()) + } +} diff --git a/common/rust-psutil/src/process/cpu_times.rs b/common/rust-psutil/src/process/cpu_times.rs new file mode 100644 index 00000000000..34a36e5ee34 --- /dev/null +++ b/common/rust-psutil/src/process/cpu_times.rs @@ -0,0 +1,69 @@ +// https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/process/cpu_times.rs +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::time::Duration; + +#[cfg(target_os = "linux")] +use crate::process::os::linux::ProcfsStat; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +pub struct ProcessCpuTimes { + pub(crate) user: Duration, + pub(crate) system: Duration, + pub(crate) children_user: Duration, + pub(crate) children_system: Duration, + + #[cfg(target_os = "linux")] + pub(crate) iowait: Option, +} + +impl ProcessCpuTimes { + pub fn user(&self) -> Duration { + self.user + } + + pub fn system(&self) -> Duration { + self.system + } + + pub fn children_user(&self) -> Duration { + self.children_user + } + + pub fn children_system(&self) -> Duration { + self.children_system + } + + /// New method, not in Python psutil. + pub fn busy(&self) -> Duration { + self.user() + self.system() + } +} + +#[cfg(target_os = "linux")] +impl From<&ProcfsStat> for ProcessCpuTimes { + fn from(procfs_stat: &ProcfsStat) -> Self { + ProcessCpuTimes { + user: procfs_stat.utime, + system: procfs_stat.stime, + children_user: procfs_stat.cutime, + children_system: procfs_stat.cstime, + iowait: procfs_stat.delayacct_blkio, + } + } +} + +#[cfg(target_os = "macos")] +impl From for ProcessCpuTimes { + fn from(info: darwin_libproc::proc_taskinfo) -> Self { + ProcessCpuTimes { + user: Duration::from_nanos(info.pti_total_user), + system: Duration::from_nanos(info.pti_total_system), + children_user: Duration::default(), + children_system: Duration::default(), + } + } +} diff --git a/common/rust-psutil/src/process/errors.rs b/common/rust-psutil/src/process/errors.rs new file mode 100644 index 00000000000..0c03923f298 --- /dev/null +++ b/common/rust-psutil/src/process/errors.rs @@ -0,0 +1,40 @@ +use std::io; + +use crate::{Error, Pid}; + +pub type ProcessResult = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum ProcessError { + #[error("Process {} does not exist", pid)] + NoSuchProcess { pid: Pid }, + + #[error("Process {} is a zombie", pid)] + ZombieProcess { pid: Pid }, + + #[error("Access denied for process {}", pid)] + AccessDenied { pid: Pid }, + + #[error("psutil error for process {}: {}", pid, source)] + PsutilError { pid: Pid, source: Error }, +} + +pub(crate) fn psutil_error_to_process_error(e: Error, pid: Pid) -> ProcessError { + match e { + Error::ReadFile { source, .. } | Error::OsError { source, .. } => { + io_error_to_process_error(source, pid) + } + _ => ProcessError::PsutilError { pid, source: e }, + } +} + +pub(crate) fn io_error_to_process_error(e: io::Error, pid: Pid) -> ProcessError { + match e.kind() { + io::ErrorKind::NotFound => ProcessError::NoSuchProcess { pid }, + io::ErrorKind::PermissionDenied => ProcessError::AccessDenied { pid }, + _ => ProcessError::PsutilError { + pid, + source: Error::OsError { source: e }, + }, + } +} diff --git a/common/rust-psutil/src/process/memory.rs b/common/rust-psutil/src/process/memory.rs new file mode 100644 index 00000000000..8e449d6add0 --- /dev/null +++ b/common/rust-psutil/src/process/memory.rs @@ -0,0 +1,71 @@ +// https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/process/memory.rs +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Bytes; + +#[cfg(target_os = "linux")] +use crate::process::os::linux::ProcfsStatm; +#[cfg(target_os = "macos")] +use crate::Count; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum MemType { + // TODO +} + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct MemoryInfo { + pub(crate) rss: Bytes, + pub(crate) vms: Bytes, + + #[cfg(target_os = "linux")] + pub(crate) shared: Bytes, + #[cfg(target_os = "linux")] + pub(crate) text: Bytes, + #[cfg(target_os = "linux")] + pub(crate) data: Bytes, + + #[cfg(target_os = "macos")] + pub(crate) page_faults: Count, + #[cfg(target_os = "macos")] + pub(crate) pageins: Count, +} + +impl MemoryInfo { + pub fn rss(&self) -> Bytes { + self.rss + } + + pub fn vms(&self) -> Bytes { + self.vms + } +} + +#[cfg(target_os = "linux")] +impl From for MemoryInfo { + fn from(statm: ProcfsStatm) -> Self { + MemoryInfo { + rss: statm.resident, + vms: statm.size, + shared: statm.shared, + text: statm.text, + data: statm.data, + } + } +} + +#[cfg(target_os = "macos")] +impl From for MemoryInfo { + fn from(info: darwin_libproc::proc_taskinfo) -> Self { + MemoryInfo { + rss: info.pti_resident_size, + vms: info.pti_virtual_size, + page_faults: info.pti_faults as u64, + pageins: info.pti_pageins as u64, + } + } +} diff --git a/common/rust-psutil/src/process/mod.rs b/common/rust-psutil/src/process/mod.rs new file mode 100644 index 00000000000..0b587c5d4d2 --- /dev/null +++ b/common/rust-psutil/src/process/mod.rs @@ -0,0 +1,21 @@ +mod collector; +mod cpu_times; +mod errors; +mod memory; +mod open_file; +pub mod os; +#[allow(clippy::module_inception)] +mod process; +mod status; +mod sys; + +pub use nix::sys::signal::Signal; + +pub use collector::*; +pub use cpu_times::*; +pub use errors::*; +pub use memory::*; +pub use open_file::*; +pub use process::*; +pub use status::*; +pub use sys::*; diff --git a/common/rust-psutil/src/process/open_file.rs b/common/rust-psutil/src/process/open_file.rs new file mode 100644 index 00000000000..eb21fab917e --- /dev/null +++ b/common/rust-psutil/src/process/open_file.rs @@ -0,0 +1,13 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::path::PathBuf; + +use crate::Fd; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct OpenFile { + pub path: PathBuf, + pub fd: Option, +} diff --git a/common/rust-psutil/src/process/os/bsd.rs b/common/rust-psutil/src/process/os/bsd.rs new file mode 100644 index 00000000000..3220655a0d1 --- /dev/null +++ b/common/rust-psutil/src/process/os/bsd.rs @@ -0,0 +1,13 @@ +use crate::process::Process; + +pub struct IoCounters {} + +pub trait ProcessExt { + fn io_counters(&self) -> IoCounters; +} + +impl ProcessExt for Process { + fn io_counters(&self) -> IoCounters { + todo!() + } +} diff --git a/common/rust-psutil/src/process/os/freebsd.rs b/common/rust-psutil/src/process/os/freebsd.rs new file mode 100644 index 00000000000..d2247f7a522 --- /dev/null +++ b/common/rust-psutil/src/process/os/freebsd.rs @@ -0,0 +1,29 @@ +use crate::process::Process; + +pub trait ProcessExt { + fn get_cpu_affinity(&self) -> i32; + + fn set_cpu_affinity(&self, nice: i32); + + fn cpu_num(&self); + + fn memory_maps(&self); +} + +impl ProcessExt for Process { + fn get_cpu_affinity(&self) -> i32 { + todo!() + } + + fn set_cpu_affinity(&self, _nice: i32) { + todo!() + } + + fn cpu_num(&self) { + todo!() + } + + fn memory_maps(&self) { + todo!() + } +} diff --git a/common/rust-psutil/src/process/os/linux/cpu_times.rs b/common/rust-psutil/src/process/os/linux/cpu_times.rs new file mode 100644 index 00000000000..fdddd08638e --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/cpu_times.rs @@ -0,0 +1,13 @@ +use std::time::Duration; + +use crate::process::ProcessCpuTimes; + +pub trait ProcessCpuTimesExt { + fn iowait(&self) -> Option; +} + +impl ProcessCpuTimesExt for ProcessCpuTimes { + fn iowait(&self) -> Option { + self.iowait + } +} diff --git a/common/rust-psutil/src/process/os/linux/mod.rs b/common/rust-psutil/src/process/os/linux/mod.rs new file mode 100644 index 00000000000..5d10a329aaf --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/mod.rs @@ -0,0 +1,9 @@ +mod cpu_times; +mod oneshot; +mod process; +mod procfs; + +pub use cpu_times::*; +pub use oneshot::*; +pub use process::*; +pub use procfs::*; diff --git a/common/rust-psutil/src/process/os/linux/oneshot.rs b/common/rust-psutil/src/process/os/linux/oneshot.rs new file mode 100644 index 00000000000..3d2b6fbdbeb --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/oneshot.rs @@ -0,0 +1,46 @@ +use std::time::Instant; + +use crate::process::{Process, ProcessCpuTimes}; +use crate::utils::duration_percent; +use crate::Percent; + +pub trait Oneshot { + fn name_oneshot(&self) -> String; + + fn cpu_times_oneshot(&self) -> ProcessCpuTimes; + + fn cpu_percent_oneshot(&mut self) -> Percent; +} + +impl Oneshot for Process { + fn name_oneshot(&self) -> String { + self.procfs_stat.comm.to_string() + } + + fn cpu_times_oneshot(&self) -> ProcessCpuTimes { + ProcessCpuTimes::from(&self.procfs_stat) + } + + /// Returns the cpu percent since the process was created, replaced, or since the last time this + /// method was called. + /// Differs from Python psutil since there is no interval argument. + fn cpu_percent_oneshot(&mut self) -> Percent { + let busy = self.cpu_times_oneshot().busy(); + let instant = Instant::now(); + + let percent = duration_percent( + // have to use checked_sub since CPU times can decrease over time at least on Linux + // https://github.com/cjbassi/ytop/issues/34 + // TODO: figure out why. hibernation? something to do with running VMs? + busy.checked_sub(self.busy).unwrap_or_default(), + // TODO: can duration be zero if cpu_percent is called consecutively without allowing + // enough time to pass? Cause then we have division by zero. + instant - self.instant, + ); + + self.busy = busy; + self.instant = instant; + + percent + } +} diff --git a/common/rust-psutil/src/process/os/linux/process.rs b/common/rust-psutil/src/process/os/linux/process.rs new file mode 100644 index 00000000000..b478687a698 --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/process.rs @@ -0,0 +1,128 @@ +use std::collections::HashMap; + +use crate::process::os::linux::{ + procfs_stat, procfs_statm, procfs_status, ProcfsStat, ProcfsStatm, ProcfsStatus, +}; +use crate::process::{psutil_error_to_process_error, Process, ProcessResult}; +use crate::{read_file, Error, Result}; + +fn parse_environ(contents: &str) -> Result> { + contents + .split_terminator('\0') + .map(|mapping| { + let split = match mapping.splitn(2, '=').collect::>() { + split if split.len() == 2 => Ok(split), + _ => Err(Error::MissingData { + path: "environ".into(), + contents: contents.to_string(), + }), + }?; + + Ok((split[0].to_owned(), split[1].to_owned())) + }) + .collect() +} + +pub struct IoCounters {} + +pub trait ProcessExt { + fn environ(&self) -> ProcessResult>; + + fn get_ionice(&self) -> i32; + + fn set_ionice(&self, nice: i32); + + fn get_rlimit(&self) -> i32; + + fn set_rlimit(&self, nice: i32); + + fn io_counters(&self) -> IoCounters; + + fn get_cpu_affinity(&self) -> i32; + + fn set_cpu_affinity(&self, nice: i32); + + fn cpu_num(&self) -> i32; + + fn memory_maps(&self); + + /// New method, not in Python psutil + fn procfs_stat(&self) -> ProcessResult; + + /// New method, not in Python psutil + fn procfs_statm(&self) -> ProcessResult; + + /// New method, not in Python psutil + fn procfs_status(&self) -> ProcessResult; +} + +impl ProcessExt for Process { + fn environ(&self) -> ProcessResult> { + let contents = read_file(self.procfs_path("environ")) + .map_err(|e| psutil_error_to_process_error(e, self.pid))?; + + parse_environ(&contents).map_err(|e| psutil_error_to_process_error(e, self.pid)) + } + + fn get_ionice(&self) -> i32 { + todo!() + } + + fn set_ionice(&self, _nice: i32) { + todo!() + } + + fn get_rlimit(&self) -> i32 { + todo!() + } + + fn set_rlimit(&self, _nice: i32) { + todo!() + } + + fn io_counters(&self) -> IoCounters { + todo!() + } + + fn get_cpu_affinity(&self) -> i32 { + todo!() + } + + fn set_cpu_affinity(&self, _nice: i32) { + todo!() + } + + fn cpu_num(&self) -> i32 { + todo!() + } + + fn memory_maps(&self) { + todo!() + } + + fn procfs_stat(&self) -> ProcessResult { + procfs_stat(self.pid) + } + + fn procfs_statm(&self) -> ProcessResult { + procfs_statm(self.pid) + } + + fn procfs_status(&self) -> ProcessResult { + procfs_status(self.pid) + } +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_parse_environ() { + let data = "HOME=/\0init=/sbin/init\0recovery=\0TERM=linux\0BOOT_IMAGE=/boot/vmlinuz-3.13.0-128-generic\0PATH=/sbin:/usr/sbin:/bin:/usr/bin\0PWD=/\0rootmnt=/root\0"; + let env = parse_environ(data).unwrap(); + assert_eq!(env["HOME"], "/"); + assert_eq!(env["rootmnt"], "/root"); + assert_eq!(env["recovery"], ""); + } +} diff --git a/common/rust-psutil/src/process/os/linux/procfs/mod.rs b/common/rust-psutil/src/process/os/linux/procfs/mod.rs new file mode 100644 index 00000000000..2c3415ffb00 --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/procfs/mod.rs @@ -0,0 +1,7 @@ +mod stat; +mod statm; +mod status; + +pub use stat::*; +pub use statm::*; +pub use status::*; diff --git a/common/rust-psutil/src/process/os/linux/procfs/stat.rs b/common/rust-psutil/src/process/os/linux/procfs/stat.rs new file mode 100644 index 00000000000..bfd98f5c23c --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/procfs/stat.rs @@ -0,0 +1,386 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use std::str::FromStr; +use std::time::Duration; + +use crate::process::{procfs_path, psutil_error_to_process_error, ProcessResult, Status}; +use crate::{read_file, Error, Pid, Result, PAGE_SIZE, TICKS_PER_SECOND}; + +const STAT: &str = "stat"; + +/// New struct, not in Python psutil. +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +pub struct ProcfsStat { + /// PID of the process. + pub pid: Pid, + + /// Filename of the executable. + pub comm: String, + + /// State of the process as an enum. + pub state: Status, + + /// PID of the parent process. + pub ppid: Option, + + /// Process group ID. + pub pgrp: i32, + + /// Session ID. + pub session: i32, + + /// Controlling terminal of the process [TODO: Actually two numbers]. + pub tty_nr: i32, + + /// ID of the foreground group of the controlling terminal. + pub tpgid: i32, + + /// Kernel flags for the process. + pub flags: u32, + + /// Minor faults. + pub minflt: u64, + + /// Minor faults by child processes. + pub cminflt: u64, + + /// Major faults. + pub majflt: u64, + + /// Major faults by child processes. + pub cmajflt: u64, + + /// Time scheduled in user mode (seconds). + pub utime: Duration, + + /// Time scheduled in user mode (ticks). + pub utime_ticks: u64, + + /// Time scheduled in kernel mode (seconds). + pub stime: Duration, + + /// Time scheduled in kernel mode (ticks). + pub stime_ticks: u64, + + /// Time waited-for child processes were scheduled in user mode (seconds). + pub cutime: Duration, + + /// Time waited-for child processes were scheduled in user mode (ticks). + pub cutime_ticks: i64, + + /// Time waited-for child processes were scheduled in kernel mode (seconds). + pub cstime: Duration, + + /// Time waited-for child processes were scheduled in kernel mode (ticks). + pub cstime_ticks: i64, + + /// Priority value (-100..-2 | 0..39). + pub priority: i64, + + /// Nice value (-20..19). + pub nice: i64, + + /// Number of threads in the process. + pub num_threads: i64, + + /// Unmaintained field since linux 2.6.17, always 0. + pub itrealvalue: i64, + + /// Time the process was started after system boot (seconds). + pub starttime: Duration, + + /// Time the process was started after system boot (ticks). + pub starttime_ticks: u128, + + /// Virtual memory size in bytes. + pub vsize: u64, + + /// Resident Set Size (bytes). + pub rss: i64, + + /// Current soft limit on process RSS (bytes). + pub rsslim: u64, + + // These values are memory addresses + startcode: u64, + endcode: u64, + startstack: u64, + kstkesp: u64, + kstkeip: u64, + + // Signal bitmaps. + // These are obsolete, use `/proc/[pid]/status` instead. + signal: u64, + blocked: u64, + sigignore: u64, + sigcatch: u64, + + /// Channel the process is waiting on (address of a system call). + pub wchan: u64, + + // Number of pages swapped (not maintained). + // pub nswap: u64, + + // Number of pages swapped for child processes (not maintained). + // pub cnswap: u64, + /// Signal sent to parent when process dies. + pub exit_signal: i32, + + /// Number of the CPU the process was last executed on. + pub processor: i32, + + /// Real-time scheduling priority (0 | 1..99). + pub rt_priority: u32, + + /// Scheduling policy. + pub policy: u64, + + /// Aggregated block I/O delays (seconds). + pub delayacct_blkio: Option, + + /// Aggregated block I/O delays (ticks). + pub delayacct_blkio_ticks: Option, + + /// Guest time of the process (seconds). + pub guest_time: Option, + + /// Guest time of the process (ticks). + pub guest_time_ticks: Option, + + /// Guest time of the process's children (seconds). + pub cguest_time: Option, + + /// Guest time of the process's children (ticks). + pub cguest_time_ticks: Option, + + // More memory addresses. + start_data: Option, + end_data: Option, + start_brk: Option, + arg_start: Option, + arg_end: Option, + env_start: Option, + env_end: Option, + + /// The thread's exit status. + pub exit_code: Option, +} + +impl FromStr for ProcfsStat { + type Err = Error; + + fn from_str(contents: &str) -> Result { + let missing_stat_data = |contents: &str| -> Error { + Error::MissingData { + path: STAT.into(), + contents: contents.to_string(), + } + }; + // We parse the comm field and everything before it seperately since + // the comm field is delimited by parens and can contain spaces + let (pid_field, leftover) = contents + .find('(') + .map(|i| contents.split_at(i - 1)) + .ok_or_else(|| missing_stat_data(contents))?; + let (comm_field, leftover) = leftover + .rfind(')') + .map(|i| leftover.split_at(i + 2)) + .ok_or_else(|| missing_stat_data(contents))?; + + let mut fields: Vec<&str> = vec![pid_field, &comm_field[2..comm_field.len() - 2]]; + fields.extend(leftover.trim_end().split_whitespace()); + + if fields.len() < 41 { + return Err(missing_stat_data(contents)); + } + + let parse_int = |err: std::num::ParseIntError| -> Error { + Error::ParseInt { + path: STAT.into(), + contents: contents.to_string(), + source: err, + } + }; + + let parse_u32 = |s: &str| -> Result { s.parse().map_err(parse_int) }; + let parse_i32 = |s: &str| -> Result { s.parse().map_err(parse_int) }; + let parse_u64 = |s: &str| -> Result { s.parse().map_err(parse_int) }; + let parse_i64 = |s: &str| -> Result { s.parse().map_err(parse_int) }; + let parse_u128 = |s: &str| -> Result { s.parse().map_err(parse_int) }; + + let pid = parse_u32(fields[0])?; + let comm = fields[1].to_string(); + let state = Status::from_str(fields[2])?; + + let ppid = parse_u32(fields[3])?; + let ppid = if ppid == 0 { None } else { Some(ppid) }; + + let pgrp = parse_i32(fields[4])?; + let session = parse_i32(fields[5])?; + let tty_nr = parse_i32(fields[6])?; + let tpgid = parse_i32(fields[7])?; + let flags = parse_u32(fields[8])?; + let minflt = parse_u64(fields[9])?; + let cminflt = parse_u64(fields[10])?; + let majflt = parse_u64(fields[11])?; + let cmajflt = parse_u64(fields[12])?; + + let utime_ticks = parse_u64(fields[13])?; + let utime = Duration::from_secs_f64(utime_ticks as f64 / *TICKS_PER_SECOND); + + let stime_ticks = parse_u64(fields[14])?; + let stime = Duration::from_secs_f64(stime_ticks as f64 / *TICKS_PER_SECOND); + + let cutime_ticks = parse_i64(fields[15])?; + let cutime = Duration::from_secs_f64(cutime_ticks as f64 / *TICKS_PER_SECOND); + + let cstime_ticks = parse_i64(fields[16])?; + let cstime = Duration::from_secs_f64(cstime_ticks as f64 / *TICKS_PER_SECOND); + + let priority = parse_i64(fields[17])?; + let nice = parse_i64(fields[18])?; + let num_threads = parse_i64(fields[19])?; + let itrealvalue = parse_i64(fields[20])?; + + let starttime_ticks = parse_u128(fields[21])?; + let starttime = Duration::from_secs_f64(starttime_ticks as f64 / *TICKS_PER_SECOND); + + let vsize = parse_u64(fields[22])?; + let rss = parse_i64(fields[23])? * *PAGE_SIZE as i64; + let rsslim = parse_u64(fields[24])?; + let startcode = parse_u64(fields[25])?; + let endcode = parse_u64(fields[26])?; + let startstack = parse_u64(fields[27])?; + let kstkesp = parse_u64(fields[28])?; + let kstkeip = parse_u64(fields[29])?; + let signal = parse_u64(fields[30])?; + let blocked = parse_u64(fields[31])?; + let sigignore = parse_u64(fields[32])?; + let sigcatch = parse_u64(fields[33])?; + let wchan = parse_u64(fields[34])?; + // let nswap = parse_u64(fields[35])?; + // let cnswap = parse_u64(fields[36])?; + let exit_signal = parse_i32(fields[37])?; + let processor = parse_i32(fields[38])?; + let rt_priority = parse_u32(fields[39])?; + let policy = parse_u64(fields[40])?; + + // since kernel 2.6.18 + let delayacct_blkio_ticks = if fields.len() >= 42 { + Some(parse_u128(fields[41])?) + } else { + None + }; + let delayacct_blkio = delayacct_blkio_ticks + .map(|val| Duration::from_secs_f64(val as f64 / *TICKS_PER_SECOND)); + + // since kernel 2.6.24 + let (guest_time_ticks, cguest_time_ticks) = if fields.len() >= 44 { + (Some(parse_u64(fields[42])?), Some(parse_i64(fields[43])?)) + } else { + (None, None) + }; + let guest_time = + guest_time_ticks.map(|val| Duration::from_secs_f64(val as f64 / *TICKS_PER_SECOND)); + let cguest_time = + cguest_time_ticks.map(|val| Duration::from_secs_f64(val as f64 / *TICKS_PER_SECOND)); + + // since kernel 3.3 + let (start_data, end_data, start_brk) = if fields.len() >= 47 { + ( + Some(parse_u64(fields[44])?), + Some(parse_u64(fields[45])?), + Some(parse_u64(fields[46])?), + ) + } else { + (None, None, None) + }; + + // since kernel 3.5 + let (arg_start, arg_end, env_start, env_end, exit_code) = if fields.len() >= 52 { + ( + Some(parse_u64(fields[47])?), + Some(parse_u64(fields[48])?), + Some(parse_u64(fields[49])?), + Some(parse_u64(fields[50])?), + Some(parse_i32(fields[51])?), + ) + } else { + (None, None, None, None, None) + }; + + Ok(ProcfsStat { + pid, + comm, + state, + ppid, + pgrp, + session, + tty_nr, + tpgid, + flags, + minflt, + cminflt, + majflt, + cmajflt, + utime, + utime_ticks, + stime, + stime_ticks, + cutime, + cutime_ticks, + cstime, + cstime_ticks, + priority, + nice, + num_threads, + itrealvalue, + starttime, + starttime_ticks, + vsize, + rss, + rsslim, + startcode, + endcode, + startstack, + kstkesp, + kstkeip, + signal, + blocked, + sigignore, + sigcatch, + wchan, + // nswap, + // cnswap, + exit_signal, + processor, + rt_priority, + policy, + delayacct_blkio, + delayacct_blkio_ticks, + guest_time, + guest_time_ticks, + cguest_time, + cguest_time_ticks, + start_data, + end_data, + start_brk, + arg_start, + arg_end, + env_start, + env_end, + exit_code, + }) + } +} + +/// New function, not in Python psutil. +pub fn procfs_stat(pid: Pid) -> ProcessResult { + let contents = + read_file(procfs_path(pid, STAT)).map_err(|e| psutil_error_to_process_error(e, pid))?; + + ProcfsStat::from_str(&contents).map_err(|e| psutil_error_to_process_error(e, pid)) +} diff --git a/common/rust-psutil/src/process/os/linux/procfs/statm.rs b/common/rust-psutil/src/process/os/linux/procfs/statm.rs new file mode 100644 index 00000000000..c05f7914de6 --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/procfs/statm.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; + +use crate::process::{procfs_path, psutil_error_to_process_error, ProcessResult}; +use crate::{read_file, Error, Pid, Result, PAGE_SIZE}; + +const STATM: &str = "statm"; + +/// Memory usage of a process read from `/proc/[pid]/statm`. +/// +/// The `lib` [4, u64] and `dt` [6, u64] fields are ignored. +/// New struct, not in Python psutil. +#[derive(Clone, Debug)] +pub struct ProcfsStatm { + /// Total program size (bytes). + pub size: u64, + + /// Resident Set Size (bytes). + pub resident: u64, + + /// Shared pages (bytes). + pub shared: u64, + + /// Text. + pub text: u64, + + /// Data + stack. + pub data: u64, +} + +impl FromStr for ProcfsStatm { + type Err = Error; + + fn from_str(contents: &str) -> Result { + let fields = match contents.trim_end().split_whitespace().collect::>() { + fields if fields.len() >= 7 => Ok(fields), + _ => Err(Error::MissingData { + path: STATM.into(), + contents: contents.to_string(), + }), + }?; + + let parse = |s: &str| -> Result { + s.parse().map_err(|err| Error::ParseInt { + path: STATM.into(), + contents: contents.to_string(), + source: err, + }) + }; + + Ok(ProcfsStatm { + size: parse(fields[0])? * *PAGE_SIZE, + resident: parse(fields[1])? * *PAGE_SIZE, + shared: parse(fields[2])? * *PAGE_SIZE, + text: parse(fields[3])? * *PAGE_SIZE, + data: parse(fields[5])? * *PAGE_SIZE, + }) + } +} + +/// New function, not in Python psutil. +pub fn procfs_statm(pid: Pid) -> ProcessResult { + let contents = + read_file(procfs_path(pid, STATM)).map_err(|e| psutil_error_to_process_error(e, pid))?; + + ProcfsStatm::from_str(&contents).map_err(|e| psutil_error_to_process_error(e, pid)) +} diff --git a/common/rust-psutil/src/process/os/linux/procfs/status.rs b/common/rust-psutil/src/process/os/linux/procfs/status.rs new file mode 100644 index 00000000000..5f8a8156220 --- /dev/null +++ b/common/rust-psutil/src/process/os/linux/procfs/status.rs @@ -0,0 +1,117 @@ +use std::collections::HashMap; +use std::str::FromStr; + +use crate::process::os::unix::{Gid, Uid}; +use crate::process::{procfs_path, psutil_error_to_process_error, ProcessResult}; +use crate::{read_file, Error, Pid, Result}; + +const STATUS: &str = "status"; + +// TODO: rest of the fields +/// New struct, not in Python psutil. +#[derive(Clone, Debug)] +pub struct ProcfsStatus { + pub uid: [Uid; 4], + + pub gid: [Gid; 4], + + /// Voluntary context switches. + pub voluntary_ctxt_switches: Option, + + /// Non-voluntary context switches. + pub nonvoluntary_ctxt_switches: Option, +} + +impl FromStr for ProcfsStatus { + type Err = Error; + + fn from_str(contents: &str) -> Result { + let missing_status_data = |contents: &str| -> Error { + Error::MissingData { + path: STATUS.into(), + contents: contents.to_string(), + } + }; + + let map = contents + .lines() + .map(|line| { + let fields = match line.splitn(2, ':').collect::>() { + fields if fields.len() == 2 => Ok(fields), + _ => Err(missing_status_data(line)), + }?; + + Ok((fields[0], fields[1].trim())) + }) + .collect::>>()?; + + let parse_u32 = |s: &str| -> Result { + s.parse().map_err(|err| Error::ParseInt { + path: STATUS.into(), + contents: contents.to_string(), + source: err, + }) + }; + let parse_u64 = |s: &str| -> Result { + s.parse().map_err(|err| Error::ParseInt { + path: STATUS.into(), + contents: contents.to_string(), + source: err, + }) + }; + + let get = |key: &str| -> Result<&str> { + map.get(key) + .copied() + .ok_or_else(|| missing_status_data(contents)) + }; + + let uid_fields = match get("Uid")?.split_whitespace().collect::>() { + fields if fields.len() >= 4 => Ok(fields), + _ => Err(missing_status_data(contents)), + }?; + + let uid = [ + parse_u32(uid_fields[0])?, + parse_u32(uid_fields[1])?, + parse_u32(uid_fields[2])?, + parse_u32(uid_fields[3])?, + ]; + + let gid_fields = match get("Gid")?.split_whitespace().collect::>() { + fields if fields.len() >= 4 => Ok(fields), + _ => Err(missing_status_data(contents)), + }?; + + let gid = [ + parse_u32(gid_fields[0])?, + parse_u32(gid_fields[1])?, + parse_u32(gid_fields[2])?, + parse_u32(gid_fields[3])?, + ]; + + let voluntary_ctxt_switches = map + .get("voluntary_ctxt_switches") + .map(|entry| -> Result { parse_u64(entry) }) + .transpose()?; + let nonvoluntary_ctxt_switches = map + .get("nonvoluntary_ctxt_switches") + .map(|entry| -> Result { parse_u64(entry) }) + .transpose()?; + + Ok(ProcfsStatus { + uid, + gid, + voluntary_ctxt_switches, + nonvoluntary_ctxt_switches, + }) + } +} + +/// New function, not in Python psutil. +pub fn procfs_status(pid: Pid) -> ProcessResult { + let contents = + read_file(procfs_path(pid, STATUS)).map_err(|e| psutil_error_to_process_error(e, pid))?; + + ProcfsStatus::from_str(&contents).map_err(|e| psutil_error_to_process_error(e, pid)) +} diff --git a/common/rust-psutil/src/process/os/macos/kinfo.rs b/common/rust-psutil/src/process/os/macos/kinfo.rs new file mode 100644 index 00000000000..93e51dc4e94 --- /dev/null +++ b/common/rust-psutil/src/process/os/macos/kinfo.rs @@ -0,0 +1,257 @@ +// https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/bindings/process.rs +// https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/mod.rs + +use std::io; +use std::mem; +use std::ptr; + +use mach::{boolean, vm_types}; +use nix::{errno, libc}; + +use crate::process::{io_error_to_process_error, ProcessError, ProcessResult}; +use crate::Pid; + +#[allow(non_camel_case_types)] +type caddr_t = *const libc::c_char; +#[allow(non_camel_case_types)] +type segsz_t = i32; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct kinfo_proc { + pub kp_proc: extern_proc, + pub kp_eproc: kinfo_proc_eproc, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct extern_proc { + pub p_un: p_un, + pub p_vmspace: vm_types::user_addr_t, + pub p_sigacts: vm_types::user_addr_t, + + pub p_flag: libc::c_int, + pub p_stat: libc::c_char, + pub p_pid: libc::pid_t, + pub p_oppid: libc::pid_t, + pub p_dupfd: libc::c_int, + pub user_stack: caddr_t, + pub exit_thread: *mut libc::c_void, + pub p_debugger: libc::c_int, + pub sigwait: boolean::boolean_t, + pub p_estcpu: libc::c_uint, + pub p_cpticks: libc::c_int, + pub p_pctcpu: u32, + pub p_wchan: *mut libc::c_void, + pub p_wmesg: *mut libc::c_char, + pub p_swtime: libc::c_uint, + pub p_slptime: libc::c_uint, + pub p_realtimer: libc::itimerval, + pub p_rtime: libc::timeval, + pub p_uticks: u64, + pub p_sticks: u64, + pub p_iticks: u64, + pub p_traceflag: libc::c_int, + pub p_tracep: *mut libc::c_void, + pub p_siglist: libc::c_int, + // TODO: It was a pointer to `struct vnode` + pub p_textvp: *mut libc::c_void, + pub p_holdcnt: libc::c_int, + pub p_sigmask: libc::sigset_t, + pub p_sigignore: libc::sigset_t, + pub p_sigcatch: libc::sigset_t, + pub p_priority: libc::c_uchar, + pub p_usrpri: libc::c_uchar, + pub p_nice: libc::c_char, + pub p_comm: [libc::c_char; 17], + // TODO: It was a pointer to `struct proc`, declared at `bsd/sys/proc.h` + pub p_pgrp: *mut libc::c_void, + // TODO: It was a pointer to `struct user`, declared at `bsd/sys/user.h` + // but it is not used anymore and we do not need it too + pub p_addr: *mut libc::c_void, + pub p_xstat: libc::c_ushort, + pub p_acflag: libc::c_ushort, + pub p_ru: *mut libc::rusage, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub union p_un { + pub p_st1: run_sleep_queue, + pub p_starttime: libc::timeval, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct run_sleep_queue { + p_forw: vm_types::user_addr_t, + p_back: vm_types::user_addr_t, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct kinfo_proc_eproc { + // TODO: It should be a pointer to `struct proc` + pub e_paddr: *mut libc::c_void, + // TODO: It should be a pointer to `struct session` + // but since we are not using it and it's declaration kinda big, + // it was skipped. Same goes to `e_tsess` field below. + pub e_sess: *mut libc::c_void, + pub e_pcred: pcred, + pub e_ucred: libc::xucred, + pub e_vm: vmspace, + pub e_ppid: libc::pid_t, + pub e_pgid: libc::pid_t, + pub e_jobc: libc::c_short, + pub e_tdev: libc::dev_t, + pub e_tpgid: libc::pid_t, + pub e_tsess: *mut libc::c_void, // TODO: See `TODO` comment from above + pub e_wmesg: [libc::c_char; 8], + pub e_xsize: segsz_t, + pub e_xrssize: libc::c_short, + pub e_xccount: libc::c_short, + pub e_xswrss: libc::c_short, + pub e_flag: i32, + pub e_login: [libc::c_char; 12], + pub e_spare: [i32; 4], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct pcred { + pub pc_lock: [libc::c_char; 72], + pub pc_ucred: *mut libc::xucred, + pub p_ruid: libc::uid_t, + pub p_svuid: libc::uid_t, + pub p_rgid: libc::gid_t, + pub p_svgid: libc::gid_t, + pub p_refcnt: libc::c_int, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vmspace { + pub dummy: i32, + pub dummy2: caddr_t, + pub dummy3: [i32; 5], + pub dummy4: [caddr_t; 3], +} + +pub fn kinfo_processes() -> io::Result> { + let mut name: [i32; 3] = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_ALL]; + let mut size: libc::size_t = 0; + let mut processes: Vec = Vec::new(); + + loop { + // Dry-run to get the size required for the process list + let result = unsafe { + libc::sysctl( + name.as_mut_ptr(), + name.len() as libc::c_uint, + ptr::null_mut(), + &mut size, + ptr::null_mut(), + 0, + ) + }; + if result < 0 { + return Err(io::Error::last_os_error()); + } + + // Reserve enough room to store the whole process list. + // `size` is the number of bytes, not the number of processes. + let num_processes = size / mem::size_of::(); + if num_processes > processes.capacity() { + processes.reserve_exact(num_processes - processes.capacity()); + } + + // Attempt to store the process list in `processes` + let result = unsafe { + libc::sysctl( + name.as_mut_ptr(), + name.len() as libc::c_uint, + processes.as_mut_ptr() as *mut libc::c_void, + &mut size, + ptr::null_mut(), + 0, + ) + }; + + if result < 0 { + // Need to check to see if the error was due to `libc::ENOMEM`, this indicates that + // there was not enough space in `processes` to store the whole process list which can + // occur when a new process spawns between getting the size and storing. In this case + // we can simply try again. + if libc::ENOMEM == errno::errno() { + continue; + } else { + return Err(io::Error::last_os_error()); + } + } else { + // Getting the list succeeded so let `processes` know how many processes it holds. + // Have to recompute `num_processes` since `size` may have changed. + let num_processes = size / mem::size_of::(); + unsafe { + processes.set_len(num_processes); + } + debug_assert!(!processes.is_empty()); + + return Ok(processes); + } + } +} + +pub fn kinfo_process(pid: Pid) -> ProcessResult { + let mut name: [i32; 4] = [ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_PID, + pid as i32, + ]; + let mut size: libc::size_t = mem::size_of::(); + let mut info = mem::MaybeUninit::::uninit(); + + let result = unsafe { + libc::sysctl( + name.as_mut_ptr(), + name.len() as libc::c_uint, + info.as_mut_ptr() as *mut libc::c_void, + &mut size, + ptr::null_mut(), + 0, + ) + }; + + if result < 0 { + return Err(io_error_to_process_error(io::Error::last_os_error(), pid)); + } + + // sysctl succeeds but size is zero, happens when process has gone away + if size == 0 { + return Err(ProcessError::NoSuchProcess { pid }); + } + + unsafe { Ok(info.assume_init()) } +} + +#[cfg(test)] +mod tests { + use std::mem; + + use super::{kinfo_proc, kinfo_proc_eproc, pcred, vmspace}; + + #[test] + fn test_layout() { + assert_eq!(mem::size_of::(), 64); + assert_eq!(mem::align_of::(), 8); + + assert_eq!(mem::size_of::(), 104); + assert_eq!(mem::align_of::(), 8); + + assert_eq!(mem::size_of::(), 648); + assert_eq!(mem::align_of::(), 8); + + assert_eq!(mem::size_of::(), 352); + assert_eq!(mem::align_of::(), 8); + } +} diff --git a/common/rust-psutil/src/process/os/macos/mod.rs b/common/rust-psutil/src/process/os/macos/mod.rs new file mode 100644 index 00000000000..12fcd0926fe --- /dev/null +++ b/common/rust-psutil/src/process/os/macos/mod.rs @@ -0,0 +1,5 @@ +mod kinfo; +mod process; + +pub use kinfo::*; +pub use process::*; diff --git a/common/rust-psutil/src/process/os/macos/process.rs b/common/rust-psutil/src/process/os/macos/process.rs new file mode 100644 index 00000000000..a691cdca3b5 --- /dev/null +++ b/common/rust-psutil/src/process/os/macos/process.rs @@ -0,0 +1,13 @@ +use std::collections::HashMap; + +use crate::process::{Process, ProcessResult}; + +pub trait ProcessExt { + fn environ(&self) -> ProcessResult>; +} + +impl ProcessExt for Process { + fn environ(&self) -> ProcessResult> { + todo!() + } +} diff --git a/common/rust-psutil/src/process/os/mod.rs b/common/rust-psutil/src/process/os/mod.rs new file mode 100644 index 00000000000..cf52ed24992 --- /dev/null +++ b/common/rust-psutil/src/process/os/mod.rs @@ -0,0 +1,17 @@ +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +pub mod bsd; +#[cfg(target_os = "freebsd")] +pub mod freebsd; +#[cfg(target_os = "linux")] +pub mod linux; +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_family = "unix")] +pub mod unix; +#[cfg(target_os = "windows")] +pub mod windows; diff --git a/common/rust-psutil/src/process/os/unix.rs b/common/rust-psutil/src/process/os/unix.rs new file mode 100644 index 00000000000..7d55c354c6d --- /dev/null +++ b/common/rust-psutil/src/process/os/unix.rs @@ -0,0 +1,88 @@ +use crate::process::{Process, ProcessResult}; +use crate::Count; + +#[cfg(target_os = "linux")] +use crate::process::os::linux::{ProcessExt as _, ProcfsStatus}; + +pub type Uid = u32; +pub type Gid = u32; + +pub struct Uids { + pub real: Uid, + pub effective: Uid, + pub saved: Uid, +} + +pub struct Gids { + pub real: Gid, + pub effective: Gid, + pub saved: Gid, +} + +#[cfg(target_os = "linux")] +impl From for Uids { + fn from(procfs_status: ProcfsStatus) -> Self { + Uids { + real: procfs_status.uid[0], + effective: procfs_status.uid[1], + saved: procfs_status.uid[2], + } + } +} + +#[cfg(target_os = "linux")] +impl From for Gids { + fn from(procfs_status: ProcfsStatus) -> Self { + Gids { + real: procfs_status.gid[0], + effective: procfs_status.gid[1], + saved: procfs_status.gid[2], + } + } +} + +pub trait ProcessExt { + fn uids(&self) -> ProcessResult; + + fn gids(&self) -> ProcessResult; + + fn terminal(&self) -> Option; + + fn num_fds(&self) -> Count; +} + +impl ProcessExt for Process { + fn uids(&self) -> ProcessResult { + #[cfg(target_os = "linux")] + { + let procfs_status = self.procfs_status()?; + + Ok(Uids::from(procfs_status)) + } + #[cfg(not(any(target_os = "linux")))] + { + todo!() + } + } + + fn gids(&self) -> ProcessResult { + #[cfg(target_os = "linux")] + { + let procfs_status = self.procfs_status()?; + + Ok(Gids::from(procfs_status)) + } + #[cfg(not(any(target_os = "linux")))] + { + todo!() + } + } + + fn terminal(&self) -> Option { + todo!() + } + + fn num_fds(&self) -> Count { + todo!() + } +} diff --git a/common/rust-psutil/src/process/os/windows.rs b/common/rust-psutil/src/process/os/windows.rs new file mode 100644 index 00000000000..2ee1bfb82a8 --- /dev/null +++ b/common/rust-psutil/src/process/os/windows.rs @@ -0,0 +1,58 @@ +use std::collections::HashMap; + +use crate::process::{Process, ProcessResult}; +use crate::Count; + +pub struct IoCounters {} + +pub trait ProcessExt { + fn environ(&self) -> ProcessResult>; + + fn get_ionice(&self) -> i32; + + fn set_ionice(&self, nice: i32); + + fn io_counters(&self) -> IoCounters; + + fn num_handles(&self) -> Count; + + fn get_cpu_affinity(&self) -> i32; + + fn set_cpu_affinity(&self, nice: i32); + + fn memory_maps(&self); +} + +impl ProcessExt for Process { + fn environ(&self) -> ProcessResult> { + todo!() + } + + fn get_ionice(&self) -> i32 { + todo!() + } + + fn set_ionice(&self, _nice: i32) { + todo!() + } + + fn io_counters(&self) -> IoCounters { + todo!() + } + + fn num_handles(&self) -> Count { + todo!() + } + + fn get_cpu_affinity(&self) -> i32 { + todo!() + } + + fn set_cpu_affinity(&self, _nice: i32) { + todo!() + } + + fn memory_maps(&self) { + todo!() + } +} diff --git a/common/rust-psutil/src/process/process.rs b/common/rust-psutil/src/process/process.rs new file mode 100644 index 00000000000..4fa27857963 --- /dev/null +++ b/common/rust-psutil/src/process/process.rs @@ -0,0 +1,344 @@ +// #[cfg(feature = "serde")] +// use serde::{Deserialize, Serialize}; + +use std::cmp; +use std::hash::{Hash, Hasher}; +use std::path::PathBuf; +use std::time::{Duration, Instant}; + +use nix::sys::signal::{kill, Signal}; +use nix::unistd; + +use crate::common::NetConnectionType; +use crate::memory; +use crate::process::{ + psutil_error_to_process_error, MemType, MemoryInfo, OpenFile, ProcessCpuTimes, ProcessError, + ProcessResult, Status, +}; +use crate::utils::duration_percent; +use crate::{Count, Percent, Pid}; + +#[cfg(target_os = "linux")] +use crate::process::os::linux::ProcfsStat; + +// #[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +// #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug)] +pub struct Process { + pub(crate) pid: Pid, + pub(crate) create_time: Duration, + pub(crate) busy: Duration, + // FIXME: Instant does not serialize/deserialize, cannot add serde + // #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) instant: Instant, + + #[cfg(target_os = "linux")] + // TODO: ignore debug for this field when the feature becomes available. + // https://github.com/rust-lang/rust/issues/37009 + pub(crate) procfs_stat: ProcfsStat, +} + +impl Process { + pub fn new(pid: Pid) -> ProcessResult { + Process::sys_new(pid) + } + + pub fn current() -> ProcessResult { + Process::new(std::process::id()) + } + + pub fn pid(&self) -> Pid { + self.pid + } + + pub fn ppid(&self) -> ProcessResult> { + self.sys_ppid() + } + + pub fn name(&self) -> ProcessResult { + self.sys_name() + } + + pub fn exe(&self) -> ProcessResult { + self.sys_exe() + } + + /// On Linux, an `Ok(None)` is usually due to the process being a kernel thread. + /// The return value is different from Python psutil. + pub fn cmdline(&self) -> ProcessResult> { + self.sys_cmdline() + } + + /// New method, not in Python psutil. + /// On Linux, an `Ok(None)` is usually due to the process being a kernel thread. + pub fn cmdline_vec(&self) -> ProcessResult>> { + self.sys_cmdline_vec() + } + + /// The process creation time as a `Duration` since the boot time. + /// The return value is different from Python psutil. + pub fn create_time(&self) -> Duration { + self.create_time + } + + /// Preemptively checks if the process is still alive. + pub fn parent(&self) -> ProcessResult> { + if !self.is_running() { + return Err(ProcessError::NoSuchProcess { pid: self.pid }); + } + + self.ppid()?.map(Process::new).transpose() + } + + pub fn parents(&self) -> Option> { + self.sys_parents() + } + + pub fn status(&self) -> ProcessResult { + self.sys_status() + } + + pub fn cwd(&self) -> ProcessResult { + self.sys_cwd() + } + + pub fn username(&self) -> String { + self.sys_username() + } + + pub fn get_nice(&self) -> i32 { + self.sys_get_nice() + } + + pub fn set_nice(&self, nice: i32) { + self.sys_set_nice(nice) + } + + pub fn num_ctx_switches(&self) -> Count { + self.sys_num_ctx_switches() + } + + pub fn num_threads(&self) -> Count { + self.sys_num_threads() + } + + pub fn threads(&self) { + self.sys_threads() + } + + pub fn cpu_times(&self) -> ProcessResult { + self.sys_cpu_times() + } + + /// Returns the cpu percent since the process was created, replaced, or since the last time this + /// method was called. + /// Differs from Python psutil since there is no interval argument. + pub fn cpu_percent(&mut self) -> ProcessResult { + let busy = self.cpu_times()?.busy(); + let instant = Instant::now(); + + let percent = duration_percent( + // have to use checked_sub since CPU times can decrease over time at least on Linux + // https://github.com/cjbassi/ytop/issues/34 + // TODO: figure out why. hibernation? something to do with running VMs? + busy.checked_sub(self.busy).unwrap_or_default(), + // TODO: can duration be zero if cpu_percent is called consecutively without allowing + // enough time to pass? Cause then we have division by zero. + instant - self.instant, + ); + + self.busy = busy; + self.instant = instant; + + Ok(percent) + } + + pub fn memory_info(&self) -> ProcessResult { + self.sys_memory_info() + } + + pub fn memory_full_info(&self) { + self.sys_memory_full_info() + } + + pub fn memory_percent(&self) -> ProcessResult { + let virtual_memory = + memory::virtual_memory().map_err(|e| psutil_error_to_process_error(e, self.pid))?; + + self.memory_percent_oneshot(&virtual_memory) + } + + pub fn memory_percent_oneshot( + &self, + virtual_memory: &memory::VirtualMemory, + ) -> ProcessResult { + let memory_info = self.memory_info()?; + let percent = (memory_info.rss() as f64 / virtual_memory.total() as f64) * 100.0; + + Ok(percent as f32) + } + + pub fn memory_percent_with_type(&self, r#type: MemType) -> ProcessResult { + self.sys_memory_percent_with_type(r#type) + } + + pub fn children(&self) { + self.sys_children() + } + + pub fn open_files(&self) -> ProcessResult> { + self.sys_open_files() + } + + pub fn connections(&self) { + self.sys_connections() + } + + pub fn connections_with_type(&self, type_: NetConnectionType) { + self.sys_connections_with_type(type_) + } + + pub fn is_running(&self) -> bool { + match Process::new(self.pid) { + Ok(p) => p == *self, + Err(_) => false, + } + } + + /// New method, not in Python psutil. + pub fn is_replaced(&self) -> bool { + match Process::new(self.pid) { + Ok(p) => p != *self, + Err(_) => false, + } + } + + /// New method, not in Python psutil. + pub fn replace(&mut self) -> bool { + match Process::new(self.pid) { + Ok(p) if p != *self => { + *self = p; + true + } + _ => false, + } + } + + /// Preemptively checks if the process is still alive. + pub fn send_signal(&self, signal: Signal) -> ProcessResult<()> { + if !self.is_running() { + return Err(ProcessError::NoSuchProcess { pid: self.pid }); + } + + #[cfg(target_family = "unix")] + { + kill(unistd::Pid::from_raw(self.pid as i32), signal) + .map_err(From::from) + .map_err(|e| psutil_error_to_process_error(e, self.pid)) + } + #[cfg(not(any(target_family = "unix")))] + { + // e.g. windows only supports relatively few signals. + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal?view=vs-2019 + todo!() + } + } + + /// Preemptively checks if the process is still alive. + pub fn suspend(&self) -> ProcessResult<()> { + #[cfg(target_family = "unix")] + { + self.send_signal(Signal::SIGSTOP) + } + #[cfg(not(any(target_family = "unix")))] + { + todo!() + } + } + + /// Preemptively checks if the process is still alive. + pub fn resume(&self) -> ProcessResult<()> { + #[cfg(target_family = "unix")] + { + self.send_signal(Signal::SIGCONT) + } + #[cfg(not(any(target_family = "unix")))] + { + todo!() + } + } + + /// Preemptively checks if the process is still alive. + pub fn terminate(&self) -> ProcessResult<()> { + self.send_signal(Signal::SIGTERM) + } + + /// Preemptively checks if the process is still alive. + pub fn kill(&self) -> ProcessResult<()> { + #[cfg(target_family = "unix")] + { + self.send_signal(Signal::SIGKILL) + } + #[cfg(not(any(target_family = "unix")))] + { + todo!() + } + } + + pub fn wait(&self) { + self.sys_wait() + } +} + +impl PartialEq for Process { + // Compares processes using their pid and create_time as a unique identifier. + fn eq(&self, other: &Process) -> bool { + (self.pid() == other.pid()) && (self.create_time() == other.create_time()) + } +} + +impl cmp::Eq for Process {} + +impl Hash for Process { + fn hash(&self, state: &mut H) { + self.pid().hash(state); + self.create_time().hash(state); + } +} + +#[cfg(test)] +mod unit_tests { + use super::*; + use crate::process::processes; + + #[test] + fn test_process_exe() { + assert!(Process::current().unwrap().exe().is_ok()); + } + + #[test] + fn test_process_cmdline() { + assert!(Process::current().unwrap().cmdline().is_ok()); + } + + #[test] + fn test_process_cwd() { + assert!(Process::current().unwrap().cwd().is_ok()); + } + + #[test] + fn test_process_equality() { + assert_eq!(Process::current().unwrap(), Process::current().unwrap()); + } + + /// This could fail if you run the tests as PID 1. Please don't do that. + #[test] + fn test_process_inequality() { + assert_ne!(Process::current().unwrap(), Process::new(1).unwrap()); + } + + #[test] + fn test_processes() { + processes().unwrap(); + } +} diff --git a/common/rust-psutil/src/process/status.rs b/common/rust-psutil/src/process/status.rs new file mode 100644 index 00000000000..1bfee8bc704 --- /dev/null +++ b/common/rust-psutil/src/process/status.rs @@ -0,0 +1,52 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Possible statuses for a process. +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug)] +pub enum Status { + /// (R) + Running, + + /// (S) Sleeping in an interruptible wait + Sleeping, + + /// (D) Waiting in uninterruptible disk sleep + DiskSleep, + + /// (T) Stopped (on a signal) + /// + /// Or before Linux 2.6.33, trace stopped + Stopped, + + /// (t) (Linux 2.6.33 onward) + TracingStop, + + /// (Z) + Zombie, + + /// (X) + Dead, + + /// (Linux 2.6.33 to 3.13 only) + WakeKill, + + /// (Linux 2.6.33 to 3.13 only) + Waking, + + /// (P) (Linux 3.9 to 3.13 only) + Parked, + + /// (I) (Linux, macOS, FreeBSD) + Idle, + + /// (FreeBSD) + Locked, + + /// (FreeBSD) + Waiting, + + /// (NetBSD) + Suspended, +} diff --git a/common/rust-psutil/src/process/sys/linux/mod.rs b/common/rust-psutil/src/process/sys/linux/mod.rs new file mode 100644 index 00000000000..c3b4c6c4b24 --- /dev/null +++ b/common/rust-psutil/src/process/sys/linux/mod.rs @@ -0,0 +1,7 @@ +mod pids; +mod process; +mod status; + +pub use pids::*; +pub use process::*; +pub use status::*; diff --git a/common/rust-psutil/src/process/sys/linux/pids.rs b/common/rust-psutil/src/process/sys/linux/pids.rs new file mode 100644 index 00000000000..a4c89f76348 --- /dev/null +++ b/common/rust-psutil/src/process/sys/linux/pids.rs @@ -0,0 +1,21 @@ +use std::path::Path; + +use crate::{read_dir, Pid, Result}; + +// TODO: should we return an `io::Result>>` instead? +pub fn pids() -> Result> { + let mut pids = Vec::new(); + + for entry in read_dir("/proc")? { + let filename = entry.file_name(); + if let Ok(pid) = filename.to_string_lossy().parse::() { + pids.push(pid); + } + } + + Ok(pids) +} + +pub fn pid_exists(pid: Pid) -> bool { + Path::new(&format!("/proc/{}", pid)).exists() +} diff --git a/common/rust-psutil/src/process/sys/linux/process.rs b/common/rust-psutil/src/process/sys/linux/process.rs new file mode 100644 index 00000000000..942751c48d4 --- /dev/null +++ b/common/rust-psutil/src/process/sys/linux/process.rs @@ -0,0 +1,170 @@ +use std::path::PathBuf; +use std::string::ToString; +use std::time::Instant; + +use crate::common::NetConnectionType; +use crate::process::os::linux::{procfs_stat, ProcessExt as _}; +use crate::process::{ + pids, psutil_error_to_process_error, MemType, MemoryInfo, OpenFile, Process, ProcessCpuTimes, + ProcessResult, Status, +}; +use crate::{read_dir, read_file, read_link, Count, Percent, Pid, Result}; + +/// Returns a path to a file in `/proc/[pid]/`. +pub(crate) fn procfs_path(pid: Pid, name: &str) -> PathBuf { + PathBuf::from("/proc").join(pid.to_string()).join(&name) +} + +impl Process { + pub(crate) fn sys_new(pid: Pid) -> ProcessResult { + let procfs_stat = procfs_stat(pid)?; + + let create_time = procfs_stat.starttime; + let busy = ProcessCpuTimes::from(&procfs_stat).busy(); + let instant = Instant::now(); + + Ok(Process { + pid, + create_time, + busy, + instant, + procfs_stat, + }) + } + + pub(crate) fn procfs_path(&self, name: &str) -> PathBuf { + procfs_path(self.pid, name) + } + + pub(crate) fn sys_ppid(&self) -> ProcessResult> { + Ok(self.procfs_stat()?.ppid) + } + + pub(crate) fn sys_name(&self) -> ProcessResult { + Ok(self.procfs_stat()?.comm) + } + + pub(crate) fn sys_exe(&self) -> ProcessResult { + read_link(self.procfs_path("exe")).map_err(|e| psutil_error_to_process_error(e, self.pid)) + } + + pub(crate) fn sys_cmdline(&self) -> ProcessResult> { + Ok(self.cmdline_vec()?.map(|c| c.join(" "))) + } + + pub(crate) fn sys_cmdline_vec(&self) -> ProcessResult>> { + let cmdline = read_file(&self.procfs_path("cmdline")) + .map_err(|e| psutil_error_to_process_error(e, self.pid))?; + + if cmdline.is_empty() { + return Ok(None); + } + + let split = cmdline + .split_terminator('\0') + .map(|x| x.to_string()) + .collect(); + + Ok(Some(split)) + } + + pub(crate) fn sys_parents(&self) -> Option> { + todo!() + } + + pub(crate) fn sys_status(&self) -> ProcessResult { + Ok(self.procfs_stat()?.state) + } + + pub(crate) fn sys_cwd(&self) -> ProcessResult { + read_link(self.procfs_path("cwd")).map_err(|e| psutil_error_to_process_error(e, self.pid)) + } + + pub(crate) fn sys_username(&self) -> String { + todo!() + } + + pub(crate) fn sys_get_nice(&self) -> i32 { + todo!() + } + + pub(crate) fn sys_set_nice(&self, _nice: i32) { + todo!() + } + + pub(crate) fn sys_num_ctx_switches(&self) -> Count { + todo!() + } + + pub(crate) fn sys_num_threads(&self) -> Count { + todo!() + } + + pub(crate) fn sys_threads(&self) { + todo!() + } + + pub(crate) fn sys_cpu_times(&self) -> ProcessResult { + Ok(ProcessCpuTimes::from(&self.procfs_stat()?)) + } + + pub(crate) fn sys_memory_info(&self) -> ProcessResult { + Ok(self.procfs_statm()?.into()) + } + + pub(crate) fn sys_memory_full_info(&self) { + todo!() + } + + pub(crate) fn sys_memory_percent_with_type(&self, _type: MemType) -> ProcessResult { + todo!() + } + + pub(crate) fn sys_children(&self) { + todo!() + } + + pub(crate) fn sys_open_files(&self) -> ProcessResult> { + read_dir(self.procfs_path("fd")) + .map_err(|e| psutil_error_to_process_error(e, self.pid))? + .into_iter() + .filter_map(|entry| { + let path = entry.path(); + + let fd = path + .file_name() + .expect("directory entries should always contain a file name") + .to_string_lossy() + .parse::() + .ok()?; + let open_file = match read_link(&path) { + Ok(path) => path, + Err(e) => return Some(Err(psutil_error_to_process_error(e, self.pid))), + }; + + Some(Ok(OpenFile { + fd: Some(fd), + path: open_file, + })) + }) + .collect() + } + + pub(crate) fn sys_connections(&self) { + todo!() + } + + pub(crate) fn sys_connections_with_type(&self, _type: NetConnectionType) { + todo!() + } + + pub(crate) fn sys_wait(&self) { + todo!() + } +} + +pub fn processes() -> Result>> { + let processes = pids()?.into_iter().map(Process::new).collect(); + + Ok(processes) +} diff --git a/common/rust-psutil/src/process/sys/linux/status.rs b/common/rust-psutil/src/process/sys/linux/status.rs new file mode 100644 index 00000000000..58ca9c3b494 --- /dev/null +++ b/common/rust-psutil/src/process/sys/linux/status.rs @@ -0,0 +1,68 @@ +use std::convert::TryFrom; +use std::str::FromStr; + +use crate::process::Status; +use crate::ParseStatusError; + +/// Returns a Status based on a status character from `/proc/[pid]/stat`. +/// +/// See [array.c:115] and [proc(5)]. +/// +/// [array.c:115]: https://github.com/torvalds/linux/blob/master/fs/proc/array.c#L115 +/// [proc(5)]: http://man7.org/linux/man-pages/man5/proc.5.html +impl TryFrom for Status { + type Error = ParseStatusError; + + fn try_from(value: char) -> Result { + match value { + 'R' => Ok(Status::Running), + 'S' => Ok(Status::Sleeping), + 'D' => Ok(Status::Waiting), + 'Z' => Ok(Status::Zombie), + 'T' => Ok(Status::Stopped), + 't' => Ok(Status::TracingStop), + 'X' | 'x' => Ok(Status::Dead), + 'K' => Ok(Status::WakeKill), + 'W' => Ok(Status::Waking), + 'P' => Ok(Status::Parked), + 'I' => Ok(Status::Idle), + _ => Err(ParseStatusError::IncorrectChar { + contents: value.to_string(), + }), + } + } +} + +impl FromStr for Status { + type Err = ParseStatusError; + + fn from_str(s: &str) -> Result { + if s.len() != 1 { + return Err(ParseStatusError::IncorrectLength { + contents: s.to_string(), + }); + } + + Status::try_from(s.chars().next().unwrap()) + } +} + +impl ToString for Status { + fn to_string(&self) -> String { + match *self { + Status::Running => "R", + Status::Sleeping => "S", + Status::DiskSleep => "D", + Status::Zombie => "Z", + Status::Stopped => "T", + Status::TracingStop => "t", + Status::Dead => "X", + Status::WakeKill => "K", + Status::Waking => "W", + Status::Parked => "P", + Status::Idle => "I", + _ => "", + } + .to_string() + } +} diff --git a/common/rust-psutil/src/process/sys/macos/mod.rs b/common/rust-psutil/src/process/sys/macos/mod.rs new file mode 100644 index 00000000000..c3b4c6c4b24 --- /dev/null +++ b/common/rust-psutil/src/process/sys/macos/mod.rs @@ -0,0 +1,7 @@ +mod pids; +mod process; +mod status; + +pub use pids::*; +pub use process::*; +pub use status::*; diff --git a/common/rust-psutil/src/process/sys/macos/pids.rs b/common/rust-psutil/src/process/sys/macos/pids.rs new file mode 100644 index 00000000000..efdd5f22f96 --- /dev/null +++ b/common/rust-psutil/src/process/sys/macos/pids.rs @@ -0,0 +1,14 @@ +use crate::process::processes; +use crate::{Pid, Result}; + +pub fn pids() -> Result> { + Ok(processes()? + .into_iter() + .filter_map(|process| process.ok()) + .map(|process| process.pid()) + .collect()) +} + +pub fn pid_exists(_pid: Pid) -> bool { + todo!() +} diff --git a/common/rust-psutil/src/process/sys/macos/process.rs b/common/rust-psutil/src/process/sys/macos/process.rs new file mode 100644 index 00000000000..1883e9294c3 --- /dev/null +++ b/common/rust-psutil/src/process/sys/macos/process.rs @@ -0,0 +1,187 @@ +// https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/process/mod.rs +// https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/utils.rs + +use std::convert::TryFrom; +use std::ffi::CStr; +use std::path::PathBuf; +use std::time::{Duration, Instant}; + +use nix::libc; + +use crate::common::NetConnectionType; +use crate::process::os::macos::{kinfo_proc, kinfo_process, kinfo_processes}; +use crate::process::{ + io_error_to_process_error, psutil_error_to_process_error, MemType, MemoryInfo, OpenFile, + Process, ProcessCpuTimes, ProcessError, ProcessResult, Status, +}; +use crate::{Count, Error, Percent, Pid, Result}; + +fn catch_zombie(proc_err: ProcessError) -> ProcessError { + if let ProcessError::PsutilError { + pid, + source: ref psutil_err, + } = proc_err + { + if let Error::OsError { source: io_err } = psutil_err { + if io_err.raw_os_error() == Some(libc::ESRCH) { + let kinfo_proc = match kinfo_process(pid) { + Ok(info) => info, + Err(e) => return e, + }; + + return match Status::try_from(kinfo_proc.kp_proc.p_stat) { + Ok(Status::Zombie) => ProcessError::ZombieProcess { pid }, + Ok(_) => ProcessError::AccessDenied { pid }, + Err(e) => psutil_error_to_process_error(e.into(), pid), + }; + } + } + } + + proc_err +} + +fn cpu_times(pid: Pid) -> ProcessResult { + darwin_libproc::task_info(pid as i32) + .map(ProcessCpuTimes::from) + .map_err(|e| catch_zombie(io_error_to_process_error(e, pid))) +} + +fn process_id(kinfo_proc: kinfo_proc) -> (Pid, Duration) { + let pid = kinfo_proc.kp_proc.p_pid as u32; + let timeval = unsafe { + // TODO: How can it be guaranteed that in this case + // `p_un.p_starttime` will be filled correctly? + kinfo_proc.kp_proc.p_un.p_starttime + }; + let create_time = + Duration::from_secs(timeval.tv_sec as u64) + Duration::from_micros(timeval.tv_usec as u64); + + (pid, create_time) +} + +fn process_new(kinfo_proc: kinfo_proc) -> ProcessResult { + let (pid, create_time) = process_id(kinfo_proc); + let busy = cpu_times(pid)?.busy(); + let instant = Instant::now(); + + Ok(Process { + pid, + create_time, + busy, + instant, + }) +} + +impl Process { + pub(crate) fn sys_new(pid: Pid) -> ProcessResult { + let kinfo_proc = kinfo_process(pid).map_err(catch_zombie)?; + + process_new(kinfo_proc) + } + + pub(crate) fn sys_ppid(&self) -> ProcessResult> { + todo!() + } + + pub(crate) fn sys_name(&self) -> ProcessResult { + kinfo_process(self.pid) + .map(|kinfo_proc| { + let raw_str = unsafe { CStr::from_ptr(kinfo_proc.kp_proc.p_comm.as_ptr()) }; + let name = raw_str.to_string_lossy().into_owned(); + + name + }) + .map_err(catch_zombie) + } + + pub(crate) fn sys_exe(&self) -> ProcessResult { + todo!() + } + + pub(crate) fn sys_cmdline(&self) -> ProcessResult> { + todo!() + } + + pub(crate) fn sys_cmdline_vec(&self) -> ProcessResult>> { + todo!() + } + + pub(crate) fn sys_parents(&self) -> Option> { + todo!() + } + + pub(crate) fn sys_status(&self) -> ProcessResult { + todo!() + } + + pub(crate) fn sys_cwd(&self) -> ProcessResult { + todo!() + } + + pub(crate) fn sys_username(&self) -> String { + todo!() + } + + pub(crate) fn sys_get_nice(&self) -> i32 { + todo!() + } + + pub(crate) fn sys_set_nice(&self, _nice: i32) { + todo!() + } + + pub(crate) fn sys_num_ctx_switches(&self) -> Count { + todo!() + } + + pub(crate) fn sys_num_threads(&self) -> Count { + todo!() + } + + pub(crate) fn sys_threads(&self) { + todo!() + } + + pub(crate) fn sys_cpu_times(&self) -> ProcessResult { + cpu_times(self.pid) + } + + pub(crate) fn sys_memory_info(&self) -> ProcessResult { + darwin_libproc::task_info(self.pid as i32) + .map(MemoryInfo::from) + .map_err(|e| catch_zombie(io_error_to_process_error(e, self.pid))) + } + + pub(crate) fn sys_memory_full_info(&self) { + todo!() + } + + pub(crate) fn sys_memory_percent_with_type(&self, _type: MemType) -> ProcessResult { + todo!() + } + + pub(crate) fn sys_children(&self) { + todo!() + } + + pub(crate) fn sys_open_files(&self) -> ProcessResult> { + todo!() + } + + pub(crate) fn sys_connections(&self) { + todo!() + } + + pub(crate) fn sys_connections_with_type(&self, _type: NetConnectionType) { + todo!() + } + + pub(crate) fn sys_wait(&self) { + todo!() + } +} + +pub fn processes() -> Result>> { + Ok(kinfo_processes()?.into_iter().map(process_new).collect()) +} diff --git a/common/rust-psutil/src/process/sys/macos/status.rs b/common/rust-psutil/src/process/sys/macos/status.rs new file mode 100644 index 00000000000..a84ee4a1857 --- /dev/null +++ b/common/rust-psutil/src/process/sys/macos/status.rs @@ -0,0 +1,40 @@ +// https://github.com/heim-rs/heim/blob/master/heim-process/src/sys/macos/bindings/process.rs + +use std::convert::TryFrom; + +use nix::libc; + +use crate::process::Status; +use crate::ParseStatusError; + +// Process status values, declared at `bsd/sys/proc.h` +// ex. http://fxr.watson.org/fxr/source/bsd/sys/proc.h?v=xnu-792.6.70#L149 +// Used in `extern_proc.p_stat` field + +/// Process being created by fork. +const SIDL: libc::c_char = 1; +/// Currently runnable. +const SRUN: libc::c_char = 2; +/// Sleeping on an address. +const SSLEEP: libc::c_char = 3; +/// Process debugging or suspension. +const SSTOP: libc::c_char = 4; +/// Awaiting collection by parent. +const SZOMB: libc::c_char = 5; + +impl TryFrom for Status { + type Error = ParseStatusError; + + fn try_from(value: libc::c_char) -> Result { + match value { + SIDL => Ok(Status::Idle), + SRUN => Ok(Status::Running), + SSLEEP => Ok(Status::Sleeping), + SSTOP => Ok(Status::Stopped), + SZOMB => Ok(Status::Zombie), + other => Err(ParseStatusError::IncorrectChar { + contents: other.to_string(), + }), + } + } +} diff --git a/common/rust-psutil/src/process/sys/mod.rs b/common/rust-psutil/src/process/sys/mod.rs new file mode 100644 index 00000000000..691f8ce1721 --- /dev/null +++ b/common/rust-psutil/src/process/sys/mod.rs @@ -0,0 +1,9 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "macos")] { + mod macos; + pub use macos::*; + } +} diff --git a/common/rust-psutil/src/sensors/fan_sensor.rs b/common/rust-psutil/src/sensors/fan_sensor.rs new file mode 100644 index 00000000000..c42067c752a --- /dev/null +++ b/common/rust-psutil/src/sensors/fan_sensor.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Rpm; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct FanSensor { + pub(crate) _label: String, + pub(crate) _current: Rpm, +} diff --git a/common/rust-psutil/src/sensors/mod.rs b/common/rust-psutil/src/sensors/mod.rs new file mode 100644 index 00000000000..45bfcd15912 --- /dev/null +++ b/common/rust-psutil/src/sensors/mod.rs @@ -0,0 +1,11 @@ +//! Temperatures and Fans. +//! +//! For battery information, check out [rust-battery](https://github.com/svartalf/rust-battery). + +mod fan_sensor; +mod sys; +mod temperature_sensor; + +pub use fan_sensor::*; +pub use sys::*; +pub use temperature_sensor::*; diff --git a/common/rust-psutil/src/sensors/sys/linux/fans.rs b/common/rust-psutil/src/sensors/sys/linux/fans.rs new file mode 100644 index 00000000000..b0031065c15 --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/linux/fans.rs @@ -0,0 +1,8 @@ +use std::collections::HashMap; + +use crate::sensors::FanSensor; +use crate::Result; + +pub fn fans() -> Result>> { + todo!() +} diff --git a/common/rust-psutil/src/sensors/sys/linux/mod.rs b/common/rust-psutil/src/sensors/sys/linux/mod.rs new file mode 100644 index 00000000000..90d8ea2232c --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/linux/mod.rs @@ -0,0 +1,5 @@ +mod fans; +mod temperatures; + +pub use fans::*; +pub use temperatures::*; diff --git a/common/rust-psutil/src/sensors/sys/linux/temperatures.rs b/common/rust-psutil/src/sensors/sys/linux/temperatures.rs new file mode 100644 index 00000000000..472f3aea625 --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/linux/temperatures.rs @@ -0,0 +1,166 @@ +// https://github.com/heim-rs/heim/blob/master/heim-sensors/src/temperatures.rs +// https://github.com/heim-rs/heim/blob/master/heim-sensors/src/sys/linux/temperatures.rs + +use std::ffi::{OsStr, OsString}; +use std::os::unix::ffi::OsStrExt; +use std::path::PathBuf; + +use crate::sensors::TemperatureSensor; +use crate::{glob, read_file, Error, Result, Temperature}; + +#[inline] +fn file_name(prefix: &OsStr, postfix: &[u8]) -> OsString { + let mut name = OsString::with_capacity(prefix.len() + postfix.len()); + name.push(prefix); + name.push(OsStr::from_bytes(postfix)); + + name +} + +fn read_temperature(path: PathBuf) -> Result { + let contents = read_file(&path)?; + match contents.trim_end().parse::() { + // Originally value is in millidegrees of Celsius + Ok(value) => Ok(Temperature::new(value / 1_000.0)), + Err(err) => Err(Error::ParseFloat { + path, + contents, + source: err, + }), + } +} + +fn hwmon_sensor(input: PathBuf) -> Result { + // It is guaranteed by `hwmon` and `hwmon_sensor` directory traversals, + // that it is not a root directory and it points to a file. + // Otherwise it is an implementation bug. + let root = input.parent().unwrap_or_else(|| unreachable!()); + let prefix = match input.file_name() { + Some(name) => { + let offset = name.len() - b"input".len(); + OsStr::from_bytes(&name.as_bytes()[..offset]) + } + None => unreachable!(), + }; + + let mut unit = read_file(root.join("name"))?; + // Drop trailing `\n` + unit.pop(); + + let label_path = root.join(file_name(prefix, b"label")); + let label = if label_path.exists() { + let mut label = read_file(label_path)?; + // Drop trailing `\n` + label.pop(); + Some(label) + } else { + None + }; + + let max_path = root.join(file_name(prefix, b"max")); + let max = if max_path.exists() { + Some(read_temperature(max_path)?) + } else { + None + }; + + let crit_path = root.join(file_name(prefix, b"crit")); + let crit = if crit_path.exists() { + Some(read_temperature(crit_path)?) + } else { + None + }; + + let current = read_temperature(input)?; + + Ok(TemperatureSensor { + unit, + label, + current, + max, + crit, + }) +} + +// https://github.com/shirou/gopsutil/blob/2cbc9195c892b304060269ef280375236d2fcac9/host/host_linux.go#L624 +fn hwmon() -> Vec> { + let mut glob_results = glob("/sys/class/hwmon/hwmon*/temp*_input"); + + if glob_results.is_empty() { + // CentOS has an intermediate `device` directory: + // https://github.com/giampaolo/psutil/issues/971 + // https://github.com/nicolargo/glances/issues/1060 + glob_results = glob("/sys/class/hwmon/hwmon*/device/temp*_input"); + } + + glob_results + .into_iter() + .map(|result| match result { + Ok(path) => hwmon_sensor(path), + Err(e) => Err(e), + }) + .collect() +} + +// https://www.kernel.org/doc/Documentation/thermal/sysfs-api.txt +fn thermal_zone() -> Vec> { + glob("/sys/class/thermal/thermal_zone*") + .into_iter() + .map(|result| { + let path = result?; + + let current = read_temperature(path.join("temp"))?; + + let mut unit = read_file(path.join("type"))?; + unit.pop(); // dropping trailing `\n` + + let mut max = None; + let mut crit = None; + + glob(&path.join("trip_point_*_type").to_string_lossy().to_string()) + .into_iter() + .map(|result| -> Result<()> { + let path = result?; + + let name = path.file_name().unwrap(); + let offset = name.len() - b"type".len(); + let prefix = OsStr::from_bytes(&name.as_bytes()[..offset]); + let root = path.parent().unwrap_or_else(|| unreachable!()); + let temp_path = root.join(file_name(prefix, b"temp")); + + let mut contents = read_file(path)?; + contents.pop(); // dropping trailing `\n` + match contents.as_str() { + "critical" => { + crit = Some(read_temperature(temp_path)?); + } + "high" => { + max = Some(read_temperature(temp_path)?); + } + _ => {} + } + + Ok(()) + }) + .collect::>>()?; + + Ok(TemperatureSensor { + unit, + label: None, // TODO + current, + max, + crit, + }) + }) + .collect() +} + +pub fn temperatures() -> Vec> { + let hwmon = hwmon(); + + if hwmon.is_empty() { + thermal_zone() + } else { + hwmon + } +} diff --git a/common/rust-psutil/src/sensors/sys/macos/fans.rs b/common/rust-psutil/src/sensors/sys/macos/fans.rs new file mode 100644 index 00000000000..b0031065c15 --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/macos/fans.rs @@ -0,0 +1,8 @@ +use std::collections::HashMap; + +use crate::sensors::FanSensor; +use crate::Result; + +pub fn fans() -> Result>> { + todo!() +} diff --git a/common/rust-psutil/src/sensors/sys/macos/mod.rs b/common/rust-psutil/src/sensors/sys/macos/mod.rs new file mode 100644 index 00000000000..90d8ea2232c --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/macos/mod.rs @@ -0,0 +1,5 @@ +mod fans; +mod temperatures; + +pub use fans::*; +pub use temperatures::*; diff --git a/common/rust-psutil/src/sensors/sys/macos/temperatures.rs b/common/rust-psutil/src/sensors/sys/macos/temperatures.rs new file mode 100644 index 00000000000..7cebe0f3df0 --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/macos/temperatures.rs @@ -0,0 +1,6 @@ +use crate::sensors::TemperatureSensor; +use crate::Result; + +pub fn temperatures() -> Vec> { + todo!() +} diff --git a/common/rust-psutil/src/sensors/sys/mod.rs b/common/rust-psutil/src/sensors/sys/mod.rs new file mode 100644 index 00000000000..691f8ce1721 --- /dev/null +++ b/common/rust-psutil/src/sensors/sys/mod.rs @@ -0,0 +1,9 @@ +cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub use linux::*; + } else if #[cfg(target_os = "macos")] { + mod macos; + pub use macos::*; + } +} diff --git a/common/rust-psutil/src/sensors/temperature_sensor.rs b/common/rust-psutil/src/sensors/temperature_sensor.rs new file mode 100644 index 00000000000..fcd9c375036 --- /dev/null +++ b/common/rust-psutil/src/sensors/temperature_sensor.rs @@ -0,0 +1,42 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use crate::Temperature; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct TemperatureSensor { + pub(crate) unit: String, + pub(crate) label: Option, + pub(crate) current: Temperature, + pub(crate) max: Option, + pub(crate) crit: Option, +} + +impl TemperatureSensor { + /// Returns sensor unit name. + pub fn unit(&self) -> &str { + &self.unit + } + + /// Returns sensor label. + pub fn label(&self) -> Option<&str> { + self.label.as_deref() + } + + /// Returns current temperature reported by sensor. + pub fn current(&self) -> &Temperature { + &self.current + } + + /// Returns high trip point for sensor if available. + pub fn high(&self) -> Option<&Temperature> { + self.max.as_ref() + } + + /// Returns critical trip point for sensor if available. + pub fn critical(&self) -> Option<&Temperature> { + self.crit.as_ref() + } +} diff --git a/common/rust-psutil/src/types.rs b/common/rust-psutil/src/types.rs new file mode 100644 index 00000000000..f3114b9c769 --- /dev/null +++ b/common/rust-psutil/src/types.rs @@ -0,0 +1,37 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub type Fd = u32; +pub type Pid = u32; + +pub type Count = u64; +pub type Bytes = Count; +pub type Rpm = Count; + +pub type Percent = f32; + +pub type FloatCount = f64; +pub type Degrees = FloatCount; +pub type Mhz = FloatCount; + +#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone)] +pub struct Temperature { + celsius: Degrees, +} + +impl Temperature { + pub fn new(celsius: Degrees) -> Temperature { + Temperature { celsius } + } + + pub fn celsius(&self) -> Degrees { + self.celsius + } + + #[allow(clippy::unnecessary_cast)] + pub fn fahrenheit(&self) -> Degrees { + (self.celsius * (9 as Degrees / 5 as Degrees)) + 32 as Degrees + } +} diff --git a/common/rust-psutil/src/unix.rs b/common/rust-psutil/src/unix.rs new file mode 100644 index 00000000000..322facc8a06 --- /dev/null +++ b/common/rust-psutil/src/unix.rs @@ -0,0 +1,18 @@ +// https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/unix.rs + +use nix::unistd; +use once_cell::sync::Lazy; + +use crate::{Bytes, FloatCount}; + +pub(crate) static TICKS_PER_SECOND: Lazy = Lazy::new(|| { + unistd::sysconf(unistd::SysconfVar::CLK_TCK) + .unwrap() + .unwrap() as FloatCount +}); + +pub(crate) static PAGE_SIZE: Lazy = Lazy::new(|| { + unistd::sysconf(unistd::SysconfVar::PAGE_SIZE) + .unwrap() + .unwrap() as Bytes +}); diff --git a/common/rust-psutil/src/utils.rs b/common/rust-psutil/src/utils.rs new file mode 100644 index 00000000000..d0d157e3858 --- /dev/null +++ b/common/rust-psutil/src/utils.rs @@ -0,0 +1,16 @@ +use std::time::Duration; + +use crate::Percent; + +// TODO: switch this to nightly div_duration_f64 +pub(crate) fn div_duration_f64(lhs: Duration, rhs: Duration) -> f64 { + lhs.as_secs_f64() / rhs.as_secs_f64() +} + +pub(crate) fn duration_percent(lhs: Duration, rhs: Duration) -> Percent { + (div_duration_f64(lhs, rhs) * 100.0) as f32 +} + +pub(crate) fn u64_percent(lhs: u64, rhs: u64) -> Percent { + ((lhs as f64 / rhs as f64) * 100.0) as f32 +}