refactor: migrate disk collection code off of heim, remove heim (#1064)
Migrates existing heim-based disk data collection code off of it to either sysinfo or vendored code based on heim/sysinfo/other sources. This also allows us to remove heim completely from bottom. --- * refactor: fix some refresh code * remove async from the freebsd code * some file/implementation organization Turns out sysinfo lacks a lot of data I need. I can still use it for the Windows disk usage implementation, but I'm probably going to manually implement macos/linux usage and all io usage stats. * more restructuring * Some other fixes * remove futures * ready for some big changes? * big changes * linux io + reads * use lossy conversion for mount point * add windows refresh * so long heim, and thanks for all the fish * fix filter behaviour, remove string allocation when reading lines * rename unix -> system for more accurate file struct representation * fix freebsd * port generic unix partition code * add bindings and fix errors * finish macOS bindings for I/O * disable conform check, this seems to... make disk I/O work on macOS????? * fix linux * add safety comments * more comments * update changelog * changelog * We're going full 0.9.0 for this * update lock * fix some typing * bleh * some file management * hoist out get_disk_usage * fix some stuff for Windows * typing and remove dead code allow lint * unify typing * fix * fix 2 * macOS fix * Add bindings file for windows * add windows implementation * fix macos
This commit is contained in:
parent
b2801b16a9
commit
9edde9b133
|
@ -5,12 +5,15 @@ 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).
|
||||
|
||||
## [0.9.0]/[0.8.1] - Unreleased
|
||||
## [0.9.0] - Unreleased
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- [#1021](https://github.com/ClementTsang/bottom/pull/1021): Fix selected text background colour being wrong if only the foreground colour was set.
|
||||
- [#1037](https://github.com/ClementTsang/bottom/pull/1037): Fix `is_list_ignored` accepting all results if set to `false`.
|
||||
- [#1064](https://github.com/ClementTsang/bottom/pull/1064): Disk name/mount filter now doesn't always show all entries if one filter wasn't set.
|
||||
- [#1064](https://github.com/ClementTsang/bottom/pull/1064): macOS disk I/O is potentially working now.
|
||||
- [#597](https://github.com/ClementTsang/bottom/issues/597): Resolve RUSTSEC-2021-0119 by removing heim.
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -25,6 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- [#1036](https://github.com/ClementTsang/bottom/pull/1036): Migrate away from heim for memory information; Linux
|
||||
platforms will also now try to use `MemAvailable` to determine used memory if supported.
|
||||
- [#1041](https://github.com/ClementTsang/bottom/pull/1041): Migrate away from heim for network information.
|
||||
- [#1064](https://github.com/ClementTsang/bottom/pull/1064): Migrate away from heim for storage information.
|
||||
- [#812](https://github.com/ClementTsang/bottom/issues/812): Fully remove heim from bottom.
|
||||
- [#1075](https://github.com/ClementTsang/bottom/issues/1075): Update how drives are named in Windows.
|
||||
|
||||
## Other
|
||||
|
||||
|
|
|
@ -52,110 +52,6 @@ dependencies = [
|
|||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"once_cell",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-fs"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"futures-lite",
|
||||
"libc",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking",
|
||||
"polling",
|
||||
"slab",
|
||||
"socket2",
|
||||
"waker-fn",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-net"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5373304df79b9b4395068fb080369ec7178608827306ce4d081cba51cac551df"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"signal-hook",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
@ -194,23 +90,9 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bottom"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
|
@ -222,15 +104,13 @@ dependencies = [
|
|||
"clap_mangen",
|
||||
"concat-string",
|
||||
"const_format",
|
||||
"core-foundation 0.9.3",
|
||||
"crossterm 0.26.1",
|
||||
"ctrlc",
|
||||
"dirs",
|
||||
"fern",
|
||||
"filedescriptor",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"fxhash",
|
||||
"heim",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"indexmap",
|
||||
|
@ -276,12 +156,6 @@ version = "1.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cache-padded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-husky"
|
||||
version = "1.5.0"
|
||||
|
@ -357,15 +231,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
|
||||
dependencies = [
|
||||
"cache-padded",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.30"
|
||||
|
@ -402,7 +267,7 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.8.3",
|
||||
"core-foundation-sys 0.8.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -414,9 +279,9 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
|||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
|
@ -620,21 +485,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.6.2"
|
||||
|
@ -670,116 +520,6 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
|
@ -818,65 +558,6 @@ version = "0.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heim"
|
||||
version = "0.1.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8a653442b9bdd11a77d3753a60443c60c4437d3acac8e6c3d4a6a9acd7cceed"
|
||||
dependencies = [
|
||||
"heim-common",
|
||||
"heim-disk",
|
||||
"heim-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heim-common"
|
||||
version = "0.1.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767e6e47cf88abe7c9a5ebb4df82f180d30d9c0ba0269b6d166482461765834"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation 0.9.3",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mach",
|
||||
"nix 0.19.1",
|
||||
"pin-utils",
|
||||
"uom",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heim-disk"
|
||||
version = "0.1.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75603ff3868851c04954ee86bf610a6bd45be2732a0e81c35fd72b2b90fa4718"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"core-foundation 0.9.3",
|
||||
"heim-common",
|
||||
"heim-runtime",
|
||||
"libc",
|
||||
"mach",
|
||||
"widestring",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heim-runtime"
|
||||
version = "0.1.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ec7e5238c8f0dd0cc60914d31a5a7aadd4cde74c966a76c1caed1f5224e9b8"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"once_cell",
|
||||
"smol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
@ -924,15 +605,6 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.4"
|
||||
|
@ -1074,18 +746,6 @@ dependencies = [
|
|||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.1"
|
||||
|
@ -1126,27 +786,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
|
@ -1210,12 +849,6 @@ version = "6.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
|
@ -1239,31 +872,6 @@ dependencies = [
|
|||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"log",
|
||||
"wepoll-ffi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.5"
|
||||
|
@ -1521,46 +1129,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "smol"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-net",
|
||||
"async-process",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starship-battery"
|
||||
version = "0.7.9"
|
||||
|
@ -1639,7 +1213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys 0.8.3",
|
||||
"core-foundation-sys 0.8.4",
|
||||
"libc",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
|
@ -1810,7 +1384,6 @@ version = "0.30.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e76503e636584f1e10b9b3b9498538279561adcef5412927ba00c2b32c4ce5ed"
|
||||
dependencies = [
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
@ -1824,12 +1397,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
|
@ -1853,21 +1420,6 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wepoll-ffi"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bottom"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
authors = ["Clement Tsang <cjhtsang@uwaterloo.ca>"]
|
||||
edition = "2021"
|
||||
repository = "https://github.com/ClementTsang/bottom"
|
||||
|
@ -82,8 +82,6 @@ crossterm = "0.26.1"
|
|||
ctrlc = { version = "3.2.5", features = ["termination"] }
|
||||
dirs = "5.0.0"
|
||||
fern = { version = "0.6.2", optional = true }
|
||||
futures = "0.3.26"
|
||||
futures-timer = "3.0.2"
|
||||
fxhash = "0.2.1"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
|
@ -109,17 +107,19 @@ unicode-width = "0.1.10"
|
|||
libc = "0.2.141"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
heim = { version = "0.1.0-rc.1", features = ["disk"] }
|
||||
procfs = { version = "0.15.1", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
heim = { version = "0.1.0-rc.1", features = ["disk"] }
|
||||
core-foundation = "0.9.3"
|
||||
mach2 = "0.4.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
heim = { version = "0.1.0-rc.1", features = ["disk"] }
|
||||
windows = { version = "0.48.0", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_IO",
|
||||
"Win32_System_Ioctl",
|
||||
"Win32_System_ProcessStatus",
|
||||
"Win32_System_Threading",
|
||||
] }
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
use std::{collections::BTreeMap, time::Instant, vec::Vec};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
#[cfg(feature = "battery")]
|
||||
use crate::data_harvester::batteries;
|
||||
|
@ -317,23 +315,43 @@ impl DataCollection {
|
|||
&mut self, disks: Vec<disks::DiskHarvest>, io: disks::IoHarvest, harvested_time: Instant,
|
||||
) {
|
||||
// TODO: [PO] To implement
|
||||
|
||||
let time_since_last_harvest = harvested_time
|
||||
.duration_since(self.current_instant)
|
||||
.as_secs_f64();
|
||||
|
||||
for (itx, device) in disks.iter().enumerate() {
|
||||
if let Some(trim) = device.name.split('/').last() {
|
||||
let io_device = if cfg!(target_os = "macos") {
|
||||
// Must trim one level further for macOS!
|
||||
static DISK_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"disk\d+").unwrap());
|
||||
if let Some(disk_trim) = DISK_REGEX.find(trim) {
|
||||
io.get(disk_trim.as_str())
|
||||
let checked_name = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "windows")] {
|
||||
match &device.volume_name {
|
||||
Some(volume_name) => Some(volume_name.as_str()),
|
||||
None => device.name.split('/').last(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
device.name.split('/').last()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(checked_name) = checked_name {
|
||||
let io_device = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "macos")] {
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
// Must trim one level further for macOS!
|
||||
static DISK_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"disk\d+").unwrap());
|
||||
if let Some(new_name) = DISK_REGEX.find(checked_name) {
|
||||
io.get(new_name.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
io.get(checked_name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
io.get(trim)
|
||||
};
|
||||
|
||||
if let Some(io_device) = io_device {
|
||||
|
|
|
@ -171,18 +171,27 @@ impl DataCollector {
|
|||
}
|
||||
}
|
||||
|
||||
// Sysinfo-related list refreshing.
|
||||
if self.widgets_to_harvest.use_net {
|
||||
self.sys.refresh_networks_list();
|
||||
}
|
||||
|
||||
if self.widgets_to_harvest.use_temp {
|
||||
self.sys.refresh_components_list();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
self.sys.refresh_users_list();
|
||||
{
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
self.sys.refresh_users_list();
|
||||
}
|
||||
|
||||
if self.widgets_to_harvest.use_disk {
|
||||
self.sys.refresh_disks_list();
|
||||
}
|
||||
}
|
||||
|
||||
futures::executor::block_on(self.update_data());
|
||||
self.update_data();
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
self.data.cleanup();
|
||||
|
@ -208,7 +217,13 @@ impl DataCollector {
|
|||
self.show_average_cpu = show_average_cpu;
|
||||
}
|
||||
|
||||
/// Refresh sysinfo data.
|
||||
/// Refresh sysinfo data. We use sysinfo for the following data:
|
||||
/// - CPU usage
|
||||
/// - Memory usage
|
||||
/// - Network usage
|
||||
/// - Processes (non-Linux)
|
||||
/// - Disk (Windows)
|
||||
/// - Temperatures (non-Linux)
|
||||
fn refresh_sysinfo_data(&mut self) {
|
||||
// Refresh once every minute. If it's too frequent it can cause segfaults.
|
||||
const LIST_REFRESH_TIME: Duration = Duration::from_secs(60);
|
||||
|
@ -229,9 +244,14 @@ impl DataCollector {
|
|||
self.sys.refresh_networks();
|
||||
}
|
||||
|
||||
// sysinfo is used on non-Linux systems for the following:
|
||||
// - Processes (users list as well for Windows)
|
||||
// - Disks (Windows only)
|
||||
// - Temperatures and temperature components list.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
if self.widgets_to_harvest.use_proc {
|
||||
// For Windows, sysinfo also handles the users list.
|
||||
#[cfg(target_os = "windows")]
|
||||
if refresh_start.duration_since(self.last_collection_time) > LIST_REFRESH_TIME {
|
||||
self.sys.refresh_users_list();
|
||||
|
@ -247,9 +267,17 @@ impl DataCollector {
|
|||
self.sys.refresh_components();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if self.widgets_to_harvest.use_disk {
|
||||
if refresh_start.duration_since(self.last_collection_time) > LIST_REFRESH_TIME {
|
||||
self.sys.refresh_disks_list();
|
||||
}
|
||||
self.sys.refresh_disks();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_data(&mut self) {
|
||||
pub fn update_data(&mut self) {
|
||||
self.refresh_sysinfo_data();
|
||||
|
||||
let current_instant = Instant::now();
|
||||
|
@ -262,27 +290,11 @@ impl DataCollector {
|
|||
);
|
||||
self.update_temps();
|
||||
self.update_network_usage(current_instant);
|
||||
self.update_disks();
|
||||
|
||||
#[cfg(feature = "battery")]
|
||||
self.update_batteries();
|
||||
|
||||
let (disk_res, io_res) = futures::join!(
|
||||
disks::get_disk_usage(
|
||||
self.widgets_to_harvest.use_disk,
|
||||
&self.filters.disk_filter,
|
||||
&self.filters.mount_filter,
|
||||
),
|
||||
disks::get_io_usage(self.widgets_to_harvest.use_disk)
|
||||
);
|
||||
|
||||
if let Ok(disks) = disk_res {
|
||||
self.data.disks = disks;
|
||||
}
|
||||
|
||||
if let Ok(io) = io_res {
|
||||
self.data.io = io;
|
||||
}
|
||||
|
||||
// Update times for future reference.
|
||||
self.last_collection_time = current_instant;
|
||||
self.data.last_collection_time = current_instant;
|
||||
|
@ -440,6 +452,29 @@ impl DataCollector {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_disks(&mut self) {
|
||||
if self.widgets_to_harvest.use_disk {
|
||||
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let disk_filter = &self.filters.disk_filter;
|
||||
let mount_filter = &self.filters.mount_filter;
|
||||
self.data.disks = disks::get_disk_usage(disk_filter, mount_filter).ok();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
self.data.disks = Some(disks::get_disk_usage(
|
||||
&self.sys,
|
||||
&self.filters.disk_filter,
|
||||
&self.filters.mount_filter,
|
||||
));
|
||||
}
|
||||
|
||||
self.data.io = disks::get_io_usage().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
|
|
|
@ -1,22 +1,37 @@
|
|||
//! Data collection for disks (IO, usage, space, etc.).
|
||||
//!
|
||||
//! For Linux, macOS, and Windows, this is handled by heim. For FreeBSD there is a custom
|
||||
//! implementation.
|
||||
//! Data collection about disks (e.g. I/O, usage, space).
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))] {
|
||||
pub mod heim;
|
||||
pub use self::heim::*;
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
pub mod freebsd;
|
||||
pub use self::freebsd::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::app::filter::Filter;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_os = "freebsd")] {
|
||||
mod freebsd;
|
||||
pub(crate) use self::freebsd::*;
|
||||
} else if #[cfg(target_os = "windows")] {
|
||||
mod windows;
|
||||
pub(crate) use self::windows::*;
|
||||
} else if #[cfg(target_os = "linux")] {
|
||||
mod unix;
|
||||
pub(crate) use self::unix::*;
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
mod unix;
|
||||
pub(crate) use self::unix::*;
|
||||
}
|
||||
// TODO: Add dummy impls here for other OSes?
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DiskHarvest {
|
||||
pub name: String,
|
||||
pub mount_point: String,
|
||||
|
||||
/// Windows also contains an additional volume name field.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub volume_name: Option<String>,
|
||||
|
||||
// TODO: Maybe unify all these?
|
||||
pub free_space: Option<u64>,
|
||||
pub used_space: Option<u64>,
|
||||
pub total_space: Option<u64>,
|
||||
|
@ -28,4 +43,137 @@ pub struct IoData {
|
|||
pub write_bytes: u64,
|
||||
}
|
||||
|
||||
pub type IoHarvest = std::collections::HashMap<String, Option<IoData>>;
|
||||
pub type IoHarvest = HashMap<String, Option<IoData>>;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(target_os = "freebsd"))] {
|
||||
mod io_counters;
|
||||
pub use io_counters::IoCounters;
|
||||
|
||||
/// Returns the I/O usage of certain mount points.
|
||||
pub fn get_io_usage() -> anyhow::Result<IoHarvest> {
|
||||
let mut io_hash: HashMap<String, Option<IoData>> = HashMap::new();
|
||||
|
||||
for io in io_stats()?.into_iter().flatten() {
|
||||
let mount_point = io.device_name().to_string_lossy();
|
||||
|
||||
io_hash.insert(
|
||||
mount_point.to_string(),
|
||||
Some(IoData {
|
||||
read_bytes: io.read_bytes(),
|
||||
write_bytes: io.write_bytes(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(io_hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to keep the current disk entry given the filters, disk name, and disk mount.
|
||||
/// Precedence ordering in the case where name and mount filters disagree, "allow"
|
||||
/// takes precedence over "deny".
|
||||
///
|
||||
/// For implementation, we do this as follows:
|
||||
///
|
||||
/// 1. Is the entry allowed through any filter? That is, does it match an entry in a
|
||||
/// filter where `is_list_ignored` is `false`? If so, we always keep this entry.
|
||||
/// 2. Is the entry denied through any filter? That is, does it match an entry in a
|
||||
/// filter where `is_list_ignored` is `true`? If so, we always deny this entry.
|
||||
/// 3. Anything else is allowed.
|
||||
pub(self) fn keep_disk_entry(
|
||||
disk_name: &str, mount_point: &str, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> bool {
|
||||
match (disk_filter, mount_filter) {
|
||||
(Some(d), Some(m)) => match (d.is_list_ignored, m.is_list_ignored) {
|
||||
(true, true) => !(d.has_match(disk_name) || m.has_match(mount_point)),
|
||||
(true, false) => {
|
||||
if m.has_match(mount_point) {
|
||||
true
|
||||
} else {
|
||||
d.keep_entry(disk_name)
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
if d.has_match(disk_name) {
|
||||
true
|
||||
} else {
|
||||
m.keep_entry(mount_point)
|
||||
}
|
||||
}
|
||||
(false, false) => d.has_match(disk_name) || m.has_match(mount_point),
|
||||
},
|
||||
(Some(d), None) => d.keep_entry(disk_name),
|
||||
(None, Some(m)) => m.keep_entry(mount_point),
|
||||
(None, None) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use regex::Regex;
|
||||
|
||||
use crate::app::filter::Filter;
|
||||
|
||||
use super::keep_disk_entry;
|
||||
|
||||
fn run_filter(disk_filter: &Option<Filter>, mount_filter: &Option<Filter>) -> Vec<usize> {
|
||||
let targets = [
|
||||
("/dev/nvme0n1p1", "/boot"),
|
||||
("/dev/nvme0n1p2", "/"),
|
||||
("/dev/nvme0n1p3", "/home"),
|
||||
("/dev/sda1", "/mnt/test"),
|
||||
("/dev/sda2", "/mnt/boot"),
|
||||
];
|
||||
|
||||
targets
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(itx, (name, mount))| {
|
||||
if keep_disk_entry(name, mount, disk_filter, mount_filter) {
|
||||
Some(itx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keeping_disk_entry() {
|
||||
let disk_ignore = Some(Filter {
|
||||
is_list_ignored: true,
|
||||
list: vec![Regex::new("nvme").unwrap()],
|
||||
});
|
||||
|
||||
let disk_keep = Some(Filter {
|
||||
is_list_ignored: false,
|
||||
list: vec![Regex::new("nvme").unwrap()],
|
||||
});
|
||||
|
||||
let mount_ignore = Some(Filter {
|
||||
is_list_ignored: true,
|
||||
list: vec![Regex::new("boot").unwrap()],
|
||||
});
|
||||
|
||||
let mount_keep = Some(Filter {
|
||||
is_list_ignored: false,
|
||||
list: vec![Regex::new("boot").unwrap()],
|
||||
});
|
||||
|
||||
assert_eq!(run_filter(&None, &None), vec![0, 1, 2, 3, 4]);
|
||||
|
||||
assert_eq!(run_filter(&disk_ignore, &None), vec![3, 4]);
|
||||
assert_eq!(run_filter(&disk_keep, &None), vec![0, 1, 2]);
|
||||
|
||||
assert_eq!(run_filter(&None, &mount_ignore), vec![1, 2, 3]);
|
||||
assert_eq!(run_filter(&None, &mount_keep), vec![0, 4]);
|
||||
|
||||
assert_eq!(run_filter(&disk_ignore, &mount_ignore), vec![3]);
|
||||
assert_eq!(run_filter(&disk_keep, &mount_ignore), vec![0, 1, 2, 3]);
|
||||
|
||||
assert_eq!(run_filter(&disk_ignore, &mount_keep), vec![0, 3, 4]);
|
||||
assert_eq!(run_filter(&disk_keep, &mount_keep), vec![0, 1, 2, 4]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@ use std::io;
|
|||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{DiskHarvest, IoHarvest};
|
||||
use crate::app::Filter;
|
||||
use crate::data_harvester::deserialize_xo;
|
||||
use super::{keep_disk_entry, DiskHarvest, IoHarvest};
|
||||
use crate::{app::Filter, data_harvester::deserialize_xo, utils::error};
|
||||
|
||||
#[derive(Deserialize, Debug, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
@ -24,11 +23,7 @@ struct FileSystem {
|
|||
mounted_on: String,
|
||||
}
|
||||
|
||||
pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IoHarvest>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
pub fn get_io_usage() -> error::Result<IoHarvest> {
|
||||
let io_harvest = get_disk_info().map(|storage_system_information| {
|
||||
storage_system_information
|
||||
.filesystem
|
||||
|
@ -36,36 +31,19 @@ pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Opt
|
|||
.map(|disk| (disk.name, None))
|
||||
.collect()
|
||||
})?;
|
||||
Ok(Some(io_harvest))
|
||||
|
||||
Ok(io_harvest)
|
||||
}
|
||||
|
||||
pub async fn get_disk_usage(
|
||||
actually_get: bool, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
pub fn get_disk_usage(
|
||||
disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> error::Result<Vec<DiskHarvest>> {
|
||||
let vec_disks: Vec<DiskHarvest> = get_disk_info().map(|storage_system_information| {
|
||||
storage_system_information
|
||||
.filesystem
|
||||
.into_iter()
|
||||
.filter_map(|disk| {
|
||||
// Precedence ordering in the case where name and mount filters disagree, "allow"
|
||||
// takes precedence over "deny".
|
||||
//
|
||||
// For implementation, we do this as follows:
|
||||
//
|
||||
// 1. Is the entry allowed through any filter? That is, does it match an entry in a
|
||||
// filter where `is_list_ignored` is `false`? If so, we always keep this entry.
|
||||
// 2. Is the entry denied through any filter? That is, does it match an entry in a
|
||||
// filter where `is_list_ignored` is `true`? If so, we always deny this entry.
|
||||
// 3. Anything else is allowed.
|
||||
let filter_check_map =
|
||||
[(disk_filter, &disk.name), (mount_filter, &disk.mounted_on)];
|
||||
if matches_allow_list(filter_check_map.as_slice())
|
||||
|| !matches_ignore_list(filter_check_map.as_slice())
|
||||
{
|
||||
if keep_disk_entry(&disk.name, &disk.mounted_on, disk_filter, mount_filter) {
|
||||
Some(DiskHarvest {
|
||||
free_space: Some(disk.available_blocks * 1024),
|
||||
used_space: Some(disk.used_blocks * 1024),
|
||||
|
@ -80,21 +58,7 @@ pub async fn get_disk_usage(
|
|||
.collect()
|
||||
})?;
|
||||
|
||||
Ok(Some(vec_disks))
|
||||
}
|
||||
|
||||
fn matches_allow_list(filter_check_map: &[(&Option<Filter>, &String)]) -> bool {
|
||||
filter_check_map.iter().any(|(filter, text)| match filter {
|
||||
Some(f) if !f.is_list_ignored => f.list.iter().any(|r| r.is_match(text)),
|
||||
Some(_) | None => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn matches_ignore_list(filter_check_map: &[(&Option<Filter>, &String)]) -> bool {
|
||||
filter_check_map.iter().any(|(filter, text)| match filter {
|
||||
Some(f) if f.is_list_ignored => f.list.iter().any(|r| r.is_match(text)),
|
||||
Some(_) | None => false,
|
||||
})
|
||||
Ok(vec_disks)
|
||||
}
|
||||
|
||||
fn get_disk_info() -> io::Result<StorageSystemInformation> {
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
//! Disk stats through heim.
|
||||
//! Supports macOS, Linux, and Windows.
|
||||
|
||||
use crate::app::Filter;
|
||||
use crate::data_harvester::disks::{DiskHarvest, IoData, IoHarvest};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub mod linux;
|
||||
pub use linux::*;
|
||||
} else if #[cfg(any(target_os = "macos", target_os = "windows"))] {
|
||||
pub mod windows_macos;
|
||||
pub use windows_macos::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_io_usage(actually_get: bool) -> crate::utils::error::Result<Option<IoHarvest>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut io_hash: std::collections::HashMap<String, Option<IoData>> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
let counter_stream = heim::disk::io_counters().await?;
|
||||
futures::pin_mut!(counter_stream);
|
||||
|
||||
while let Some(io) = counter_stream.next().await {
|
||||
if let Ok(io) = io {
|
||||
let mount_point = io.device_name().to_str().unwrap_or("Name Unavailable");
|
||||
|
||||
io_hash.insert(
|
||||
mount_point.to_string(),
|
||||
Some(IoData {
|
||||
read_bytes: io.read_bytes().get::<heim::units::information::byte>(),
|
||||
write_bytes: io.write_bytes().get::<heim::units::information::byte>(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(io_hash))
|
||||
}
|
||||
|
||||
pub async fn get_disk_usage(
|
||||
actually_get: bool, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> crate::utils::error::Result<Option<Vec<DiskHarvest>>> {
|
||||
if !actually_get {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut vec_disks: Vec<DiskHarvest> = Vec::new();
|
||||
let partitions_stream = heim::disk::partitions_physical().await?;
|
||||
futures::pin_mut!(partitions_stream);
|
||||
|
||||
while let Some(part) = partitions_stream.next().await {
|
||||
if let Ok(partition) = part {
|
||||
let name = get_device_name(&partition);
|
||||
|
||||
let mount_point = (partition
|
||||
.mount_point()
|
||||
.to_str()
|
||||
.unwrap_or("Name Unavailable"))
|
||||
.to_string();
|
||||
|
||||
// Precedence ordering in the case where name and mount filters disagree, "allow" takes precedence over "deny".
|
||||
//
|
||||
// For implementation, we do this as follows:
|
||||
// 1. Is the entry allowed through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `false`? If so, we always keep this entry.
|
||||
// 2. Is the entry denied through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `true`? If so, we always deny this entry.
|
||||
// 3. Anything else is allowed.
|
||||
|
||||
let filter_check_map = [(disk_filter, &name), (mount_filter, &mount_point)];
|
||||
|
||||
// This represents case 1. That is, if there is a match in an allowing list - if there is, then
|
||||
// immediately allow it!
|
||||
let matches_allow_list = filter_check_map.iter().any(|(filter, text)| {
|
||||
if let Some(filter) = filter {
|
||||
if !filter.is_list_ignored {
|
||||
for r in &filter.list {
|
||||
if r.is_match(text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
let to_keep = if matches_allow_list {
|
||||
true
|
||||
} else {
|
||||
// If it doesn't match an allow list, then check if it is denied.
|
||||
// That is, if it matches in a reject filter, then reject. Otherwise, we always keep it.
|
||||
!filter_check_map.iter().any(|(filter, text)| {
|
||||
if let Some(filter) = filter {
|
||||
if filter.is_list_ignored {
|
||||
for r in &filter.list {
|
||||
if r.is_match(text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
};
|
||||
|
||||
if to_keep {
|
||||
// The usage line can fail in some cases (for example, if you use Void Linux + LUKS,
|
||||
// see https://github.com/ClementTsang/bottom/issues/419 for details). As such, check
|
||||
// it like this instead.
|
||||
if let Ok(usage) = heim::disk::usage(partition.mount_point()).await {
|
||||
vec_disks.push(DiskHarvest {
|
||||
free_space: Some(usage.free().get::<heim::units::information::byte>()),
|
||||
used_space: Some(usage.used().get::<heim::units::information::byte>()),
|
||||
total_space: Some(usage.total().get::<heim::units::information::byte>()),
|
||||
mount_point,
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
vec_disks.push(DiskHarvest {
|
||||
free_space: None,
|
||||
used_space: None,
|
||||
total_space: None,
|
||||
mount_point,
|
||||
name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(vec_disks))
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
//! Linux-specific things for Heim disk data collection.
|
||||
|
||||
use heim::disk::Partition;
|
||||
|
||||
pub fn get_device_name(partition: &Partition) -> String {
|
||||
if let Some(device) = partition.device() {
|
||||
// See if this disk is actually mounted elsewhere on Linux...
|
||||
// This is a workaround to properly map I/O in some cases (i.e. disk encryption), see
|
||||
// https://github.com/ClementTsang/bottom/issues/419
|
||||
if let Ok(path) = std::fs::read_link(device) {
|
||||
if path.is_absolute() {
|
||||
path.into_os_string()
|
||||
} else {
|
||||
let mut combined_path = std::path::PathBuf::new();
|
||||
combined_path.push(device);
|
||||
combined_path.pop(); // Pop the current file...
|
||||
combined_path.push(path);
|
||||
|
||||
if let Ok(canon_path) = std::fs::canonicalize(combined_path) {
|
||||
// Resolve the local path into an absolute one...
|
||||
canon_path.into_os_string()
|
||||
} else {
|
||||
device.to_os_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
device.to_os_string()
|
||||
}
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "Name Unavailable".to_string())
|
||||
} else {
|
||||
"Name Unavailable".to_string()
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
//! macOS and Windows-specific things for Heim disk data collection.
|
||||
|
||||
use heim::disk::Partition;
|
||||
|
||||
pub fn get_device_name(partition: &Partition) -> String {
|
||||
if let Some(device) = partition.device() {
|
||||
device
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "Name Unavailable".to_string())
|
||||
} else {
|
||||
"Name Unavailable".to_string()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
use std::ffi::OsStr;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IoCounters {
|
||||
name: String,
|
||||
read_bytes: u64,
|
||||
write_bytes: u64,
|
||||
}
|
||||
|
||||
impl IoCounters {
|
||||
pub fn new(name: String, read_bytes: u64, write_bytes: u64) -> Self {
|
||||
Self {
|
||||
name,
|
||||
read_bytes,
|
||||
write_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn device_name(&self) -> &OsStr {
|
||||
OsStr::new(&self.name)
|
||||
}
|
||||
|
||||
pub(crate) fn read_bytes(&self) -> u64 {
|
||||
self.read_bytes
|
||||
}
|
||||
|
||||
pub(crate) fn write_bytes(&self) -> u64 {
|
||||
self.write_bytes
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
//! Disk stats for Unix-like systems that aren't supported through other means. Officially,
|
||||
//! for now, this means Linux and macOS.
|
||||
|
||||
mod file_systems;
|
||||
|
||||
use file_systems::*;
|
||||
|
||||
mod usage;
|
||||
use usage::*;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
mod linux;
|
||||
pub use linux::*;
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
mod other;
|
||||
use other::*;
|
||||
|
||||
mod macos;
|
||||
pub use macos::*;
|
||||
} else {
|
||||
mod other;
|
||||
use other::*;
|
||||
}
|
||||
}
|
||||
|
||||
use super::{keep_disk_entry, DiskHarvest};
|
||||
use crate::app::Filter;
|
||||
|
||||
/// Returns the disk usage of the mounted (and for now, physical) disks.
|
||||
pub fn get_disk_usage(
|
||||
disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> anyhow::Result<Vec<DiskHarvest>> {
|
||||
let mut vec_disks: Vec<DiskHarvest> = Vec::new();
|
||||
|
||||
for partition in physical_partitions()? {
|
||||
let name = partition.get_device_name();
|
||||
let mount_point = partition.mount_point().to_string_lossy().to_string();
|
||||
|
||||
// Precedence ordering in the case where name and mount filters disagree, "allow" takes precedence over "deny".
|
||||
//
|
||||
// For implementation, we do this as follows:
|
||||
// 1. Is the entry allowed through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `false`? If so, we always keep this entry.
|
||||
// 2. Is the entry denied through any filter? That is, does it match an entry in a filter where `is_list_ignored` is `true`? If so, we always deny this entry.
|
||||
// 3. Anything else is allowed.
|
||||
|
||||
if keep_disk_entry(&name, &mount_point, disk_filter, mount_filter) {
|
||||
// The usage line can fail in some cases (for example, if you use Void Linux + LUKS,
|
||||
// see https://github.com/ClementTsang/bottom/issues/419 for details).
|
||||
if let Ok(usage) = partition.usage() {
|
||||
let total = usage.total();
|
||||
|
||||
vec_disks.push(DiskHarvest {
|
||||
free_space: Some(usage.free()),
|
||||
used_space: Some(total - usage.available()),
|
||||
total_space: Some(total),
|
||||
mount_point,
|
||||
name,
|
||||
});
|
||||
} else {
|
||||
vec_disks.push(DiskHarvest {
|
||||
free_space: None,
|
||||
used_space: None,
|
||||
total_space: None,
|
||||
mount_point,
|
||||
name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec_disks)
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
/// Known filesystems. From [heim](https://github.com/heim-rs/heim/blob/master/heim-disk/src/filesystem.rs).
|
||||
///
|
||||
/// All physical filesystems should have their own enum element and all virtual filesystems will go into
|
||||
/// the [`FileSystem::Other`] element.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
#[non_exhaustive]
|
||||
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.
|
||||
#[inline]
|
||||
pub fn is_physical(&self) -> bool {
|
||||
!self.is_virtual()
|
||||
}
|
||||
|
||||
/// Checks if filesystem is used for a virtual devices (such as `tmpfs` or `smb` mounts).
|
||||
#[inline]
|
||||
pub fn is_virtual(&self) -> bool {
|
||||
matches!(self, FileSystem::Other(..))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// 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 = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||
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())),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
//! Based on [heim's implementation](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/linux/counters.rs).
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader},
|
||||
num::ParseIntError,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::app::data_harvester::disks::IoCounters;
|
||||
|
||||
/// 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;
|
||||
|
||||
impl FromStr for IoCounters {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
/// Converts a `&str` to an [`IoStats`].
|
||||
///
|
||||
/// Follows the format used in Linux 2.6+. Note that this completely ignores the following stats:
|
||||
/// - Discard stats from 4.18+
|
||||
/// - Flush stats from 5.5+
|
||||
///
|
||||
/// https://www.kernel.org/doc/Documentation/iostats.txt
|
||||
/// https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats
|
||||
fn from_str(s: &str) -> anyhow::Result<IoCounters> {
|
||||
fn next_part<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, io::Error> {
|
||||
iter.next()
|
||||
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))
|
||||
}
|
||||
|
||||
fn next_part_to_u64<'a>(iter: &mut impl Iterator<Item = &'a str>) -> anyhow::Result<u64> {
|
||||
next_part(iter)?
|
||||
.parse()
|
||||
.map_err(|err: ParseIntError| err.into())
|
||||
}
|
||||
|
||||
// Skip the major and minor numbers.
|
||||
let mut parts = s.split_whitespace().skip(2);
|
||||
|
||||
let name = next_part(&mut parts)?.to_string();
|
||||
|
||||
// Skip read count, read merged count.
|
||||
let mut parts = parts.skip(2);
|
||||
let read_bytes = next_part_to_u64(&mut parts)? * DISK_SECTOR_SIZE;
|
||||
|
||||
// Skip read time seconds, write count, and write merged count.
|
||||
let mut parts = parts.skip(3);
|
||||
let write_bytes = next_part_to_u64(&mut parts)? * DISK_SECTOR_SIZE;
|
||||
|
||||
Ok(IoCounters::new(name, read_bytes, write_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of disk I/O stats. Pulls data from `/proc/diskstats`.
|
||||
pub fn io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
|
||||
const PROC_DISKSTATS: &str = "/proc/diskstats";
|
||||
|
||||
let mut results = vec![];
|
||||
let mut reader = BufReader::new(File::open(PROC_DISKSTATS)?);
|
||||
let mut line = String::new();
|
||||
|
||||
// This saves us from doing a string allocation on each iteration compared to `lines()`.
|
||||
while let Ok(bytes) = reader.read_line(&mut line) {
|
||||
if bytes > 0 {
|
||||
results.push(IoCounters::from_str(&line));
|
||||
line.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
mod partition;
|
||||
pub(crate) use partition::*;
|
||||
|
||||
mod counters;
|
||||
pub use counters::*;
|
|
@ -0,0 +1,197 @@
|
|||
//! Implementation based on [heim's](https://github.com/heim-rs/heim)
|
||||
//! Unix disk usage.
|
||||
|
||||
use std::{
|
||||
ffi::CString,
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader},
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
|
||||
use crate::app::data_harvester::disks::unix::{FileSystem, Usage};
|
||||
|
||||
/// Representation of partition details. Based on [`heim`](https://github.com/heim-rs/heim/tree/master).
|
||||
pub(crate) struct Partition {
|
||||
device: Option<String>,
|
||||
mount_point: PathBuf,
|
||||
fs_type: FileSystem,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
/// Returns the device name, if there is one.
|
||||
#[inline]
|
||||
pub fn device(&self) -> Option<&str> {
|
||||
self.device.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the mount point for this partition.
|
||||
#[inline]
|
||||
pub fn mount_point(&self) -> &Path {
|
||||
self.mount_point.as_path()
|
||||
}
|
||||
|
||||
/// Returns the [`FileSystem`] of this partition.
|
||||
#[inline]
|
||||
pub fn fs_type(&self) -> &FileSystem {
|
||||
&self.fs_type
|
||||
}
|
||||
|
||||
/// Returns the device name for the partition.
|
||||
pub fn get_device_name(&self) -> String {
|
||||
if let Some(device) = self.device() {
|
||||
// See if this disk is actually mounted elsewhere on Linux. This is a workaround properly map I/O
|
||||
// in some cases (i.e. disk encryption, https://github.com/ClementTsang/bottom/issues/419).
|
||||
if let Ok(path) = std::fs::read_link(device) {
|
||||
if path.is_absolute() {
|
||||
path.into_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "Name Unavailable".to_string())
|
||||
} else {
|
||||
let mut combined_path = std::path::PathBuf::new();
|
||||
combined_path.push(device);
|
||||
combined_path.pop(); // Pop the current file...
|
||||
combined_path.push(path);
|
||||
|
||||
if let Ok(canon_path) = std::fs::canonicalize(combined_path) {
|
||||
// Resolve the local path into an absolute one...
|
||||
canon_path
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "Name Unavailable".to_string())
|
||||
} else {
|
||||
device.to_owned()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
device.to_owned()
|
||||
}
|
||||
} else {
|
||||
"Name Unavailable".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the usage stats for this partition.
|
||||
pub fn usage(&self) -> anyhow::Result<Usage> {
|
||||
let path = self
|
||||
.mount_point
|
||||
.to_str()
|
||||
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))
|
||||
.and_then(|string| {
|
||||
CString::new(string).map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))
|
||||
})
|
||||
.map_err(|e| anyhow::anyhow!("invalid path: {e:?}"))?;
|
||||
|
||||
let mut vfs = mem::MaybeUninit::<libc::statvfs>::uninit();
|
||||
|
||||
// SAFETY: libc call, `path` is a valid C string and buf is a valid pointer to write to.
|
||||
let result = unsafe { libc::statvfs(path.as_ptr(), vfs.as_mut_ptr()) };
|
||||
|
||||
if result == 0 {
|
||||
// SAFETY: If result is 0, it succeeded, and vfs should be non-null.
|
||||
let vfs = unsafe { vfs.assume_init() };
|
||||
Ok(Usage::new(vfs))
|
||||
} else {
|
||||
Err(anyhow::anyhow!(
|
||||
"statvfs had an issue getting info from {path:?}"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Partition {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(line: &str) -> anyhow::Result<Partition> {
|
||||
// Example: `/dev/sda3 /home ext4 rw,relatime,data=ordered 0 0`
|
||||
let mut parts = line.splitn(5, ' ');
|
||||
|
||||
let device = match parts.next() {
|
||||
Some(device) if device == "none" => None,
|
||||
Some(device) => Some(device.to_string()),
|
||||
None => {
|
||||
bail!("missing device");
|
||||
}
|
||||
};
|
||||
let mount_point = match parts.next() {
|
||||
Some(point) => PathBuf::from(point),
|
||||
None => {
|
||||
bail!("missing mount point");
|
||||
}
|
||||
};
|
||||
let fs_type = match parts.next() {
|
||||
Some(fs) => FileSystem::from_str(fs)?,
|
||||
_ => {
|
||||
bail!("missing filesystem type");
|
||||
}
|
||||
};
|
||||
|
||||
// let options = match parts.next() {
|
||||
// Some(opts) => opts.to_string(),
|
||||
// None => {
|
||||
// bail!("missing options");
|
||||
// }
|
||||
// };
|
||||
|
||||
Ok(Partition {
|
||||
device,
|
||||
mount_point,
|
||||
fs_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Returns a [`Vec`] containing all partitions.
|
||||
pub(crate) fn partitions() -> anyhow::Result<Vec<Partition>> {
|
||||
const PROC_MOUNTS: &str = "/proc/mounts";
|
||||
|
||||
let mut results = vec![];
|
||||
let mut reader = BufReader::new(File::open(PROC_MOUNTS)?);
|
||||
let mut line = String::new();
|
||||
|
||||
// This saves us from doing a string allocation on each iteration compared to `lines()`.
|
||||
while let Ok(bytes) = reader.read_line(&mut line) {
|
||||
if bytes > 0 {
|
||||
if let Ok(partition) = Partition::from_str(&line) {
|
||||
results.push(partition);
|
||||
}
|
||||
|
||||
line.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Returns a [`Vec`] containing all *physical* partitions. This is defined by
|
||||
/// [`FileSystem::is_physical()`].
|
||||
pub(crate) fn physical_partitions() -> anyhow::Result<Vec<Partition>> {
|
||||
const PROC_MOUNTS: &str = "/proc/mounts";
|
||||
|
||||
let mut results = vec![];
|
||||
let mut reader = BufReader::new(File::open(PROC_MOUNTS)?);
|
||||
let mut line = String::new();
|
||||
|
||||
// This saves us from doing a string allocation on each iteration compared to `lines()`.
|
||||
while let Ok(bytes) = reader.read_line(&mut line) {
|
||||
if bytes > 0 {
|
||||
if let Ok(partition) = Partition::from_str(&line) {
|
||||
if partition.fs_type().is_physical() {
|
||||
results.push(partition);
|
||||
}
|
||||
}
|
||||
|
||||
line.clear();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
//! Based on [heim's implementation](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/macos/counters.rs).
|
||||
|
||||
use super::io_kit::{self, get_dict, get_disks, get_i64, get_string};
|
||||
use crate::app::data_harvester::disks::IoCounters;
|
||||
|
||||
fn get_device_io(device: io_kit::IoObject) -> anyhow::Result<IoCounters> {
|
||||
let parent = device.service_parent()?;
|
||||
|
||||
// XXX: Re: Conform check being disabled.
|
||||
//
|
||||
// Okay, so this is weird.
|
||||
//
|
||||
// The problem is that if I have this check - this is what sources like psutil use, for
|
||||
// example (see https://github.com/giampaolo/psutil/blob/7eadee31db2f038763a3a6f978db1ea76bbc4674/psutil/_psutil_osx.c#LL1422C20-L1422C20)
|
||||
// then this will only return stuff like disk0.
|
||||
//
|
||||
// The problem with this is that there is *never* a disk0 *disk* entry to correspond to this,
|
||||
// so there will be entries like disk1 or whatnot. Someone's done some digging on the gopsutil
|
||||
// repo (https://github.com/shirou/gopsutil/issues/855#issuecomment-610016435), and it seems
|
||||
// like this is a consequence of how Apple does logical volumes.
|
||||
//
|
||||
// So with all that said, what I've found is that I *can* still get a mapping - but I have
|
||||
// to disable the conform check, which... is weird. I'm not sure if this is valid at all. But
|
||||
// it *does* seem to match Activity Monitor with regards to disk activity, so... I guess we
|
||||
// can leave this for now...?
|
||||
|
||||
// if !parent.conforms_to_block_storage_driver() {
|
||||
// anyhow::bail!("{parent:?}, the parent of {device:?} does not conform to IOBlockStorageDriver")
|
||||
// }
|
||||
|
||||
let disk_props = device.properties()?;
|
||||
let parent_props = parent.properties()?;
|
||||
|
||||
let name = get_string(&disk_props, "BSD Name")?;
|
||||
let stats = get_dict(&parent_props, "Statistics")?;
|
||||
|
||||
let read_bytes = get_i64(&stats, "Bytes (Read)")? as u64;
|
||||
let write_bytes = get_i64(&stats, "Bytes (Write)")? as u64;
|
||||
|
||||
// let read_count = stats.get_i64("Operations (Read)")? as u64;
|
||||
// let write_count = stats.get_i64("Operations (Write)")? as u64;
|
||||
|
||||
Ok(IoCounters::new(name, read_bytes, write_bytes))
|
||||
}
|
||||
|
||||
/// Returns an iterator of disk I/O stats. Pulls data through IOKit.
|
||||
pub fn io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
|
||||
Ok(get_disks()?.map(get_device_io).collect())
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
mod bindings;
|
||||
|
||||
mod io_iterator;
|
||||
pub use io_iterator::*;
|
||||
|
||||
mod io_object;
|
||||
pub use io_object::*;
|
||||
|
||||
mod io_disks;
|
||||
pub use io_disks::get_disks;
|
|
@ -0,0 +1,59 @@
|
|||
//! C FFI bindings for [IOKit](https://developer.apple.com/documentation/iokit/).
|
||||
//!
|
||||
//! Based on [heim](https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/iokit/io_master_port.rs)
|
||||
//! and [sysinfo's implementation](https://github.com/GuillaumeGomez/sysinfo/blob/master/src/apple/macos/ffi.rs).
|
||||
//!
|
||||
//! Ideally, we can remove this if sysinfo ever gains disk I/O capabilities.
|
||||
|
||||
use core_foundation::base::{mach_port_t, CFAllocatorRef};
|
||||
use core_foundation::dictionary::CFMutableDictionaryRef;
|
||||
|
||||
use libc::c_char;
|
||||
use mach2::kern_return::kern_return_t;
|
||||
use mach2::port::MACH_PORT_NULL;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type io_object_t = mach_port_t;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type io_iterator_t = io_object_t;
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type io_registry_entry_t = io_object_t;
|
||||
|
||||
pub type IOOptionBits = u32;
|
||||
|
||||
/// See https://github.com/1kc/librazermacos/pull/27#issuecomment-1042368531.
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const kIOMasterPortDefault: mach_port_t = MACH_PORT_NULL;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const kIOServicePlane: &str = "IOService\0";
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const kIOMediaClass: &str = "IOMedia\0";
|
||||
|
||||
// See [here](https://developer.apple.com/documentation/iokit) for more details.
|
||||
extern "C" {
|
||||
|
||||
pub fn IOServiceGetMatchingServices(
|
||||
mainPort: mach_port_t, matching: CFMutableDictionaryRef, existing: *mut io_iterator_t,
|
||||
) -> kern_return_t;
|
||||
|
||||
pub fn IOServiceMatching(name: *const c_char) -> CFMutableDictionaryRef;
|
||||
|
||||
pub fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t;
|
||||
|
||||
pub fn IOObjectRelease(obj: io_object_t) -> kern_return_t;
|
||||
|
||||
pub fn IORegistryEntryGetParentEntry(
|
||||
entry: io_registry_entry_t, plane: *const libc::c_char, parent: *mut io_registry_entry_t,
|
||||
) -> kern_return_t;
|
||||
|
||||
// pub fn IOObjectConformsTo(object: io_object_t, className: *const libc::c_char) -> mach2::boolean::boolean_t;
|
||||
|
||||
pub fn IORegistryEntryCreateCFProperties(
|
||||
entry: io_registry_entry_t, properties: *mut CFMutableDictionaryRef,
|
||||
allocator: CFAllocatorRef, options: IOOptionBits,
|
||||
) -> kern_return_t;
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use anyhow::bail;
|
||||
|
||||
use mach2::kern_return;
|
||||
|
||||
use super::{bindings::*, IoIterator};
|
||||
|
||||
pub fn get_disks() -> anyhow::Result<IoIterator> {
|
||||
let mut media_iter: io_iterator_t = 0;
|
||||
|
||||
// SAFETY: This is a safe syscall via IOKit, all the arguments should be safe.
|
||||
let result = unsafe {
|
||||
IOServiceGetMatchingServices(
|
||||
kIOMasterPortDefault,
|
||||
IOServiceMatching(kIOMediaClass.as_ptr().cast()),
|
||||
&mut media_iter,
|
||||
)
|
||||
};
|
||||
|
||||
if result == kern_return::KERN_SUCCESS {
|
||||
Ok(media_iter.into())
|
||||
} else {
|
||||
bail!("IOServiceGetMatchingServices failed, error code {result}");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//! Based on [heim's](https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/iokit/io_iterator.rs).
|
||||
//! implementation.
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use mach2::kern_return;
|
||||
|
||||
use super::{bindings::*, io_object::IoObject};
|
||||
|
||||
/// Safe wrapper around the IOKit `io_iterator_t` type.
|
||||
#[derive(Debug)]
|
||||
pub struct IoIterator(io_iterator_t);
|
||||
|
||||
impl From<io_iterator_t> for IoIterator {
|
||||
fn from(iter: io_iterator_t) -> IoIterator {
|
||||
IoIterator(iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IoIterator {
|
||||
type Target = io_iterator_t;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for IoIterator {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for IoIterator {
|
||||
type Item = IoObject;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Basically, we just stop when we hit 0.
|
||||
|
||||
// SAFETY: IOKit call, the passed argument (an `io_iterator_t`) is what is expected.
|
||||
match unsafe { IOIteratorNext(self.0) } {
|
||||
0 => None,
|
||||
io_object => Some(IoObject::from(io_object)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IoIterator {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: IOKit call, the passed argument (an `io_iterator_t`) is what is expected.
|
||||
let result = unsafe { IOObjectRelease(self.0) };
|
||||
assert_eq!(result, kern_return::KERN_SUCCESS);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
//! Based on [heim's](https://github.com/heim-rs/heim/blob/master/heim-common/src/sys/macos/iokit/io_object.rs)
|
||||
//! implementation.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use core_foundation::base::{kCFAllocatorDefault, CFType, TCFType, ToVoid};
|
||||
use core_foundation::dictionary::{
|
||||
CFDictionary, CFDictionaryGetTypeID, CFDictionaryRef, CFMutableDictionary,
|
||||
CFMutableDictionaryRef,
|
||||
};
|
||||
use core_foundation::number::{CFNumber, CFNumberGetTypeID};
|
||||
use core_foundation::string::{CFString, CFStringGetTypeID};
|
||||
use mach2::kern_return;
|
||||
|
||||
use super::bindings::*;
|
||||
|
||||
/// Safe wrapper around the IOKit `io_object_t` type.
|
||||
#[derive(Debug)]
|
||||
pub struct IoObject(io_object_t);
|
||||
|
||||
impl IoObject {
|
||||
/// Returns a typed dictionary with this object's properties.
|
||||
pub fn properties(&self) -> anyhow::Result<CFDictionary<CFString, CFType>> {
|
||||
// SAFETY: The IOKit call should be fine, the arguments are safe. The `assume_init` should also be fine, as
|
||||
// we guard against it with a check against `result` to ensure it succeeded.
|
||||
unsafe {
|
||||
let mut props = mem::MaybeUninit::<CFMutableDictionaryRef>::uninit();
|
||||
|
||||
let result = IORegistryEntryCreateCFProperties(
|
||||
self.0,
|
||||
props.as_mut_ptr(),
|
||||
kCFAllocatorDefault,
|
||||
0,
|
||||
);
|
||||
|
||||
if result != kern_return::KERN_SUCCESS {
|
||||
bail!("IORegistryEntryCreateCFProperties failed, error code {result}.")
|
||||
} else {
|
||||
let props = props.assume_init();
|
||||
Ok(CFMutableDictionary::wrap_under_create_rule(props).to_immutable())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the [`kIOServicePlane`] parent [`io_object_t`] for this [`io_object_t`], if there
|
||||
/// is one.
|
||||
pub fn service_parent(&self) -> anyhow::Result<IoObject> {
|
||||
let mut parent: io_registry_entry_t = 0;
|
||||
|
||||
// SAFETY: IOKit call, the arguments should be safe.
|
||||
let result = unsafe {
|
||||
IORegistryEntryGetParentEntry(self.0, kIOServicePlane.as_ptr().cast(), &mut parent)
|
||||
};
|
||||
|
||||
if result != kern_return::KERN_SUCCESS {
|
||||
bail!("IORegistryEntryGetParentEntry failed, error code {result}.")
|
||||
} else {
|
||||
Ok(parent.into())
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn conforms_to_block_storage_driver(&self) -> bool {
|
||||
// // SAFETY: IOKit call, the arguments should be safe.
|
||||
// let result =
|
||||
// unsafe { IOObjectConformsTo(self.0, "IOBlockStorageDriver\0".as_ptr().cast()) };
|
||||
|
||||
// result != 0
|
||||
// }
|
||||
}
|
||||
|
||||
impl From<io_object_t> for IoObject {
|
||||
fn from(obj: io_object_t) -> IoObject {
|
||||
IoObject(obj)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IoObject {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: IOKit call, the argument here (an `io_object_t`) should be safe and expected.
|
||||
let result = unsafe { IOObjectRelease(self.0) };
|
||||
assert_eq!(result, kern_return::KERN_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dict(
|
||||
dict: &CFDictionary<CFString, CFType>, raw_key: &'static str,
|
||||
) -> anyhow::Result<CFDictionary<CFString, CFType>> {
|
||||
let key = CFString::from_static_string(raw_key);
|
||||
|
||||
dict.find(&key)
|
||||
.map(|value_ref| {
|
||||
// SAFETY: Only used for debug asserts, system API call that should be safe.
|
||||
unsafe {
|
||||
debug_assert!(value_ref.type_of() == CFDictionaryGetTypeID());
|
||||
}
|
||||
|
||||
// "Casting" `CFDictionary<*const void, *const void>` into a needed dict type
|
||||
let ptr = value_ref.to_void() as CFDictionaryRef;
|
||||
|
||||
// SAFETY: System API call, it should be safe?
|
||||
unsafe { CFDictionary::wrap_under_get_rule(ptr) }
|
||||
})
|
||||
.ok_or_else(|| anyhow!("missing key"))
|
||||
}
|
||||
|
||||
pub fn get_i64(
|
||||
dict: &CFDictionary<CFString, CFType>, raw_key: &'static str,
|
||||
) -> anyhow::Result<i64> {
|
||||
let key = CFString::from_static_string(raw_key);
|
||||
|
||||
dict.find(&key)
|
||||
.and_then(|value_ref| {
|
||||
// SAFETY: Only used for debug asserts, system API call that should be safe.
|
||||
unsafe {
|
||||
debug_assert!(value_ref.type_of() == CFNumberGetTypeID());
|
||||
}
|
||||
value_ref.downcast::<CFNumber>()
|
||||
})
|
||||
.and_then(|number| number.to_i64())
|
||||
.ok_or_else(|| anyhow!("missing key"))
|
||||
}
|
||||
|
||||
pub fn get_string(
|
||||
dict: &CFDictionary<CFString, CFType>, raw_key: &'static str,
|
||||
) -> anyhow::Result<String> {
|
||||
let key = CFString::from_static_string(raw_key);
|
||||
|
||||
dict.find(&key)
|
||||
.and_then(|value_ref| {
|
||||
// SAFETY: Only used for debug asserts, system API call that should be safe.
|
||||
unsafe {
|
||||
debug_assert!(value_ref.type_of() == CFStringGetTypeID());
|
||||
}
|
||||
|
||||
value_ref.downcast::<CFString>()
|
||||
})
|
||||
.map(|cf_string| cf_string.to_string())
|
||||
.ok_or_else(|| anyhow!("missing key"))
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
mod counters;
|
||||
pub use counters::*;
|
||||
|
||||
mod io_kit;
|
|
@ -0,0 +1,46 @@
|
|||
//! Based on [heim's](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/unix/bindings/mod.rs)
|
||||
//! implementation.
|
||||
|
||||
use std::io::Error;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// Returns all the mounts on the system at the moment.
|
||||
pub(crate) fn mounts() -> anyhow::Result<Vec<libc::statfs>> {
|
||||
// SAFETY: System API FFI call, arguments should be correct.
|
||||
let expected_len = unsafe { getfsstat64(std::ptr::null_mut(), 0, MNT_NOWAIT) };
|
||||
|
||||
let mut mounts: Vec<libc::statfs> = Vec::with_capacity(expected_len as usize);
|
||||
|
||||
// SAFETY: System API FFI call, arguments should be correct.
|
||||
let result = unsafe {
|
||||
getfsstat64(
|
||||
mounts.as_mut_ptr(),
|
||||
std::mem::size_of::<libc::statfs>() as libc::c_int * expected_len,
|
||||
MNT_NOWAIT,
|
||||
)
|
||||
};
|
||||
|
||||
if result == -1 {
|
||||
Err(anyhow::Error::from(Error::last_os_error()).context("getfsstat64"))
|
||||
} else {
|
||||
debug_assert_eq!(
|
||||
expected_len, result,
|
||||
"Expected {expected_len} statfs entries, but instead got {result} entries",
|
||||
);
|
||||
|
||||
// SAFETY: We have a debug assert check, and if `result` is not correct (-1), we check against it.
|
||||
// Otherwise, getfsstat64 should return the number of statfs structures if it succeeded.
|
||||
//
|
||||
// Source: https://man.freebsd.org/cgi/man.cgi?query=getfsstat&sektion=2&format=html
|
||||
unsafe {
|
||||
mounts.set_len(result as usize);
|
||||
}
|
||||
Ok(mounts)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
mod bindings;
|
||||
mod partition;
|
||||
|
||||
pub(crate) use partition::*;
|
|
@ -0,0 +1,99 @@
|
|||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
os::unix::prelude::OsStrExt,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
|
||||
use super::bindings;
|
||||
use crate::app::data_harvester::disks::unix::{FileSystem, Usage};
|
||||
|
||||
pub(crate) struct Partition {
|
||||
device: String,
|
||||
mount_point: PathBuf,
|
||||
fs_type: FileSystem,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
/// Returns the mount point for this partition.
|
||||
#[inline]
|
||||
pub fn mount_point(&self) -> &Path {
|
||||
self.mount_point.as_path()
|
||||
}
|
||||
|
||||
/// Returns the [`FileSystem`] of this partition.
|
||||
#[inline]
|
||||
pub fn fs_type(&self) -> &FileSystem {
|
||||
&self.fs_type
|
||||
}
|
||||
|
||||
/// Returns the usage stats for this partition.
|
||||
pub fn usage(&self) -> anyhow::Result<Usage> {
|
||||
let path = CString::new(self.mount_point().as_os_str().as_bytes())?;
|
||||
let mut vfs = std::mem::MaybeUninit::<libc::statvfs>::uninit();
|
||||
|
||||
// SAFETY: System API call. Arguments should be correct.
|
||||
let result = unsafe { libc::statvfs(path.as_ptr(), vfs.as_mut_ptr()) };
|
||||
|
||||
if result == 0 {
|
||||
// SAFETY: We check that it succeeded (result is 0), which means vfs should be populated.
|
||||
Ok(Usage::new(unsafe { vfs.assume_init() }))
|
||||
} else {
|
||||
bail!("statvfs failed to get the disk usage for disk {path:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the device name.
|
||||
#[inline]
|
||||
pub fn get_device_name(&self) -> String {
|
||||
self.device.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn partitions_iter() -> anyhow::Result<impl Iterator<Item = Partition>> {
|
||||
let mounts = bindings::mounts()?;
|
||||
|
||||
unsafe fn ptr_to_cow<'a>(ptr: *const i8) -> std::borrow::Cow<'a, str> {
|
||||
CStr::from_ptr(ptr).to_string_lossy()
|
||||
}
|
||||
|
||||
Ok(mounts.into_iter().map(|stat| {
|
||||
// SAFETY: Should be a non-null pointer.
|
||||
let device = unsafe { ptr_to_cow(stat.f_mntfromname.as_ptr()).to_string() };
|
||||
|
||||
let fs_type = {
|
||||
// SAFETY: Should be a non-null pointer.
|
||||
let fs_type_str = unsafe { ptr_to_cow(stat.f_fstypename.as_ptr()) };
|
||||
FileSystem::from_str(&fs_type_str).unwrap_or(FileSystem::Other(fs_type_str.to_string()))
|
||||
};
|
||||
|
||||
let mount_point = {
|
||||
// SAFETY: Should be a non-null pointer.
|
||||
let path_str = unsafe { ptr_to_cow(stat.f_mntonname.as_ptr()).to_string() };
|
||||
PathBuf::from(path_str)
|
||||
};
|
||||
|
||||
Partition {
|
||||
device,
|
||||
mount_point,
|
||||
fs_type,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Returns a [`Vec`] containing all partitions.
|
||||
pub(crate) fn partitions() -> anyhow::Result<Vec<Partition>> {
|
||||
partitions_iter().map(|iter| iter.collect())
|
||||
}
|
||||
|
||||
/// Returns a [`Vec`] containing all *physical* partitions. This is defined by
|
||||
/// [`FileSystem::is_physical()`].
|
||||
pub(crate) fn physical_partitions() -> anyhow::Result<Vec<Partition>> {
|
||||
partitions_iter().map(|iter| {
|
||||
iter.filter(|partition| partition.fs_type().is_physical())
|
||||
.collect()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
pub struct Usage(libc::statvfs);
|
||||
|
||||
// Note that x86 returns `u32` values while x86-64 returns `u64`s, so we convert everything
|
||||
// to `u64` for consistency.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
impl Usage {
|
||||
pub(crate) fn new(vfs: libc::statvfs) -> Self {
|
||||
Self(vfs)
|
||||
}
|
||||
|
||||
/// Returns the total number of bytes available.
|
||||
pub fn total(&self) -> u64 {
|
||||
u64::from(self.0.f_blocks) * u64::from(self.0.f_frsize)
|
||||
}
|
||||
|
||||
/// Returns the available number of bytes used. Note this is not necessarily the same as [`free`].
|
||||
pub fn available(&self) -> u64 {
|
||||
u64::from(self.0.f_bfree) * u64::from(self.0.f_frsize)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Returns the total number of bytes used. Equal to `total - available` on Unix.
|
||||
pub fn used(&self) -> u64 {
|
||||
let avail_to_root = u64::from(self.0.f_bfree) * u64::from(self.0.f_frsize);
|
||||
self.total() - avail_to_root
|
||||
}
|
||||
|
||||
/// Returns the total number of bytes free. Note this is not necessarily the same as [`available`].
|
||||
pub fn free(&self) -> u64 {
|
||||
u64::from(self.0.f_bavail) * u64::from(self.0.f_frsize)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
//! Disk stats via sysinfo.
|
||||
|
||||
use itertools::Itertools;
|
||||
use sysinfo::{DiskExt, System, SystemExt};
|
||||
|
||||
use super::{keep_disk_entry, DiskHarvest};
|
||||
use crate::app::data_harvester::disks::IoCounters;
|
||||
use crate::app::filter::Filter;
|
||||
|
||||
mod bindings;
|
||||
use bindings::*;
|
||||
|
||||
/// Returns I/O stats.
|
||||
pub(crate) fn io_stats() -> anyhow::Result<Vec<anyhow::Result<IoCounters>>> {
|
||||
let volume_io = all_volume_io()?;
|
||||
|
||||
Ok(volume_io
|
||||
.into_iter()
|
||||
.map_ok(|(performance, volume_name)| {
|
||||
let name = volume_name;
|
||||
let read_bytes = performance.BytesRead as u64;
|
||||
let write_bytes = performance.BytesWritten as u64;
|
||||
|
||||
IoCounters::new(name, read_bytes, write_bytes)
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub(crate) fn get_disk_usage(
|
||||
sys: &System, disk_filter: &Option<Filter>, mount_filter: &Option<Filter>,
|
||||
) -> Vec<DiskHarvest> {
|
||||
let disks = sys.disks();
|
||||
disks
|
||||
.iter()
|
||||
.filter_map(|disk| {
|
||||
let name = {
|
||||
let name = disk.name();
|
||||
|
||||
if name.is_empty() {
|
||||
"No Name".to_string()
|
||||
} else {
|
||||
name.to_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "Name Unavailable".to_string())
|
||||
}
|
||||
};
|
||||
|
||||
let mount_point = disk
|
||||
.mount_point()
|
||||
.as_os_str()
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "Mount Unavailable".to_string());
|
||||
|
||||
let volume_name = volume_name_from_mount(&mount_point).ok();
|
||||
|
||||
if keep_disk_entry(&name, &mount_point, disk_filter, mount_filter) {
|
||||
let free_space = disk.available_space();
|
||||
let total_space = disk.total_space();
|
||||
let used_space = total_space - free_space;
|
||||
|
||||
Some(DiskHarvest {
|
||||
name,
|
||||
mount_point,
|
||||
volume_name,
|
||||
free_space: Some(free_space),
|
||||
used_space: Some(used_space),
|
||||
total_space: Some(total_space),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
//! Windows bindings to get disk I/O counters.
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
io, mem,
|
||||
os::windows::prelude::{OsStrExt, OsStringExt},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use windows::Win32::{
|
||||
Foundation::{self, CloseHandle},
|
||||
Storage::FileSystem::{
|
||||
CreateFileW, FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, FindVolumeHandle,
|
||||
GetVolumeNameForVolumeMountPointW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ,
|
||||
FILE_SHARE_WRITE, OPEN_EXISTING,
|
||||
},
|
||||
System::{
|
||||
Ioctl::{DISK_PERFORMANCE, IOCTL_DISK_PERFORMANCE},
|
||||
IO::DeviceIoControl,
|
||||
},
|
||||
};
|
||||
|
||||
/// Returns the I/O for a given volume.
|
||||
///
|
||||
/// Based on [psutil's implementation](https://github.com/giampaolo/psutil/blob/52fe5517f716dedf9c9918e56325e49a49146130/psutil/arch/windows/disk.c#L78-L83)
|
||||
/// and [heim's implementation](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/windows/bindings/perf.rs).
|
||||
fn volume_io(volume: &Path) -> anyhow::Result<DISK_PERFORMANCE> {
|
||||
if volume.is_file() {
|
||||
// We assume the volume is a directory, so bail ASAP if it isn't.
|
||||
bail!("Expects a directory to be passed in.");
|
||||
}
|
||||
|
||||
let volume = {
|
||||
let mut wide_path = volume.as_os_str().encode_wide().collect::<Vec<_>>();
|
||||
|
||||
// We replace the trailing backslash and replace it with a \0.
|
||||
wide_path.pop();
|
||||
wide_path.push(0x0000);
|
||||
|
||||
wide_path
|
||||
};
|
||||
|
||||
// SAFETY: API call, arguments should be correct. We must also check after the call to ensure it is valid.
|
||||
let h_device = unsafe {
|
||||
CreateFileW(
|
||||
windows::core::PCWSTR(volume.as_ptr()),
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
None,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAGS_AND_ATTRIBUTES(0),
|
||||
Foundation::HANDLE::default(),
|
||||
)?
|
||||
};
|
||||
|
||||
if h_device.is_invalid() {
|
||||
bail!("Invalid handle value: {:?}", io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut disk_performance = DISK_PERFORMANCE::default();
|
||||
let mut bytes_returned = 0;
|
||||
|
||||
// SAFETY: This should be safe, we'll manually check the results and the arguments should be valid.
|
||||
let ret = unsafe {
|
||||
DeviceIoControl(
|
||||
h_device,
|
||||
IOCTL_DISK_PERFORMANCE,
|
||||
None,
|
||||
0,
|
||||
Some(&mut disk_performance as *mut _ as _),
|
||||
mem::size_of::<DISK_PERFORMANCE>() as u32,
|
||||
Some(&mut bytes_returned),
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
// SAFETY: This should be safe, we will check the result as well.
|
||||
let handle_result = unsafe { CloseHandle(h_device) };
|
||||
if !handle_result.as_bool() {
|
||||
const ERROR_INVALID_FUNCTION: i32 = Foundation::ERROR_INVALID_FUNCTION.0 as i32;
|
||||
const ERROR_NOT_SUPPORTED: i32 = Foundation::ERROR_NOT_SUPPORTED.0 as i32;
|
||||
|
||||
match io::Error::last_os_error().raw_os_error() {
|
||||
Some(ERROR_INVALID_FUNCTION) => {
|
||||
bail!("Handle error: invalid function");
|
||||
}
|
||||
Some(ERROR_NOT_SUPPORTED) => {
|
||||
bail!("Handle error: not supported");
|
||||
}
|
||||
_ => {
|
||||
bail!(
|
||||
"Unknown handle device result error: {:?}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !ret.as_bool() {
|
||||
bail!("Device I/O error: {:?}", io::Error::last_os_error());
|
||||
} else {
|
||||
Ok(disk_performance)
|
||||
}
|
||||
}
|
||||
|
||||
fn current_volume(buffer: &[u16]) -> PathBuf {
|
||||
let first_null = buffer.iter().position(|byte| *byte == 0x00).unwrap_or(0);
|
||||
let path_string = OsString::from_wide(&buffer[..first_null]);
|
||||
|
||||
PathBuf::from(path_string)
|
||||
}
|
||||
|
||||
fn close_find_handle(handle: FindVolumeHandle) -> anyhow::Result<()> {
|
||||
// Clean up the handle.
|
||||
// SAFETY: This should be safe, we will check the result as well.
|
||||
let handle_result = unsafe { FindVolumeClose(handle) };
|
||||
if !handle_result.as_bool() {
|
||||
bail!("Could not close volume handle.");
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the I/O for all volumes.
|
||||
///
|
||||
/// Based on [psutil's implementation](https://github.com/giampaolo/psutil/blob/52fe5517f716dedf9c9918e56325e49a49146130/psutil/arch/windows/disk.c#L78-L83)
|
||||
/// and [heim's implementation](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/windows/bindings/perf.rs).
|
||||
pub(crate) fn all_volume_io() -> anyhow::Result<Vec<anyhow::Result<(DISK_PERFORMANCE, String)>>> {
|
||||
const ERROR_NO_MORE_FILES: i32 = Foundation::ERROR_NO_MORE_FILES.0 as i32;
|
||||
let mut ret = vec![];
|
||||
let mut buffer = [0_u16; Foundation::MAX_PATH as usize];
|
||||
|
||||
// Get the first volume and add the stats needed.
|
||||
// SAFETY: We must verify the handle is correct. If no volume is found, it will be set to `INVALID_HANDLE_VALUE`.
|
||||
let handle = unsafe { FindFirstVolumeW(&mut buffer) }?;
|
||||
if handle.is_invalid() {
|
||||
bail!("Invalid handle value: {:?}", io::Error::last_os_error());
|
||||
}
|
||||
|
||||
{
|
||||
let volume = current_volume(&buffer);
|
||||
ret.push(volume_io(&volume).map(|res| (res, volume.to_string_lossy().to_string())));
|
||||
}
|
||||
|
||||
// Now iterate until there are no more volumes.
|
||||
while unsafe { FindNextVolumeW(handle, &mut buffer) }.as_bool() {
|
||||
let volume = current_volume(&buffer);
|
||||
ret.push(volume_io(&volume).map(|res| (res, volume.to_string_lossy().to_string())));
|
||||
}
|
||||
|
||||
let err = io::Error::last_os_error();
|
||||
match err.raw_os_error() {
|
||||
// Iteration completed successfully, continue on.
|
||||
Some(ERROR_NO_MORE_FILES) => {}
|
||||
// Some error occured.
|
||||
_ => {
|
||||
close_find_handle(handle)?;
|
||||
bail!("Error while iterating over volumes: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
close_find_handle(handle)?;
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Returns the volume name from a mount name if possible.
|
||||
pub(crate) fn volume_name_from_mount(mount: &str) -> anyhow::Result<String> {
|
||||
// According to winapi docs 50 is a reasonable length to accomodate the volume path
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumenameforvolumemountpointw
|
||||
const VOLUME_MAX_LEN: usize = 50;
|
||||
|
||||
let mount = {
|
||||
let mount_path = Path::new(mount);
|
||||
let mut wide_path = mount_path.as_os_str().encode_wide().collect::<Vec<_>>();
|
||||
|
||||
// Always push on a \0 character, without this it will occasionally break.
|
||||
wide_path.push(0x0000);
|
||||
|
||||
wide_path
|
||||
};
|
||||
let mut buffer = [0_u16; VOLUME_MAX_LEN];
|
||||
|
||||
// SAFETY: API call, we must check the result for validating safety.
|
||||
let result = unsafe {
|
||||
GetVolumeNameForVolumeMountPointW(windows::core::PCWSTR(mount.as_ptr()), &mut buffer)
|
||||
};
|
||||
|
||||
if !result.as_bool() {
|
||||
bail!(
|
||||
"Could not get volume name for mount point: {:?}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
} else {
|
||||
Ok(current_volume(&buffer).to_string_lossy().to_string())
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use super::MemHarvest;
|
|||
/// Return ARC usage.
|
||||
#[cfg(feature = "zfs")]
|
||||
pub(crate) fn get_arc_usage() -> Option<MemHarvest> {
|
||||
let (mem_total_in_kib, mem_used_in_kib) = {
|
||||
let (mem_total, mem_used) = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
// TODO: [OPT] is this efficient?
|
||||
|
@ -64,12 +64,12 @@ pub(crate) fn get_arc_usage() -> Option<MemHarvest> {
|
|||
};
|
||||
|
||||
Some(MemHarvest {
|
||||
total_bytes: mem_total_in_kib,
|
||||
used_bytes: mem_used_in_kib,
|
||||
use_percent: if mem_total_in_kib == 0 {
|
||||
total_bytes: mem_total,
|
||||
used_bytes: mem_used,
|
||||
use_percent: if mem_total == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
|
||||
Some(mem_used as f64 / mem_total as f64 * 100.0)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ fn get_from_hwmon(
|
|||
let mut temperature_vec: Vec<TempHarvest> = vec![];
|
||||
let path = Path::new("/sys/class/hwmon");
|
||||
|
||||
// NOTE: Technically none of this is async, *but* sysfs is in memory,
|
||||
// so in theory none of this should block if we're slightly careful.
|
||||
// Note that none of this is async if we ever go back to it, but sysfs is in
|
||||
// memory, so in theory none of this should block if we're slightly careful.
|
||||
// Of note is that reading the temperature sensors of a device that has
|
||||
// `/sys/class/hwmon/hwmon*/device/power_state` == `D3cold` will
|
||||
// wake the device up, and will block until it initializes.
|
||||
|
|
|
@ -9,7 +9,7 @@ impl Filter {
|
|||
/// Whether the filter should keep the entry or reject it.
|
||||
#[inline]
|
||||
pub(crate) fn keep_entry(&self, value: &str) -> bool {
|
||||
if self.list.iter().any(|regex| regex.is_match(value)) {
|
||||
if self.has_match(value) {
|
||||
// If a match is found, then if we wanted to ignore if we match, return false. If we want
|
||||
// to keep if we match, return true. Thus, return the inverse of `is_list_ignored`.
|
||||
!self.is_list_ignored
|
||||
|
@ -17,6 +17,12 @@ impl Filter {
|
|||
self.is_list_ignored
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether there is a filter that matches the result.
|
||||
#[inline]
|
||||
pub(crate) fn has_match(&self, value: &str) -> bool {
|
||||
self.list.iter().any(|regex| regex.is_match(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -299,11 +299,8 @@ fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
// I think doing it in this order is safe...
|
||||
|
||||
*thread_termination_lock.lock().unwrap() = true;
|
||||
|
||||
thread_termination_cvar.notify_all();
|
||||
|
||||
cleanup_terminal(&mut terminal)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -97,6 +97,7 @@ impl ConvertedData {
|
|||
.iter()
|
||||
.zip(&data.io_labels)
|
||||
.for_each(|(disk, (io_read, io_write))| {
|
||||
// Because this sometimes does *not* equal to disk.total.
|
||||
let summed_total_bytes = match (disk.used_space, disk.free_space) {
|
||||
(Some(used), Some(free)) => Some(used + free),
|
||||
_ => None,
|
||||
|
|
|
@ -533,8 +533,7 @@ pub fn create_collection_thread(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: [OPT] this feels like it might not be totally optimal. Hm.
|
||||
futures::executor::block_on(data_state.update_data());
|
||||
data_state.update_data();
|
||||
|
||||
// Yet another check to bail if needed...
|
||||
if let Ok(is_terminated) = termination_ctrl_lock.try_lock() {
|
||||
|
|
|
@ -14,9 +14,6 @@ pub enum BottomError {
|
|||
/// An error when there is an IO exception.
|
||||
#[error("IO exception, {0}")]
|
||||
InvalidIo(String),
|
||||
/// An error when the heim library encounters a problem.
|
||||
#[error("Error caused by Heim, {0}")]
|
||||
InvalidHeim(String),
|
||||
/// An error when the Crossterm library encounters a problem.
|
||||
#[error("Error caused by Crossterm, {0}")]
|
||||
CrosstermError(String),
|
||||
|
@ -50,13 +47,6 @@ impl From<std::io::Error> for BottomError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
impl From<heim::Error> for BottomError {
|
||||
fn from(err: heim::Error) -> Self {
|
||||
BottomError::InvalidHeim(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseIntError> for BottomError {
|
||||
fn from(err: std::num::ParseIntError) -> Self {
|
||||
BottomError::ConfigError(err.to_string())
|
||||
|
|
|
@ -78,7 +78,11 @@ impl DiskWidgetData {
|
|||
if let (Some(used_bytes), Some(summed_total_bytes)) =
|
||||
(self.used_bytes, self.summed_total_bytes)
|
||||
{
|
||||
Some(used_bytes as f64 / summed_total_bytes as f64 * 100_f64)
|
||||
if summed_total_bytes > 0 {
|
||||
Some(used_bytes as f64 / summed_total_bytes as f64 * 100_f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue