From 4c6dc7cfce0d8611ac2051d4ad20a7f9972e6236 Mon Sep 17 00:00:00 2001 From: Adam Cosner Date: Fri, 6 Dec 2024 19:16:58 -0800 Subject: [PATCH] Overview page now uses the new daemon backend --- .gitignore | 4 +- Cargo.lock | 287 +-- Cargo.toml | 7 +- justfile | 6 + src/app.rs | 44 +- src/app/message.rs | 4 +- src/main.rs | 4 +- .../3rdparty/nvtop/nvtop.json | 13 + .../nvtop/patches/nvtop-amdgpu-h.patch | 1882 +++++++++++++++++ .../nvtop/patches/nvtop-amdgpu_drm-h.patch | 1167 ++++++++++ .../nvtop/patches/nvtop-constructors.patch | 52 + .../3rdparty/nvtop/patches/nvtop-kcmp-h.patch | 34 + .../patches/nvtop-no-drmGetDevices2.patch | 13 + src/observatory-daemon/Cargo.lock | 1556 ++++++++++++++ src/observatory-daemon/Cargo.toml | 47 + src/observatory-daemon/build/build.rs | 181 ++ src/observatory-daemon/build/util.rs | 95 + src/observatory-daemon/src/logging.rs | 269 +++ src/observatory-daemon/src/main.rs | 838 ++++++++ src/observatory-daemon/src/platform/apps.rs | 92 + .../src/platform/cpu_info.rs | 213 ++ .../src/platform/disk_info.rs | 131 ++ .../src/platform/fan_info.rs | 102 + .../src/platform/gpu_info.rs | 279 +++ .../src/platform/linux/apps.rs | 198 ++ .../src/platform/linux/cpu_info.rs | 1580 ++++++++++++++ .../src/platform/linux/disk_info.rs | 726 +++++++ .../src/platform/linux/fan_info.rs | 255 +++ .../src/platform/linux/fork.rs | 97 + .../src/platform/linux/gpu_info/mod.rs | 978 +++++++++ .../src/platform/linux/gpu_info/nvtop.rs | 240 +++ .../platform/linux/gpu_info/vulkan_info.rs | 127 ++ .../src/platform/linux/mod.rs | 76 + .../src/platform/linux/openrc/controller.rs | 98 + .../src/platform/linux/openrc/mod.rs | 295 +++ .../src/platform/linux/openrc/service.rs | 83 + .../src/platform/linux/openrc/string_list.rs | 17 + .../src/platform/linux/processes.rs | 628 ++++++ .../src/platform/linux/services/mod.rs | 318 +++ .../src/platform/linux/services/openrc.rs | 134 ++ .../src/platform/linux/services/systemd.rs | 56 + .../src/platform/linux/systemd/controller.rs | 218 ++ .../src/platform/linux/systemd/mod.rs | 497 +++++ .../src/platform/linux/systemd/service.rs | 435 ++++ .../src/platform/linux/utilities.rs | 104 + src/observatory-daemon/src/platform/mod.rs | 71 + .../src/platform/processes.rs | 133 ++ .../src/platform/services.rs | 121 ++ .../src/platform/utilities.rs | 25 + src/observatory-daemon/src/utils.rs | 61 + src/pages/mod.rs | 4 +- src/pages/overview.rs | 146 +- src/pages/overview/applications.rs | 3 +- src/pages/page.rs | 4 +- src/pages/processes.rs | 4 +- src/pages/resources.rs | 3 +- src/pages/resources/cpu.rs | 3 +- src/pages/resources/disk.rs | 3 +- src/pages/resources/mem.rs | 3 +- src/system_info/dbus_interface/apps.rs | 232 ++ src/system_info/dbus_interface/arc_str_vec.rs | 91 + .../dbus_interface/cpu_dynamic_info.rs | 367 ++++ .../dbus_interface/cpu_static_info.rs | 357 ++++ src/system_info/dbus_interface/disk_info.rs | 392 ++++ src/system_info/dbus_interface/fan_info.rs | 323 +++ .../dbus_interface/gpu_dynamic_info.rs | 474 +++++ .../dbus_interface/gpu_static_info.rs | 489 +++++ src/system_info/dbus_interface/mod.rs | 216 ++ src/system_info/dbus_interface/processes.rs | 379 ++++ src/system_info/dbus_interface/service.rs | 315 +++ src/system_info/mem_info.rs | 321 +++ src/system_info/mod.rs | 308 +++ src/system_info/net_info.rs | 1098 ++++++++++ src/system_info/proc_info.rs | 141 ++ 74 files changed, 20254 insertions(+), 313 deletions(-) create mode 100644 src/observatory-daemon/3rdparty/nvtop/nvtop.json create mode 100644 src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu-h.patch create mode 100644 src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu_drm-h.patch create mode 100644 src/observatory-daemon/3rdparty/nvtop/patches/nvtop-constructors.patch create mode 100644 src/observatory-daemon/3rdparty/nvtop/patches/nvtop-kcmp-h.patch create mode 100644 src/observatory-daemon/3rdparty/nvtop/patches/nvtop-no-drmGetDevices2.patch create mode 100644 src/observatory-daemon/Cargo.lock create mode 100644 src/observatory-daemon/Cargo.toml create mode 100644 src/observatory-daemon/build/build.rs create mode 100644 src/observatory-daemon/build/util.rs create mode 100644 src/observatory-daemon/src/logging.rs create mode 100644 src/observatory-daemon/src/main.rs create mode 100644 src/observatory-daemon/src/platform/apps.rs create mode 100644 src/observatory-daemon/src/platform/cpu_info.rs create mode 100644 src/observatory-daemon/src/platform/disk_info.rs create mode 100644 src/observatory-daemon/src/platform/fan_info.rs create mode 100644 src/observatory-daemon/src/platform/gpu_info.rs create mode 100644 src/observatory-daemon/src/platform/linux/apps.rs create mode 100644 src/observatory-daemon/src/platform/linux/cpu_info.rs create mode 100644 src/observatory-daemon/src/platform/linux/disk_info.rs create mode 100644 src/observatory-daemon/src/platform/linux/fan_info.rs create mode 100644 src/observatory-daemon/src/platform/linux/fork.rs create mode 100644 src/observatory-daemon/src/platform/linux/gpu_info/mod.rs create mode 100644 src/observatory-daemon/src/platform/linux/gpu_info/nvtop.rs create mode 100644 src/observatory-daemon/src/platform/linux/gpu_info/vulkan_info.rs create mode 100644 src/observatory-daemon/src/platform/linux/mod.rs create mode 100644 src/observatory-daemon/src/platform/linux/openrc/controller.rs create mode 100644 src/observatory-daemon/src/platform/linux/openrc/mod.rs create mode 100644 src/observatory-daemon/src/platform/linux/openrc/service.rs create mode 100644 src/observatory-daemon/src/platform/linux/openrc/string_list.rs create mode 100644 src/observatory-daemon/src/platform/linux/processes.rs create mode 100644 src/observatory-daemon/src/platform/linux/services/mod.rs create mode 100644 src/observatory-daemon/src/platform/linux/services/openrc.rs create mode 100644 src/observatory-daemon/src/platform/linux/services/systemd.rs create mode 100644 src/observatory-daemon/src/platform/linux/systemd/controller.rs create mode 100644 src/observatory-daemon/src/platform/linux/systemd/mod.rs create mode 100644 src/observatory-daemon/src/platform/linux/systemd/service.rs create mode 100644 src/observatory-daemon/src/platform/linux/utilities.rs create mode 100644 src/observatory-daemon/src/platform/mod.rs create mode 100644 src/observatory-daemon/src/platform/processes.rs create mode 100644 src/observatory-daemon/src/platform/services.rs create mode 100644 src/observatory-daemon/src/platform/utilities.rs create mode 100644 src/observatory-daemon/src/utils.rs create mode 100644 src/system_info/dbus_interface/apps.rs create mode 100644 src/system_info/dbus_interface/arc_str_vec.rs create mode 100644 src/system_info/dbus_interface/cpu_dynamic_info.rs create mode 100644 src/system_info/dbus_interface/cpu_static_info.rs create mode 100644 src/system_info/dbus_interface/disk_info.rs create mode 100644 src/system_info/dbus_interface/fan_info.rs create mode 100644 src/system_info/dbus_interface/gpu_dynamic_info.rs create mode 100644 src/system_info/dbus_interface/gpu_static_info.rs create mode 100644 src/system_info/dbus_interface/mod.rs create mode 100644 src/system_info/dbus_interface/processes.rs create mode 100644 src/system_info/dbus_interface/service.rs create mode 100644 src/system_info/mem_info.rs create mode 100644 src/system_info/mod.rs create mode 100644 src/system_info/net_info.rs create mode 100644 src/system_info/proc_info.rs diff --git a/.gitignore b/.gitignore index 509cdef..9b2d1a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ /.idea /.flatpak-builder /cargo-sources.json -/repo \ No newline at end of file +/repo + +/src/observatory-daemon/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b11a87d..8b35460 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -807,16 +807,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -1304,6 +1294,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1682,7 +1683,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] @@ -1695,18 +1696,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flatpak-unsandbox" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c07802b9ff8a2a7a73e3485521cc3aeeec7db91a4903f370a9a2b29c6683ea" -dependencies = [ - "gio", - "glib", - "log", - "thiserror", -] - [[package]] name = "float-cmp" version = "0.9.0" @@ -1872,7 +1861,7 @@ checksum = "a8ef34245e0540c9a3ce7a28340b98d2c12b75da0d446da4e8224923fcaa0c16" dependencies = [ "dirs 5.0.1", "once_cell", - "rust-ini", + "rust-ini 0.20.0", "thiserror", "xdg", ] @@ -2071,37 +2060,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "gio" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c49f117d373ffcc98a35d114db5478bc223341cff53e39a5d6feced9e2ddffe" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd743ba4714d671ad6b6234e8ab2a13b42304d0e13ab7eba1dcdd78a7d6d4ef" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.52.0", -] - [[package]] name = "gl_generator" version = "0.14.0" @@ -2119,51 +2077,6 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" -[[package]] -name = "glib" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39650279f135469465018daae0ba53357942a5212137515777d5fdca74984a44" -dependencies = [ - "bitflags 2.6.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4429b0277a14ae9751350ad9b658b1be0abb5b54faa5bcdf6e74a3372582fad7" -dependencies = [ - "heck 0.5.0", - "proc-macro-crate 3.2.0", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "glib-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2dc18d3a82b0006d470b13304fbbb3e0a9bd4884cf985a60a7ed733ac2c4a5" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "glow" version = "0.13.1" @@ -2185,17 +2098,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gobject-sys" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697e252d6e0416fd1d9e169bda51c0f1c926026c39ca21fbe8b1bb5c3b8b9e" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "gpu-alloc" version = "0.6.0" @@ -2311,12 +2213,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -3143,6 +3039,15 @@ dependencies = [ "zbus 4.4.0", ] +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "pkg-config", +] + [[package]] name = "libloading" version = "0.8.6" @@ -3521,15 +3426,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -3816,18 +3712,21 @@ dependencies = [ name = "observatory" version = "0.1.0" dependencies = [ + "dbus", "env_logger", - "flatpak-unsandbox", "i18n-embed", "i18n-embed-fl", + "lazy_static", + "libc", "libcosmic", "log", "open", "raw-cpuid", "rust-embed", + "rust-ini 0.21.1", "serde", "shlex", - "sysinfo", + "static_assertions", ] [[package]] @@ -3899,7 +3798,7 @@ version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ - "heck 0.4.1", + "heck", "itertools", "proc-macro2", "proc-macro2-diagnostics", @@ -4550,6 +4449,17 @@ dependencies = [ "ordered-multimap", ] +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4711,15 +4621,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "sha1" version = "0.10.6" @@ -5021,33 +4922,6 @@ dependencies = [ "libc", ] -[[package]] -name = "sysinfo" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" -dependencies = [ - "core-foundation-sys", - "libc", - "memchr", - "ntapi", - "rayon", - "windows 0.57.0", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.19", - "version-compare", -] - [[package]] name = "taffy" version = "0.3.11" @@ -5059,12 +4933,6 @@ dependencies = [ "slotmap", ] -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "temp-dir" version = "0.1.14" @@ -5246,26 +5114,11 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.22", -] - [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -5285,8 +5138,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", "winnow 0.6.20", ] @@ -5322,6 +5173,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "ttf-parser" version = "0.20.0" @@ -5527,12 +5384,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.5" @@ -5970,18 +5821,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ "windows-core 0.54.0", - "windows-implement 0.53.0", - "windows-interface 0.53.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" -dependencies = [ - "windows-core 0.57.0", + "windows-implement", + "windows-interface", "windows-targets 0.52.6", ] @@ -6004,18 +5845,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" -dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result", - "windows-targets 0.52.6", -] - [[package]] name = "windows-implement" version = "0.53.0" @@ -6027,17 +5856,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "windows-implement" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "windows-interface" version = "0.53.0" @@ -6049,17 +5867,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "windows-interface" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "windows-result" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 107a738..245220f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ repository = "https://github.com/cosmic-utils/observatory.git" [dependencies] raw-cpuid = "11.2.0" -sysinfo = "0.32.0" i18n-embed-fl = "0.9.2" open = "5.3.0" rust-embed = "8.5.0" @@ -15,7 +14,11 @@ serde = { version = "1.0.215", features = ["derive"] } log = "0.4.22" env_logger = "0.11.5" shlex = "1.3.0" -flatpak-unsandbox = "0.1.0" +dbus = "0.9.7" +static_assertions = "1.1.0" +libc = "0.2.167" +lazy_static = "1.5.0" +rust-ini = "0.21.1" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic.git" diff --git a/justfile b/justfile index 76b255a..9f6ed1e 100644 --- a/justfile +++ b/justfile @@ -10,6 +10,9 @@ share-dir := base-dir / 'share' bin-src := 'target' / 'release' / name bin-dst := base-dir / 'bin' / name +dae-src := 'src' / 'observatory-daemon' / 'target' / 'release' / 'observatory-daemon' +dae-dst := base-dir / 'bin' / 'observatory-daemon' + desktop := appid + '.desktop' desktop-src := 'res' / desktop desktop-dst := share-dir / 'applications' / desktop @@ -64,6 +67,9 @@ install: install -Dm0644 {{icon-svg-src}} {{icon-svg-dst}} install -Dm0644 res/metainfo.xml {{metainfo-dst}} +install-daemon: + install -Dm0755 {{dae-src}} {{dae-dst}} + # Uninstalls installed files uninstall: rm {{bin-dst}} {{desktop-dst}} {{icon-svg-dst}} {{metainfo-dst}} diff --git a/src/app.rs b/src/app.rs index 4a0dfa3..868f754 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,7 @@ pub mod message; use crate::core::config::ObservatoryConfig; use crate::core::icons; use crate::fl; -use crate::pages::{self, overview, processes, resources}; +use crate::pages::{self, overview/*, processes, resources*/}; use action::Action; use bindings::key_binds; use context::ContextPage; @@ -26,7 +26,6 @@ pub use cosmic::{executor, ApplicationExt, Element}; use message::AppMessage; use std::any::TypeId; use std::collections::HashMap; -use sysinfo::{ProcessRefreshKind, ProcessesToUpdate}; /// The [`App`] stores application-specific state. pub struct App { @@ -39,7 +38,7 @@ pub struct App { modifiers: Modifiers, key_binds: HashMap, context_page: ContextPage, - sys: sysinfo::System, + sys_info: Option } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -72,20 +71,18 @@ impl cosmic::Application for App { .text("Overview") .icon(icons::get_icon("user-home-symbolic", 18)) .data(Box::new(overview::OverviewPage::new()) as Box); - nav_model - .insert() - .text("Resources") - .icon(icons::get_icon("speedometer-symbolic", 18)) - .data(Box::new(resources::ResourcePage::new()) as Box); - nav_model - .insert() - .text("Processes") - .icon(icons::get_icon("view-list-symbolic", 18)) - .data(Box::new(processes::ProcessPage::new()) as Box); + // nav_model + // .insert() + // .text("Resources") + // .icon(icons::get_icon("speedometer-symbolic", 18)) + // .data(Box::new(resources::ResourcePage::new()) as Box); + // nav_model + // .insert() + // .text("Processes") + // .icon(icons::get_icon("view-list-symbolic", 18)) + // .data(Box::new(processes::ProcessPage::new()) as Box); nav_model.activate_position(0); - let sys = sysinfo::System::new_all(); - let (config, handler) = ( ObservatoryConfig::config(), ObservatoryConfig::config_handler(), @@ -121,7 +118,7 @@ impl cosmic::Application for App { modifiers: Modifiers::empty(), key_binds: key_binds(), context_page: ContextPage::Settings, - sys, + sys_info: Some(crate::system_info::SystemInfo::new()) }; let command = Task::batch([ @@ -234,16 +231,6 @@ impl cosmic::Application for App { fn update(&mut self, message: Self::Message) -> Task> { let mut tasks = vec![]; match message { - AppMessage::Refresh => { - let sys = &mut self.sys; - sys.refresh_cpu_all(); - sys.refresh_memory(); - sys.refresh_processes_specifics( - ProcessesToUpdate::All, - true, - ProcessRefreshKind::everything(), - ); - } AppMessage::SystemThemeChanged => tasks.push(self.update_theme()), AppMessage::Open(ref url) => { if let Err(err) = open::that_detached(url) { @@ -287,7 +274,10 @@ impl cosmic::Application for App { for entity in entities { let page = self.nav_model.data_mut::>(entity); if let Some(page) = page { - tasks.push(page.update(&self.sys, message.clone())); + if let Some(sys_info) = &self.sys_info { + tasks.push(page.update(sys_info, message.clone())); + + } } } diff --git a/src/app/message.rs b/src/app/message.rs index 07d9a31..ba7fff6 100644 --- a/src/app/message.rs +++ b/src/app/message.rs @@ -7,12 +7,12 @@ pub enum AppMessage { Refresh, KeyPressed(Key), - ApplicationSelect(String), + ApplicationSelect(Option), ApplicationClose, ProcessTermActive, ProcessKillActive, - ProcessClick(Option), + ProcessClick(Option), ProcessCategoryClick(u8), MulticoreView(bool), diff --git a/src/main.rs b/src/main.rs index 7578e27..e9bcc13 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,9 @@ mod app; mod core; mod pages; mod widgets; +mod system_info; fn main() -> cosmic::iced::Result { - if flatpak_unsandbox::unsandbox(None).is_ok() { - return Ok(()) - } settings::init(); cosmic::app::run::(settings::settings(), settings::flags()) } diff --git a/src/observatory-daemon/3rdparty/nvtop/nvtop.json b/src/observatory-daemon/3rdparty/nvtop/nvtop.json new file mode 100644 index 0000000..61ffdf0 --- /dev/null +++ b/src/observatory-daemon/3rdparty/nvtop/nvtop.json @@ -0,0 +1,13 @@ +{ + "package-name": "nvtop", + "directory": "nvtop-6e91c745cd051d902fc57e9195068df03eb192bb", + "source-url": "https://github.com/Syllo/nvtop/archive/6e91c745cd051d902fc57e9195068df03eb192bb.tar.gz", + "source-hash": "dd162f984893c63b67c07113dec5fb327b6f643889dadd0ced69db4184523ffa", + "patches": [ + "patches/nvtop-amdgpu-h.patch", + "patches/nvtop-amdgpu_drm-h.patch", + "patches/nvtop-constructors.patch", + "patches/nvtop-kcmp-h.patch", + "patches/nvtop-no-drmGetDevices2.patch" + ] +} \ No newline at end of file diff --git a/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu-h.patch b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu-h.patch new file mode 100644 index 0000000..ce4c478 --- /dev/null +++ b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu-h.patch @@ -0,0 +1,1882 @@ +diff --git a/include/libdrm/amdgpu.h b/include/libdrm/amdgpu.h +new file mode 100644 +index 0000000..5ef2524 +--- /dev/null ++++ b/include/libdrm/amdgpu.h +@@ -0,0 +1,1876 @@ ++/* ++ * Copyright 2014 Advanced Micro Devices, Inc. ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a ++ * copy of this software and associated documentation files (the "Software"), ++ * to deal in the Software without restriction, including without limitation ++ * the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ * and/or sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR ++ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ++ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ++ * OTHER DEALINGS IN THE SOFTWARE. ++ * ++ */ ++ ++/** ++ * \file amdgpu.h ++ * ++ * Declare public libdrm_amdgpu API ++ * ++ * This file define API exposed by libdrm_amdgpu library. ++ * User wanted to use libdrm_amdgpu functionality must include ++ * this file. ++ * ++ */ ++#ifndef _AMDGPU_H_ ++#define _AMDGPU_H_ ++ ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++struct drm_amdgpu_info_hw_ip; ++struct drm_amdgpu_bo_list_entry; ++ ++/*--------------------------------------------------------------------------*/ ++/* --------------------------- Defines ------------------------------------ */ ++/*--------------------------------------------------------------------------*/ ++ ++/** ++ * Define max. number of Command Buffers (IB) which could be sent to the single ++ * hardware IP to accommodate CE/DE requirements ++ * ++ * \sa amdgpu_cs_ib_info ++*/ ++#define AMDGPU_CS_MAX_IBS_PER_SUBMIT 4 ++ ++/** ++ * Special timeout value meaning that the timeout is infinite. ++ */ ++#define AMDGPU_TIMEOUT_INFINITE 0xffffffffffffffffull ++ ++/** ++ * Used in amdgpu_cs_query_fence_status(), meaning that the given timeout ++ * is absolute. ++ */ ++#define AMDGPU_QUERY_FENCE_TIMEOUT_IS_ABSOLUTE (1 << 0) ++ ++/*--------------------------------------------------------------------------*/ ++/* ----------------------------- Enums ------------------------------------ */ ++/*--------------------------------------------------------------------------*/ ++ ++/** ++ * Enum describing possible handle types ++ * ++ * \sa amdgpu_bo_import, amdgpu_bo_export ++ * ++*/ ++enum amdgpu_bo_handle_type { ++ /** GEM flink name (needs DRM authentication, used by DRI2) */ ++ amdgpu_bo_handle_type_gem_flink_name = 0, ++ ++ /** KMS handle which is used by all driver ioctls */ ++ amdgpu_bo_handle_type_kms = 1, ++ ++ /** DMA-buf fd handle */ ++ amdgpu_bo_handle_type_dma_buf_fd = 2, ++ ++ /** Deprecated in favour of and same behaviour as ++ * amdgpu_bo_handle_type_kms, use that instead of this ++ */ ++ amdgpu_bo_handle_type_kms_noimport = 3, ++}; ++ ++/** Define known types of GPU VM VA ranges */ ++enum amdgpu_gpu_va_range ++{ ++ /** Allocate from "normal"/general range */ ++ amdgpu_gpu_va_range_general = 0 ++}; ++ ++enum amdgpu_sw_info { ++ amdgpu_sw_info_address32_hi = 0, ++}; ++ ++/*--------------------------------------------------------------------------*/ ++/* -------------------------- Datatypes ----------------------------------- */ ++/*--------------------------------------------------------------------------*/ ++ ++/** ++ * Define opaque pointer to context associated with fd. ++ * This context will be returned as the result of ++ * "initialize" function and should be pass as the first ++ * parameter to any API call ++ */ ++typedef struct amdgpu_device *amdgpu_device_handle; ++ ++/** ++ * Define GPU Context type as pointer to opaque structure ++ * Example of GPU Context is the "rendering" context associated ++ * with OpenGL context (glCreateContext) ++ */ ++typedef struct amdgpu_context *amdgpu_context_handle; ++ ++/** ++ * Define handle for amdgpu resources: buffer, GDS, etc. ++ */ ++typedef struct amdgpu_bo *amdgpu_bo_handle; ++ ++/** ++ * Define handle for list of BOs ++ */ ++typedef struct amdgpu_bo_list *amdgpu_bo_list_handle; ++ ++/** ++ * Define handle to be used to work with VA allocated ranges ++ */ ++typedef struct amdgpu_va *amdgpu_va_handle; ++ ++/** ++ * Define handle for semaphore ++ */ ++typedef struct amdgpu_semaphore *amdgpu_semaphore_handle; ++ ++/*--------------------------------------------------------------------------*/ ++/* -------------------------- Structures ---------------------------------- */ ++/*--------------------------------------------------------------------------*/ ++ ++/** ++ * Structure describing memory allocation request ++ * ++ * \sa amdgpu_bo_alloc() ++ * ++*/ ++struct amdgpu_bo_alloc_request { ++ /** Allocation request. It must be aligned correctly. */ ++ uint64_t alloc_size; ++ ++ /** ++ * It may be required to have some specific alignment requirements ++ * for physical back-up storage (e.g. for displayable surface). ++ * If 0 there is no special alignment requirement ++ */ ++ uint64_t phys_alignment; ++ ++ /** ++ * UMD should specify where to allocate memory and how it ++ * will be accessed by the CPU. ++ */ ++ uint32_t preferred_heap; ++ ++ /** Additional flags passed on allocation */ ++ uint64_t flags; ++}; ++ ++/** ++ * Special UMD specific information associated with buffer. ++ * ++ * It may be need to pass some buffer charactersitic as part ++ * of buffer sharing. Such information are defined UMD and ++ * opaque for libdrm_amdgpu as well for kernel driver. ++ * ++ * \sa amdgpu_bo_set_metadata(), amdgpu_bo_query_info, ++ * amdgpu_bo_import(), amdgpu_bo_export ++ * ++*/ ++struct amdgpu_bo_metadata { ++ /** Special flag associated with surface */ ++ uint64_t flags; ++ ++ /** ++ * ASIC-specific tiling information (also used by DCE). ++ * The encoding is defined by the AMDGPU_TILING_* definitions. ++ */ ++ uint64_t tiling_info; ++ ++ /** Size of metadata associated with the buffer, in bytes. */ ++ uint32_t size_metadata; ++ ++ /** UMD specific metadata. Opaque for kernel */ ++ uint32_t umd_metadata[64]; ++}; ++ ++/** ++ * Structure describing allocated buffer. Client may need ++ * to query such information as part of 'sharing' buffers mechanism ++ * ++ * \sa amdgpu_bo_set_metadata(), amdgpu_bo_query_info(), ++ * amdgpu_bo_import(), amdgpu_bo_export() ++*/ ++struct amdgpu_bo_info { ++ /** Allocated memory size */ ++ uint64_t alloc_size; ++ ++ /** ++ * It may be required to have some specific alignment requirements ++ * for physical back-up storage. ++ */ ++ uint64_t phys_alignment; ++ ++ /** Heap where to allocate memory. */ ++ uint32_t preferred_heap; ++ ++ /** Additional allocation flags. */ ++ uint64_t alloc_flags; ++ ++ /** Metadata associated with buffer if any. */ ++ struct amdgpu_bo_metadata metadata; ++}; ++ ++/** ++ * Structure with information about "imported" buffer ++ * ++ * \sa amdgpu_bo_import() ++ * ++ */ ++struct amdgpu_bo_import_result { ++ /** Handle of memory/buffer to use */ ++ amdgpu_bo_handle buf_handle; ++ ++ /** Buffer size */ ++ uint64_t alloc_size; ++}; ++ ++/** ++ * ++ * Structure to describe GDS partitioning information. ++ * \note OA and GWS resources are asscoiated with GDS partition ++ * ++ * \sa amdgpu_gpu_resource_query_gds_info ++ * ++*/ ++struct amdgpu_gds_resource_info { ++ uint32_t gds_gfx_partition_size; ++ uint32_t compute_partition_size; ++ uint32_t gds_total_size; ++ uint32_t gws_per_gfx_partition; ++ uint32_t gws_per_compute_partition; ++ uint32_t oa_per_gfx_partition; ++ uint32_t oa_per_compute_partition; ++}; ++ ++/** ++ * Structure describing CS fence ++ * ++ * \sa amdgpu_cs_query_fence_status(), amdgpu_cs_request, amdgpu_cs_submit() ++ * ++*/ ++struct amdgpu_cs_fence { ++ ++ /** In which context IB was sent to execution */ ++ amdgpu_context_handle context; ++ ++ /** To which HW IP type the fence belongs */ ++ uint32_t ip_type; ++ ++ /** IP instance index if there are several IPs of the same type. */ ++ uint32_t ip_instance; ++ ++ /** Ring index of the HW IP */ ++ uint32_t ring; ++ ++ /** Specify fence for which we need to check submission status.*/ ++ uint64_t fence; ++}; ++ ++/** ++ * Structure describing IB ++ * ++ * \sa amdgpu_cs_request, amdgpu_cs_submit() ++ * ++*/ ++struct amdgpu_cs_ib_info { ++ /** Special flags */ ++ uint64_t flags; ++ ++ /** Virtual MC address of the command buffer */ ++ uint64_t ib_mc_address; ++ ++ /** ++ * Size of Command Buffer to be submitted. ++ * - The size is in units of dwords (4 bytes). ++ * - Could be 0 ++ */ ++ uint32_t size; ++}; ++ ++/** ++ * Structure describing fence information ++ * ++ * \sa amdgpu_cs_request, amdgpu_cs_query_fence, ++ * amdgpu_cs_submit(), amdgpu_cs_query_fence_status() ++*/ ++struct amdgpu_cs_fence_info { ++ /** buffer object for the fence */ ++ amdgpu_bo_handle handle; ++ ++ /** fence offset in the unit of sizeof(uint64_t) */ ++ uint64_t offset; ++}; ++ ++/** ++ * Structure describing submission request ++ * ++ * \note We could have several IBs as packet. e.g. CE, CE, DE case for gfx ++ * ++ * \sa amdgpu_cs_submit() ++*/ ++struct amdgpu_cs_request { ++ /** Specify flags with additional information */ ++ uint64_t flags; ++ ++ /** Specify HW IP block type to which to send the IB. */ ++ unsigned ip_type; ++ ++ /** IP instance index if there are several IPs of the same type. */ ++ unsigned ip_instance; ++ ++ /** ++ * Specify ring index of the IP. We could have several rings ++ * in the same IP. E.g. 0 for SDMA0 and 1 for SDMA1. ++ */ ++ uint32_t ring; ++ ++ /** ++ * List handle with resources used by this request. ++ */ ++ amdgpu_bo_list_handle resources; ++ ++ /** ++ * Number of dependencies this Command submission needs to ++ * wait for before starting execution. ++ */ ++ uint32_t number_of_dependencies; ++ ++ /** ++ * Array of dependencies which need to be met before ++ * execution can start. ++ */ ++ struct amdgpu_cs_fence *dependencies; ++ ++ /** Number of IBs to submit in the field ibs. */ ++ uint32_t number_of_ibs; ++ ++ /** ++ * IBs to submit. Those IBs will be submit together as single entity ++ */ ++ struct amdgpu_cs_ib_info *ibs; ++ ++ /** ++ * The returned sequence number for the command submission ++ */ ++ uint64_t seq_no; ++ ++ /** ++ * The fence information ++ */ ++ struct amdgpu_cs_fence_info fence_info; ++}; ++ ++/** ++ * Structure which provide information about GPU VM MC Address space ++ * alignments requirements ++ * ++ * \sa amdgpu_query_buffer_size_alignment ++ */ ++struct amdgpu_buffer_size_alignments { ++ /** Size alignment requirement for allocation in ++ * local memory */ ++ uint64_t size_local; ++ ++ /** ++ * Size alignment requirement for allocation in remote memory ++ */ ++ uint64_t size_remote; ++}; ++ ++/** ++ * Structure which provide information about heap ++ * ++ * \sa amdgpu_query_heap_info() ++ * ++ */ ++struct amdgpu_heap_info { ++ /** Theoretical max. available memory in the given heap */ ++ uint64_t heap_size; ++ ++ /** ++ * Number of bytes allocated in the heap. This includes all processes ++ * and private allocations in the kernel. It changes when new buffers ++ * are allocated, freed, and moved. It cannot be larger than ++ * heap_size. ++ */ ++ uint64_t heap_usage; ++ ++ /** ++ * Theoretical possible max. size of buffer which ++ * could be allocated in the given heap ++ */ ++ uint64_t max_allocation; ++}; ++ ++/** ++ * Describe GPU h/w info needed for UMD correct initialization ++ * ++ * \sa amdgpu_query_gpu_info() ++*/ ++struct amdgpu_gpu_info { ++ /** Asic id */ ++ uint32_t asic_id; ++ /** Chip revision */ ++ uint32_t chip_rev; ++ /** Chip external revision */ ++ uint32_t chip_external_rev; ++ /** Family ID */ ++ uint32_t family_id; ++ /** Special flags */ ++ uint64_t ids_flags; ++ /** max engine clock*/ ++ uint64_t max_engine_clk; ++ /** max memory clock */ ++ uint64_t max_memory_clk; ++ /** number of shader engines */ ++ uint32_t num_shader_engines; ++ /** number of shader arrays per engine */ ++ uint32_t num_shader_arrays_per_engine; ++ /** Number of available good shader pipes */ ++ uint32_t avail_quad_shader_pipes; ++ /** Max. number of shader pipes.(including good and bad pipes */ ++ uint32_t max_quad_shader_pipes; ++ /** Number of parameter cache entries per shader quad pipe */ ++ uint32_t cache_entries_per_quad_pipe; ++ /** Number of available graphics context */ ++ uint32_t num_hw_gfx_contexts; ++ /** Number of render backend pipes */ ++ uint32_t rb_pipes; ++ /** Enabled render backend pipe mask */ ++ uint32_t enabled_rb_pipes_mask; ++ /** Frequency of GPU Counter */ ++ uint32_t gpu_counter_freq; ++ /** CC_RB_BACKEND_DISABLE.BACKEND_DISABLE per SE */ ++ uint32_t backend_disable[4]; ++ /** Value of MC_ARB_RAMCFG register*/ ++ uint32_t mc_arb_ramcfg; ++ /** Value of GB_ADDR_CONFIG */ ++ uint32_t gb_addr_cfg; ++ /** Values of the GB_TILE_MODE0..31 registers */ ++ uint32_t gb_tile_mode[32]; ++ /** Values of GB_MACROTILE_MODE0..15 registers */ ++ uint32_t gb_macro_tile_mode[16]; ++ /** Value of PA_SC_RASTER_CONFIG register per SE */ ++ uint32_t pa_sc_raster_cfg[4]; ++ /** Value of PA_SC_RASTER_CONFIG_1 register per SE */ ++ uint32_t pa_sc_raster_cfg1[4]; ++ /* CU info */ ++ uint32_t cu_active_number; ++ uint32_t cu_ao_mask; ++ uint32_t cu_bitmap[4][4]; ++ /* video memory type info*/ ++ uint32_t vram_type; ++ /* video memory bit width*/ ++ uint32_t vram_bit_width; ++ /** constant engine ram size*/ ++ uint32_t ce_ram_size; ++ /* vce harvesting instance */ ++ uint32_t vce_harvest_config; ++ /* PCI revision ID */ ++ uint32_t pci_rev_id; ++}; ++ ++ ++/*--------------------------------------------------------------------------*/ ++/*------------------------- Functions --------------------------------------*/ ++/*--------------------------------------------------------------------------*/ ++ ++/* ++ * Initialization / Cleanup ++ * ++*/ ++ ++/** ++ * ++ * \param fd - \c [in] File descriptor for AMD GPU device ++ * received previously as the result of ++ * e.g. drmOpen() call. ++ * For legacy fd type, the DRI2/DRI3 ++ * authentication should be done before ++ * calling this function. ++ * \param major_version - \c [out] Major version of library. It is assumed ++ * that adding new functionality will cause ++ * increase in major version ++ * \param minor_version - \c [out] Minor version of library ++ * \param device_handle - \c [out] Pointer to opaque context which should ++ * be passed as the first parameter on each ++ * API call ++ * ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * ++ * \sa amdgpu_device_deinitialize() ++*/ ++int amdgpu_device_initialize(int fd, ++ uint32_t *major_version, ++ uint32_t *minor_version, ++ amdgpu_device_handle *device_handle); ++ ++/** ++ * ++ * When access to such library does not needed any more the special ++ * function must be call giving opportunity to clean up any ++ * resources if needed. ++ * ++ * \param device_handle - \c [in] Context associated with file ++ * descriptor for AMD GPU device ++ * received previously as the ++ * result e.g. of drmOpen() call. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_device_initialize() ++ * ++*/ ++int amdgpu_device_deinitialize(amdgpu_device_handle device_handle); ++ ++/** ++ * ++ * /param device_handle - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * ++ * \return Returns the drm fd used for operations on this ++ * device. This is still owned by the library and hence ++ * should not be closed. Guaranteed to be valid until ++ * #amdgpu_device_deinitialize gets called. ++ * ++*/ ++int amdgpu_device_get_fd(amdgpu_device_handle device_handle); ++ ++/* ++ * Memory Management ++ * ++*/ ++ ++/** ++ * Allocate memory to be used by UMD for GPU related operations ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param alloc_buffer - \c [in] Pointer to the structure describing an ++ * allocation request ++ * \param buf_handle - \c [out] Allocated buffer handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_free() ++*/ ++int amdgpu_bo_alloc(amdgpu_device_handle dev, ++ struct amdgpu_bo_alloc_request *alloc_buffer, ++ amdgpu_bo_handle *buf_handle); ++ ++/** ++ * Associate opaque data with buffer to be queried by another UMD ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param buf_handle - \c [in] Buffer handle ++ * \param info - \c [in] Metadata to associated with buffer ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++*/ ++int amdgpu_bo_set_metadata(amdgpu_bo_handle buf_handle, ++ struct amdgpu_bo_metadata *info); ++ ++/** ++ * Query buffer information including metadata previusly associated with ++ * buffer. ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param buf_handle - \c [in] Buffer handle ++ * \param info - \c [out] Structure describing buffer ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_set_metadata(), amdgpu_bo_alloc() ++*/ ++int amdgpu_bo_query_info(amdgpu_bo_handle buf_handle, ++ struct amdgpu_bo_info *info); ++ ++/** ++ * Allow others to get access to buffer ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param buf_handle - \c [in] Buffer handle ++ * \param type - \c [in] Type of handle requested ++ * \param shared_handle - \c [out] Special "shared" handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_import() ++ * ++*/ ++int amdgpu_bo_export(amdgpu_bo_handle buf_handle, ++ enum amdgpu_bo_handle_type type, ++ uint32_t *shared_handle); ++ ++/** ++ * Request access to "shared" buffer ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param type - \c [in] Type of handle requested ++ * \param shared_handle - \c [in] Shared handle received as result "import" ++ * operation ++ * \param output - \c [out] Pointer to structure with information ++ * about imported buffer ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \note Buffer must be "imported" only using new "fd" (different from ++ * one used by "exporter"). ++ * ++ * \sa amdgpu_bo_export() ++ * ++*/ ++int amdgpu_bo_import(amdgpu_device_handle dev, ++ enum amdgpu_bo_handle_type type, ++ uint32_t shared_handle, ++ struct amdgpu_bo_import_result *output); ++ ++/** ++ * Request GPU access to user allocated memory e.g. via "malloc" ++ * ++ * \param dev - [in] Device handle. See #amdgpu_device_initialize() ++ * \param cpu - [in] CPU address of user allocated memory which we ++ * want to map to GPU address space (make GPU accessible) ++ * (This address must be correctly aligned). ++ * \param size - [in] Size of allocation (must be correctly aligned) ++ * \param buf_handle - [out] Buffer handle for the userptr memory ++ * resource on submission and be used in other operations. ++ * ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \note ++ * This call doesn't guarantee that such memory will be persistently ++ * "locked" / make non-pageable. The purpose of this call is to provide ++ * opportunity for GPU get access to this resource during submission. ++ * ++ * The maximum amount of memory which could be mapped in this call depends ++ * if overcommit is disabled or not. If overcommit is disabled than the max. ++ * amount of memory to be pinned will be limited by left "free" size in total ++ * amount of memory which could be locked simultaneously ("GART" size). ++ * ++ * Supported (theoretical) max. size of mapping is restricted only by ++ * "GART" size. ++ * ++ * It is responsibility of caller to correctly specify access rights ++ * on VA assignment. ++*/ ++int amdgpu_create_bo_from_user_mem(amdgpu_device_handle dev, ++ void *cpu, uint64_t size, ++ amdgpu_bo_handle *buf_handle); ++ ++/** ++ * Validate if the user memory comes from BO ++ * ++ * \param dev - [in] Device handle. See #amdgpu_device_initialize() ++ * \param cpu - [in] CPU address of user allocated memory which we ++ * want to map to GPU address space (make GPU accessible) ++ * (This address must be correctly aligned). ++ * \param size - [in] Size of allocation (must be correctly aligned) ++ * \param buf_handle - [out] Buffer handle for the userptr memory ++ * if the user memory is not from BO, the buf_handle will be NULL. ++ * \param offset_in_bo - [out] offset in this BO for this user memory ++ * ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_find_bo_by_cpu_mapping(amdgpu_device_handle dev, ++ void *cpu, ++ uint64_t size, ++ amdgpu_bo_handle *buf_handle, ++ uint64_t *offset_in_bo); ++ ++/** ++ * Free previously allocated memory ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param buf_handle - \c [in] Buffer handle to free ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \note In the case of memory shared between different applications all ++ * resources will be “physically” freed only all such applications ++ * will be terminated ++ * \note If is UMD responsibility to ‘free’ buffer only when there is no ++ * more GPU access ++ * ++ * \sa amdgpu_bo_set_metadata(), amdgpu_bo_alloc() ++ * ++*/ ++int amdgpu_bo_free(amdgpu_bo_handle buf_handle); ++ ++/** ++ * Increase the reference count of a buffer object ++ * ++ * \param bo - \c [in] Buffer object handle to increase the reference count ++ * ++ * \sa amdgpu_bo_alloc(), amdgpu_bo_free() ++ * ++*/ ++void amdgpu_bo_inc_ref(amdgpu_bo_handle bo); ++ ++/** ++ * Request CPU access to GPU accessible memory ++ * ++ * \param buf_handle - \c [in] Buffer handle ++ * \param cpu - \c [out] CPU address to be used for access ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_cpu_unmap() ++ * ++*/ ++int amdgpu_bo_cpu_map(amdgpu_bo_handle buf_handle, void **cpu); ++ ++/** ++ * Release CPU access to GPU memory ++ * ++ * \param buf_handle - \c [in] Buffer handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_cpu_map() ++ * ++*/ ++int amdgpu_bo_cpu_unmap(amdgpu_bo_handle buf_handle); ++ ++/** ++ * Wait until a buffer is not used by the device. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param buf_handle - \c [in] Buffer handle. ++ * \param timeout_ns - Timeout in nanoseconds. ++ * \param buffer_busy - 0 if buffer is idle, all GPU access was completed ++ * and no GPU access is scheduled. ++ * 1 GPU access is in fly or scheduled ++ * ++ * \return 0 - on success ++ * <0 - Negative POSIX Error code ++ */ ++int amdgpu_bo_wait_for_idle(amdgpu_bo_handle buf_handle, ++ uint64_t timeout_ns, ++ bool *buffer_busy); ++ ++/** ++ * Creates a BO list handle for command submission. ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param number_of_buffers - \c [in] Number of BOs in the list ++ * \param buffers - \c [in] List of BO handles ++ * \param result - \c [out] Created BO list handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_list_destroy_raw(), amdgpu_cs_submit_raw2() ++*/ ++int amdgpu_bo_list_create_raw(amdgpu_device_handle dev, ++ uint32_t number_of_buffers, ++ struct drm_amdgpu_bo_list_entry *buffers, ++ uint32_t *result); ++ ++/** ++ * Destroys a BO list handle. ++ * ++ * \param bo_list - \c [in] BO list handle. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_list_create_raw(), amdgpu_cs_submit_raw2() ++*/ ++int amdgpu_bo_list_destroy_raw(amdgpu_device_handle dev, uint32_t bo_list); ++ ++/** ++ * Creates a BO list handle for command submission. ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param number_of_resources - \c [in] Number of BOs in the list ++ * \param resources - \c [in] List of BO handles ++ * \param resource_prios - \c [in] Optional priority for each handle ++ * \param result - \c [out] Created BO list handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_list_destroy() ++*/ ++int amdgpu_bo_list_create(amdgpu_device_handle dev, ++ uint32_t number_of_resources, ++ amdgpu_bo_handle *resources, ++ uint8_t *resource_prios, ++ amdgpu_bo_list_handle *result); ++ ++/** ++ * Destroys a BO list handle. ++ * ++ * \param handle - \c [in] BO list handle. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_list_create() ++*/ ++int amdgpu_bo_list_destroy(amdgpu_bo_list_handle handle); ++ ++/** ++ * Update resources for existing BO list ++ * ++ * \param handle - \c [in] BO list handle ++ * \param number_of_resources - \c [in] Number of BOs in the list ++ * \param resources - \c [in] List of BO handles ++ * \param resource_prios - \c [in] Optional priority for each handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_list_update() ++*/ ++int amdgpu_bo_list_update(amdgpu_bo_list_handle handle, ++ uint32_t number_of_resources, ++ amdgpu_bo_handle *resources, ++ uint8_t *resource_prios); ++ ++/* ++ * GPU Execution context ++ * ++*/ ++ ++/** ++ * Create GPU execution Context ++ * ++ * For the purpose of GPU Scheduler and GPU Robustness extensions it is ++ * necessary to have information/identify rendering/compute contexts. ++ * It also may be needed to associate some specific requirements with such ++ * contexts. Kernel driver will guarantee that submission from the same ++ * context will always be executed in order (first come, first serve). ++ * ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param priority - \c [in] Context creation flags. See AMDGPU_CTX_PRIORITY_* ++ * \param context - \c [out] GPU Context handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_cs_ctx_free() ++ * ++*/ ++int amdgpu_cs_ctx_create2(amdgpu_device_handle dev, ++ uint32_t priority, ++ amdgpu_context_handle *context); ++/** ++ * Create GPU execution Context ++ * ++ * Refer to amdgpu_cs_ctx_create2 for full documentation. This call ++ * is missing the priority parameter. ++ * ++ * \sa amdgpu_cs_ctx_create2() ++ * ++*/ ++int amdgpu_cs_ctx_create(amdgpu_device_handle dev, ++ amdgpu_context_handle *context); ++ ++/** ++ * ++ * Destroy GPU execution context when not needed any more ++ * ++ * \param context - \c [in] GPU Context handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_cs_ctx_create() ++ * ++*/ ++int amdgpu_cs_ctx_free(amdgpu_context_handle context); ++ ++/** ++ * Override the submission priority for the given context using a master fd. ++ * ++ * \param dev - \c [in] device handle ++ * \param context - \c [in] context handle for context id ++ * \param master_fd - \c [in] The master fd to authorize the override. ++ * \param priority - \c [in] The priority to assign to the context. ++ * ++ * \return 0 on success or a a negative Posix error code on failure. ++ */ ++int amdgpu_cs_ctx_override_priority(amdgpu_device_handle dev, ++ amdgpu_context_handle context, ++ int master_fd, ++ unsigned priority); ++ ++/** ++ * Set or query the stable power state for GPU profiling. ++ * ++ * \param dev - \c [in] device handle ++ * \param op - \c [in] AMDGPU_CTX_OP_{GET,SET}_STABLE_PSTATE ++ * \param flags - \c [in] AMDGPU_CTX_STABLE_PSTATE_* ++ * \param out_flags - \c [out] output current stable pstate ++ * ++ * \return 0 on success otherwise POSIX Error code. ++ */ ++int amdgpu_cs_ctx_stable_pstate(amdgpu_context_handle context, ++ uint32_t op, ++ uint32_t flags, ++ uint32_t *out_flags); ++ ++/** ++ * Query reset state for the specific GPU Context ++ * ++ * \param context - \c [in] GPU Context handle ++ * \param state - \c [out] One of AMDGPU_CTX_*_RESET ++ * \param hangs - \c [out] Number of hangs caused by the context. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_cs_ctx_create() ++ * ++*/ ++int amdgpu_cs_query_reset_state(amdgpu_context_handle context, ++ uint32_t *state, uint32_t *hangs); ++ ++/** ++ * Query reset state for the specific GPU Context. ++ * ++ * \param context - \c [in] GPU Context handle ++ * \param flags - \c [out] A combination of AMDGPU_CTX_QUERY2_FLAGS_* ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_cs_ctx_create() ++ * ++*/ ++int amdgpu_cs_query_reset_state2(amdgpu_context_handle context, ++ uint64_t *flags); ++ ++/* ++ * Command Buffers Management ++ * ++*/ ++ ++/** ++ * Send request to submit command buffers to hardware. ++ * ++ * Kernel driver could use GPU Scheduler to make decision when physically ++ * sent this request to the hardware. Accordingly this request could be put ++ * in queue and sent for execution later. The only guarantee is that request ++ * from the same GPU context to the same ip:ip_instance:ring will be executed in ++ * order. ++ * ++ * The caller can specify the user fence buffer/location with the fence_info in the ++ * cs_request.The sequence number is returned via the 'seq_no' parameter ++ * in ibs_request structure. ++ * ++ * ++ * \param dev - \c [in] Device handle. ++ * See #amdgpu_device_initialize() ++ * \param context - \c [in] GPU Context ++ * \param flags - \c [in] Global submission flags ++ * \param ibs_request - \c [in/out] Pointer to submission requests. ++ * We could submit to the several ++ * engines/rings simulteniously as ++ * 'atomic' operation ++ * \param number_of_requests - \c [in] Number of submission requests ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \note It is required to pass correct resource list with buffer handles ++ * which will be accessible by command buffers from submission ++ * This will allow kernel driver to correctly implement "paging". ++ * Failure to do so will have unpredictable results. ++ * ++ * \sa amdgpu_command_buffer_alloc(), amdgpu_command_buffer_free(), ++ * amdgpu_cs_query_fence_status() ++ * ++*/ ++int amdgpu_cs_submit(amdgpu_context_handle context, ++ uint64_t flags, ++ struct amdgpu_cs_request *ibs_request, ++ uint32_t number_of_requests); ++ ++/** ++ * Query status of Command Buffer Submission ++ * ++ * \param fence - \c [in] Structure describing fence to query ++ * \param timeout_ns - \c [in] Timeout value to wait ++ * \param flags - \c [in] Flags for the query ++ * \param expired - \c [out] If fence expired or not.\n ++ * 0 – if fence is not expired\n ++ * !0 - otherwise ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \note If UMD wants only to check operation status and returned immediately ++ * then timeout value as 0 must be passed. In this case success will be ++ * returned in the case if submission was completed or timeout error ++ * code. ++ * ++ * \sa amdgpu_cs_submit() ++*/ ++int amdgpu_cs_query_fence_status(struct amdgpu_cs_fence *fence, ++ uint64_t timeout_ns, ++ uint64_t flags, ++ uint32_t *expired); ++ ++/** ++ * Wait for multiple fences ++ * ++ * \param fences - \c [in] The fence array to wait ++ * \param fence_count - \c [in] The fence count ++ * \param wait_all - \c [in] If true, wait all fences to be signaled, ++ * otherwise, wait at least one fence ++ * \param timeout_ns - \c [in] The timeout to wait, in nanoseconds ++ * \param status - \c [out] '1' for signaled, '0' for timeout ++ * \param first - \c [out] the index of the first signaled fence from @fences ++ * ++ * \return 0 on success ++ * <0 - Negative POSIX Error code ++ * ++ * \note Currently it supports only one amdgpu_device. All fences come from ++ * the same amdgpu_device with the same fd. ++*/ ++int amdgpu_cs_wait_fences(struct amdgpu_cs_fence *fences, ++ uint32_t fence_count, ++ bool wait_all, ++ uint64_t timeout_ns, ++ uint32_t *status, uint32_t *first); ++ ++/* ++ * Query / Info API ++ * ++*/ ++ ++/** ++ * Query allocation size alignments ++ * ++ * UMD should query information about GPU VM MC size alignments requirements ++ * to be able correctly choose required allocation size and implement ++ * internal optimization if needed. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param info - \c [out] Pointer to structure to get size alignment ++ * requirements ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_buffer_size_alignment(amdgpu_device_handle dev, ++ struct amdgpu_buffer_size_alignments ++ *info); ++ ++/** ++ * Query firmware versions ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param fw_type - \c [in] AMDGPU_INFO_FW_* ++ * \param ip_instance - \c [in] Index of the IP block of the same type. ++ * \param index - \c [in] Index of the engine. (for SDMA and MEC) ++ * \param version - \c [out] Pointer to to the "version" return value ++ * \param feature - \c [out] Pointer to to the "feature" return value ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_firmware_version(amdgpu_device_handle dev, unsigned fw_type, ++ unsigned ip_instance, unsigned index, ++ uint32_t *version, uint32_t *feature); ++ ++/** ++ * Query the number of HW IP instances of a certain type. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param type - \c [in] Hardware IP block type = AMDGPU_HW_IP_* ++ * \param count - \c [out] Pointer to structure to get information ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++*/ ++int amdgpu_query_hw_ip_count(amdgpu_device_handle dev, unsigned type, ++ uint32_t *count); ++ ++/** ++ * Query engine information ++ * ++ * This query allows UMD to query information different engines and their ++ * capabilities. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param type - \c [in] Hardware IP block type = AMDGPU_HW_IP_* ++ * \param ip_instance - \c [in] Index of the IP block of the same type. ++ * \param info - \c [out] Pointer to structure to get information ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++*/ ++int amdgpu_query_hw_ip_info(amdgpu_device_handle dev, unsigned type, ++ unsigned ip_instance, ++ struct drm_amdgpu_info_hw_ip *info); ++ ++/** ++ * Query heap information ++ * ++ * This query allows UMD to query potentially available memory resources and ++ * adjust their logic if necessary. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param heap - \c [in] Heap type ++ * \param info - \c [in] Pointer to structure to get needed information ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_heap_info(amdgpu_device_handle dev, uint32_t heap, ++ uint32_t flags, struct amdgpu_heap_info *info); ++ ++/** ++ * Get the CRTC ID from the mode object ID ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param id - \c [in] Mode object ID ++ * \param result - \c [in] Pointer to the CRTC ID ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_crtc_from_id(amdgpu_device_handle dev, unsigned id, ++ int32_t *result); ++ ++/** ++ * Query GPU H/w Info ++ * ++ * Query hardware specific information ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param heap - \c [in] Heap type ++ * \param info - \c [in] Pointer to structure to get needed information ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_gpu_info(amdgpu_device_handle dev, ++ struct amdgpu_gpu_info *info); ++ ++/** ++ * Query hardware or driver information. ++ * ++ * The return size is query-specific and depends on the "info_id" parameter. ++ * No more than "size" bytes is returned. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param info_id - \c [in] AMDGPU_INFO_* ++ * \param size - \c [in] Size of the returned value. ++ * \param value - \c [out] Pointer to the return value. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX error code ++ * ++*/ ++int amdgpu_query_info(amdgpu_device_handle dev, unsigned info_id, ++ unsigned size, void *value); ++ ++/** ++ * Query hardware or driver information. ++ * ++ * The return size is query-specific and depends on the "info_id" parameter. ++ * No more than "size" bytes is returned. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param info - \c [in] amdgpu_sw_info_* ++ * \param value - \c [out] Pointer to the return value. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX error code ++ * ++*/ ++int amdgpu_query_sw_info(amdgpu_device_handle dev, enum amdgpu_sw_info info, ++ void *value); ++ ++/** ++ * Query information about GDS ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param gds_info - \c [out] Pointer to structure to get GDS information ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_gds_info(amdgpu_device_handle dev, ++ struct amdgpu_gds_resource_info *gds_info); ++ ++/** ++ * Query information about sensor. ++ * ++ * The return size is query-specific and depends on the "sensor_type" ++ * parameter. No more than "size" bytes is returned. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param sensor_type - \c [in] AMDGPU_INFO_SENSOR_* ++ * \param size - \c [in] Size of the returned value. ++ * \param value - \c [out] Pointer to the return value. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_sensor_info(amdgpu_device_handle dev, unsigned sensor_type, ++ unsigned size, void *value); ++ ++/** ++ * Query information about video capabilities ++ * ++ * The return sizeof(struct drm_amdgpu_info_video_caps) ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * \param caps_type - \c [in] AMDGPU_INFO_VIDEO_CAPS_DECODE(ENCODE) ++ * \param size - \c [in] Size of the returned value. ++ * \param value - \c [out] Pointer to the return value. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_query_video_caps_info(amdgpu_device_handle dev, unsigned cap_type, ++ unsigned size, void *value); ++ ++/** ++ * Read a set of consecutive memory-mapped registers. ++ * Not all registers are allowed to be read by userspace. ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize( ++ * \param dword_offset - \c [in] Register offset in dwords ++ * \param count - \c [in] The number of registers to read starting ++ * from the offset ++ * \param instance - \c [in] GRBM_GFX_INDEX selector. It may have other ++ * uses. Set it to 0xffffffff if unsure. ++ * \param flags - \c [in] Flags with additional information. ++ * \param values - \c [out] The pointer to return values. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX error code ++ * ++*/ ++int amdgpu_read_mm_registers(amdgpu_device_handle dev, unsigned dword_offset, ++ unsigned count, uint32_t instance, uint32_t flags, ++ uint32_t *values); ++ ++/** ++ * Flag to request VA address range in the 32bit address space ++*/ ++#define AMDGPU_VA_RANGE_32_BIT 0x1 ++#define AMDGPU_VA_RANGE_HIGH 0x2 ++#define AMDGPU_VA_RANGE_REPLAYABLE 0x4 ++ ++/** ++ * Allocate virtual address range ++ * ++ * \param dev - [in] Device handle. See #amdgpu_device_initialize() ++ * \param va_range_type - \c [in] Type of MC va range from which to allocate ++ * \param size - \c [in] Size of range. Size must be correctly* aligned. ++ * It is client responsibility to correctly aligned size based on the future ++ * usage of allocated range. ++ * \param va_base_alignment - \c [in] Overwrite base address alignment ++ * requirement for GPU VM MC virtual ++ * address assignment. Must be multiple of size alignments received as ++ * 'amdgpu_buffer_size_alignments'. ++ * If 0 use the default one. ++ * \param va_base_required - \c [in] Specified required va base address. ++ * If 0 then library choose available one. ++ * If !0 value will be passed and those value already "in use" then ++ * corresponding error status will be returned. ++ * \param va_base_allocated - \c [out] On return: Allocated VA base to be used ++ * by client. ++ * \param va_range_handle - \c [out] On return: Handle assigned to allocation ++ * \param flags - \c [in] flags for special VA range ++ * ++ * \return 0 on success\n ++ * >0 - AMD specific error code\n ++ * <0 - Negative POSIX Error code ++ * ++ * \notes \n ++ * It is client responsibility to correctly handle VA assignments and usage. ++ * Neither kernel driver nor libdrm_amdpgu are able to prevent and ++ * detect wrong va assignment. ++ * ++ * It is client responsibility to correctly handle multi-GPU cases and to pass ++ * the corresponding arrays of all devices handles where corresponding VA will ++ * be used. ++ * ++*/ ++int amdgpu_va_range_alloc(amdgpu_device_handle dev, ++ enum amdgpu_gpu_va_range va_range_type, ++ uint64_t size, ++ uint64_t va_base_alignment, ++ uint64_t va_base_required, ++ uint64_t *va_base_allocated, ++ amdgpu_va_handle *va_range_handle, ++ uint64_t flags); ++ ++/** ++ * Free previously allocated virtual address range ++ * ++ * ++ * \param va_range_handle - \c [in] Handle assigned to VA allocation ++ * ++ * \return 0 on success\n ++ * >0 - AMD specific error code\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_va_range_free(amdgpu_va_handle va_range_handle); ++ ++/** ++* Query virtual address range ++* ++* UMD can query GPU VM range supported by each device ++* to initialize its own VAM accordingly. ++* ++* \param dev - [in] Device handle. See #amdgpu_device_initialize() ++* \param type - \c [in] Type of virtual address range ++* \param offset - \c [out] Start offset of virtual address range ++* \param size - \c [out] Size of virtual address range ++* ++* \return 0 on success\n ++* <0 - Negative POSIX Error code ++* ++*/ ++ ++int amdgpu_va_range_query(amdgpu_device_handle dev, ++ enum amdgpu_gpu_va_range type, ++ uint64_t *start, ++ uint64_t *end); ++ ++/** ++ * VA mapping/unmapping for the buffer object ++ * ++ * \param bo - \c [in] BO handle ++ * \param offset - \c [in] Start offset to map ++ * \param size - \c [in] Size to map ++ * \param addr - \c [in] Start virtual address. ++ * \param flags - \c [in] Supported flags for mapping/unmapping ++ * \param ops - \c [in] AMDGPU_VA_OP_MAP or AMDGPU_VA_OP_UNMAP ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++ ++int amdgpu_bo_va_op(amdgpu_bo_handle bo, ++ uint64_t offset, ++ uint64_t size, ++ uint64_t addr, ++ uint64_t flags, ++ uint32_t ops); ++ ++/** ++ * VA mapping/unmapping for a buffer object or PRT region. ++ * ++ * This is not a simple drop-in extension for amdgpu_bo_va_op; instead, all ++ * parameters are treated "raw", i.e. size is not automatically aligned, and ++ * all flags must be specified explicitly. ++ * ++ * \param dev - \c [in] device handle ++ * \param bo - \c [in] BO handle (may be NULL) ++ * \param offset - \c [in] Start offset to map ++ * \param size - \c [in] Size to map ++ * \param addr - \c [in] Start virtual address. ++ * \param flags - \c [in] Supported flags for mapping/unmapping ++ * \param ops - \c [in] AMDGPU_VA_OP_MAP or AMDGPU_VA_OP_UNMAP ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++ ++int amdgpu_bo_va_op_raw(amdgpu_device_handle dev, ++ amdgpu_bo_handle bo, ++ uint64_t offset, ++ uint64_t size, ++ uint64_t addr, ++ uint64_t flags, ++ uint32_t ops); ++ ++/** ++ * create semaphore ++ * ++ * \param sem - \c [out] semaphore handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_create_semaphore(amdgpu_semaphore_handle *sem); ++ ++/** ++ * signal semaphore ++ * ++ * \param context - \c [in] GPU Context ++ * \param ip_type - \c [in] Hardware IP block type = AMDGPU_HW_IP_* ++ * \param ip_instance - \c [in] Index of the IP block of the same type ++ * \param ring - \c [in] Specify ring index of the IP ++ * \param sem - \c [in] semaphore handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_signal_semaphore(amdgpu_context_handle ctx, ++ uint32_t ip_type, ++ uint32_t ip_instance, ++ uint32_t ring, ++ amdgpu_semaphore_handle sem); ++ ++/** ++ * wait semaphore ++ * ++ * \param context - \c [in] GPU Context ++ * \param ip_type - \c [in] Hardware IP block type = AMDGPU_HW_IP_* ++ * \param ip_instance - \c [in] Index of the IP block of the same type ++ * \param ring - \c [in] Specify ring index of the IP ++ * \param sem - \c [in] semaphore handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_wait_semaphore(amdgpu_context_handle ctx, ++ uint32_t ip_type, ++ uint32_t ip_instance, ++ uint32_t ring, ++ amdgpu_semaphore_handle sem); ++ ++/** ++ * destroy semaphore ++ * ++ * \param sem - \c [in] semaphore handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_destroy_semaphore(amdgpu_semaphore_handle sem); ++ ++/** ++ * Get the ASIC marketing name ++ * ++ * \param dev - \c [in] Device handle. See #amdgpu_device_initialize() ++ * ++ * \return the constant string of the marketing name ++ * "NULL" means the ASIC is not found ++*/ ++const char *amdgpu_get_marketing_name(amdgpu_device_handle dev); ++ ++/** ++ * Create kernel sync object ++ * ++ * \param dev - \c [in] device handle ++ * \param flags - \c [in] flags that affect creation ++ * \param syncobj - \c [out] sync object handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_create_syncobj2(amdgpu_device_handle dev, ++ uint32_t flags, ++ uint32_t *syncobj); ++ ++/** ++ * Create kernel sync object ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [out] sync object handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_create_syncobj(amdgpu_device_handle dev, ++ uint32_t *syncobj); ++/** ++ * Destroy kernel sync object ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [in] sync object handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_destroy_syncobj(amdgpu_device_handle dev, ++ uint32_t syncobj); ++ ++/** ++ * Reset kernel sync objects to unsignalled state. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobjs - \c [in] array of sync object handles ++ * \param syncobj_count - \c [in] number of handles in syncobjs ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_syncobj_reset(amdgpu_device_handle dev, ++ const uint32_t *syncobjs, uint32_t syncobj_count); ++ ++/** ++ * Signal kernel sync objects. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobjs - \c [in] array of sync object handles ++ * \param syncobj_count - \c [in] number of handles in syncobjs ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_syncobj_signal(amdgpu_device_handle dev, ++ const uint32_t *syncobjs, uint32_t syncobj_count); ++ ++/** ++ * Signal kernel timeline sync objects. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobjs - \c [in] array of sync object handles ++ * \param points - \c [in] array of timeline points ++ * \param syncobj_count - \c [in] number of handles in syncobjs ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_syncobj_timeline_signal(amdgpu_device_handle dev, ++ const uint32_t *syncobjs, ++ uint64_t *points, ++ uint32_t syncobj_count); ++ ++/** ++ * Wait for one or all sync objects to signal. ++ * ++ * \param dev - \c [in] self-explanatory ++ * \param handles - \c [in] array of sync object handles ++ * \param num_handles - \c [in] self-explanatory ++ * \param timeout_nsec - \c [in] self-explanatory ++ * \param flags - \c [in] a bitmask of DRM_SYNCOBJ_WAIT_FLAGS_* ++ * \param first_signaled - \c [in] self-explanatory ++ * ++ * \return 0 on success\n ++ * -ETIME - Timeout ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_wait(amdgpu_device_handle dev, ++ uint32_t *handles, unsigned num_handles, ++ int64_t timeout_nsec, unsigned flags, ++ uint32_t *first_signaled); ++ ++/** ++ * Wait for one or all sync objects on their points to signal. ++ * ++ * \param dev - \c [in] self-explanatory ++ * \param handles - \c [in] array of sync object handles ++ * \param points - \c [in] array of sync points to wait ++ * \param num_handles - \c [in] self-explanatory ++ * \param timeout_nsec - \c [in] self-explanatory ++ * \param flags - \c [in] a bitmask of DRM_SYNCOBJ_WAIT_FLAGS_* ++ * \param first_signaled - \c [in] self-explanatory ++ * ++ * \return 0 on success\n ++ * -ETIME - Timeout ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_timeline_wait(amdgpu_device_handle dev, ++ uint32_t *handles, uint64_t *points, ++ unsigned num_handles, ++ int64_t timeout_nsec, unsigned flags, ++ uint32_t *first_signaled); ++/** ++ * Query sync objects payloads. ++ * ++ * \param dev - \c [in] self-explanatory ++ * \param handles - \c [in] array of sync object handles ++ * \param points - \c [out] array of sync points returned, which presents ++ * syncobj payload. ++ * \param num_handles - \c [in] self-explanatory ++ * ++ * \return 0 on success\n ++ * -ETIME - Timeout ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_query(amdgpu_device_handle dev, ++ uint32_t *handles, uint64_t *points, ++ unsigned num_handles); ++/** ++ * Query sync objects last signaled or submitted point. ++ * ++ * \param dev - \c [in] self-explanatory ++ * \param handles - \c [in] array of sync object handles ++ * \param points - \c [out] array of sync points returned, which presents ++ * syncobj payload. ++ * \param num_handles - \c [in] self-explanatory ++ * \param flags - \c [in] a bitmask of DRM_SYNCOBJ_QUERY_FLAGS_* ++ * ++ * \return 0 on success\n ++ * -ETIME - Timeout ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_query2(amdgpu_device_handle dev, ++ uint32_t *handles, uint64_t *points, ++ unsigned num_handles, uint32_t flags); ++ ++/** ++ * Export kernel sync object to shareable fd. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [in] sync object handle ++ * \param shared_fd - \c [out] shared file descriptor. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_export_syncobj(amdgpu_device_handle dev, ++ uint32_t syncobj, ++ int *shared_fd); ++/** ++ * Import kernel sync object from shareable fd. ++ * ++ * \param dev - \c [in] device handle ++ * \param shared_fd - \c [in] shared file descriptor. ++ * \param syncobj - \c [out] sync object handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++*/ ++int amdgpu_cs_import_syncobj(amdgpu_device_handle dev, ++ int shared_fd, ++ uint32_t *syncobj); ++ ++/** ++ * Export kernel sync object to a sync_file. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [in] sync object handle ++ * \param sync_file_fd - \c [out] sync_file file descriptor. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_export_sync_file(amdgpu_device_handle dev, ++ uint32_t syncobj, ++ int *sync_file_fd); ++ ++/** ++ * Import kernel sync object from a sync_file. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [in] sync object handle ++ * \param sync_file_fd - \c [in] sync_file file descriptor. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_import_sync_file(amdgpu_device_handle dev, ++ uint32_t syncobj, ++ int sync_file_fd); ++/** ++ * Export kernel timeline sync object to a sync_file. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [in] sync object handle ++ * \param point - \c [in] timeline point ++ * \param flags - \c [in] flags ++ * \param sync_file_fd - \c [out] sync_file file descriptor. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_export_sync_file2(amdgpu_device_handle dev, ++ uint32_t syncobj, ++ uint64_t point, ++ uint32_t flags, ++ int *sync_file_fd); ++ ++/** ++ * Import kernel timeline sync object from a sync_file. ++ * ++ * \param dev - \c [in] device handle ++ * \param syncobj - \c [in] sync object handle ++ * \param point - \c [in] timeline point ++ * \param sync_file_fd - \c [in] sync_file file descriptor. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_import_sync_file2(amdgpu_device_handle dev, ++ uint32_t syncobj, ++ uint64_t point, ++ int sync_file_fd); ++ ++/** ++ * transfer between syncbojs. ++ * ++ * \param dev - \c [in] device handle ++ * \param dst_handle - \c [in] sync object handle ++ * \param dst_point - \c [in] timeline point, 0 presents dst is binary ++ * \param src_handle - \c [in] sync object handle ++ * \param src_point - \c [in] timeline point, 0 presents src is binary ++ * \param flags - \c [in] flags ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ */ ++int amdgpu_cs_syncobj_transfer(amdgpu_device_handle dev, ++ uint32_t dst_handle, ++ uint64_t dst_point, ++ uint32_t src_handle, ++ uint64_t src_point, ++ uint32_t flags); ++ ++/** ++ * Export an amdgpu fence as a handle (syncobj or fd). ++ * ++ * \param what AMDGPU_FENCE_TO_HANDLE_GET_{SYNCOBJ, FD} ++ * \param out_handle returned handle ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ */ ++int amdgpu_cs_fence_to_handle(amdgpu_device_handle dev, ++ struct amdgpu_cs_fence *fence, ++ uint32_t what, ++ uint32_t *out_handle); ++ ++/** ++ * Submit raw command submission to kernel ++ * ++ * \param dev - \c [in] device handle ++ * \param context - \c [in] context handle for context id ++ * \param bo_list_handle - \c [in] request bo list handle (0 for none) ++ * \param num_chunks - \c [in] number of CS chunks to submit ++ * \param chunks - \c [in] array of CS chunks ++ * \param seq_no - \c [out] output sequence number for submission. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ */ ++struct drm_amdgpu_cs_chunk; ++struct drm_amdgpu_cs_chunk_dep; ++struct drm_amdgpu_cs_chunk_data; ++ ++int amdgpu_cs_submit_raw(amdgpu_device_handle dev, ++ amdgpu_context_handle context, ++ amdgpu_bo_list_handle bo_list_handle, ++ int num_chunks, ++ struct drm_amdgpu_cs_chunk *chunks, ++ uint64_t *seq_no); ++ ++/** ++ * Submit raw command submission to the kernel with a raw BO list handle. ++ * ++ * \param dev - \c [in] device handle ++ * \param context - \c [in] context handle for context id ++ * \param bo_list_handle - \c [in] raw bo list handle (0 for none) ++ * \param num_chunks - \c [in] number of CS chunks to submit ++ * \param chunks - \c [in] array of CS chunks ++ * \param seq_no - \c [out] output sequence number for submission. ++ * ++ * \return 0 on success\n ++ * <0 - Negative POSIX Error code ++ * ++ * \sa amdgpu_bo_list_create_raw(), amdgpu_bo_list_destroy_raw() ++ */ ++int amdgpu_cs_submit_raw2(amdgpu_device_handle dev, ++ amdgpu_context_handle context, ++ uint32_t bo_list_handle, ++ int num_chunks, ++ struct drm_amdgpu_cs_chunk *chunks, ++ uint64_t *seq_no); ++ ++void amdgpu_cs_chunk_fence_to_dep(struct amdgpu_cs_fence *fence, ++ struct drm_amdgpu_cs_chunk_dep *dep); ++void amdgpu_cs_chunk_fence_info_to_data(struct amdgpu_cs_fence_info *fence_info, ++ struct drm_amdgpu_cs_chunk_data *data); ++ ++/** ++ * Reserve VMID ++ * \param context - \c [in] GPU Context ++ * \param flags - \c [in] TBD ++ * ++ * \return 0 on success otherwise POSIX Error code ++*/ ++int amdgpu_vm_reserve_vmid(amdgpu_device_handle dev, uint32_t flags); ++ ++/** ++ * Free reserved VMID ++ * \param context - \c [in] GPU Context ++ * \param flags - \c [in] TBD ++ * ++ * \return 0 on success otherwise POSIX Error code ++*/ ++int amdgpu_vm_unreserve_vmid(amdgpu_device_handle dev, uint32_t flags); ++ ++#ifdef __cplusplus ++} ++#endif ++#endif /* #ifdef _AMDGPU_H_ */ diff --git a/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu_drm-h.patch b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu_drm-h.patch new file mode 100644 index 0000000..1abd4c3 --- /dev/null +++ b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-amdgpu_drm-h.patch @@ -0,0 +1,1167 @@ +diff --git a/include/libdrm/amdgpu_drm.h b/include/libdrm/amdgpu_drm.h +new file mode 100644 +index 0000000..c0a0ad1 +--- /dev/null ++++ b/include/libdrm/amdgpu_drm.h +@@ -0,0 +1,1161 @@ ++/* amdgpu_drm.h -- Public header for the amdgpu driver -*- linux-c -*- ++ * ++ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas. ++ * Copyright 2000 VA Linux Systems, Inc., Fremont, California. ++ * Copyright 2002 Tungsten Graphics, Inc., Cedar Park, Texas. ++ * Copyright 2014 Advanced Micro Devices, Inc. ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a ++ * copy of this software and associated documentation files (the "Software"), ++ * to deal in the Software without restriction, including without limitation ++ * the rights to use, copy, modify, merge, publish, distribute, sublicense, ++ * and/or sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ++ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR ++ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ++ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ++ * OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * Authors: ++ * Kevin E. Martin ++ * Gareth Hughes ++ * Keith Whitwell ++ */ ++ ++#ifndef __AMDGPU_DRM_H__ ++#define __AMDGPU_DRM_H__ ++ ++#include "drm.h" ++ ++#if defined(__cplusplus) ++extern "C" { ++#endif ++ ++#define DRM_AMDGPU_GEM_CREATE 0x00 ++#define DRM_AMDGPU_GEM_MMAP 0x01 ++#define DRM_AMDGPU_CTX 0x02 ++#define DRM_AMDGPU_BO_LIST 0x03 ++#define DRM_AMDGPU_CS 0x04 ++#define DRM_AMDGPU_INFO 0x05 ++#define DRM_AMDGPU_GEM_METADATA 0x06 ++#define DRM_AMDGPU_GEM_WAIT_IDLE 0x07 ++#define DRM_AMDGPU_GEM_VA 0x08 ++#define DRM_AMDGPU_WAIT_CS 0x09 ++#define DRM_AMDGPU_GEM_OP 0x10 ++#define DRM_AMDGPU_GEM_USERPTR 0x11 ++#define DRM_AMDGPU_WAIT_FENCES 0x12 ++#define DRM_AMDGPU_VM 0x13 ++#define DRM_AMDGPU_FENCE_TO_HANDLE 0x14 ++#define DRM_AMDGPU_SCHED 0x15 ++ ++#define DRM_IOCTL_AMDGPU_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_CREATE, union drm_amdgpu_gem_create) ++#define DRM_IOCTL_AMDGPU_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_MMAP, union drm_amdgpu_gem_mmap) ++#define DRM_IOCTL_AMDGPU_CTX DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_CTX, union drm_amdgpu_ctx) ++#define DRM_IOCTL_AMDGPU_BO_LIST DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_BO_LIST, union drm_amdgpu_bo_list) ++#define DRM_IOCTL_AMDGPU_CS DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_CS, union drm_amdgpu_cs) ++#define DRM_IOCTL_AMDGPU_INFO DRM_IOW(DRM_COMMAND_BASE + DRM_AMDGPU_INFO, struct drm_amdgpu_info) ++#define DRM_IOCTL_AMDGPU_GEM_METADATA DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_METADATA, struct drm_amdgpu_gem_metadata) ++#define DRM_IOCTL_AMDGPU_GEM_WAIT_IDLE DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_WAIT_IDLE, union drm_amdgpu_gem_wait_idle) ++#define DRM_IOCTL_AMDGPU_GEM_VA DRM_IOW(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_VA, struct drm_amdgpu_gem_va) ++#define DRM_IOCTL_AMDGPU_WAIT_CS DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_WAIT_CS, union drm_amdgpu_wait_cs) ++#define DRM_IOCTL_AMDGPU_GEM_OP DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_OP, struct drm_amdgpu_gem_op) ++#define DRM_IOCTL_AMDGPU_GEM_USERPTR DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_GEM_USERPTR, struct drm_amdgpu_gem_userptr) ++#define DRM_IOCTL_AMDGPU_WAIT_FENCES DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_WAIT_FENCES, union drm_amdgpu_wait_fences) ++#define DRM_IOCTL_AMDGPU_VM DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_VM, union drm_amdgpu_vm) ++#define DRM_IOCTL_AMDGPU_FENCE_TO_HANDLE DRM_IOWR(DRM_COMMAND_BASE + DRM_AMDGPU_FENCE_TO_HANDLE, union drm_amdgpu_fence_to_handle) ++#define DRM_IOCTL_AMDGPU_SCHED DRM_IOW(DRM_COMMAND_BASE + DRM_AMDGPU_SCHED, union drm_amdgpu_sched) ++ ++/** ++ * DOC: memory domains ++ * ++ * %AMDGPU_GEM_DOMAIN_CPU System memory that is not GPU accessible. ++ * Memory in this pool could be swapped out to disk if there is pressure. ++ * ++ * %AMDGPU_GEM_DOMAIN_GTT GPU accessible system memory, mapped into the ++ * GPU's virtual address space via gart. Gart memory linearizes non-contiguous ++ * pages of system memory, allows GPU access system memory in a linearized ++ * fashion. ++ * ++ * %AMDGPU_GEM_DOMAIN_VRAM Local video memory. For APUs, it is memory ++ * carved out by the BIOS. ++ * ++ * %AMDGPU_GEM_DOMAIN_GDS Global on-chip data storage used to share data ++ * across shader threads. ++ * ++ * %AMDGPU_GEM_DOMAIN_GWS Global wave sync, used to synchronize the ++ * execution of all the waves on a device. ++ * ++ * %AMDGPU_GEM_DOMAIN_OA Ordered append, used by 3D or Compute engines ++ * for appending data. ++ */ ++#define AMDGPU_GEM_DOMAIN_CPU 0x1 ++#define AMDGPU_GEM_DOMAIN_GTT 0x2 ++#define AMDGPU_GEM_DOMAIN_VRAM 0x4 ++#define AMDGPU_GEM_DOMAIN_GDS 0x8 ++#define AMDGPU_GEM_DOMAIN_GWS 0x10 ++#define AMDGPU_GEM_DOMAIN_OA 0x20 ++#define AMDGPU_GEM_DOMAIN_MASK (AMDGPU_GEM_DOMAIN_CPU | \ ++ AMDGPU_GEM_DOMAIN_GTT | \ ++ AMDGPU_GEM_DOMAIN_VRAM | \ ++ AMDGPU_GEM_DOMAIN_GDS | \ ++ AMDGPU_GEM_DOMAIN_GWS | \ ++ AMDGPU_GEM_DOMAIN_OA) ++ ++/* Flag that CPU access will be required for the case of VRAM domain */ ++#define AMDGPU_GEM_CREATE_CPU_ACCESS_REQUIRED (1 << 0) ++/* Flag that CPU access will not work, this VRAM domain is invisible */ ++#define AMDGPU_GEM_CREATE_NO_CPU_ACCESS (1 << 1) ++/* Flag that USWC attributes should be used for GTT */ ++#define AMDGPU_GEM_CREATE_CPU_GTT_USWC (1 << 2) ++/* Flag that the memory should be in VRAM and cleared */ ++#define AMDGPU_GEM_CREATE_VRAM_CLEARED (1 << 3) ++/* Flag that allocating the BO should use linear VRAM */ ++#define AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS (1 << 5) ++/* Flag that BO is always valid in this VM */ ++#define AMDGPU_GEM_CREATE_VM_ALWAYS_VALID (1 << 6) ++/* Flag that BO sharing will be explicitly synchronized */ ++#define AMDGPU_GEM_CREATE_EXPLICIT_SYNC (1 << 7) ++/* Flag that indicates allocating MQD gart on GFX9, where the mtype ++ * for the second page onward should be set to NC. It should never ++ * be used by user space applications. ++ */ ++#define AMDGPU_GEM_CREATE_CP_MQD_GFX9 (1 << 8) ++/* Flag that BO may contain sensitive data that must be wiped before ++ * releasing the memory ++ */ ++#define AMDGPU_GEM_CREATE_VRAM_WIPE_ON_RELEASE (1 << 9) ++/* Flag that BO will be encrypted and that the TMZ bit should be ++ * set in the PTEs when mapping this buffer via GPUVM or ++ * accessing it with various hw blocks ++ */ ++#define AMDGPU_GEM_CREATE_ENCRYPTED (1 << 10) ++/* Flag that BO will be used only in preemptible context, which does ++ * not require GTT memory accounting ++ */ ++#define AMDGPU_GEM_CREATE_PREEMPTIBLE (1 << 11) ++ ++struct drm_amdgpu_gem_create_in { ++ /** the requested memory size */ ++ __u64 bo_size; ++ /** physical start_addr alignment in bytes for some HW requirements */ ++ __u64 alignment; ++ /** the requested memory domains */ ++ __u64 domains; ++ /** allocation flags */ ++ __u64 domain_flags; ++}; ++ ++struct drm_amdgpu_gem_create_out { ++ /** returned GEM object handle */ ++ __u32 handle; ++ __u32 _pad; ++}; ++ ++union drm_amdgpu_gem_create { ++ struct drm_amdgpu_gem_create_in in; ++ struct drm_amdgpu_gem_create_out out; ++}; ++ ++/** Opcode to create new residency list. */ ++#define AMDGPU_BO_LIST_OP_CREATE 0 ++/** Opcode to destroy previously created residency list */ ++#define AMDGPU_BO_LIST_OP_DESTROY 1 ++/** Opcode to update resource information in the list */ ++#define AMDGPU_BO_LIST_OP_UPDATE 2 ++ ++struct drm_amdgpu_bo_list_in { ++ /** Type of operation */ ++ __u32 operation; ++ /** Handle of list or 0 if we want to create one */ ++ __u32 list_handle; ++ /** Number of BOs in list */ ++ __u32 bo_number; ++ /** Size of each element describing BO */ ++ __u32 bo_info_size; ++ /** Pointer to array describing BOs */ ++ __u64 bo_info_ptr; ++}; ++ ++struct drm_amdgpu_bo_list_entry { ++ /** Handle of BO */ ++ __u32 bo_handle; ++ /** New (if specified) BO priority to be used during migration */ ++ __u32 bo_priority; ++}; ++ ++struct drm_amdgpu_bo_list_out { ++ /** Handle of resource list */ ++ __u32 list_handle; ++ __u32 _pad; ++}; ++ ++union drm_amdgpu_bo_list { ++ struct drm_amdgpu_bo_list_in in; ++ struct drm_amdgpu_bo_list_out out; ++}; ++ ++/* context related */ ++#define AMDGPU_CTX_OP_ALLOC_CTX 1 ++#define AMDGPU_CTX_OP_FREE_CTX 2 ++#define AMDGPU_CTX_OP_QUERY_STATE 3 ++#define AMDGPU_CTX_OP_QUERY_STATE2 4 ++#define AMDGPU_CTX_OP_GET_STABLE_PSTATE 5 ++#define AMDGPU_CTX_OP_SET_STABLE_PSTATE 6 ++ ++/* GPU reset status */ ++#define AMDGPU_CTX_NO_RESET 0 ++/* this the context caused it */ ++#define AMDGPU_CTX_GUILTY_RESET 1 ++/* some other context caused it */ ++#define AMDGPU_CTX_INNOCENT_RESET 2 ++/* unknown cause */ ++#define AMDGPU_CTX_UNKNOWN_RESET 3 ++ ++/* indicate gpu reset occured after ctx created */ ++#define AMDGPU_CTX_QUERY2_FLAGS_RESET (1<<0) ++/* indicate vram lost occured after ctx created */ ++#define AMDGPU_CTX_QUERY2_FLAGS_VRAMLOST (1<<1) ++/* indicate some job from this context once cause gpu hang */ ++#define AMDGPU_CTX_QUERY2_FLAGS_GUILTY (1<<2) ++/* indicate some errors are detected by RAS */ ++#define AMDGPU_CTX_QUERY2_FLAGS_RAS_CE (1<<3) ++#define AMDGPU_CTX_QUERY2_FLAGS_RAS_UE (1<<4) ++ ++/* Context priority level */ ++#define AMDGPU_CTX_PRIORITY_UNSET -2048 ++#define AMDGPU_CTX_PRIORITY_VERY_LOW -1023 ++#define AMDGPU_CTX_PRIORITY_LOW -512 ++#define AMDGPU_CTX_PRIORITY_NORMAL 0 ++/* ++ * When used in struct drm_amdgpu_ctx_in, a priority above NORMAL requires ++ * CAP_SYS_NICE or DRM_MASTER ++*/ ++#define AMDGPU_CTX_PRIORITY_HIGH 512 ++#define AMDGPU_CTX_PRIORITY_VERY_HIGH 1023 ++ ++/* select a stable profiling pstate for perfmon tools */ ++#define AMDGPU_CTX_STABLE_PSTATE_FLAGS_MASK 0xf ++#define AMDGPU_CTX_STABLE_PSTATE_NONE 0 ++#define AMDGPU_CTX_STABLE_PSTATE_STANDARD 1 ++#define AMDGPU_CTX_STABLE_PSTATE_MIN_SCLK 2 ++#define AMDGPU_CTX_STABLE_PSTATE_MIN_MCLK 3 ++#define AMDGPU_CTX_STABLE_PSTATE_PEAK 4 ++ ++struct drm_amdgpu_ctx_in { ++ /** AMDGPU_CTX_OP_* */ ++ __u32 op; ++ /** Flags */ ++ __u32 flags; ++ __u32 ctx_id; ++ /** AMDGPU_CTX_PRIORITY_* */ ++ __s32 priority; ++}; ++ ++union drm_amdgpu_ctx_out { ++ struct { ++ __u32 ctx_id; ++ __u32 _pad; ++ } alloc; ++ ++ struct { ++ /** For future use, no flags defined so far */ ++ __u64 flags; ++ /** Number of resets caused by this context so far. */ ++ __u32 hangs; ++ /** Reset status since the last call of the ioctl. */ ++ __u32 reset_status; ++ } state; ++ ++ struct { ++ __u32 flags; ++ __u32 _pad; ++ } pstate; ++}; ++ ++union drm_amdgpu_ctx { ++ struct drm_amdgpu_ctx_in in; ++ union drm_amdgpu_ctx_out out; ++}; ++ ++/* vm ioctl */ ++#define AMDGPU_VM_OP_RESERVE_VMID 1 ++#define AMDGPU_VM_OP_UNRESERVE_VMID 2 ++ ++struct drm_amdgpu_vm_in { ++ /** AMDGPU_VM_OP_* */ ++ __u32 op; ++ __u32 flags; ++}; ++ ++struct drm_amdgpu_vm_out { ++ /** For future use, no flags defined so far */ ++ __u64 flags; ++}; ++ ++union drm_amdgpu_vm { ++ struct drm_amdgpu_vm_in in; ++ struct drm_amdgpu_vm_out out; ++}; ++ ++/* sched ioctl */ ++#define AMDGPU_SCHED_OP_PROCESS_PRIORITY_OVERRIDE 1 ++#define AMDGPU_SCHED_OP_CONTEXT_PRIORITY_OVERRIDE 2 ++ ++struct drm_amdgpu_sched_in { ++ /* AMDGPU_SCHED_OP_* */ ++ __u32 op; ++ __u32 fd; ++ /** AMDGPU_CTX_PRIORITY_* */ ++ __s32 priority; ++ __u32 ctx_id; ++}; ++ ++union drm_amdgpu_sched { ++ struct drm_amdgpu_sched_in in; ++}; ++ ++/* ++ * This is not a reliable API and you should expect it to fail for any ++ * number of reasons and have fallback path that do not use userptr to ++ * perform any operation. ++ */ ++#define AMDGPU_GEM_USERPTR_READONLY (1 << 0) ++#define AMDGPU_GEM_USERPTR_ANONONLY (1 << 1) ++#define AMDGPU_GEM_USERPTR_VALIDATE (1 << 2) ++#define AMDGPU_GEM_USERPTR_REGISTER (1 << 3) ++ ++struct drm_amdgpu_gem_userptr { ++ __u64 addr; ++ __u64 size; ++ /* AMDGPU_GEM_USERPTR_* */ ++ __u32 flags; ++ /* Resulting GEM handle */ ++ __u32 handle; ++}; ++ ++/* SI-CI-VI: */ ++/* same meaning as the GB_TILE_MODE and GL_MACRO_TILE_MODE fields */ ++#define AMDGPU_TILING_ARRAY_MODE_SHIFT 0 ++#define AMDGPU_TILING_ARRAY_MODE_MASK 0xf ++#define AMDGPU_TILING_PIPE_CONFIG_SHIFT 4 ++#define AMDGPU_TILING_PIPE_CONFIG_MASK 0x1f ++#define AMDGPU_TILING_TILE_SPLIT_SHIFT 9 ++#define AMDGPU_TILING_TILE_SPLIT_MASK 0x7 ++#define AMDGPU_TILING_MICRO_TILE_MODE_SHIFT 12 ++#define AMDGPU_TILING_MICRO_TILE_MODE_MASK 0x7 ++#define AMDGPU_TILING_BANK_WIDTH_SHIFT 15 ++#define AMDGPU_TILING_BANK_WIDTH_MASK 0x3 ++#define AMDGPU_TILING_BANK_HEIGHT_SHIFT 17 ++#define AMDGPU_TILING_BANK_HEIGHT_MASK 0x3 ++#define AMDGPU_TILING_MACRO_TILE_ASPECT_SHIFT 19 ++#define AMDGPU_TILING_MACRO_TILE_ASPECT_MASK 0x3 ++#define AMDGPU_TILING_NUM_BANKS_SHIFT 21 ++#define AMDGPU_TILING_NUM_BANKS_MASK 0x3 ++ ++/* GFX9 and later: */ ++#define AMDGPU_TILING_SWIZZLE_MODE_SHIFT 0 ++#define AMDGPU_TILING_SWIZZLE_MODE_MASK 0x1f ++#define AMDGPU_TILING_DCC_OFFSET_256B_SHIFT 5 ++#define AMDGPU_TILING_DCC_OFFSET_256B_MASK 0xFFFFFF ++#define AMDGPU_TILING_DCC_PITCH_MAX_SHIFT 29 ++#define AMDGPU_TILING_DCC_PITCH_MAX_MASK 0x3FFF ++#define AMDGPU_TILING_DCC_INDEPENDENT_64B_SHIFT 43 ++#define AMDGPU_TILING_DCC_INDEPENDENT_64B_MASK 0x1 ++#define AMDGPU_TILING_DCC_INDEPENDENT_128B_SHIFT 44 ++#define AMDGPU_TILING_DCC_INDEPENDENT_128B_MASK 0x1 ++#define AMDGPU_TILING_SCANOUT_SHIFT 63 ++#define AMDGPU_TILING_SCANOUT_MASK 0x1 ++ ++/* Set/Get helpers for tiling flags. */ ++#define AMDGPU_TILING_SET(field, value) \ ++ (((__u64)(value) & AMDGPU_TILING_##field##_MASK) << AMDGPU_TILING_##field##_SHIFT) ++#define AMDGPU_TILING_GET(value, field) \ ++ (((__u64)(value) >> AMDGPU_TILING_##field##_SHIFT) & AMDGPU_TILING_##field##_MASK) ++ ++#define AMDGPU_GEM_METADATA_OP_SET_METADATA 1 ++#define AMDGPU_GEM_METADATA_OP_GET_METADATA 2 ++ ++/** The same structure is shared for input/output */ ++struct drm_amdgpu_gem_metadata { ++ /** GEM Object handle */ ++ __u32 handle; ++ /** Do we want get or set metadata */ ++ __u32 op; ++ struct { ++ /** For future use, no flags defined so far */ ++ __u64 flags; ++ /** family specific tiling info */ ++ __u64 tiling_info; ++ __u32 data_size_bytes; ++ __u32 data[64]; ++ } data; ++}; ++ ++struct drm_amdgpu_gem_mmap_in { ++ /** the GEM object handle */ ++ __u32 handle; ++ __u32 _pad; ++}; ++ ++struct drm_amdgpu_gem_mmap_out { ++ /** mmap offset from the vma offset manager */ ++ __u64 addr_ptr; ++}; ++ ++union drm_amdgpu_gem_mmap { ++ struct drm_amdgpu_gem_mmap_in in; ++ struct drm_amdgpu_gem_mmap_out out; ++}; ++ ++struct drm_amdgpu_gem_wait_idle_in { ++ /** GEM object handle */ ++ __u32 handle; ++ /** For future use, no flags defined so far */ ++ __u32 flags; ++ /** Absolute timeout to wait */ ++ __u64 timeout; ++}; ++ ++struct drm_amdgpu_gem_wait_idle_out { ++ /** BO status: 0 - BO is idle, 1 - BO is busy */ ++ __u32 status; ++ /** Returned current memory domain */ ++ __u32 domain; ++}; ++ ++union drm_amdgpu_gem_wait_idle { ++ struct drm_amdgpu_gem_wait_idle_in in; ++ struct drm_amdgpu_gem_wait_idle_out out; ++}; ++ ++struct drm_amdgpu_wait_cs_in { ++ /* Command submission handle ++ * handle equals 0 means none to wait for ++ * handle equals ~0ull means wait for the latest sequence number ++ */ ++ __u64 handle; ++ /** Absolute timeout to wait */ ++ __u64 timeout; ++ __u32 ip_type; ++ __u32 ip_instance; ++ __u32 ring; ++ __u32 ctx_id; ++}; ++ ++struct drm_amdgpu_wait_cs_out { ++ /** CS status: 0 - CS completed, 1 - CS still busy */ ++ __u64 status; ++}; ++ ++union drm_amdgpu_wait_cs { ++ struct drm_amdgpu_wait_cs_in in; ++ struct drm_amdgpu_wait_cs_out out; ++}; ++ ++struct drm_amdgpu_fence { ++ __u32 ctx_id; ++ __u32 ip_type; ++ __u32 ip_instance; ++ __u32 ring; ++ __u64 seq_no; ++}; ++ ++struct drm_amdgpu_wait_fences_in { ++ /** This points to uint64_t * which points to fences */ ++ __u64 fences; ++ __u32 fence_count; ++ __u32 wait_all; ++ __u64 timeout_ns; ++}; ++ ++struct drm_amdgpu_wait_fences_out { ++ __u32 status; ++ __u32 first_signaled; ++}; ++ ++union drm_amdgpu_wait_fences { ++ struct drm_amdgpu_wait_fences_in in; ++ struct drm_amdgpu_wait_fences_out out; ++}; ++ ++#define AMDGPU_GEM_OP_GET_GEM_CREATE_INFO 0 ++#define AMDGPU_GEM_OP_SET_PLACEMENT 1 ++ ++/* Sets or returns a value associated with a buffer. */ ++struct drm_amdgpu_gem_op { ++ /** GEM object handle */ ++ __u32 handle; ++ /** AMDGPU_GEM_OP_* */ ++ __u32 op; ++ /** Input or return value */ ++ __u64 value; ++}; ++ ++#define AMDGPU_VA_OP_MAP 1 ++#define AMDGPU_VA_OP_UNMAP 2 ++#define AMDGPU_VA_OP_CLEAR 3 ++#define AMDGPU_VA_OP_REPLACE 4 ++ ++/* Delay the page table update till the next CS */ ++#define AMDGPU_VM_DELAY_UPDATE (1 << 0) ++ ++/* Mapping flags */ ++/* readable mapping */ ++#define AMDGPU_VM_PAGE_READABLE (1 << 1) ++/* writable mapping */ ++#define AMDGPU_VM_PAGE_WRITEABLE (1 << 2) ++/* executable mapping, new for VI */ ++#define AMDGPU_VM_PAGE_EXECUTABLE (1 << 3) ++/* partially resident texture */ ++#define AMDGPU_VM_PAGE_PRT (1 << 4) ++/* MTYPE flags use bit 5 to 8 */ ++#define AMDGPU_VM_MTYPE_MASK (0xf << 5) ++/* Default MTYPE. Pre-AI must use this. Recommended for newer ASICs. */ ++#define AMDGPU_VM_MTYPE_DEFAULT (0 << 5) ++/* Use Non Coherent MTYPE instead of default MTYPE */ ++#define AMDGPU_VM_MTYPE_NC (1 << 5) ++/* Use Write Combine MTYPE instead of default MTYPE */ ++#define AMDGPU_VM_MTYPE_WC (2 << 5) ++/* Use Cache Coherent MTYPE instead of default MTYPE */ ++#define AMDGPU_VM_MTYPE_CC (3 << 5) ++/* Use UnCached MTYPE instead of default MTYPE */ ++#define AMDGPU_VM_MTYPE_UC (4 << 5) ++/* Use Read Write MTYPE instead of default MTYPE */ ++#define AMDGPU_VM_MTYPE_RW (5 << 5) ++ ++struct drm_amdgpu_gem_va { ++ /** GEM object handle */ ++ __u32 handle; ++ __u32 _pad; ++ /** AMDGPU_VA_OP_* */ ++ __u32 operation; ++ /** AMDGPU_VM_PAGE_* */ ++ __u32 flags; ++ /** va address to assign . Must be correctly aligned.*/ ++ __u64 va_address; ++ /** Specify offset inside of BO to assign. Must be correctly aligned.*/ ++ __u64 offset_in_bo; ++ /** Specify mapping size. Must be correctly aligned. */ ++ __u64 map_size; ++}; ++ ++#define AMDGPU_HW_IP_GFX 0 ++#define AMDGPU_HW_IP_COMPUTE 1 ++#define AMDGPU_HW_IP_DMA 2 ++#define AMDGPU_HW_IP_UVD 3 ++#define AMDGPU_HW_IP_VCE 4 ++#define AMDGPU_HW_IP_UVD_ENC 5 ++#define AMDGPU_HW_IP_VCN_DEC 6 ++/* ++ * From VCN4, AMDGPU_HW_IP_VCN_ENC is re-used to support ++ * both encoding and decoding jobs. ++ */ ++#define AMDGPU_HW_IP_VCN_ENC 7 ++#define AMDGPU_HW_IP_VCN_JPEG 8 ++#define AMDGPU_HW_IP_NUM 9 ++ ++#define AMDGPU_HW_IP_INSTANCE_MAX_COUNT 1 ++ ++#define AMDGPU_CHUNK_ID_IB 0x01 ++#define AMDGPU_CHUNK_ID_FENCE 0x02 ++#define AMDGPU_CHUNK_ID_DEPENDENCIES 0x03 ++#define AMDGPU_CHUNK_ID_SYNCOBJ_IN 0x04 ++#define AMDGPU_CHUNK_ID_SYNCOBJ_OUT 0x05 ++#define AMDGPU_CHUNK_ID_BO_HANDLES 0x06 ++#define AMDGPU_CHUNK_ID_SCHEDULED_DEPENDENCIES 0x07 ++#define AMDGPU_CHUNK_ID_SYNCOBJ_TIMELINE_WAIT 0x08 ++#define AMDGPU_CHUNK_ID_SYNCOBJ_TIMELINE_SIGNAL 0x09 ++ ++struct drm_amdgpu_cs_chunk { ++ __u32 chunk_id; ++ __u32 length_dw; ++ __u64 chunk_data; ++}; ++ ++struct drm_amdgpu_cs_in { ++ /** Rendering context id */ ++ __u32 ctx_id; ++ /** Handle of resource list associated with CS */ ++ __u32 bo_list_handle; ++ __u32 num_chunks; ++ __u32 flags; ++ /** this points to __u64 * which point to cs chunks */ ++ __u64 chunks; ++}; ++ ++struct drm_amdgpu_cs_out { ++ __u64 handle; ++}; ++ ++union drm_amdgpu_cs { ++ struct drm_amdgpu_cs_in in; ++ struct drm_amdgpu_cs_out out; ++}; ++ ++/* Specify flags to be used for IB */ ++ ++/* This IB should be submitted to CE */ ++#define AMDGPU_IB_FLAG_CE (1<<0) ++ ++/* Preamble flag, which means the IB could be dropped if no context switch */ ++#define AMDGPU_IB_FLAG_PREAMBLE (1<<1) ++ ++/* Preempt flag, IB should set Pre_enb bit if PREEMPT flag detected */ ++#define AMDGPU_IB_FLAG_PREEMPT (1<<2) ++ ++/* The IB fence should do the L2 writeback but not invalidate any shader ++ * caches (L2/vL1/sL1/I$). */ ++#define AMDGPU_IB_FLAG_TC_WB_NOT_INVALIDATE (1 << 3) ++ ++/* Set GDS_COMPUTE_MAX_WAVE_ID = DEFAULT before PACKET3_INDIRECT_BUFFER. ++ * This will reset wave ID counters for the IB. ++ */ ++#define AMDGPU_IB_FLAG_RESET_GDS_MAX_WAVE_ID (1 << 4) ++ ++/* Flag the IB as secure (TMZ) ++ */ ++#define AMDGPU_IB_FLAGS_SECURE (1 << 5) ++ ++/* Tell KMD to flush and invalidate caches ++ */ ++#define AMDGPU_IB_FLAG_EMIT_MEM_SYNC (1 << 6) ++ ++struct drm_amdgpu_cs_chunk_ib { ++ __u32 _pad; ++ /** AMDGPU_IB_FLAG_* */ ++ __u32 flags; ++ /** Virtual address to begin IB execution */ ++ __u64 va_start; ++ /** Size of submission */ ++ __u32 ib_bytes; ++ /** HW IP to submit to */ ++ __u32 ip_type; ++ /** HW IP index of the same type to submit to */ ++ __u32 ip_instance; ++ /** Ring index to submit to */ ++ __u32 ring; ++}; ++ ++struct drm_amdgpu_cs_chunk_dep { ++ __u32 ip_type; ++ __u32 ip_instance; ++ __u32 ring; ++ __u32 ctx_id; ++ __u64 handle; ++}; ++ ++struct drm_amdgpu_cs_chunk_fence { ++ __u32 handle; ++ __u32 offset; ++}; ++ ++struct drm_amdgpu_cs_chunk_sem { ++ __u32 handle; ++}; ++ ++struct drm_amdgpu_cs_chunk_syncobj { ++ __u32 handle; ++ __u32 flags; ++ __u64 point; ++}; ++ ++#define AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ 0 ++#define AMDGPU_FENCE_TO_HANDLE_GET_SYNCOBJ_FD 1 ++#define AMDGPU_FENCE_TO_HANDLE_GET_SYNC_FILE_FD 2 ++ ++union drm_amdgpu_fence_to_handle { ++ struct { ++ struct drm_amdgpu_fence fence; ++ __u32 what; ++ __u32 pad; ++ } in; ++ struct { ++ __u32 handle; ++ } out; ++}; ++ ++struct drm_amdgpu_cs_chunk_data { ++ union { ++ struct drm_amdgpu_cs_chunk_ib ib_data; ++ struct drm_amdgpu_cs_chunk_fence fence_data; ++ }; ++}; ++ ++/* ++ * Query h/w info: Flag that this is integrated (a.h.a. fusion) GPU ++ * ++ */ ++#define AMDGPU_IDS_FLAGS_FUSION 0x1 ++#define AMDGPU_IDS_FLAGS_PREEMPTION 0x2 ++#define AMDGPU_IDS_FLAGS_TMZ 0x4 ++ ++/* indicate if acceleration can be working */ ++#define AMDGPU_INFO_ACCEL_WORKING 0x00 ++/* get the crtc_id from the mode object id? */ ++#define AMDGPU_INFO_CRTC_FROM_ID 0x01 ++/* query hw IP info */ ++#define AMDGPU_INFO_HW_IP_INFO 0x02 ++/* query hw IP instance count for the specified type */ ++#define AMDGPU_INFO_HW_IP_COUNT 0x03 ++/* timestamp for GL_ARB_timer_query */ ++#define AMDGPU_INFO_TIMESTAMP 0x05 ++/* Query the firmware version */ ++#define AMDGPU_INFO_FW_VERSION 0x0e ++ /* Subquery id: Query VCE firmware version */ ++ #define AMDGPU_INFO_FW_VCE 0x1 ++ /* Subquery id: Query UVD firmware version */ ++ #define AMDGPU_INFO_FW_UVD 0x2 ++ /* Subquery id: Query GMC firmware version */ ++ #define AMDGPU_INFO_FW_GMC 0x03 ++ /* Subquery id: Query GFX ME firmware version */ ++ #define AMDGPU_INFO_FW_GFX_ME 0x04 ++ /* Subquery id: Query GFX PFP firmware version */ ++ #define AMDGPU_INFO_FW_GFX_PFP 0x05 ++ /* Subquery id: Query GFX CE firmware version */ ++ #define AMDGPU_INFO_FW_GFX_CE 0x06 ++ /* Subquery id: Query GFX RLC firmware version */ ++ #define AMDGPU_INFO_FW_GFX_RLC 0x07 ++ /* Subquery id: Query GFX MEC firmware version */ ++ #define AMDGPU_INFO_FW_GFX_MEC 0x08 ++ /* Subquery id: Query SMC firmware version */ ++ #define AMDGPU_INFO_FW_SMC 0x0a ++ /* Subquery id: Query SDMA firmware version */ ++ #define AMDGPU_INFO_FW_SDMA 0x0b ++ /* Subquery id: Query PSP SOS firmware version */ ++ #define AMDGPU_INFO_FW_SOS 0x0c ++ /* Subquery id: Query PSP ASD firmware version */ ++ #define AMDGPU_INFO_FW_ASD 0x0d ++ /* Subquery id: Query VCN firmware version */ ++ #define AMDGPU_INFO_FW_VCN 0x0e ++ /* Subquery id: Query GFX RLC SRLC firmware version */ ++ #define AMDGPU_INFO_FW_GFX_RLC_RESTORE_LIST_CNTL 0x0f ++ /* Subquery id: Query GFX RLC SRLG firmware version */ ++ #define AMDGPU_INFO_FW_GFX_RLC_RESTORE_LIST_GPM_MEM 0x10 ++ /* Subquery id: Query GFX RLC SRLS firmware version */ ++ #define AMDGPU_INFO_FW_GFX_RLC_RESTORE_LIST_SRM_MEM 0x11 ++ /* Subquery id: Query DMCU firmware version */ ++ #define AMDGPU_INFO_FW_DMCU 0x12 ++ #define AMDGPU_INFO_FW_TA 0x13 ++ /* Subquery id: Query DMCUB firmware version */ ++ #define AMDGPU_INFO_FW_DMCUB 0x14 ++ /* Subquery id: Query TOC firmware version */ ++ #define AMDGPU_INFO_FW_TOC 0x15 ++ ++/* number of bytes moved for TTM migration */ ++#define AMDGPU_INFO_NUM_BYTES_MOVED 0x0f ++/* the used VRAM size */ ++#define AMDGPU_INFO_VRAM_USAGE 0x10 ++/* the used GTT size */ ++#define AMDGPU_INFO_GTT_USAGE 0x11 ++/* Information about GDS, etc. resource configuration */ ++#define AMDGPU_INFO_GDS_CONFIG 0x13 ++/* Query information about VRAM and GTT domains */ ++#define AMDGPU_INFO_VRAM_GTT 0x14 ++/* Query information about register in MMR address space*/ ++#define AMDGPU_INFO_READ_MMR_REG 0x15 ++/* Query information about device: rev id, family, etc. */ ++#define AMDGPU_INFO_DEV_INFO 0x16 ++/* visible vram usage */ ++#define AMDGPU_INFO_VIS_VRAM_USAGE 0x17 ++/* number of TTM buffer evictions */ ++#define AMDGPU_INFO_NUM_EVICTIONS 0x18 ++/* Query memory about VRAM and GTT domains */ ++#define AMDGPU_INFO_MEMORY 0x19 ++/* Query vce clock table */ ++#define AMDGPU_INFO_VCE_CLOCK_TABLE 0x1A ++/* Query vbios related information */ ++#define AMDGPU_INFO_VBIOS 0x1B ++ /* Subquery id: Query vbios size */ ++ #define AMDGPU_INFO_VBIOS_SIZE 0x1 ++ /* Subquery id: Query vbios image */ ++ #define AMDGPU_INFO_VBIOS_IMAGE 0x2 ++ /* Subquery id: Query vbios info */ ++ #define AMDGPU_INFO_VBIOS_INFO 0x3 ++/* Query UVD handles */ ++#define AMDGPU_INFO_NUM_HANDLES 0x1C ++/* Query sensor related information */ ++#define AMDGPU_INFO_SENSOR 0x1D ++ /* Subquery id: Query GPU shader clock */ ++ #define AMDGPU_INFO_SENSOR_GFX_SCLK 0x1 ++ /* Subquery id: Query GPU memory clock */ ++ #define AMDGPU_INFO_SENSOR_GFX_MCLK 0x2 ++ /* Subquery id: Query GPU temperature */ ++ #define AMDGPU_INFO_SENSOR_GPU_TEMP 0x3 ++ /* Subquery id: Query GPU load */ ++ #define AMDGPU_INFO_SENSOR_GPU_LOAD 0x4 ++ /* Subquery id: Query average GPU power */ ++ #define AMDGPU_INFO_SENSOR_GPU_AVG_POWER 0x5 ++ /* Subquery id: Query northbridge voltage */ ++ #define AMDGPU_INFO_SENSOR_VDDNB 0x6 ++ /* Subquery id: Query graphics voltage */ ++ #define AMDGPU_INFO_SENSOR_VDDGFX 0x7 ++ /* Subquery id: Query GPU stable pstate shader clock */ ++ #define AMDGPU_INFO_SENSOR_STABLE_PSTATE_GFX_SCLK 0x8 ++ /* Subquery id: Query GPU stable pstate memory clock */ ++ #define AMDGPU_INFO_SENSOR_STABLE_PSTATE_GFX_MCLK 0x9 ++/* Number of VRAM page faults on CPU access. */ ++#define AMDGPU_INFO_NUM_VRAM_CPU_PAGE_FAULTS 0x1E ++#define AMDGPU_INFO_VRAM_LOST_COUNTER 0x1F ++/* query ras mask of enabled features*/ ++#define AMDGPU_INFO_RAS_ENABLED_FEATURES 0x20 ++/* RAS MASK: UMC (VRAM) */ ++#define AMDGPU_INFO_RAS_ENABLED_UMC (1 << 0) ++/* RAS MASK: SDMA */ ++#define AMDGPU_INFO_RAS_ENABLED_SDMA (1 << 1) ++/* RAS MASK: GFX */ ++#define AMDGPU_INFO_RAS_ENABLED_GFX (1 << 2) ++/* RAS MASK: MMHUB */ ++#define AMDGPU_INFO_RAS_ENABLED_MMHUB (1 << 3) ++/* RAS MASK: ATHUB */ ++#define AMDGPU_INFO_RAS_ENABLED_ATHUB (1 << 4) ++/* RAS MASK: PCIE */ ++#define AMDGPU_INFO_RAS_ENABLED_PCIE (1 << 5) ++/* RAS MASK: HDP */ ++#define AMDGPU_INFO_RAS_ENABLED_HDP (1 << 6) ++/* RAS MASK: XGMI */ ++#define AMDGPU_INFO_RAS_ENABLED_XGMI (1 << 7) ++/* RAS MASK: DF */ ++#define AMDGPU_INFO_RAS_ENABLED_DF (1 << 8) ++/* RAS MASK: SMN */ ++#define AMDGPU_INFO_RAS_ENABLED_SMN (1 << 9) ++/* RAS MASK: SEM */ ++#define AMDGPU_INFO_RAS_ENABLED_SEM (1 << 10) ++/* RAS MASK: MP0 */ ++#define AMDGPU_INFO_RAS_ENABLED_MP0 (1 << 11) ++/* RAS MASK: MP1 */ ++#define AMDGPU_INFO_RAS_ENABLED_MP1 (1 << 12) ++/* RAS MASK: FUSE */ ++#define AMDGPU_INFO_RAS_ENABLED_FUSE (1 << 13) ++/* query video encode/decode caps */ ++#define AMDGPU_INFO_VIDEO_CAPS 0x21 ++ /* Subquery id: Decode */ ++ #define AMDGPU_INFO_VIDEO_CAPS_DECODE 0 ++ /* Subquery id: Encode */ ++ #define AMDGPU_INFO_VIDEO_CAPS_ENCODE 1 ++ ++#define AMDGPU_INFO_MMR_SE_INDEX_SHIFT 0 ++#define AMDGPU_INFO_MMR_SE_INDEX_MASK 0xff ++#define AMDGPU_INFO_MMR_SH_INDEX_SHIFT 8 ++#define AMDGPU_INFO_MMR_SH_INDEX_MASK 0xff ++ ++struct drm_amdgpu_query_fw { ++ /** AMDGPU_INFO_FW_* */ ++ __u32 fw_type; ++ /** ++ * Index of the IP if there are more IPs of ++ * the same type. ++ */ ++ __u32 ip_instance; ++ /** ++ * Index of the engine. Whether this is used depends ++ * on the firmware type. (e.g. MEC, SDMA) ++ */ ++ __u32 index; ++ __u32 _pad; ++}; ++ ++/* Input structure for the INFO ioctl */ ++struct drm_amdgpu_info { ++ /* Where the return value will be stored */ ++ __u64 return_pointer; ++ /* The size of the return value. Just like "size" in "snprintf", ++ * it limits how many bytes the kernel can write. */ ++ __u32 return_size; ++ /* The query request id. */ ++ __u32 query; ++ ++ union { ++ struct { ++ __u32 id; ++ __u32 _pad; ++ } mode_crtc; ++ ++ struct { ++ /** AMDGPU_HW_IP_* */ ++ __u32 type; ++ /** ++ * Index of the IP if there are more IPs of the same ++ * type. Ignored by AMDGPU_INFO_HW_IP_COUNT. ++ */ ++ __u32 ip_instance; ++ } query_hw_ip; ++ ++ struct { ++ __u32 dword_offset; ++ /** number of registers to read */ ++ __u32 count; ++ __u32 instance; ++ /** For future use, no flags defined so far */ ++ __u32 flags; ++ } read_mmr_reg; ++ ++ struct drm_amdgpu_query_fw query_fw; ++ ++ struct { ++ __u32 type; ++ __u32 offset; ++ } vbios_info; ++ ++ struct { ++ __u32 type; ++ } sensor_info; ++ ++ struct { ++ __u32 type; ++ } video_cap; ++ }; ++}; ++ ++struct drm_amdgpu_info_gds { ++ /** GDS GFX partition size */ ++ __u32 gds_gfx_partition_size; ++ /** GDS compute partition size */ ++ __u32 compute_partition_size; ++ /** total GDS memory size */ ++ __u32 gds_total_size; ++ /** GWS size per GFX partition */ ++ __u32 gws_per_gfx_partition; ++ /** GSW size per compute partition */ ++ __u32 gws_per_compute_partition; ++ /** OA size per GFX partition */ ++ __u32 oa_per_gfx_partition; ++ /** OA size per compute partition */ ++ __u32 oa_per_compute_partition; ++ __u32 _pad; ++}; ++ ++struct drm_amdgpu_info_vram_gtt { ++ __u64 vram_size; ++ __u64 vram_cpu_accessible_size; ++ __u64 gtt_size; ++}; ++ ++struct drm_amdgpu_heap_info { ++ /** max. physical memory */ ++ __u64 total_heap_size; ++ ++ /** Theoretical max. available memory in the given heap */ ++ __u64 usable_heap_size; ++ ++ /** ++ * Number of bytes allocated in the heap. This includes all processes ++ * and private allocations in the kernel. It changes when new buffers ++ * are allocated, freed, and moved. It cannot be larger than ++ * heap_size. ++ */ ++ __u64 heap_usage; ++ ++ /** ++ * Theoretical possible max. size of buffer which ++ * could be allocated in the given heap ++ */ ++ __u64 max_allocation; ++}; ++ ++struct drm_amdgpu_memory_info { ++ struct drm_amdgpu_heap_info vram; ++ struct drm_amdgpu_heap_info cpu_accessible_vram; ++ struct drm_amdgpu_heap_info gtt; ++}; ++ ++struct drm_amdgpu_info_firmware { ++ __u32 ver; ++ __u32 feature; ++}; ++ ++struct drm_amdgpu_info_vbios { ++ __u8 name[64]; ++ __u8 vbios_pn[64]; ++ __u32 version; ++ __u32 pad; ++ __u8 vbios_ver_str[32]; ++ __u8 date[32]; ++}; ++ ++#define AMDGPU_VRAM_TYPE_UNKNOWN 0 ++#define AMDGPU_VRAM_TYPE_GDDR1 1 ++#define AMDGPU_VRAM_TYPE_DDR2 2 ++#define AMDGPU_VRAM_TYPE_GDDR3 3 ++#define AMDGPU_VRAM_TYPE_GDDR4 4 ++#define AMDGPU_VRAM_TYPE_GDDR5 5 ++#define AMDGPU_VRAM_TYPE_HBM 6 ++#define AMDGPU_VRAM_TYPE_DDR3 7 ++#define AMDGPU_VRAM_TYPE_DDR4 8 ++#define AMDGPU_VRAM_TYPE_GDDR6 9 ++#define AMDGPU_VRAM_TYPE_DDR5 10 ++ ++struct drm_amdgpu_info_device { ++ /** PCI Device ID */ ++ __u32 device_id; ++ /** Internal chip revision: A0, A1, etc.) */ ++ __u32 chip_rev; ++ __u32 external_rev; ++ /** Revision id in PCI Config space */ ++ __u32 pci_rev; ++ __u32 family; ++ __u32 num_shader_engines; ++ __u32 num_shader_arrays_per_engine; ++ /* in KHz */ ++ __u32 gpu_counter_freq; ++ __u64 max_engine_clock; ++ __u64 max_memory_clock; ++ /* cu information */ ++ __u32 cu_active_number; ++ /* NOTE: cu_ao_mask is INVALID, DON'T use it */ ++ __u32 cu_ao_mask; ++ __u32 cu_bitmap[4][4]; ++ /** Render backend pipe mask. One render backend is CB+DB. */ ++ __u32 enabled_rb_pipes_mask; ++ __u32 num_rb_pipes; ++ __u32 num_hw_gfx_contexts; ++ __u32 _pad; ++ __u64 ids_flags; ++ /** Starting virtual address for UMDs. */ ++ __u64 virtual_address_offset; ++ /** The maximum virtual address */ ++ __u64 virtual_address_max; ++ /** Required alignment of virtual addresses. */ ++ __u32 virtual_address_alignment; ++ /** Page table entry - fragment size */ ++ __u32 pte_fragment_size; ++ __u32 gart_page_size; ++ /** constant engine ram size*/ ++ __u32 ce_ram_size; ++ /** video memory type info*/ ++ __u32 vram_type; ++ /** video memory bit width*/ ++ __u32 vram_bit_width; ++ /* vce harvesting instance */ ++ __u32 vce_harvest_config; ++ /* gfx double offchip LDS buffers */ ++ __u32 gc_double_offchip_lds_buf; ++ /* NGG Primitive Buffer */ ++ __u64 prim_buf_gpu_addr; ++ /* NGG Position Buffer */ ++ __u64 pos_buf_gpu_addr; ++ /* NGG Control Sideband */ ++ __u64 cntl_sb_buf_gpu_addr; ++ /* NGG Parameter Cache */ ++ __u64 param_buf_gpu_addr; ++ __u32 prim_buf_size; ++ __u32 pos_buf_size; ++ __u32 cntl_sb_buf_size; ++ __u32 param_buf_size; ++ /* wavefront size*/ ++ __u32 wave_front_size; ++ /* shader visible vgprs*/ ++ __u32 num_shader_visible_vgprs; ++ /* CU per shader array*/ ++ __u32 num_cu_per_sh; ++ /* number of tcc blocks*/ ++ __u32 num_tcc_blocks; ++ /* gs vgt table depth*/ ++ __u32 gs_vgt_table_depth; ++ /* gs primitive buffer depth*/ ++ __u32 gs_prim_buffer_depth; ++ /* max gs wavefront per vgt*/ ++ __u32 max_gs_waves_per_vgt; ++ __u32 _pad1; ++ /* always on cu bitmap */ ++ __u32 cu_ao_bitmap[4][4]; ++ /** Starting high virtual address for UMDs. */ ++ __u64 high_va_offset; ++ /** The maximum high virtual address */ ++ __u64 high_va_max; ++ /* gfx10 pa_sc_tile_steering_override */ ++ __u32 pa_sc_tile_steering_override; ++ /* disabled TCCs */ ++ __u64 tcc_disabled_mask; ++}; ++ ++struct drm_amdgpu_info_hw_ip { ++ /** Version of h/w IP */ ++ __u32 hw_ip_version_major; ++ __u32 hw_ip_version_minor; ++ /** Capabilities */ ++ __u64 capabilities_flags; ++ /** command buffer address start alignment*/ ++ __u32 ib_start_alignment; ++ /** command buffer size alignment*/ ++ __u32 ib_size_alignment; ++ /** Bitmask of available rings. Bit 0 means ring 0, etc. */ ++ __u32 available_rings; ++ __u32 _pad; ++}; ++ ++struct drm_amdgpu_info_num_handles { ++ /** Max handles as supported by firmware for UVD */ ++ __u32 uvd_max_handles; ++ /** Handles currently in use for UVD */ ++ __u32 uvd_used_handles; ++}; ++ ++#define AMDGPU_VCE_CLOCK_TABLE_ENTRIES 6 ++ ++struct drm_amdgpu_info_vce_clock_table_entry { ++ /** System clock */ ++ __u32 sclk; ++ /** Memory clock */ ++ __u32 mclk; ++ /** VCE clock */ ++ __u32 eclk; ++ __u32 pad; ++}; ++ ++struct drm_amdgpu_info_vce_clock_table { ++ struct drm_amdgpu_info_vce_clock_table_entry entries[AMDGPU_VCE_CLOCK_TABLE_ENTRIES]; ++ __u32 num_valid_entries; ++ __u32 pad; ++}; ++ ++/* query video encode/decode caps */ ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_MPEG2 0 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_MPEG4 1 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_VC1 2 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_MPEG4_AVC 3 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_HEVC 4 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_JPEG 5 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_VP9 6 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_AV1 7 ++#define AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_COUNT 8 ++ ++struct drm_amdgpu_info_video_codec_info { ++ __u32 valid; ++ __u32 max_width; ++ __u32 max_height; ++ __u32 max_pixels_per_frame; ++ __u32 max_level; ++ __u32 pad; ++}; ++ ++struct drm_amdgpu_info_video_caps { ++ struct drm_amdgpu_info_video_codec_info codec_info[AMDGPU_INFO_VIDEO_CAPS_CODEC_IDX_COUNT]; ++}; ++ ++/* ++ * Supported GPU families ++ */ ++#define AMDGPU_FAMILY_UNKNOWN 0 ++#define AMDGPU_FAMILY_SI 110 /* Hainan, Oland, Verde, Pitcairn, Tahiti */ ++#define AMDGPU_FAMILY_CI 120 /* Bonaire, Hawaii */ ++#define AMDGPU_FAMILY_KV 125 /* Kaveri, Kabini, Mullins */ ++#define AMDGPU_FAMILY_VI 130 /* Iceland, Tonga */ ++#define AMDGPU_FAMILY_CZ 135 /* Carrizo, Stoney */ ++#define AMDGPU_FAMILY_AI 141 /* Vega10 */ ++#define AMDGPU_FAMILY_RV 142 /* Raven */ ++#define AMDGPU_FAMILY_NV 143 /* Navi10 */ ++#define AMDGPU_FAMILY_VGH 144 /* Van Gogh */ ++#define AMDGPU_FAMILY_YC 146 /* Yellow Carp */ ++ ++#if defined(__cplusplus) ++} ++#endif ++ ++#endif diff --git a/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-constructors.patch b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-constructors.patch new file mode 100644 index 0000000..bb58f87 --- /dev/null +++ b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-constructors.patch @@ -0,0 +1,52 @@ +diff --git a/src/extract_gpuinfo_amdgpu.c b/src/extract_gpuinfo_amdgpu.c +index b5d4584..b5a974e 100644 +--- a/src/extract_gpuinfo_amdgpu.c ++++ b/src/extract_gpuinfo_amdgpu.c +@@ -158,7 +158,7 @@ struct gpu_vendor gpu_vendor_amdgpu = { + + static int readAttributeFromDevice(nvtop_device *dev, const char *sysAttr, const char *format, ...); + +-__attribute__((constructor)) static void init_extract_gpuinfo_amdgpu(void) { register_gpu_vendor(&gpu_vendor_amdgpu); } ++void init_extract_gpuinfo_amdgpu(void) { register_gpu_vendor(&gpu_vendor_amdgpu); } + + static int wrap_drmGetDevices(drmDevicePtr devices[], int max_devices) { + assert(_drmGetDevices2 || _drmGetDevices); +diff --git a/src/extract_gpuinfo_intel.c b/src/extract_gpuinfo_intel.c +index a57f9fb..9404d23 100644 +--- a/src/extract_gpuinfo_intel.c ++++ b/src/extract_gpuinfo_intel.c +@@ -100,7 +100,7 @@ static struct gpu_info_intel *gpu_infos; + // Discrete GPU are others + #define INTEGRATED_I915_GPU_PCI_ID "0000:00:02.0" + +-__attribute__((constructor)) static void init_extract_gpuinfo_intel(void) { register_gpu_vendor(&gpu_vendor_intel); } ++void init_extract_gpuinfo_intel(void) { register_gpu_vendor(&gpu_vendor_intel); } + + bool gpuinfo_intel_init(void) { return true; } + void gpuinfo_intel_shutdown(void) { +diff --git a/src/extract_gpuinfo_msm.c b/src/extract_gpuinfo_msm.c +index 244240e..7cf46f6 100644 +--- a/src/extract_gpuinfo_msm.c ++++ b/src/extract_gpuinfo_msm.c +@@ -146,7 +146,7 @@ static void authenticate_drm(int fd) { + #define STRINGIFY(x) STRINGIFY_HELPER_(x) + #define STRINGIFY_HELPER_(x) #x + +-__attribute__((constructor)) static void init_extract_gpuinfo_msm(void) { register_gpu_vendor(&gpu_vendor_msm); } ++void init_extract_gpuinfo_msm(void) { register_gpu_vendor(&gpu_vendor_msm); } + + bool gpuinfo_msm_init(void) { + libdrm_handle = dlopen("libdrm.so", RTLD_LAZY); +diff --git a/src/extract_gpuinfo_nvidia.c b/src/extract_gpuinfo_nvidia.c +index f0356ed..4e6baca 100644 +--- a/src/extract_gpuinfo_nvidia.c ++++ b/src/extract_gpuinfo_nvidia.c +@@ -201,7 +201,7 @@ struct gpu_vendor gpu_vendor_nvidia = { + .name = "NVIDIA", + }; + +-__attribute__((constructor)) static void init_extract_gpuinfo_nvidia(void) { register_gpu_vendor(&gpu_vendor_nvidia); } ++void init_extract_gpuinfo_nvidia(void) { register_gpu_vendor(&gpu_vendor_nvidia); } + + /* + * diff --git a/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-kcmp-h.patch b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-kcmp-h.patch new file mode 100644 index 0000000..1ca4eb8 --- /dev/null +++ b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-kcmp-h.patch @@ -0,0 +1,34 @@ +diff --git a/include/linux/kcmp.h b/include/linux/kcmp.h +new file mode 100644 +index 0000000..ef13050 +--- /dev/null ++++ b/include/linux/kcmp.h +@@ -0,0 +1,28 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++#ifndef _UAPI_LINUX_KCMP_H ++#define _UAPI_LINUX_KCMP_H ++ ++#include ++ ++/* Comparison type */ ++enum kcmp_type { ++ KCMP_FILE, ++ KCMP_VM, ++ KCMP_FILES, ++ KCMP_FS, ++ KCMP_SIGHAND, ++ KCMP_IO, ++ KCMP_SYSVSEM, ++ KCMP_EPOLL_TFD, ++ ++ KCMP_TYPES, ++}; ++ ++/* Slot for KCMP_EPOLL_TFD */ ++struct kcmp_epoll_slot { ++ __u32 efd; /* epoll file descriptor */ ++ __u32 tfd; /* target file number */ ++ __u32 toff; /* target offset within same numbered sequence */ ++}; ++ ++#endif /* _UAPI_LINUX_KCMP_H */ diff --git a/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-no-drmGetDevices2.patch b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-no-drmGetDevices2.patch new file mode 100644 index 0000000..6cb9572 --- /dev/null +++ b/src/observatory-daemon/3rdparty/nvtop/patches/nvtop-no-drmGetDevices2.patch @@ -0,0 +1,13 @@ +diff --git a/src/extract_gpuinfo_amdgpu.c b/src/extract_gpuinfo_amdgpu.c +index ef5b635..b96a5fa 100644 +--- a/src/extract_gpuinfo_amdgpu.c ++++ b/src/extract_gpuinfo_amdgpu.c +@@ -57,7 +57,7 @@ const char *amdgpu_parse_marketing_name(struct amdgpu_gpu_info *info); + + // Local function pointers to DRM interface + static typeof(drmGetDevices) *_drmGetDevices; +-static typeof(drmGetDevices2) *_drmGetDevices2; ++static typeof(int(uint32_t, drmDevicePtr *, int)) *_drmGetDevices2; + static typeof(drmFreeDevices) *_drmFreeDevices; + static typeof(drmGetVersion) *_drmGetVersion; + static typeof(drmFreeVersion) *_drmFreeVersion; diff --git a/src/observatory-daemon/Cargo.lock b/src/observatory-daemon/Cargo.lock new file mode 100644 index 0000000..d5dcb0b --- /dev/null +++ b/src/observatory-daemon/Cargo.lock @@ -0,0 +1,1556 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" + +[[package]] +name = "app-rummage" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1321031f83546d8c1c81840700838c47e1fbf8eba93c393f2821573624b296f0" +dependencies = [ + "log", + "nix", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cargo-util" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dd67a24439ca5260a08128b6cbf4b0f4453497a2f60508163ab9d5b534b122" +dependencies = [ + "anyhow", + "core-foundation", + "filetime", + "hex", + "ignore", + "jobserver", + "libc 0.2.164", + "miow", + "same-file", + "sha2", + "shell-escape", + "tempfile", + "tracing", + "walkdir", + "windows-sys 0.59.0", +] + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc 0.2.164", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc 0.2.164", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc 0.2.164", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "dbus-crossroads" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0" +dependencies = [ + "dbus", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "drm" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc 0.2.164", + "rustix", +] + +[[package]] +name = "drm-ffi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b" +dependencies = [ + "drm-sys", + "rustix", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c" +dependencies = [ + "libc 0.2.164", + "linux-raw-sys 0.6.5", +] + +[[package]] +name = "egl" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a373bc9844200b1ff15bd1b245931d1c20d09d06e4ec09f361171f29a4b0752d" +dependencies = [ + "khronos", + "libc 0.2.164", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc 0.2.164", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc 0.2.164", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "gbm" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c724107aa10444b1d2709aae4727c18a33c16b3e15ea8a46cc4ae226c084c88a" +dependencies = [ + "bitflags", + "drm", + "drm-fourcc", + "gbm-sys", + "libc 0.2.164", +] + +[[package]] +name = "gbm-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9cc2f64de9fa707b5c6b2d2f10d7a7e49e845018a9f5685891eb40d3bab2538" +dependencies = [ + "libc 0.2.164", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc 0.2.164", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc 0.2.164", +] + +[[package]] +name = "khronos" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0711aaa80e6ba6eb1fa8978f1f46bfcb38ceb2f3f33f3736efbff39dac89f50" +dependencies = [ + "libc 0.1.12", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32a70cf75e5846d53a673923498228bbec6a8624708a9ea5645f075d6276122" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc 0.2.164", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "miow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "359f76430b20a79f9e20e115b3428614e654f04fab314482fc0fda0ebd3c6044" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc 0.2.164", +] + +[[package]] +name = "observatory-daemon" +version = "0.6.2" +dependencies = [ + "anyhow", + "app-rummage", + "arrayvec", + "ash", + "bincode", + "cargo-util", + "cc", + "convert_case", + "dbus", + "dbus-crossroads", + "drm", + "egl", + "flate2", + "gbm", + "glob", + "lazy_static", + "libc 0.2.164", + "libloading", + "log", + "nix", + "pkg-config", + "rayon", + "rust-ini", + "serde", + "serde_json", + "sha2", + "static_assertions", + "tar", + "thiserror", + "ureq", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc 0.2.164", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + +[[package]] +name = "rustix" +version = "0.38.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +dependencies = [ + "bitflags", + "errno", + "libc 0.2.164", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc 0.2.164", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc 0.2.164", + "linux-raw-sys 0.4.14", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/observatory-daemon/Cargo.toml b/src/observatory-daemon/Cargo.toml new file mode 100644 index 0000000..f07fb30 --- /dev/null +++ b/src/observatory-daemon/Cargo.toml @@ -0,0 +1,47 @@ +[package] +build = "build/build.rs" +name = "observatory-daemon" +version = "0.6.2" +edition = "2021" + +[dependencies] +anyhow = { version = "1.0" } +app-rummage = { version = "0.2.7" } +arrayvec = { version = "0.7" } +ash = { version = "0.38" } +bincode = { version = "1.3" } +convert_case = { version = "0.6.0" } +dbus = { version = "0.9", features = ["vendored"] } +dbus-crossroads = { version = "0.5" } +glob = { version = "0.3.1" } +drm = { version = "0.14.0" } +egl = { version = "0.2" } +gbm = { version = "0.16.0", default-features = false, features = ["drm-support"] } +lazy_static = { version = "1.4" } +libc = { version = "0.2" } +libloading = { version = "0.8" } +log = { version = "0.4" } +nix = { version = "0.29.0", features = ["user", "fs", "process"] } +rayon = { version = "1.10" } +rust-ini = { version = "0.21" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +static_assertions = { version = "1.1" } +thiserror = { version = "2.0.3" } + +[build-dependencies] +cargo-util = { version = "0.2" } +cc = { version = "1.1" } +flate2 = { version = "1.0" } +lazy_static = { version = "1.4" } +pkg-config = { version = "0.3" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +sha2 = { version = "0.10" } +tar = { version = "0.4" } +ureq = { version = "2.10" } + +[profile.release] +opt-level = 3 +lto = true +strip = "debuginfo" diff --git a/src/observatory-daemon/build/build.rs b/src/observatory-daemon/build/build.rs new file mode 100644 index 0000000..6e8d53e --- /dev/null +++ b/src/observatory-daemon/build/build.rs @@ -0,0 +1,181 @@ +/* sys_info_v2/observatory-daemon/build/build.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; + +mod util; + +lazy_static! { + static ref PATCH_EXECUTABLE: String = match std::env::var("MC_PATCH_BINARY") { + Ok(p) => { + if std::path::Path::new(&p).exists() { + p + } else { + eprintln!("{} does not exist", p); + std::process::exit(1); + } + } + Err(_) => util::find_program("patch").unwrap_or_else(|| { + eprintln!("`patch` not found"); + std::process::exit(1); + }), + }; +} + +#[derive(Serialize, Deserialize)] +struct Package { + #[serde(rename = "package-name")] + name: String, + directory: String, + #[serde(rename = "source-url")] + source_url: String, + #[serde(rename = "source-hash")] + source_hash: String, + patches: Vec, +} + +fn prepare_third_party_sources() -> Result, Box> { + let third_party_path = std::path::PathBuf::from(&format!( + "{}/3rdparty", + std::env::var("CARGO_MANIFEST_DIR")? + )); + let mut out_dir = std::env::var("OUT_DIR")?; + out_dir.push_str("/../../native"); + std::fs::create_dir_all(&out_dir)?; + let out_dir = std::path::PathBuf::from(out_dir).canonicalize()?; + + let mut result = vec![]; + + for dir in std::fs::read_dir(&third_party_path)?.filter_map(|d| d.ok()) { + if !dir.file_type()?.is_dir() { + continue; + } + + for entry in std::fs::read_dir(dir.path())?.filter_map(|e| e.ok()) { + let file_name = entry.file_name(); + let entry_name = file_name.to_string_lossy(); + if entry_name.ends_with(".json") { + let package: Package = + serde_json::from_str(&std::fs::read_to_string(entry.path())?)?; + + let extracted_path = out_dir.join(&package.directory); + result.push(extracted_path.clone()); + if extracted_path.exists() { + break; + } + + let output_path = util::download_file( + &package.source_url, + &format!("{}", out_dir.display()), + Some(&package.source_hash), + )?; + + let mut archive = std::fs::File::open(&output_path)?; + let tar = flate2::read::GzDecoder::new(&mut archive); + let mut archive = tar::Archive::new(tar); + archive.unpack(&out_dir)?; + + let patch_executable = &*PATCH_EXECUTABLE; + for patch in package.patches.iter().map(|p| p.as_str()) { + let mut cmd = std::process::Command::new(patch_executable); + cmd.args(["-p1", "-i", &format!("{}/{}", dir.path().display(), patch)]); + cmd.current_dir(&extracted_path); + cmd.stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + cmd.spawn()?.wait()?; + } + + break; + } + } + } + + Ok(result) +} + +#[cfg(target_os = "linux")] +fn build_nvtop(src_dir: &std::path::Path) -> Result<(), Box> { + let _ = pkg_config::Config::new().probe("egl")?; + let _ = pkg_config::Config::new().probe("gbm")?; + + let libdrm = pkg_config::Config::new() + .atleast_version("2.4.67") + .probe("libdrm")?; + + // Work around some linkers being stupid and requiring a certain order for libraries + libdrm.link_paths.iter().for_each(|p| { + println!("cargo:rustc-link-search=native={}", p.display()); + }); + libdrm.libs.iter().for_each(|a| { + println!("cargo:rustc-link-arg=-l{}", a); + }); + + let libudev = pkg_config::Config::new() + .atleast_version("204") + .probe("libudev")?; + + // Work around some linkers being stupid and requiring a certain order for libraries + libudev.link_paths.iter().for_each(|p| { + println!("cargo:rustc-link-search=native={}", p.display()); + }); + libudev.libs.iter().for_each(|a| { + println!("cargo:rustc-link-arg=-l{}", a); + }); + + let mut build_def = cc::Build::new(); + build_def + .define("USING_LIBUDEV", None) + .define("_GNU_SOURCE", None) + .include(src_dir.join("src")) + .include(src_dir.join("include")) + .includes(&libdrm.include_paths) + .includes(&libudev.include_paths) + .files([ + src_dir.join("src/get_process_info_linux.c"), + src_dir.join("src/extract_gpuinfo.c"), + src_dir.join("src/extract_processinfo_fdinfo.c"), + src_dir.join("src/info_messages_linux.c"), + src_dir.join("src/extract_gpuinfo_nvidia.c"), + src_dir.join("src/device_discovery_linux.c"), + src_dir.join("src/extract_gpuinfo_amdgpu.c"), + src_dir.join("src/extract_gpuinfo_amdgpu_utils.c"), + src_dir.join("src/extract_gpuinfo_intel.c"), + src_dir.join("src/time.c"), + ]); + #[cfg(not(debug_assertions))] + build_def.flag("-flto"); + build_def.flag("-Wno-unused-function"); + + build_def.compile("nvtop"); + + Ok(()) +} + +fn main() -> Result<(), Box> { + let dirs = prepare_third_party_sources()?; + + #[cfg(target_os = "linux")] + { + build_nvtop(&dirs[0])?; + } + + Ok(()) +} diff --git a/src/observatory-daemon/build/util.rs b/src/observatory-daemon/build/util.rs new file mode 100644 index 0000000..867e076 --- /dev/null +++ b/src/observatory-daemon/build/util.rs @@ -0,0 +1,95 @@ +/* sys_info_v2/observatory-daemon/build/util.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +pub fn find_program(name: &str) -> Option { + #[cfg(windows)] + const PATH_VAR_SEPARATOR: char = ';'; + + #[cfg(not(windows))] + const PATH_VAR_SEPARATOR: char = ':'; + + let path = if let Ok(path) = std::env::var("PATH") { + path + } else { + "".into() + }; + for path in path.split(PATH_VAR_SEPARATOR) { + let program_path = format!("{}/{}", path, name); + if std::path::Path::new(&program_path).exists() { + return Some(program_path); + } + } + + None +} + +pub fn download_file( + url: &str, + path: &str, + checksum: Option<&str>, +) -> Result> { + use std::io::Write; + + std::fs::create_dir_all(path)?; + + let file_name = url.split('/').last().unwrap(); + let output_path = format!("{}/{}", path, file_name); + + if std::path::Path::new(&output_path).exists() { + return Ok(output_path); + } + + let response = ureq::get(url).call()?; + + if response.status() != 200 { + return Err(format!( + "Failed to download {}. HTTP status code {} {}", + url, + response.status(), + response.status_text() + ) + .into()); + } + + let mut content = Vec::new(); + response.into_reader().read_to_end(&mut content)?; + + let mut sha256 = cargo_util::Sha256::new(); + sha256.update(&content); + + if let Some(expected_checksum) = checksum { + let actual_checksum = sha256.finish_hex(); + if actual_checksum != expected_checksum { + return Err(format!( + "Checksum validation failed! Expected: {} Actual: {}", + expected_checksum, actual_checksum, + ) + .into()); + } + } + + let mut file = std::fs::File::create(&output_path)?; + file.write_all(content.as_slice())?; + file.flush()?; + + println!("cargo:rerun-if-changed={}", output_path); + + Ok(output_path) +} diff --git a/src/observatory-daemon/src/logging.rs b/src/observatory-daemon/src/logging.rs new file mode 100644 index 0000000..c9fa635 --- /dev/null +++ b/src/observatory-daemon/src/logging.rs @@ -0,0 +1,269 @@ +/* sys_info_v2/observatory-daemon/src/logging.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use lazy_static::lazy_static; + +#[allow(unused)] +macro_rules! error { + ($domain:literal, $($arg:tt)*) => {{ + $crate::logging::Logger::log_error($domain, format_args!($($arg)*)); + }} +} +pub(crate) use error; + +#[allow(unused)] +macro_rules! critical { + ($domain:literal, $($arg:tt)*) => {{ + $crate::logging::Logger::log_critical($domain, format_args!($($arg)*)); + }} +} +pub(crate) use critical; + +#[allow(unused)] +macro_rules! warning { + ($domain:literal, $($arg:tt)*) => {{ + $crate::logging::Logger::log_warn($domain, format_args!($($arg)*)); + }} +} +pub(crate) use warning; + +#[allow(unused)] +macro_rules! message { + ($domain:literal, $($arg:tt)*) => {{ + $crate::logging::Logger::log_message($domain, format_args!($($arg)*)); + }} +} +pub(crate) use message; + +#[allow(unused)] +macro_rules! info { + ($domain:literal, $($arg:tt)*) => {{ + $crate::logging::Logger::log_info($domain, format_args!($($arg)*)); + }} +} +pub(crate) use info; + +#[allow(unused)] +macro_rules! debug { + ($domain:literal, $($arg:tt)*) => {{ + $crate::logging::Logger::log_debug($domain, format_args!($($arg)*)); + }} +} +pub(crate) use debug; + +macro_rules! now { + () => {{ + let now = std::time::SystemTime::now(); + let now = now + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or(std::time::Duration::new(0, 0)); + + let hours = (now.as_secs() / 3600) as u32; + let minutes = ((now.as_secs() - (hours as u64 * 3600)) / 60) as u16; + let seconds = (now.as_secs() - (hours as u64 * 3600) - (minutes as u64 * 60)) as u16; + let milliseconds = now.subsec_millis() as u16; + + Timestamp { + hours: hours % 24, + minutes, + seconds, + milliseconds, + } + }}; +} + +lazy_static! { + static ref PID: u32 = unsafe { libc::getpid() } as _; + static ref G_MESSAGES_DEBUG: Vec> = std::env::var("G_MESSAGES_DEBUG") + .unwrap_or_default() + .split(";") + .map(|s| std::sync::Arc::::from(s)) + .collect(); +} + +const F_COL_LIGHT_BLUE: &str = "\x1b[2;34m"; +const F_RESET: &str = "\x1b[0m"; + +struct Timestamp { + hours: u32, + minutes: u16, + seconds: u16, + milliseconds: u16, +} + +#[allow(dead_code)] +enum LogLevel { + Error, + Critical, + Warning, + Message, + Info, + Debug, +} + +pub struct Logger; + +#[allow(dead_code)] +impl Logger { + pub fn log_error(domain: &str, args: std::fmt::Arguments<'_>) { + let color = Self::log_level_to_color(LogLevel::Error); + let now = now!(); + eprintln!( + "\n(observatory-daemon:{}): {}-{}{}{} **: {}{}:{}:{}.{}{}: {}", + *PID, + domain, + color, + "ERROR", + F_RESET, + F_COL_LIGHT_BLUE, + now.hours, + now.minutes, + now.seconds, + now.milliseconds, + F_RESET, + args + ); + } + + pub fn log_critical(domain: &str, args: std::fmt::Arguments<'_>) { + let color = Self::log_level_to_color(LogLevel::Critical); + let now = now!(); + eprintln!( + "\n(observatory-daemon:{}): {}-{}{}{} **: {}{}:{}:{}.{}{}: {}", + *PID, + domain, + color, + "CRITICAL", + F_RESET, + F_COL_LIGHT_BLUE, + now.hours, + now.minutes, + now.seconds, + now.milliseconds, + F_RESET, + args + ); + } + + pub fn log_warn(domain: &str, args: std::fmt::Arguments<'_>) { + let color = Self::log_level_to_color(LogLevel::Warning); + let now = now!(); + println!( + "\n(observatory-daemon:{}): {}-{}{}{} **: {}{}:{}:{}.{}{}: {}", + *PID, + domain, + color, + "WARNING", + F_RESET, + F_COL_LIGHT_BLUE, + now.hours, + now.minutes, + now.seconds, + now.milliseconds, + F_RESET, + args + ); + } + + pub fn log_message(domain: &str, args: std::fmt::Arguments<'_>) { + let color = Self::log_level_to_color(LogLevel::Message); + let now = now!(); + println!( + "(observatory-daemon:{}): {}-{}{}{}: {}{}:{}:{}.{}{}: {}", + *PID, + domain, + color, + "MESSAGE", + F_RESET, + F_COL_LIGHT_BLUE, + now.hours, + now.minutes, + now.seconds, + now.milliseconds, + F_RESET, + args + ); + } + + pub fn log_info(domain: &str, args: std::fmt::Arguments<'_>) { + if !G_MESSAGES_DEBUG.is_empty() + && (!G_MESSAGES_DEBUG.contains(&domain.into()) + && !G_MESSAGES_DEBUG.contains(&"all".into())) + { + return; + } + + let color = Self::log_level_to_color(LogLevel::Info); + let now = now!(); + println!( + "(observatory-daemon:{}): {}-{}{}{}: {}{}:{}:{}.{}{}: {}\n", + *PID, + domain, + color, + "INFO", + F_RESET, + F_COL_LIGHT_BLUE, + now.hours, + now.minutes, + now.seconds, + now.milliseconds, + F_RESET, + args + ); + } + + pub fn log_debug(domain: &str, args: std::fmt::Arguments<'_>) { + if !G_MESSAGES_DEBUG.is_empty() + && (!G_MESSAGES_DEBUG.contains(&domain.into()) + && !G_MESSAGES_DEBUG.contains(&"all".into())) + { + return; + } + + let color = Self::log_level_to_color(LogLevel::Debug); + let now = now!(); + println!( + "(observatory-daemon:{}): {}-{}{}{}: {}{}:{}:{}.{}{}: {}", + *PID, + domain, + color, + "INFO", + F_RESET, + F_COL_LIGHT_BLUE, + now.hours, + now.minutes, + now.seconds, + now.milliseconds, + F_RESET, + args + ); + } + + const fn log_level_to_color(level: LogLevel) -> &'static str { + match level { + LogLevel::Error => "\x1b[1;31m", /* red */ + LogLevel::Critical => "\x1b[1;35m", /* magenta */ + LogLevel::Warning => "\x1b[1;33m", /* yellow */ + LogLevel::Message => "\x1b[1;32m", /* green */ + LogLevel::Info => "\x1b[1;32m", /* green */ + LogLevel::Debug => "\x1b[1;32m", /* green */ + } + } +} diff --git a/src/observatory-daemon/src/main.rs b/src/observatory-daemon/src/main.rs new file mode 100644 index 0000000..8a01e8b --- /dev/null +++ b/src/observatory-daemon/src/main.rs @@ -0,0 +1,838 @@ +/* sys_info_v2/observatory-daemon/src/main.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::{ + atomic::{self, AtomicBool, AtomicU64}, + Arc, Mutex, PoisonError, RwLock, +}; + +use dbus::arg::RefArg; +use dbus::{arg, blocking::SyncConnection, channel::MatchingReceiver}; +use dbus_crossroads::Crossroads; +use lazy_static::lazy_static; + +use crate::platform::{FanInfo, FansInfo, FansInfoExt}; +use logging::{critical, debug, error, message, warning}; +use platform::{ + Apps, AppsExt, CpuDynamicInfo, CpuInfo, CpuInfoExt, CpuStaticInfo, CpuStaticInfoExt, DiskInfo, + DisksInfo, DisksInfoExt, GpuDynamicInfo, GpuInfo, GpuInfoExt, GpuStaticInfo, + PlatformUtilitiesExt, Processes, ProcessesExt, Service, ServiceController, + ServiceControllerExt, Services, ServicesError, ServicesExt, +}; + +#[allow(unused_imports)] +mod logging; +mod platform; +mod utils; + +const DBUS_OBJECT_PATH: &str = "/io/github/cosmic_utils/observatory_daemon"; + +lazy_static! { + static ref SYSTEM_STATE: SystemState<'static> = { + let system_state = SystemState::new(); + + let service_controller = system_state + .services + .read() + .unwrap() + .controller() + .map(|sc| Some(sc)) + .unwrap_or_else(|e| { + error!( + "ObservatoryDaemon::Main", + "Failed to create service controller: {}", e + ); + None + }); + + *system_state.service_controller.write().unwrap() = service_controller; + + system_state + .cpu_info + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_static_info_cache(); + + system_state.gpu_info.write().unwrap().refresh_gpu_list(); + system_state + .gpu_info + .write() + .unwrap() + .refresh_static_info_cache(); + + system_state.snapshot(); + + system_state + }; + static ref LOGICAL_CPU_COUNT: u32 = { + SYSTEM_STATE + .cpu_info + .read() + .unwrap_or_else(PoisonError::into_inner) + .static_info() + .logical_cpu_count() + }; +} + +#[derive(Debug)] +pub struct OrgFreedesktopDBusNameLost { + pub arg0: String, +} + +impl arg::AppendAll for OrgFreedesktopDBusNameLost { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.arg0, i); + } +} + +impl arg::ReadAll for OrgFreedesktopDBusNameLost { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopDBusNameLost { arg0: i.read()? }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopDBusNameLost { + const NAME: &'static str = "NameLost"; + const INTERFACE: &'static str = "org.freedesktop.DBus"; +} + +struct SystemState<'a> { + cpu_info: Arc>, + disk_info: Arc>, + gpu_info: Arc>, + fan_info: Arc>, + services: Arc>>, + service_controller: Arc>>>, + processes: Arc>, + apps: Arc>, + + refresh_interval: Arc, + core_count_affects_percentages: Arc, +} + +impl SystemState<'_> { + pub fn snapshot(&self) { + { + let mut processes = self + .processes + .write() + .unwrap_or_else(PoisonError::into_inner); + + let timer = std::time::Instant::now(); + processes.refresh_cache(); + if !self + .core_count_affects_percentages + .load(atomic::Ordering::Relaxed) + { + let logical_cpu_count = *LOGICAL_CPU_COUNT as f32; + for (_, p) in processes.process_list_mut() { + p.usage_stats.cpu_usage /= logical_cpu_count; + } + } + debug!( + "Gatherer::Perf", + "Refreshed process cache in {:?}", + timer.elapsed() + ); + } + + let timer = std::time::Instant::now(); + self.cpu_info + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_dynamic_info_cache( + &self + .processes + .read() + .unwrap_or_else(PoisonError::into_inner), + ); + debug!( + "Gatherer::Perf", + "Refreshed CPU dynamic info cache in {:?}", + timer.elapsed() + ); + + let timer = std::time::Instant::now(); + self.disk_info + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_cache(); + debug!( + "Gatherer::Perf", + "Refreshed disk info cache in {:?}", + timer.elapsed() + ); + + let timer = std::time::Instant::now(); + self.gpu_info + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_dynamic_info_cache( + &mut self + .processes + .write() + .unwrap_or_else(PoisonError::into_inner), + ); + debug!( + "Gatherer::Perf", + "Refreshed GPU dynamic info cache in {:?}", + timer.elapsed() + ); + + let timer = std::time::Instant::now(); + self.fan_info + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_cache(); + debug!( + "Gatherer::Perf", + "Refreshed fan info cache in {:?}", + timer.elapsed() + ); + + let timer = std::time::Instant::now(); + self.apps + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_cache( + self.processes + .read() + .unwrap_or_else(PoisonError::into_inner) + .process_list(), + ); + debug!( + "Gatherer::Perf", + "Refreshed app cache in {:?}", + timer.elapsed() + ); + + let timer = std::time::Instant::now(); + self.services + .write() + .unwrap_or_else(PoisonError::into_inner) + .refresh_cache() + .unwrap_or_else(|e| { + debug!("ObservatoryDaemon::Main", "Failed to refresh service cache: {}", e); + }); + debug!( + "Gatherer::Perf", + "Refreshed service cache in {:?}", + timer.elapsed() + ); + } +} + +impl<'a> SystemState<'a> { + pub fn new() -> Self { + Self { + cpu_info: Arc::new(RwLock::new(CpuInfo::new())), + disk_info: Arc::new(RwLock::new(DisksInfo::new())), + gpu_info: Arc::new(RwLock::new(GpuInfo::new())), + fan_info: Arc::new(RwLock::new(FansInfo::new())), + services: Arc::new(RwLock::new(Services::new())), + service_controller: Arc::new(RwLock::new(None)), + processes: Arc::new(RwLock::new(Processes::new())), + apps: Arc::new(RwLock::new(Apps::new())), + + refresh_interval: Arc::new(AtomicU64::new(1000)), + core_count_affects_percentages: Arc::new(AtomicBool::new(true)), + } + } +} + +fn main() -> Result<(), Box> { + // Exit if any arguments are passed to this executable. This is done since the main app needs + // to check if the executable can be run in its current environment (glibc or musl libc) + for (i, _) in std::env::args().enumerate() { + if i > 0 { + eprintln!("👋"); + std::process::exit(0); + } + } + + #[cfg(target_os = "linux")] + unsafe { + libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL); + } + + message!( + "ObservatoryDaemon::Main", + "Starting v{}...", + env!("CARGO_PKG_VERSION") + ); + + message!("ObservatoryDaemon::Main", "Initializing system state..."); + let _ = &*SYSTEM_STATE; + let _ = &*LOGICAL_CPU_COUNT; + + message!( + "ObservatoryDaemon::Main", + "Setting up background data refresh thread..." + ); + std::thread::spawn({ + move || loop { + let refresh_interval = SYSTEM_STATE + .refresh_interval + .load(atomic::Ordering::Relaxed); + std::thread::sleep(std::time::Duration::from_millis(refresh_interval)); + + SYSTEM_STATE.snapshot(); + } + }); + + message!("ObservatoryDaemon::Main", "Initializing platform utilities..."); + let plat_utils = platform::PlatformUtilities::default(); + + message!("ObservatoryDaemon::Main", "Setting up connection to main app..."); + // Set up so that the Gatherer exists when the main app exits + plat_utils.on_main_app_exit(Box::new(|| { + message!("ObservatoryDaemon::Main", "Parent process exited, exiting..."); + std::process::exit(0); + })); + + message!("ObservatoryDaemon::Main", "Setting up D-Bus connection..."); + let c = Arc::new(SyncConnection::new_session()?); + + message!("ObservatoryDaemon::Main", "Requesting bus name..."); + c.request_name("io.github.cosmic_utils.observatory_daemon", true, true, true)?; + message!("ObservatoryDaemon::Main", "Bus name acquired"); + + message!("ObservatoryDaemon::Main", "Setting up D-Bus proxy..."); + let proxy = c.with_proxy( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + std::time::Duration::from_millis(5000), + ); + + message!("ObservatoryDaemon::Main", "Setting up D-Bus signal match..."); + let _id = proxy.match_signal( + |h: OrgFreedesktopDBusNameLost, _: &SyncConnection, _: &dbus::Message| { + if h.arg0 != "io.github.cosmic_utils.observatory_daemon" { + return true; + } + message!("ObservatoryDaemon::Main", "Bus name {} lost, exiting...", &h.arg0); + std::process::exit(0); + }, + )?; + + message!("ObservatoryDaemon::Main", "Setting up D-Bus crossroads..."); + let mut cr = Crossroads::new(); + let iface_token = cr.register("io.github.cosmic_utils.observatory_daemon", |builder| { + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus properties and methods..." + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus property `RefreshInterval`..." + ); + builder + .property("RefreshInterval") + .get_with_cr(|_, _| { + Ok(SYSTEM_STATE + .refresh_interval + .load(atomic::Ordering::Relaxed)) + }) + .set_with_cr(|_, _, value| { + if let Some(value) = value.as_u64() { + SYSTEM_STATE + .refresh_interval + .store(value, atomic::Ordering::Relaxed); + Ok(Some(value)) + } else { + Err(dbus::MethodErr::failed(&"Invalid value")) + } + }); + + builder + .property("CoreCountAffectsPercentages") + .get_with_cr(|_, _| { + Ok(SYSTEM_STATE + .core_count_affects_percentages + .load(atomic::Ordering::Relaxed)) + }) + .set_with_cr(|_, _, value| { + if let Some(value) = value.as_u64() { + let value = value != 0; + SYSTEM_STATE + .core_count_affects_percentages + .store(value, atomic::Ordering::Relaxed); + Ok(Some(value)) + } else { + Err(dbus::MethodErr::failed(&"Invalid value")) + } + }); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetCPUStaticInfo`..." + ); + builder.method_with_cr_custom::<(), (CpuStaticInfo,), &str, _>( + "GetCPUStaticInfo", + (), + ("info",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((SYSTEM_STATE + .cpu_info + .read() + .unwrap_or_else(PoisonError::into_inner) + .static_info(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetCPUDynamicInfo`..." + ); + builder.method_with_cr_custom::<(), (CpuDynamicInfo,), &str, _>( + "GetCPUDynamicInfo", + (), + ("info",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((SYSTEM_STATE + .cpu_info + .read() + .unwrap_or_else(PoisonError::into_inner) + .dynamic_info(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetDisksInfo`..." + ); + builder.method_with_cr_custom::<(), (Vec,), &str, _>( + "GetDisksInfo", + (), + ("info",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((SYSTEM_STATE + .disk_info + .read() + .unwrap_or_else(PoisonError::into_inner) + .info() + .collect::>(),))); + + Some(ctx) + }, + ); + + message!("ObservatoryDaemon::Main", "Registering D-Bus method `GetGPUList`..."); + builder.method_with_cr_custom::<(), (Vec,), &str, _>( + "GetGPUList", + (), + ("gpu_list",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((SYSTEM_STATE + .gpu_info + .read() + .unwrap_or_else(PoisonError::into_inner) + .enumerate() + .map(|id| id.to_owned()) + .collect::>(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetGPUStaticInfo`..." + ); + builder.method_with_cr_custom::<(), (Vec,), &str, _>( + "GetGPUStaticInfo", + (), + ("info",), + move |mut ctx, _, (): ()| { + let gpu_info = SYSTEM_STATE + .gpu_info + .read() + .unwrap_or_else(PoisonError::into_inner); + ctx.reply(Ok((gpu_info + .enumerate() + .map(|id| gpu_info.static_info(id).cloned().unwrap()) + .collect::>(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetGPUDynamicInfo`..." + ); + builder.method_with_cr_custom::<(), (Vec,), &str, _>( + "GetGPUDynamicInfo", + (), + ("info",), + move |mut ctx, _, (): ()| { + let gpu_info = SYSTEM_STATE + .gpu_info + .read() + .unwrap_or_else(PoisonError::into_inner); + ctx.reply(Ok((gpu_info + .enumerate() + .map(|id| gpu_info.dynamic_info(id).cloned().unwrap()) + .collect::>(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetFansInfo`..." + ); + builder.method_with_cr_custom::<(), (Vec,), &str, _>( + "GetFansInfo", + (), + ("info",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((SYSTEM_STATE + .fan_info + .read() + .unwrap_or_else(PoisonError::into_inner) + .info() + .collect::>(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetProcesses`..." + ); + builder.method_with_cr_custom::<(), (Processes,), &str, _>( + "GetProcesses", + (), + ("process_list",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((&*SYSTEM_STATE + .processes + .write() + .unwrap_or_else(PoisonError::into_inner),))); + + Some(ctx) + }, + ); + + message!("ObservatoryDaemon::Main", "Registering D-Bus method `GetApps`..."); + builder.method_with_cr_custom::<(), (Apps,), &str, _>( + "GetApps", + (), + ("app_list",), + move |mut ctx, _, (): ()| { + ctx.reply(Ok((SYSTEM_STATE + .apps + .read() + .unwrap_or_else(PoisonError::into_inner) + .app_list(),))); + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetServices`..." + ); + builder.method_with_cr_custom::<(), (Vec,), &str, _>( + "GetServices", + (), + ("service_list",), + move |mut ctx, _, (): ()| { + match SYSTEM_STATE + .services + .read() + .unwrap_or_else(PoisonError::into_inner) + .services() + { + Ok(s) => { + ctx.reply(Ok((s,))); + } + Err(e) => { + error!("ObservatoryDaemon::Main", "Failed to get services: {}", e); + ctx.reply::<(Vec,)>(Ok((vec![],))); + } + } + + Some(ctx) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `TerminateProcess`..." + ); + builder.method( + "TerminateProcess", + ("process_id",), + (), + move |_, _: &mut (), (pid,): (u32,)| { + execute_no_reply( + SYSTEM_STATE.processes.clone(), + move |processes| -> Result<(), u8> { Ok(processes.terminate_process(pid)) }, + "terminating process", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `KillProcess`..." + ); + builder.method( + "KillProcess", + ("process_id",), + (), + move |_, _: &mut (), (pid,): (u32,)| { + execute_no_reply( + SYSTEM_STATE.processes.clone(), + move |processes| -> Result<(), u8> { Ok(processes.kill_process(pid)) }, + "terminating process", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `EnableService`..." + ); + builder.method( + "EnableService", + ("service_name",), + (), + move |_, _: &mut (), (service,): (String,)| { + execute_no_reply( + SYSTEM_STATE.service_controller.clone(), + move |sc| { + if let Some(sc) = sc.as_ref() { + sc.enable_service(&service) + } else { + Err(ServicesError::MissingServiceController) + } + }, + "enabling service", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `DisableService`..." + ); + builder.method( + "DisableService", + ("service_name",), + (), + move |_, _: &mut (), (service,): (String,)| { + execute_no_reply( + SYSTEM_STATE.service_controller.clone(), + move |sc| { + if let Some(sc) = sc.as_ref() { + sc.disable_service(&service) + } else { + Err(ServicesError::MissingServiceController) + } + }, + "disabling service", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `StartService`..." + ); + builder.method( + "StartService", + ("service_name",), + (), + move |_, _: &mut (), (service,): (String,)| { + execute_no_reply( + SYSTEM_STATE.service_controller.clone(), + move |sc| { + if let Some(sc) = sc.as_ref() { + sc.start_service(&service) + } else { + Err(ServicesError::MissingServiceController) + } + }, + "starting service", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `StopService`..." + ); + builder.method( + "StopService", + ("service_name",), + (), + move |_, _: &mut (), (service,): (String,)| { + execute_no_reply( + SYSTEM_STATE.service_controller.clone(), + move |sc| { + if let Some(sc) = sc.as_ref() { + sc.stop_service(&service) + } else { + Err(ServicesError::MissingServiceController) + } + }, + "stopping service", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `RestartService`..." + ); + builder.method( + "RestartService", + ("service_name",), + (), + move |_, _: &mut (), (service,): (String,)| { + execute_no_reply( + SYSTEM_STATE.service_controller.clone(), + move |sc| { + if let Some(sc) = sc.as_ref() { + sc.restart_service(&service) + } else { + Err(ServicesError::MissingServiceController) + } + }, + "restarting service", + ) + }, + ); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetServiceLogs`..." + ); + builder.method_with_cr_custom::<(String, u32), (String,), &str, _>( + "GetServiceLogs", + ("name", "pid"), + ("service_list",), + move |mut ctx, _, (name, pid): (String, u32)| { + match SYSTEM_STATE + .services + .read() + .unwrap_or_else(PoisonError::into_inner) + .service_logs(&name, std::num::NonZeroU32::new(pid)) + { + Ok(s) => { + ctx.reply(Ok((s.as_ref().to_owned(),))); + } + Err(e) => { + ctx.reply(Result::<(Vec,), dbus::MethodErr>::Err( + dbus::MethodErr::failed::(&format!( + "Failed to get service logs: {e}" + )), + )); + } + } + + Some(ctx) + }, + ); + }); + + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus interface `org.freedesktop.DBus.Peer`..." + ); + let peer_itf = cr.register("org.freedesktop.DBus.Peer", |builder| { + message!( + "ObservatoryDaemon::Main", + "Registering D-Bus method `GetMachineId`..." + ); + builder.method("GetMachineId", (), ("machine_uuid",), |_, _, (): ()| { + Ok((std::fs::read_to_string("/var/lib/dbus/machine-id") + .map_or("UNKNOWN".into(), |s| s.trim().to_owned()),)) + }); + + message!("ObservatoryDaemon::Main", "Registering D-Bus method `Ping`..."); + builder.method("Ping", (), (), |_, _, (): ()| Ok(())); + }); + + message!( + "ObservatoryDaemon::Main", + "Instantiating System and inserting it into Crossroads..." + ); + cr.insert(DBUS_OBJECT_PATH, &[peer_itf, iface_token], ()); + + message!("ObservatoryDaemon::Main", "Creating thread pool..."); + rayon::ThreadPoolBuilder::new() + .num_threads(4) + .build_global()?; + + message!("ObservatoryDaemon::Main", "Serving D-Bus requests..."); + + let cr = Arc::new(Mutex::new(cr)); + c.start_receive(dbus::message::MatchRule::new_method_call(), { + Box::new(move |msg, conn| { + cr.lock() + .unwrap() + .handle_message(msg, conn) + .unwrap_or_else(|_| error!("ObservatoryDaemon::Main", "Failed to handle message")); + true + }) + }); + + loop { + c.process(std::time::Duration::from_millis(1000))?; + } +} + +fn execute_no_reply( + stats: Arc>, + command: impl FnOnce(&SF) -> Result<(), E> + Send + 'static, + description: &'static str, +) -> Result<(), dbus::MethodErr> { + rayon::spawn(move || { + let stats = match stats.read() { + Ok(s) => s, + Err(poisoned_lock) => { + warning!( + "ObservatoryDaemon::Main", + "Lock poisoned while executing command for {}", + description + ); + poisoned_lock.into_inner() + } + }; + + if let Err(e) = command(&stats) { + error!("ObservatoryDaemon::Main", "Failed to execute command: {}", e); + } + }); + + Ok(()) +} diff --git a/src/observatory-daemon/src/platform/apps.rs b/src/observatory-daemon/src/platform/apps.rs new file mode 100644 index 0000000..912adf4 --- /dev/null +++ b/src/observatory-daemon/src/platform/apps.rs @@ -0,0 +1,92 @@ +/* sys_info_v2/observatory-daemon/src/platform/apps.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later versionBecomeMonitor. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use dbus::arg::{Append, Arg}; + +/// A running application +pub trait AppExt<'a>: Default + Append + Arg { + type Iter: Iterator; + + /// The name of the app in human-readable form + fn name(&self) -> &str; + + /// The icon used by the app + fn icon(&self) -> Option<&str>; + + /// A platform-specific unique id + fn id(&self) -> &str; + + /// The command used to launch the app + fn command(&self) -> &str; + + /// The list of processes that the app uses + /// + /// It is expected that the iterator yields the elements from smallest to largest + fn pids(&'a self) -> Self::Iter; +} + +impl Arg for crate::platform::App { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(ssssau)") + } +} + +impl Append for crate::platform::App { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append(( + self.name(), + self.icon().unwrap_or(""), + self.id(), + self.command(), + self.pids().clone().collect::>(), + )); + } +} + +/// The public interface that describes how the list of running apps is obtained +pub trait AppsExt<'a>: Default + Append + Arg { + type A: AppExt<'a>; + type P: crate::platform::ProcessExt<'a>; + + /// Refresh the internal app cache + /// + /// It is expected that implementors of this trait cache the running app list once obtained from + /// the underlying OS + fn refresh_cache(&mut self, processes: &std::collections::HashMap); + + /// Return the list of (cached) running apps + fn app_list(&self) -> &[Self::A]; +} + +impl Arg for crate::platform::Apps { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Array; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("a(ssssau(ddddd))") + } +} + +impl Append for crate::platform::Apps { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append(self.app_list()) + } +} diff --git a/src/observatory-daemon/src/platform/cpu_info.rs b/src/observatory-daemon/src/platform/cpu_info.rs new file mode 100644 index 0000000..caeb7fb --- /dev/null +++ b/src/observatory-daemon/src/platform/cpu_info.rs @@ -0,0 +1,213 @@ +/* sys_info_v2/observatory-daemon/src/platform/cpu_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use dbus::arg::{Append, Arg}; + +#[repr(u8)] +pub enum OptionalBool { + False, + True, + None, +} + +impl From> for OptionalBool { + fn from(value: Option) -> Self { + value.map_or(OptionalBool::None, |b| { + if b { + OptionalBool::True + } else { + OptionalBool::False + } + }) + } +} + +/// Describes the static (unchanging) information about the CPU/system +pub trait CpuStaticInfoExt: Default + Append + Arg { + /// The CPU vendor and model + fn name(&self) -> &str; + + /// The number of logical CPUs (i.e. including SMT) + fn logical_cpu_count(&self) -> u32; + + /// The number of physical CPU sockets + fn socket_count(&self) -> Option; + + /// The base CPU frequency in kHz + fn base_frequency_khz(&self) -> Option; + + /// The name of the virtualization technology available on this host + fn virtualization_technology(&self) -> Option<&str>; + + /// Check if the OS is running in a virtual machine + fn is_virtual_machine(&self) -> Option; + + /// The total amount of L1 cache (instruction and data) + fn l1_combined_cache(&self) -> Option; + + /// The amount of L2 cache + fn l2_cache(&self) -> Option; + + /// The amount of L3 cache + fn l3_cache(&self) -> Option; + + /// The amount of L4 cache + fn l4_cache(&self) -> Option; +} + +impl Arg for crate::platform::CpuStaticInfo { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(suytsytttt)") + } +} + +impl Append for crate::platform::CpuStaticInfo { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append_struct(|ia| { + ia.append(self.name()); + ia.append(self.logical_cpu_count()); + ia.append(self.socket_count().unwrap_or(0)); + ia.append(self.base_frequency_khz().unwrap_or(0)); + ia.append(self.virtualization_technology().unwrap_or("")); + ia.append(OptionalBool::from(self.is_virtual_machine()) as u8); + ia.append(self.l1_combined_cache().unwrap_or(0)); + ia.append(self.l2_cache().unwrap_or(0)); + ia.append(self.l3_cache().unwrap_or(0)); + ia.append(self.l4_cache().unwrap_or(0)); + }); + } +} + +/// Describes CPU/system information that changes over time +pub trait CpuDynamicInfoExt<'a>: Default + Append + Arg { + /// An iterator that yields number of logical core f32 percentage values + /// + /// It is expected that the iterator yields as many values as exactly the number + /// of CPU logical cores + type Iter: Iterator; + + /// The overall utilization of the CPU(s) + fn overall_utilization_percent(&self) -> f32; + + /// The overall utilization of the CPU(s) by the OS kernel + fn overall_kernel_utilization_percent(&self) -> f32; + + /// The overall utilization of each logical core + fn per_logical_cpu_utilization_percent(&'a self) -> Self::Iter; + + /// The overall utilization of each logical core by the OS kernel + fn per_logical_cpu_kernel_utilization_percent(&'a self) -> Self::Iter; + + /// The current average CPU frequency + fn current_frequency_mhz(&self) -> u64; + + /// The temperature of the CPU + /// + /// While all modern chips report several temperatures from the CPU die, it is expected that + /// implementations provide the most user relevant value here + fn temperature(&self) -> Option; + + /// The number of running processes in the system + fn process_count(&self) -> u64; + + /// The number of active threads in the system + fn thread_count(&self) -> u64; + + /// The number of open file handles in the system + fn handle_count(&self) -> u64; + + /// The number of seconds that have passed since the OS was booted + fn uptime_seconds(&self) -> u64; + + /// The cpufreq driver + fn cpufreq_driver(&self) -> Option<&str>; + + /// The cpufreq governor + fn cpufreq_governor(&self) -> Option<&str>; + + /// The energy performance preference + fn energy_performance_preference(&self) -> Option<&str>; +} + +impl Arg for crate::platform::CpuDynamicInfo { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(ddadadtdttttsss)") + } +} + +impl Append for crate::platform::CpuDynamicInfo { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append_struct(|ia| { + ia.append(self.overall_utilization_percent() as f64); + ia.append(self.overall_kernel_utilization_percent() as f64); + ia.append( + self.per_logical_cpu_utilization_percent() + .map(|v| *v as f64) + .collect::>(), + ); + ia.append( + self.per_logical_cpu_kernel_utilization_percent() + .map(|v| *v as f64) + .collect::>(), + ); + ia.append(self.current_frequency_mhz()); + ia.append(self.temperature().map_or(0_f64, |v| v as f64)); + ia.append(self.process_count()); + ia.append(self.thread_count()); + ia.append(self.handle_count()); + ia.append(self.uptime_seconds()); + ia.append(self.cpufreq_driver().unwrap_or("")); + ia.append(self.cpufreq_governor().unwrap_or("")); + ia.append(self.energy_performance_preference().unwrap_or("")); + }); + } +} + +/// Provides an interface for gathering CPU/System information. +pub trait CpuInfoExt<'a> { + type S: CpuStaticInfoExt; + type D: CpuDynamicInfoExt<'a>; + type P: crate::platform::ProcessesExt<'a>; + + /// Refresh the internal static information cache + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + /// + /// It is expected that this is only called one during the lifetime of this instance, but + /// implementation should not rely on this. + fn refresh_static_info_cache(&mut self); + + /// Refresh the internal dynamic/continuously changing information cache + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + fn refresh_dynamic_info_cache(&mut self, processes: &Self::P); + + /// Returns the static information for the CPU. + fn static_info(&self) -> &Self::S; + + /// Returns the dynamic information for the CPU. + fn dynamic_info(&self) -> &Self::D; +} diff --git a/src/observatory-daemon/src/platform/disk_info.rs b/src/observatory-daemon/src/platform/disk_info.rs new file mode 100644 index 0000000..9d7f138 --- /dev/null +++ b/src/observatory-daemon/src/platform/disk_info.rs @@ -0,0 +1,131 @@ +/* sys_info_v2/observatory-daemon/src/platform/disk_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use dbus::arg::IterAppend; +use dbus::{ + arg::{Append, Arg, ArgType}, + Signature, +}; + +#[allow(non_camel_case_types)] +#[allow(dead_code)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum DiskType { + Unknown = 0, + HDD, + SSD, + NVMe, + eMMC, + SD, + iSCSI, + Optical, +} + +impl Default for DiskType { + fn default() -> Self { + Self::Unknown + } +} + +/// Describes the static (unchanging) information about a physical disk +pub trait DiskInfoExt: Default + Append + Arg { + /// The disk's unique identifier + fn id(&self) -> &str; + + /// The disk's model in human-readable form + fn model(&self) -> &str; + + /// The disk's type + fn r#type(&self) -> DiskType; + + /// The disk's capacity in bytes + fn capacity(&self) -> u64; + + /// The disk's formatted capacity in bytes + fn formatted(&self) -> u64; + + /// Check if the disk is the system disk + fn is_system_disk(&self) -> bool; + + /// The disk's busy percentage + fn busy_percent(&self) -> f32; + + /// The disk's response time in milliseconds + fn response_time_ms(&self) -> f32; + + /// The disk's read speed in bytes per second + fn read_speed(&self) -> u64; + + /// The disk's write speed in bytes per second + fn write_speed(&self) -> u64; +} + +impl Arg for crate::platform::DiskInfo { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("(ssyttbddtt)") + } +} + +impl Append for crate::platform::DiskInfo { + fn append_by_ref(&self, ia: &mut IterAppend) { + ia.append(( + self.id(), + self.model(), + self.r#type() as u8, + self.capacity(), + self.formatted(), + self.is_system_disk(), + self.busy_percent() as f64, + self.response_time_ms() as f64, + self.read_speed(), + self.write_speed(), + )); + } +} + +impl Append for crate::platform::DiskInfoIter<'_> { + fn append_by_ref(&self, ia: &mut IterAppend) { + ia.append_array(&crate::platform::DiskInfo::signature(), |a| { + for v in self.0.clone() { + a.append(v); + } + }); + } +} + +/// Provides an interface for gathering disk information +pub trait DisksInfoExt<'a> { + type S: DiskInfoExt; + type Iter: Iterator + where + >::S: 'a; + + /// Refresh the internal information cache + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + fn refresh_cache(&mut self); + + /// Returns the static information for the disks present in the system. + fn info(&'a self) -> Self::Iter; +} diff --git a/src/observatory-daemon/src/platform/fan_info.rs b/src/observatory-daemon/src/platform/fan_info.rs new file mode 100644 index 0000000..770c131 --- /dev/null +++ b/src/observatory-daemon/src/platform/fan_info.rs @@ -0,0 +1,102 @@ +/* sys_info_v2/observatory-daemon/src/platform/fan_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use dbus::arg::IterAppend; +use dbus::{ + arg::{Append, Arg, ArgType}, + Signature, +}; + +/// Describes the static (unchanging) information about a physical fan +pub trait FanInfoExt: Default + Append + Arg { + /// The fan's identifier + fn fan_label(&self) -> &str; + + /// The temp that the fan is meant to combat + fn temp_name(&self) -> &str; + + /// The fan's temperature in mC (milli celcius) + fn temp_amount(&self) -> i64; + + /// The fan's sped in rpm + fn rpm(&self) -> u64; + + /// Commanded speed in pwm percent + fn percent_vroomimg(&self) -> f32; + + /// The fan's index in its hwmon + fn fan_index(&self) -> u64; + + /// The hwmon this fan is in + fn hwmon_index(&self) -> u64; + + /// The max rpm reported by the system, 0 if none reported + fn max_speed(&self) -> u64; +} + +impl Arg for crate::platform::FanInfo { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("(ssxtdttt)") + } +} + +impl Append for crate::platform::FanInfo { + fn append_by_ref(&self, ia: &mut IterAppend) { + ia.append(( + self.fan_label(), + self.temp_name(), + self.temp_amount(), + self.rpm(), + self.percent_vroomimg() as f64, + self.fan_index(), + self.hwmon_index(), + self.max_speed(), + )); + } +} + +impl Append for crate::platform::FanInfoIter<'_> { + fn append_by_ref(&self, ia: &mut IterAppend) { + ia.append_array(&crate::platform::FanInfo::signature(), |a| { + for v in self.0.clone() { + a.append(v); + } + }); + } +} + +/// Provides an interface for gathering fan information +pub trait FansInfoExt<'a> { + type S: FanInfoExt; + type Iter: Iterator + where + >::S: 'a; + + /// Refresh the internal information cache + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + fn refresh_cache(&mut self); + + /// Returns the static information for the fans present in the system. + fn info(&'a self) -> Self::Iter; +} diff --git a/src/observatory-daemon/src/platform/gpu_info.rs b/src/observatory-daemon/src/platform/gpu_info.rs new file mode 100644 index 0000000..619950f --- /dev/null +++ b/src/observatory-daemon/src/platform/gpu_info.rs @@ -0,0 +1,279 @@ +/* sys_info_v2/observatory-daemon/src/platform/gpu_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use dbus::arg::{Append, Arg}; +use serde::{Deserialize, Serialize}; + +#[repr(u8)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub enum OpenGLApi { + OpenGL, + OpenGLES, + Invalid = 255, +} + +impl Default for OpenGLApi { + fn default() -> Self { + Self::Invalid + } +} + +#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] +pub struct OpenGLApiVersion { + pub major: u8, + pub minor: u8, + pub api: OpenGLApi, +} + +impl Arg for OpenGLApiVersion { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(yyy)") + } +} + +impl Append for OpenGLApiVersion { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append((self.major, self.minor, self.api as u8)); + } +} + +#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] +pub struct ApiVersion { + pub major: u16, + pub minor: u16, + pub patch: u16, +} + +impl Arg for ApiVersion { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(qqq)") + } +} + +impl Append for ApiVersion { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append((self.major, self.minor, self.patch)); + } +} + +/// Describes the static (unchanging) information about a GPU +pub trait GpuStaticInfoExt: Default + Clone + Append + Arg { + /// Platform specific unique identifier for a GPU + /// + /// Implementations must ensure that two separate GPUs never have the same id, even if they are + /// identical models + fn id(&self) -> &str; + + /// The human-readable name of the GPU + fn device_name(&self) -> &str; + + /// The PCI vendor identifier + fn vendor_id(&self) -> u16; + + /// The PCI device identifier + fn device_id(&self) -> u16; + + /// The total amount of GPU memory available to the device + /// + /// It is platform/driver specific if this value includes any memory shared with system RAM + fn total_memory(&self) -> u64; + + /// The total amount of gtt/gart available + fn total_gtt(&self) -> u64; + + /// The version of OpenGL that the GPU supports + /// + /// If the platform does not provide OpenGL support it should return None + fn opengl_version(&self) -> Option<&OpenGLApiVersion>; + + /// The version of Vulkan that the GPU supports + /// + /// If the platform does not provide Vulkan support it should return None + fn vulkan_version(&self) -> Option<&ApiVersion>; + + /// The version of Metal that the GPU supports + /// + /// If the platform does not provide Metal support it should return None + fn metal_version(&self) -> Option<&ApiVersion>; + + /// The version of Direct3D that the GPU supports + /// + /// If the platform does not provide Direct3D support it should return None + fn direct3d_version(&self) -> Option<&ApiVersion>; + + /// The PCI express lane generation that the GPU is mounted on + fn pcie_gen(&self) -> u8; + + /// The number of PCI express lanes in use by the GPU + fn pcie_lanes(&self) -> u8; +} + +impl Arg for crate::platform::GpuStaticInfo { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(ssqqtt(yyy)(qqq)(qqq)(qqq)yy)") + } +} + +impl Append for crate::platform::GpuStaticInfo { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append(( + self.id(), + self.device_name(), + self.vendor_id(), + self.device_id(), + self.total_memory(), + self.total_gtt(), + self.opengl_version().map(|v| *v).unwrap_or_default(), + self.vulkan_version().map(|v| *v).unwrap_or_default(), + self.metal_version().map(|v| *v).unwrap_or_default(), + self.direct3d_version().map(|v| *v).unwrap_or_default(), + self.pcie_gen(), + self.pcie_lanes(), + )); + } +} + +/// Describes GPU information that changes over time +pub trait GpuDynamicInfoExt: Default + Clone + Append + Arg { + /// Platform specific unique identifier for a GPU + /// + /// Implementations must ensure that two separate GPUs never have the same id, even if they are + /// identical models + /// Note: This value is actually static but is part of this interface to help users of the type + /// easily match these data points to a GPU + fn id(&self) -> &str; + + /// The GPU temperature in degrees Celsius + /// + /// While all modern chips report several temperatures from the GPU card, it is expected that + /// implementations provide the most user relevant value here + fn temp_celsius(&self) -> u32; + + /// The speed of the fan represented as a percentage from it's maximum speed + fn fan_speed_percent(&self) -> u32; + + /// Load of the graphics pipeline + fn util_percent(&self) -> u32; + + /// The power draw in watts + fn power_draw_watts(&self) -> f32; + + /// The maximum power that the GPU is allowed to draw + fn power_draw_max_watts(&self) -> f32; + + /// The current GPU core clock frequency + fn clock_speed_mhz(&self) -> u32; + + /// The maximum allowed GPU core clock frequency + fn clock_speed_max_mhz(&self) -> u32; + + /// The current speed of the on-board memory + fn mem_speed_mhz(&self) -> u32; + + /// The maximum speed of the on-board memory + fn mem_speed_max_mhz(&self) -> u32; + + /// The amount of memory available + fn free_memory(&self) -> u64; + + /// The memory that is currently being used + fn used_memory(&self) -> u64; + + /// The amount of gtt/gart available + fn used_gtt(&self) -> u64; + /// Utilization percent of the encoding pipeline of the GPU + fn encoder_percent(&self) -> u32; + + /// Utilization percent of the decoding pipeline of the GPU + fn decoder_percent(&self) -> u32; +} + +impl Arg for crate::platform::GpuDynamicInfo { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Struct; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("(suuudduuuutttuu)") + } +} + +impl Append for crate::platform::GpuDynamicInfo { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append_struct(|ia| { + ia.append(self.id()); + ia.append(self.temp_celsius()); + ia.append(self.fan_speed_percent()); + ia.append(self.util_percent()); + ia.append(self.power_draw_watts() as f64); + ia.append(self.power_draw_max_watts() as f64); + ia.append(self.clock_speed_mhz()); + ia.append(self.clock_speed_max_mhz()); + ia.append(self.mem_speed_mhz()); + ia.append(self.mem_speed_max_mhz()); + ia.append(self.free_memory()); + ia.append(self.used_memory()); + ia.append(self.used_gtt()); + ia.append(self.encoder_percent()); + ia.append(self.decoder_percent()); + }); + } +} + +/// Trait that provides an interface for gathering GPU information. +pub trait GpuInfoExt<'a> { + type S: GpuStaticInfoExt; + type D: GpuDynamicInfoExt; + type P: crate::platform::ProcessesExt<'a>; + + /// An iterator that yields the PCI identifiers for each GPU installed in the system + type Iter: Iterator; + + /// Refresh the list of available GPUs + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + fn refresh_gpu_list(&mut self); + + /// Refresh the internal static information cache + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + fn refresh_static_info_cache(&mut self); + + /// Refresh the internal dynamic/continuously changing information cache + /// + /// It is expected that implementors of this trait cache this information, once obtained + /// from the underlying OS + fn refresh_dynamic_info_cache(&mut self, processes: &mut Self::P); + + /// Returns the number of GPUs present in the system + fn enumerate(&'a self) -> Self::Iter; + + /// Returns the static information for GPU with the PCI id `pci_id`. + fn static_info(&self, id: &str) -> Option<&Self::S>; + + /// Returns the dynamic information for the GPU with the PCI id `pci_id`. + fn dynamic_info(&self, id: &str) -> Option<&Self::D>; +} diff --git a/src/observatory-daemon/src/platform/linux/apps.rs b/src/observatory-daemon/src/platform/linux/apps.rs new file mode 100644 index 0000000..c68e2c6 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/apps.rs @@ -0,0 +1,198 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/apps.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::num::NonZeroU32; +use std::path::PathBuf; +use std::{collections::HashMap, sync::Arc, time::Instant}; + +use crate::platform::apps::*; +use crate::platform::ProcessExt; + +use super::{INITIAL_REFRESH_TS, MIN_DELTA_REFRESH}; + +const APP_IGNORELIST: &[&str] = &[ + "guake-prefs", + "org.codeberg.dnkl.foot-server", + "org.codeberg.dnkl.footclient", +]; + +type LinuxProcess = crate::platform::Process; + +#[derive(Debug, Clone)] +pub struct LinuxApp { + pub name: Arc, + pub icon: Option>, + pub id: Arc, + pub command: Arc, + pub pids: Vec, +} + +impl Default for LinuxApp { + fn default() -> Self { + let empty_arc = Arc::::from(""); + Self { + name: empty_arc.clone(), + icon: None, + id: empty_arc.clone(), + command: empty_arc.clone(), + pids: vec![], + } + } +} + +impl<'a> AppExt<'a> for LinuxApp { + type Iter = core::slice::Iter<'a, u32>; + + fn name(&self) -> &str { + self.name.as_ref() + } + + fn icon(&self) -> Option<&str> { + self.icon.as_ref().map(|s| s.as_ref()) + } + + fn id(&self) -> &str { + self.id.as_ref() + } + + fn command(&self) -> &str { + self.command.as_ref() + } + + fn pids(&'a self) -> Self::Iter { + self.pids.iter() + } +} + +pub struct LinuxApps { + app_cache: Vec, + + refresh_timestamp: Instant, +} + +impl Default for LinuxApps { + fn default() -> Self { + Self { + app_cache: vec![], + refresh_timestamp: *INITIAL_REFRESH_TS, + } + } +} + +impl LinuxApps { + pub fn new() -> Self { + Default::default() + } +} + +impl app_rummage::Process for LinuxProcess { + fn pid(&self) -> NonZeroU32 { + NonZeroU32::new(::pid(self)).unwrap() + } + + fn executable_path(&self) -> Option { + if self.exe().is_empty() { + return None; + } + + Some(PathBuf::from(self.exe())) + } + + fn name(&self) -> &str { + ::name(self) + } +} + +impl<'a> AppsExt<'a> for LinuxApps { + type A = LinuxApp; + type P = LinuxProcess; + + fn refresh_cache(&mut self, processes: &HashMap) { + let now = Instant::now(); + if now.duration_since(self.refresh_timestamp) < MIN_DELTA_REFRESH { + return; + } + self.refresh_timestamp = now; + + let empty_string: Arc = Arc::from(""); + + let mut installed_apps = app_rummage::installed_apps(); + for app in APP_IGNORELIST { + installed_apps.remove(*app); + } + + self.app_cache = app_rummage::running_apps(&installed_apps, processes.values()) + .drain(..) + .map(|(app, mut pids)| LinuxApp { + name: Arc::from(app.name.as_ref()), + icon: app.icon.as_ref().map(|icon| { + let icon = icon.as_ref(); + // We can't access `/snap` when packaged as a Snap, we can go through the hostfs though. + // So update the icon path to reflect this change. + if let Some(_) = std::env::var_os("SNAP_CONTEXT") { + if icon.starts_with("/snap") { + Arc::from(format!("{}{}", "/var/lib/snapd/hostfs", icon)) + } else { + Arc::from(icon) + } + } else { + Arc::from(icon) + } + }), + id: Arc::from(app.id.as_ref()), + command: app + .exec + .as_ref() + .map(|exec| Arc::from(exec.as_ref())) + .unwrap_or(empty_string.clone()), + pids: pids.drain(..).map(NonZeroU32::get).collect(), + }) + .collect(); + } + + fn app_list(&self) -> &[Self::A] { + &self.app_cache + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::platform::{AppsExt, Processes, ProcessesExt}; + + #[test] + fn test_refresh_cache() { + let mut p = Processes::new(); + p.refresh_cache(); + + let mut apps = LinuxApps::new(); + assert!(apps.app_cache.is_empty()); + + apps.refresh_cache(p.process_list()); + assert!(!apps.app_cache.is_empty()); + + let sample = apps.app_cache.iter().take(20); + for app in sample { + eprintln!("{:?}", app); + } + + assert!(false) + } +} diff --git a/src/observatory-daemon/src/platform/linux/cpu_info.rs b/src/observatory-daemon/src/platform/linux/cpu_info.rs new file mode 100644 index 0000000..df6ab4b --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/cpu_info.rs @@ -0,0 +1,1580 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/cpu_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::{fs::OpenOptions, io::Read, os::unix::ffi::OsStrExt, sync::Arc, time::Instant}; + +use super::{CPU_COUNT, INITIAL_REFRESH_TS, MIN_DELTA_REFRESH}; +use crate::{critical, debug, platform::cpu_info::*}; + +const PROC_STAT_IGNORE: [usize; 2] = [0, 5]; +const PROC_STAT_IDLE: [usize; 1] = [4]; +const PROC_STAT_KERNEL: [usize; 2] = [6, 7]; + +#[derive(Debug, Copy, Clone)] +struct CpuTicks { + used: u64, + idle: u64, + kernel: u64, +} + +impl Default for CpuTicks { + fn default() -> Self { + Self { + used: 0, + idle: 0, + kernel: 0, + } + } +} + +impl CpuTicks { + pub fn update(&mut self, line: &str) -> (f32, f32) { + let failure = |e| { + critical!("Gatherer::CPU", "Failed to read /proc/stat: {}", e); + 0 + }; + let fields = line.split_whitespace(); + let mut new_ticks = CpuTicks::default(); + for (pos, field) in fields.enumerate() { + match pos { + // 0 = cpu(num), 4 = idle, 6 + 7 kernel stuff, rest = busy time + x if PROC_STAT_IGNORE.contains(&x) => (), + x if PROC_STAT_IDLE.contains(&x) => { + new_ticks.idle += field.trim().parse::().unwrap_or_else(failure) + } + x if PROC_STAT_KERNEL.contains(&x) => { + new_ticks.kernel += field.trim().parse::().unwrap_or_else(failure) + } + _ => new_ticks.used += field.trim().parse::().unwrap_or_else(failure), + } + } + let used = new_ticks.used - self.used; + let kernel = new_ticks.kernel - self.kernel; + let idle = new_ticks.idle - self.idle; + let total = (used + kernel + idle) as f32; + + let util = (used + kernel) as f32 / total; + let kernel_util = kernel as f32 / total; + + *self = new_ticks; + (util * 100.0, kernel_util * 100.0) + } +} + +#[derive(Clone, Debug)] +pub struct LinuxCpuStaticInfo { + name: Arc, + logical_cpu_count: u32, + socket_count: Option, + base_frequency_khz: Option, + virtualization_technology: Option>, + is_virtual_machine: Option, + l1_combined_cache: Option, + l2_cache: Option, + l3_cache: Option, + l4_cache: Option, +} + +impl Default for LinuxCpuStaticInfo { + fn default() -> Self { + Self { + name: Arc::from(""), + logical_cpu_count: 0, + socket_count: None, + base_frequency_khz: None, + virtualization_technology: None, + is_virtual_machine: None, + l1_combined_cache: None, + l2_cache: None, + l3_cache: None, + l4_cache: None, + } + } +} + +impl LinuxCpuStaticInfo { + pub fn new() -> Self { + Default::default() + } +} + +impl CpuStaticInfoExt for LinuxCpuStaticInfo { + fn name(&self) -> &str { + self.name.as_ref() + } + + fn logical_cpu_count(&self) -> u32 { + self.logical_cpu_count + } + + fn socket_count(&self) -> Option { + self.socket_count + } + + fn base_frequency_khz(&self) -> Option { + self.base_frequency_khz + } + + fn virtualization_technology(&self) -> Option<&str> { + self.virtualization_technology.as_ref().map(|s| s.as_ref()) + } + + fn is_virtual_machine(&self) -> Option { + self.is_virtual_machine + } + + fn l1_combined_cache(&self) -> Option { + self.l1_combined_cache + } + + fn l2_cache(&self) -> Option { + self.l2_cache + } + + fn l3_cache(&self) -> Option { + self.l3_cache + } + + fn l4_cache(&self) -> Option { + self.l4_cache + } +} + +#[derive(Clone, Default, Debug)] +pub struct LinuxCpuDynamicInfo { + overall_utilization_percent: f32, + overall_kernel_utilization_percent: f32, + cpu_store_old: CpuTicks, + per_logical_cpu_utilization_percent: Vec, + per_logical_cpu_kernel_utilization_percent: Vec, + per_logical_cpu_store_old: Vec, + current_frequency_mhz: u64, + temperature: Option, + process_count: u64, + thread_count: u64, + handle_count: u64, + uptime_seconds: u64, + cpufreq_driver: Option>, + cpufreq_governor: Option>, + energy_performance_preference: Option>, +} + +impl LinuxCpuDynamicInfo { + pub fn new() -> Self { + Self { + overall_utilization_percent: 0.0, + overall_kernel_utilization_percent: 0.0, + cpu_store_old: CpuTicks::default(), + per_logical_cpu_utilization_percent: vec![], + per_logical_cpu_kernel_utilization_percent: vec![], + per_logical_cpu_store_old: vec![], + current_frequency_mhz: 0, + temperature: None, + process_count: 0, + thread_count: 0, + handle_count: 0, + uptime_seconds: 0, + cpufreq_driver: None, + cpufreq_governor: None, + energy_performance_preference: None, + } + } +} + +impl<'a> CpuDynamicInfoExt<'a> for LinuxCpuDynamicInfo { + type Iter = std::slice::Iter<'a, f32>; + + fn overall_utilization_percent(&self) -> f32 { + self.overall_utilization_percent + } + + fn overall_kernel_utilization_percent(&self) -> f32 { + self.overall_kernel_utilization_percent + } + + fn per_logical_cpu_utilization_percent(&'a self) -> Self::Iter { + self.per_logical_cpu_utilization_percent.iter() + } + + fn per_logical_cpu_kernel_utilization_percent(&'a self) -> Self::Iter { + self.per_logical_cpu_kernel_utilization_percent.iter() + } + + fn current_frequency_mhz(&self) -> u64 { + self.current_frequency_mhz + } + + fn temperature(&self) -> Option { + self.temperature + } + + fn process_count(&self) -> u64 { + self.process_count + } + + fn thread_count(&self) -> u64 { + self.thread_count + } + + fn handle_count(&self) -> u64 { + self.handle_count + } + + fn uptime_seconds(&self) -> u64 { + self.uptime_seconds + } + + fn cpufreq_driver(&self) -> Option<&str> { + self.cpufreq_driver.as_ref().map(|s| s.as_ref()) + } + + fn cpufreq_governor(&self) -> Option<&str> { + self.cpufreq_governor.as_ref().map(|s| s.as_ref()) + } + fn energy_performance_preference(&self) -> Option<&str> { + self.energy_performance_preference + .as_ref() + .map(|s| s.as_ref()) + } +} + +#[derive(Debug)] +pub struct LinuxCpuInfo { + static_info: LinuxCpuStaticInfo, + dynamic_info: LinuxCpuDynamicInfo, + + static_refresh_timestamp: Instant, + dynamic_refresh_timestamp: Instant, +} + +impl LinuxCpuInfo { + pub fn new() -> Self { + let mut cpu_store_old = Vec::with_capacity(*CPU_COUNT + 1); + cpu_store_old.resize(*CPU_COUNT + 1, CpuTicks::default()); + + Self { + static_info: LinuxCpuStaticInfo::new(), + dynamic_info: LinuxCpuDynamicInfo::new(), + + static_refresh_timestamp: *INITIAL_REFRESH_TS, + dynamic_refresh_timestamp: *INITIAL_REFRESH_TS, + } + } + + // Code lifted and adapted from `sysinfo` crate, found in src/linux/cpu.rs + fn name() -> Arc { + fn get_value(s: &str) -> String { + s.split(':') + .last() + .map(|x| x.trim().into()) + .unwrap_or("".to_owned()) + } + + fn get_hex_value(s: &str) -> u32 { + s.split(':') + .last() + .map(|x| x.trim()) + .filter(|x| x.starts_with("0x")) + .map(|x| u32::from_str_radix(&x[2..], 16).unwrap()) + .unwrap_or_default() + } + + fn get_arm_implementer(implementer: u32) -> Option<&'static str> { + Some(match implementer { + 0x41 => "ARM", + 0x42 => "Broadcom", + 0x43 => "Cavium", + 0x44 => "DEC", + 0x46 => "FUJITSU", + 0x48 => "HiSilicon", + 0x49 => "Infineon", + 0x4d => "Motorola/Freescale", + 0x4e => "NVIDIA", + 0x50 => "APM", + 0x51 => "Qualcomm", + 0x53 => "Samsung", + 0x56 => "Marvell", + 0x61 => "Apple", + 0x66 => "Faraday", + 0x69 => "Intel", + 0x70 => "Phytium", + 0xc0 => "Ampere", + _ => return None, + }) + } + + fn get_arm_part(implementer: u32, part: u32) -> Option<&'static str> { + Some(match (implementer, part) { + // ARM + (0x41, 0x810) => "ARM810", + (0x41, 0x920) => "ARM920", + (0x41, 0x922) => "ARM922", + (0x41, 0x926) => "ARM926", + (0x41, 0x940) => "ARM940", + (0x41, 0x946) => "ARM946", + (0x41, 0x966) => "ARM966", + (0x41, 0xa20) => "ARM1020", + (0x41, 0xa22) => "ARM1022", + (0x41, 0xa26) => "ARM1026", + (0x41, 0xb02) => "ARM11 MPCore", + (0x41, 0xb36) => "ARM1136", + (0x41, 0xb56) => "ARM1156", + (0x41, 0xb76) => "ARM1176", + (0x41, 0xc05) => "Cortex-A5", + (0x41, 0xc07) => "Cortex-A7", + (0x41, 0xc08) => "Cortex-A8", + (0x41, 0xc09) => "Cortex-A9", + (0x41, 0xc0d) => "Cortex-A17", // Originally A12 + (0x41, 0xc0f) => "Cortex-A15", + (0x41, 0xc0e) => "Cortex-A17", + (0x41, 0xc14) => "Cortex-R4", + (0x41, 0xc15) => "Cortex-R5", + (0x41, 0xc17) => "Cortex-R7", + (0x41, 0xc18) => "Cortex-R8", + (0x41, 0xc20) => "Cortex-M0", + (0x41, 0xc21) => "Cortex-M1", + (0x41, 0xc23) => "Cortex-M3", + (0x41, 0xc24) => "Cortex-M4", + (0x41, 0xc27) => "Cortex-M7", + (0x41, 0xc60) => "Cortex-M0+", + (0x41, 0xd01) => "Cortex-A32", + (0x41, 0xd02) => "Cortex-A34", + (0x41, 0xd03) => "Cortex-A53", + (0x41, 0xd04) => "Cortex-A35", + (0x41, 0xd05) => "Cortex-A55", + (0x41, 0xd06) => "Cortex-A65", + (0x41, 0xd07) => "Cortex-A57", + (0x41, 0xd08) => "Cortex-A72", + (0x41, 0xd09) => "Cortex-A73", + (0x41, 0xd0a) => "Cortex-A75", + (0x41, 0xd0b) => "Cortex-A76", + (0x41, 0xd0c) => "Neoverse-N1", + (0x41, 0xd0d) => "Cortex-A77", + (0x41, 0xd0e) => "Cortex-A76AE", + (0x41, 0xd13) => "Cortex-R52", + (0x41, 0xd20) => "Cortex-M23", + (0x41, 0xd21) => "Cortex-M33", + (0x41, 0xd40) => "Neoverse-V1", + (0x41, 0xd41) => "Cortex-A78", + (0x41, 0xd42) => "Cortex-A78AE", + (0x41, 0xd43) => "Cortex-A65AE", + (0x41, 0xd44) => "Cortex-X1", + (0x41, 0xd46) => "Cortex-A510", + (0x41, 0xd47) => "Cortex-A710", + (0x41, 0xd48) => "Cortex-X2", + (0x41, 0xd49) => "Neoverse-N2", + (0x41, 0xd4a) => "Neoverse-E1", + (0x41, 0xd4b) => "Cortex-A78C", + (0x41, 0xd4c) => "Cortex-X1C", + (0x41, 0xd4d) => "Cortex-A715", + (0x41, 0xd4e) => "Cortex-X3", + + // Broadcom + (0x42, 0x00f) => "Brahma-B15", + (0x42, 0x100) => "Brahma-B53", + (0x42, 0x516) => "ThunderX2", + + // Cavium + (0x43, 0x0a0) => "ThunderX", + (0x43, 0x0a1) => "ThunderX-88XX", + (0x43, 0x0a2) => "ThunderX-81XX", + (0x43, 0x0a3) => "ThunderX-83XX", + (0x43, 0x0af) => "ThunderX2-99xx", + + // DEC + (0x44, 0xa10) => "SA110", + (0x44, 0xa11) => "SA1100", + + // Fujitsu + (0x46, 0x001) => "A64FX", + + // HiSilicon + (0x48, 0xd01) => "Kunpeng-920", // aka tsv110 + + // NVIDIA + (0x4e, 0x000) => "Denver", + (0x4e, 0x003) => "Denver 2", + (0x4e, 0x004) => "Carmel", + + // APM + (0x50, 0x000) => "X-Gene", + + // Qualcomm + (0x51, 0x00f) => "Scorpion", + (0x51, 0x02d) => "Scorpion", + (0x51, 0x04d) => "Krait", + (0x51, 0x06f) => "Krait", + (0x51, 0x201) => "Kryo", + (0x51, 0x205) => "Kryo", + (0x51, 0x211) => "Kryo", + (0x51, 0x800) => "Falkor-V1/Kryo", + (0x51, 0x801) => "Kryo-V2", + (0x51, 0x802) => "Kryo-3XX-Gold", + (0x51, 0x803) => "Kryo-3XX-Silver", + (0x51, 0x804) => "Kryo-4XX-Gold", + (0x51, 0x805) => "Kryo-4XX-Silver", + (0x51, 0xc00) => "Falkor", + (0x51, 0xc01) => "Saphira", + + // Samsung + (0x53, 0x001) => "exynos-m1", + + // Marvell + (0x56, 0x131) => "Feroceon-88FR131", + (0x56, 0x581) => "PJ4/PJ4b", + (0x56, 0x584) => "PJ4B-MP", + + // Apple + (0x61, 0x020) => "Icestorm-A14", + (0x61, 0x021) => "Firestorm-A14", + (0x61, 0x022) => "Icestorm-M1", + (0x61, 0x023) => "Firestorm-M1", + (0x61, 0x024) => "Icestorm-M1-Pro", + (0x61, 0x025) => "Firestorm-M1-Pro", + (0x61, 0x028) => "Icestorm-M1-Max", + (0x61, 0x029) => "Firestorm-M1-Max", + (0x61, 0x030) => "Blizzard-A15", + (0x61, 0x031) => "Avalanche-A15", + (0x61, 0x032) => "Blizzard-M2", + (0x61, 0x033) => "Avalanche-M2", + + // Faraday + (0x66, 0x526) => "FA526", + (0x66, 0x626) => "FA626", + + // Intel + (0x69, 0x200) => "i80200", + (0x69, 0x210) => "PXA250A", + (0x69, 0x212) => "PXA210A", + (0x69, 0x242) => "i80321-400", + (0x69, 0x243) => "i80321-600", + (0x69, 0x290) => "PXA250B/PXA26x", + (0x69, 0x292) => "PXA210B", + (0x69, 0x2c2) => "i80321-400-B0", + (0x69, 0x2c3) => "i80321-600-B0", + (0x69, 0x2d0) => "PXA250C/PXA255/PXA26x", + (0x69, 0x2d2) => "PXA210C", + (0x69, 0x411) => "PXA27x", + (0x69, 0x41c) => "IPX425-533", + (0x69, 0x41d) => "IPX425-400", + (0x69, 0x41f) => "IPX425-266", + (0x69, 0x682) => "PXA32x", + (0x69, 0x683) => "PXA930/PXA935", + (0x69, 0x688) => "PXA30x", + (0x69, 0x689) => "PXA31x", + (0x69, 0xb11) => "SA1110", + (0x69, 0xc12) => "IPX1200", + + // Phytium + (0x70, 0x660) => "FTC660", + (0x70, 0x661) => "FTC661", + (0x70, 0x662) => "FTC662", + (0x70, 0x663) => "FTC663", + + _ => return None, + }) + } + + let mut vendor_id = "".to_owned(); + let mut brand = "".to_owned(); + let mut implementer = None; + let mut part = None; + + let cpuinfo = match std::fs::read_to_string("/proc/cpuinfo") { + Ok(s) => s, + Err(e) => { + println!("Gatherer: Failed to read /proc/cpuinfo: {}", e); + return Arc::from(""); + } + }; + + for it in cpuinfo.split('\n') { + if it.starts_with("vendor_id\t") { + vendor_id = get_value(it); + } else if it.starts_with("model name\t") { + brand = get_value(it); + } else if it.starts_with("CPU implementer\t") { + implementer = Some(get_hex_value(it)); + } else if it.starts_with("CPU part\t") { + part = Some(get_hex_value(it)); + } else { + continue; + } + if (!brand.is_empty() && !vendor_id.is_empty()) + || (implementer.is_some() && part.is_some()) + { + break; + } + } + + if let (Some(implementer), Some(part)) = (implementer, part) { + match get_arm_implementer(implementer) { + Some(s) => vendor_id = s.into(), + None => return Arc::from(brand), + } + + match get_arm_part(implementer, part) { + Some(s) => { + vendor_id.push(' '); + vendor_id.push_str(s); + brand = vendor_id; + } + _ => {} + } + } + + Arc::from(brand.replace("(R)", "®").replace("(TM)", "™")) + } + + fn logical_cpu_count() -> u32 { + *CPU_COUNT as u32 + } + + fn socket_count() -> Option { + use std::{fs::*, io::*}; + + let mut sockets = std::collections::HashSet::new(); + sockets.reserve(4); + + let mut buf = String::new(); + + let entries = match read_dir("/sys/devices/system/cpu/") { + Ok(entries) => entries, + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read '/sys/devices/system/cpu': {}", + e + ); + return None; + } + }; + + for entry in entries { + let entry = match entry { + Ok(entry) => entry, + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read entry in '/sys/devices/system/cpu': {}", + e + ); + continue; + } + }; + + let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); + + let file_type = match entry.file_type() { + Ok(file_type) => file_type, + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read file type for '/sys/devices/system/cpu/{}': {}", + entry.file_name().to_string_lossy(), + e + ); + continue; + } + }; + + if !file_type.is_dir() { + continue; + } + + let mut file = match File::open(entry.path().join("topology/physical_package_id")) { + Ok(file) => file, + Err(_) => { + continue; + } + }; + + buf.clear(); + match file.read_to_string(&mut buf) { + Ok(_) => {} + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read '/sys/devices/system/cpu/{}/topology/physical_package_id': {}", + file_name, + e + ); + continue; + } + }; + + let socket_id = match buf.trim().parse::() { + Ok(socket_id) => socket_id, + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read '/sys/devices/system/cpu/{}/topology/physical_package_id': {}", + file_name, + e + ); + continue; + } + }; + sockets.insert(socket_id); + } + + if sockets.is_empty() { + critical!("Gatherer::CPU", "Could not determine socket count"); + None + } else { + Some(sockets.len() as u8) + } + } + + fn base_frequency_khz() -> Option { + fn read_from_sys_base_frequency() -> Option { + match std::fs::read("/sys/devices/system/cpu/cpu0/cpufreq/base_frequency") { + Ok(content) => { + let content = match std::str::from_utf8(&content) { + Ok(content) => content, + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read base frequency from '/sys/devices/system/cpu/cpu0/cpufreq/base_frequency': {}", + e + ); + return None; + } + }; + + match content.trim().parse() { + Ok(freq) => Some(freq), + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read base frequency from '/sys/devices/system/cpu/cpu0/cpufreq/base_frequency': {}", + e + ); + None + } + } + } + Err(e) => { + debug!( + "Gatherer::CPU", + "Could not read base frequency from '/sys/devices/system/cpu/cpu0/cpufreq/base_frequency': {}", + e + ); + + None + } + } + } + + fn read_from_sys_bios_limit() -> Option { + match std::fs::read("/sys/devices/system/cpu/cpu0/cpufreq/bios_limit") { + Ok(content) => { + let content = match std::str::from_utf8(&content) { + Ok(content) => content, + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read base frequency from '/sys/devices/system/cpu/cpu0/cpufreq/bios_limit': {}", + e + ); + return None; + } + }; + + match content.trim().parse() { + Ok(freq) => Some(freq), + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read base frequency from '/sys/devices/system/cpu/cpu0/cpufreq/bios_limit': {}", + e + ); + None + } + } + } + Err(e) => { + debug!( + "Gatherer::CPU", + "Could not read base frequency from '/sys/devices/system/cpu/cpu0/cpufreq/bios_limit': {}", + e + ); + + None + } + } + } + + const FNS: &[fn() -> Option] = + &[read_from_sys_base_frequency, read_from_sys_bios_limit]; + + for f in FNS { + if let Some(freq) = f() { + return Some(freq); + } + } + + None + } + + fn virtualization() -> Option> { + use crate::warning; + use std::io::Read; + + let mut virtualization: Option> = None; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + match std::fs::read_to_string("/proc/cpuinfo") { + Ok(cpuinfo) => { + for line in cpuinfo.split('\n').map(|l| l.trim()) { + if line.starts_with("flags") { + for flag in line.split(':').nth(1).unwrap_or("").trim().split(' ') { + if flag == "vmx" { + virtualization = Some("Intel VT-x".into()); + break; + } + + if flag == "svm" { + virtualization = Some("AMD-V".into()); + } + } + + break; + } + } + } + Err(e) => { + warning!( + "Gatherer::CPU", + "Failed to read virtualization capabilities from `/proc/cpuinfo`: {}", + e + ); + } + } + + if std::path::Path::new("/dev/kvm").exists() { + virtualization = if let Some(virt) = virtualization.as_ref() { + Some(Arc::from(format!("KVM / {}", virt).as_str())) + } else { + Some("KVM".into()) + }; + } else { + debug!("Gatherer::CPU", "Virtualization: `/dev/kvm` does not exist"); + } + + let mut buffer = [0u8; 9]; + match std::fs::File::open("/proc/xen/capabilities") { + Ok(mut file) => { + file.read(&mut buffer).unwrap(); + if &buffer == b"control_d" { + virtualization = if let Some(virt) = virtualization.as_ref() { + if virt.as_ref().starts_with("KVM") { + Some(Arc::from(format!("KVM & Xen / {}", virt).as_str())) + } else { + Some(Arc::from(format!("Xen / {}", virt).as_str())) + } + } else { + Some("Xen".into()) + }; + } + } + Err(e) => { + debug!( + "Gatherer::CPU", + "Virtualization: Failed to open /proc/xen/capabilities: {}", e + ); + } + } + + virtualization + } + + fn virtual_machine() -> Option { + use dbus::blocking::{stdintf::org_freedesktop_dbus::Properties, *}; + + let conn = match Connection::new_system() { + Ok(c) => c, + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to determine VM: Failed to connect to D-Bus: {}", + e + ); + return None; + } + }; + + let proxy = conn.with_proxy( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + std::time::Duration::from_millis(1000), + ); + + let response: String = match proxy.get("org.freedesktop.systemd1.Manager", "Virtualization") + { + Ok(m) => m, + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to determine VM: Failed to get Virtualization property: {}", + e + ); + return None; + } + }; + + Some(response.len() > 0) + } + + fn cache_info() -> [Option; 5] { + use crate::warning; + use std::{collections::HashSet, fs::*, os::unix::prelude::*, str::FromStr}; + + fn read_index_entry_content( + file_name: &str, + index_path: &std::path::Path, + ) -> Option { + let path = index_path.join(file_name); + match read_to_string(path) { + Ok(content) => Some(content), + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read '{}/{}': {}", + index_path.display(), + file_name, + e, + ); + None + } + } + } + + fn read_index_entry_number>( + file_name: &str, + index_path: &std::path::Path, + suffix: Option<&str>, + ) -> Option { + let content = match read_index_entry_content(file_name, index_path) { + Some(content) => content, + None => return None, + }; + let content = content.trim(); + let value = match suffix { + None => content.parse::(), + Some(suffix) => content.trim_end_matches(suffix).parse::(), + }; + match value { + Err(e) => { + warning!( + "Gatherer::CPU", + "Failed to parse '{}/{}': {}", + index_path.display(), + file_name, + e, + ); + None + } + Ok(v) => Some(v), + } + } + + fn read_cache_values(path: &std::path::Path) -> [Option; 5] { + let mut result = [None; 5]; + + let mut l1_visited_data = HashSet::new(); + let mut l1_visited_instr = HashSet::new(); + let mut l2_visited = HashSet::new(); + let mut l3_visited = HashSet::new(); + let mut l4_visited = HashSet::new(); + + let cpu_entries = match path.read_dir() { + Ok(entries) => entries, + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read '{}': {}", + path.display(), + e + ); + return result; + } + }; + for cpu_entry in cpu_entries { + let cpu_entry = match cpu_entry { + Ok(entry) => entry, + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read cpu entry in '{}': {}", + path.display(), + e + ); + continue; + } + }; + let mut path = cpu_entry.path(); + + let cpu_name = match path.file_name() { + Some(name) => name, + None => continue, + }; + + let is_cpu = &cpu_name.as_bytes()[0..3] == b"cpu"; + if is_cpu { + let cpu_number = + match unsafe { std::str::from_utf8_unchecked(&cpu_name.as_bytes()[3..]) } + .parse::() + { + Ok(n) => n, + Err(_) => continue, + }; + + path.push("cache"); + let cache_entries = match path.read_dir() { + Ok(entries) => entries, + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read '{}': {}", + path.display(), + e + ); + return result; + } + }; + for cache_entry in cache_entries { + let cache_entry = match cache_entry { + Ok(entry) => entry, + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read cpu entry in '{}': {}", + path.display(), + e + ); + continue; + } + }; + let path = cache_entry.path(); + let is_cache_entry = path + .file_name() + .map(|file| &file.as_bytes()[0..5] == b"index") + .unwrap_or(false); + if is_cache_entry { + let level = match read_index_entry_number::("level", &path, None) { + None => continue, + Some(l) => l, + }; + + let cache_type = match read_index_entry_content("type", &path) { + None => continue, + Some(ct) => ct, + }; + + let visited_cpus = match cache_type.trim() { + "Data" => &mut l1_visited_data, + "Instruction" => &mut l1_visited_instr, + "Unified" => match level { + 2 => &mut l2_visited, + 3 => &mut l3_visited, + 4 => &mut l4_visited, + _ => continue, + }, + _ => continue, + }; + + if visited_cpus.contains(&cpu_number) { + continue; + } + + let size = + match read_index_entry_number::("size", &path, Some("K")) { + None => continue, + Some(s) => s, + }; + + let result_index = level as usize; + result[result_index] = match result[result_index] { + None => Some(size as u64), + Some(s) => Some(s + size as u64), + }; + + match read_index_entry_content("shared_cpu_list", &path) { + Some(scl) => { + let shared_cpu_list = scl.trim().split(','); + for cpu in shared_cpu_list { + let mut shared_cpu_sequence = cpu.split('-'); + + let start = match shared_cpu_sequence + .next() + .map(|s| s.parse::()) + { + Some(Ok(s)) => s, + Some(Err(_)) | None => continue, + }; + + let end = match shared_cpu_sequence + .next() + .map(|e| e.parse::()) + { + Some(Ok(e)) => e, + Some(Err(_)) | None => { + visited_cpus.insert(start); + continue; + } + }; + + for i in start..=end { + visited_cpus.insert(i); + } + } + } + _ => {} + } + } + } + } + } + + result + } + + let mut result = [None; 5]; + + match read_dir("/sys/devices/system/node/") { + Ok(entries) => { + for nn_entry in entries { + let nn_entry = match nn_entry { + Ok(entry) => entry, + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read entry in '/sys/devices/system/node': {}", + e + ); + continue; + } + }; + let path = nn_entry.path(); + if !path.is_dir() { + continue; + } + + let is_node = path + .file_name() + .map(|file| &file.as_bytes()[0..4] == b"node") + .unwrap_or(false); + if !is_node { + continue; + } + + let node_vals = read_cache_values(&path); + for i in 0..result.len() { + if let Some(size) = node_vals[i] { + result[i] = match result[i] { + None => Some(size), + Some(s) => Some(s + size), + }; + } + } + } + } + Err(e) => { + warning!( + "Gatherer::CPU", + "Could not read '/sys/devices/system/node': {}. Falling back to '/sys/devices/system/cpu'", + e + ); + + result = read_cache_values(std::path::Path::new("/sys/devices/system/cpu")); + } + } + + for i in 1..result.len() { + result[i] = result[i].map(|size| size * 1024); + } + result + } + + fn get_cpufreq_driver_governor() -> (Option>, Option>, Option>) { + fn get_cpufreq_driver() -> Option> { + match std::fs::read("/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver") { + Ok(content) => match std::str::from_utf8(&content) { + Ok(content) => Some(Arc::from(format!("{}", content.trim()).as_str())), + Err(e) => { + debug!( + "Gatherer::CPU", + "Could not read cpufreq driver from '/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver': {}", + e + ); + + None + } + }, + Err(e) => { + debug!( + "Gatherer::CPU", + "Could not read cpufreq driver from '/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver': {}", + e + ); + + None + } + } + } + + fn get_cpufreq_governor() -> Option> { + match std::fs::read("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") { + Ok(content) => match std::str::from_utf8(&content) { + Ok(content) => Some(Arc::from(format!("{}", content.trim()).as_str())), + Err(e) => { + debug!( + "Gatherer::CPU", + "Could not read cpufreq governor from '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor': {}", + e + ); + + None + } + }, + Err(e) => { + debug!( + "Gatherer::CPU", + "Could not read cpufreq governor from '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor': {}", + e + ); + + None + } + } + } + // shouldn't show error as few people have this / the error would be normal + fn energy_performance_preference() -> Option> { + match std::fs::read( + "/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference", + ) { + Ok(content) => match std::str::from_utf8(&content) { + Ok(content) => Some(Arc::from(format!("{}", content.trim()).as_str())), + Err(e) => { + critical!( + "Gatherer::CPU", + "Could not read power preference from '/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference': {}", + e + ); + + None + } + }, + Err(_) => None, + } + } + ( + get_cpufreq_driver(), + get_cpufreq_governor(), + energy_performance_preference(), + ) + } + + // Adapted from `sysinfo` crate, linux/cpu.rs:415 + fn cpu_frequency_mhz() -> u64 { + #[inline(always)] + fn read_sys_cpufreq() -> Option { + let mut result = 0_u64; + + let sys_dev_cpu = match std::fs::read_dir("/sys/devices/system/cpu") { + Ok(d) => d, + Err(e) => { + debug!( + "Gatherer::CPU", + "Failed to read frequency: Failed to open /sys/devices/system/cpu: {}", e + ); + return None; + } + }; + + let mut buffer = String::new(); + for cpu in sys_dev_cpu.filter_map(|d| d.ok()).filter(|d| { + d.file_name().as_bytes().starts_with(b"cpu") + && d.file_type().is_ok_and(|ty| ty.is_dir()) + }) { + buffer.clear(); + + let mut path = cpu.path(); + path.push("cpufreq/scaling_cur_freq"); + + let mut file = match OpenOptions::new().read(true).open(&path) { + Ok(f) => f, + Err(e) => { + debug!( + "Gatherer::CPU", + "Failed to read frequency: Failed to open /sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq: {}", + cpu.file_name().to_string_lossy(), + e + ); + continue; + } + }; + + match file.read_to_string(&mut buffer) { + Ok(_) => {} + Err(e) => { + debug!( + "Gatherer::CPU", + "Failed to read frequency: Failed to read /sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq: {}", + cpu.file_name().to_string_lossy(), + e + ); + continue; + } + } + + let freq = match buffer.trim().parse::() { + Ok(f) => f, + Err(e) => { + debug!( + "Gatherer::CPU", + "Failed to read frequency: Failed to parse /sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq: {}", + cpu.file_name().to_string_lossy(), + e + ); + continue; + } + }; + + result = result.max(freq); + } + + if result > 0 { + Some(result / 1000) + } else { + None + } + } + + #[inline(always)] + fn read_proc_cpuinfo() -> Option { + let cpuinfo = match std::fs::read_to_string("/proc/cpuinfo") { + Ok(s) => s, + Err(e) => { + debug!( + "Gatherer::CPU", + "Failed to read frequency: Failed to open /proc/cpuinfo: {}", e + ); + return None; + } + }; + + let mut result = 0; + for line in cpuinfo + .split('\n') + .filter(|line| line.starts_with("cpu MHz\t") || line.starts_with("clock\t")) + { + result = line + .split(':') + .last() + .and_then(|val| val.replace("MHz", "").trim().parse::().ok()) + .map(|speed| speed as u64) + .unwrap_or_default() + .max(result); + } + + Some(result) + } + + if let Some(freq) = read_sys_cpufreq() { + return freq; + } + + read_proc_cpuinfo().unwrap_or_default() + } + + fn temperature() -> Option { + let dir = match std::fs::read_dir("/sys/class/hwmon") { + Ok(d) => d, + Err(e) => { + critical!("Gatherer::CPU", "Failed to open `/sys/class/hwmon`: {}", e); + return None; + } + }; + + for mut entry in dir + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|path| path.is_dir()) + { + let mut name = entry.clone(); + name.push("name"); + + let name = match std::fs::read_to_string(name) { + Ok(name) => name.trim().to_lowercase(), + Err(_) => continue, + }; + if name != "k10temp" && name != "coretemp" && name != "zenpower" { + continue; + } + + entry.push("temp1_input"); + let temp = match std::fs::read_to_string(&entry) { + Ok(temp) => temp, + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to read temperature from `{}`: {}", + entry.display(), + e + ); + continue; + } + }; + + return Some(match temp.trim().parse::() { + Ok(temp) => (temp as f32) / 1000., + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to parse temperature from `{}`: {}", + entry.display(), + e + ); + continue; + } + }); + } + + None + } + + fn process_count(processes: &crate::platform::Processes) -> u64 { + use crate::platform::ProcessesExt; + + processes.process_list().len() as _ + } + + fn thread_count(processes: &crate::platform::Processes) -> u64 { + use crate::platform::{ProcessExt, ProcessesExt}; + + processes + .process_list() + .iter() + .map(|(_, p)| p.task_count()) + .sum::() as _ + } + + fn handle_count() -> u64 { + let file_nr = match std::fs::read_to_string("/proc/sys/fs/file-nr") { + Ok(s) => s, + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to get handle count, could not read /proc/sys/fs/file-nr: {}", + e + ); + return 0; + } + }; + let file_nr = match file_nr.split_whitespace().next() { + Some(s) => s, + None => { + critical!( + "Gatherer::CPU", + "Failed to get handle count, failed to parse /proc/sys/fs/file-nr", + ); + return 0; + } + }; + + match file_nr.trim().parse() { + Ok(count) => count, + Err(e) => { + critical!("Gatherer::CPU", "Failed to get handle count, failed to parse /proc/sys/fs/file-nr content ({}): {}", file_nr, e); + 0 + } + } + } + + fn uptime() -> std::time::Duration { + let proc_uptime = match std::fs::read_to_string("/proc/uptime") { + Ok(s) => s, + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to get handle count, could not read /proc/sys/fs/file-nr: {}", + e + ); + return std::time::Duration::from_millis(0); + } + }; + + match proc_uptime + .split_whitespace() + .next() + .unwrap_or_default() + .trim() + .parse::() + { + Ok(count) => std::time::Duration::from_secs_f64(count), + Err(e) => { + critical!( + "Gatherer::CPU", + "Failed to parse uptime, failed to parse /proc/uptime content ({}): {}", + proc_uptime, + e + ); + std::time::Duration::from_millis(0) + } + } + } +} + +impl<'a> CpuInfoExt<'a> for LinuxCpuInfo { + type S = LinuxCpuStaticInfo; + type D = LinuxCpuDynamicInfo; + type P = crate::platform::Processes; + + fn refresh_static_info_cache(&mut self) { + let now = Instant::now(); + if now.duration_since(self.static_refresh_timestamp) < MIN_DELTA_REFRESH { + return; + } + self.static_refresh_timestamp = now; + + if self.static_info.logical_cpu_count == 0 { + let cache_info = Self::cache_info(); + + self.static_info = LinuxCpuStaticInfo { + name: Self::name(), + logical_cpu_count: Self::logical_cpu_count(), + socket_count: Self::socket_count(), + base_frequency_khz: Self::base_frequency_khz(), + virtualization_technology: Self::virtualization(), + is_virtual_machine: Self::virtual_machine(), + l1_combined_cache: cache_info[1], + l2_cache: cache_info[2], + l3_cache: cache_info[3], + l4_cache: cache_info[4], + } + } + } + + fn refresh_dynamic_info_cache(&mut self, processes: &crate::platform::Processes) { + let now = Instant::now(); + if now.duration_since(self.dynamic_refresh_timestamp) < MIN_DELTA_REFRESH { + return; + } + self.dynamic_refresh_timestamp = now; + + self.dynamic_info + .per_logical_cpu_utilization_percent + .resize(*CPU_COUNT, 0.0); + self.dynamic_info + .per_logical_cpu_kernel_utilization_percent + .resize(*CPU_COUNT, 0.0); + self.dynamic_info + .per_logical_cpu_store_old + .resize(*CPU_COUNT, CpuTicks::default()); + + let per_core_usage = + &mut self.dynamic_info.per_logical_cpu_utilization_percent[..*CPU_COUNT]; + let per_core_kernel_usage = + &mut self.dynamic_info.per_logical_cpu_kernel_utilization_percent[..*CPU_COUNT]; + let per_core_save = &mut self.dynamic_info.per_logical_cpu_store_old[..*CPU_COUNT]; + + let proc_stat = std::fs::read_to_string("/proc/stat").unwrap_or_else(|e| { + critical!("Gatherer::CPU", "Failed to read /proc/stat: {}", e); + "".to_owned() + }); + + let mut line_iter = proc_stat + .lines() + .map(|l| l.trim()) + .skip_while(|l| !l.starts_with("cpu")); + if let Some(cpu_overall_line) = line_iter.next() { + ( + self.dynamic_info.overall_utilization_percent, + self.dynamic_info.overall_kernel_utilization_percent, + ) = self.dynamic_info.cpu_store_old.update(cpu_overall_line); + + for (i, line) in line_iter.enumerate() { + if i >= *CPU_COUNT || !line.starts_with("cpu") { + break; + } + + (per_core_usage[i], per_core_kernel_usage[i]) = per_core_save[i].update(line); + } + } else { + self.dynamic_info.overall_utilization_percent = 0.; + self.dynamic_info.overall_kernel_utilization_percent = 0.; + per_core_usage.fill(0.); + per_core_kernel_usage.fill(0.); + } + ( + self.dynamic_info.cpufreq_driver, + self.dynamic_info.cpufreq_governor, + self.dynamic_info.energy_performance_preference, + ) = Self::get_cpufreq_driver_governor(); + + self.dynamic_info.current_frequency_mhz = Self::cpu_frequency_mhz(); + self.dynamic_info.temperature = Self::temperature(); + self.dynamic_info.process_count = Self::process_count(processes); + self.dynamic_info.thread_count = Self::thread_count(processes); + self.dynamic_info.handle_count = Self::handle_count(); + self.dynamic_info.uptime_seconds = Self::uptime().as_secs(); + + self.static_refresh_timestamp = Instant::now(); + } + + fn static_info(&self) -> &Self::S { + &self.static_info + } + + fn dynamic_info(&self) -> &Self::D { + &self.dynamic_info + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_static_info() { + let mut cpu = LinuxCpuInfo::new(); + cpu.refresh_static_info_cache(); + assert!(!cpu.static_info().name().is_empty()); + + dbg!(cpu.static_info()); + } + + #[test] + fn test_dynamic_info() { + use crate::platform::{Processes, ProcessesExt}; + + let mut p = Processes::new(); + p.refresh_cache(); + + let mut cpu = LinuxCpuInfo::new(); + cpu.refresh_dynamic_info_cache(&p); + assert!(!cpu.dynamic_info().process_count() > 0); + + dbg!(cpu.dynamic_info()); + } +} diff --git a/src/observatory-daemon/src/platform/linux/disk_info.rs b/src/observatory-daemon/src/platform/linux/disk_info.rs new file mode 100644 index 0000000..d4179d4 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/disk_info.rs @@ -0,0 +1,726 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/disk_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use super::{INITIAL_REFRESH_TS, MIN_DELTA_REFRESH}; +use crate::logging::{critical, warning}; +use crate::platform::disk_info::{DiskInfoExt, DiskType, DisksInfoExt}; +use glob::glob; +use serde::Deserialize; +use std::{sync::Arc, time::Instant}; + +#[derive(Debug, Default, Deserialize)] +struct LSBLKBlockDevice { + name: String, + mountpoints: Vec>, + children: Option>>, +} + +#[derive(Debug, Deserialize)] +struct LSBLKOutput { + blockdevices: Vec>, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LinuxDiskInfo { + pub id: Arc, + pub model: Arc, + pub r#type: DiskType, + pub capacity: u64, + pub formatted: u64, + pub system_disk: bool, + + pub busy_percent: f32, + pub response_time_ms: f32, + pub read_speed: u64, + pub write_speed: u64, +} + +impl Default for LinuxDiskInfo { + fn default() -> Self { + Self { + id: Arc::from(""), + model: Arc::from(""), + r#type: DiskType::default(), + capacity: 0, + formatted: 0, + system_disk: false, + + busy_percent: 0., + response_time_ms: 0., + read_speed: 0, + write_speed: 0, + } + } +} + +pub struct LinuxDiskInfoIter<'a>( + pub std::iter::Map< + std::slice::Iter<'a, (DiskStats, LinuxDiskInfo)>, + fn(&'a (DiskStats, LinuxDiskInfo)) -> &'a LinuxDiskInfo, + >, +); + +impl<'a> LinuxDiskInfoIter<'a> { + pub fn new(iter: std::slice::Iter<'a, (DiskStats, LinuxDiskInfo)>) -> Self { + Self(iter.map(|(_, di)| di)) + } +} + +impl<'a> Iterator for LinuxDiskInfoIter<'a> { + type Item = &'a LinuxDiskInfo; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl DiskInfoExt for LinuxDiskInfo { + fn id(&self) -> &str { + &self.id + } + + fn model(&self) -> &str { + &self.model + } + + fn r#type(&self) -> DiskType { + self.r#type + } + + fn capacity(&self) -> u64 { + self.capacity + } + + fn formatted(&self) -> u64 { + self.formatted + } + + fn is_system_disk(&self) -> bool { + self.system_disk + } + + fn busy_percent(&self) -> f32 { + self.busy_percent + } + + fn response_time_ms(&self) -> f32 { + self.response_time_ms + } + + fn read_speed(&self) -> u64 { + self.read_speed + } + + fn write_speed(&self) -> u64 { + self.write_speed + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct DiskStats { + sectors_read: u64, + sectors_written: u64, + + read_ios: u64, + write_ios: u64, + discard_ios: u64, + flush_ios: u64, + io_total_time_ms: u64, + + read_ticks_weighted_ms: u64, + write_ticks_weighted_ms: u64, + discard_ticks_weighted_ms: u64, + flush_ticks_weighted_ms: u64, + + read_time_ms: Instant, +} + +pub struct LinuxDisksInfo { + info: Vec<(DiskStats, LinuxDiskInfo)>, + + refresh_timestamp: Instant, +} + +impl<'a> DisksInfoExt<'a> for LinuxDisksInfo { + type S = LinuxDiskInfo; + type Iter = LinuxDiskInfoIter<'a>; + + fn refresh_cache(&mut self) { + use crate::{critical, warning}; + + let now = Instant::now(); + if now.duration_since(self.refresh_timestamp) < MIN_DELTA_REFRESH { + return; + } + self.refresh_timestamp = now; + + let mut prev_disks = std::mem::take(&mut self.info); + + let entries = match std::fs::read_dir("/sys/block") { + Ok(e) => e, + Err(e) => { + critical!( + "Gatherer::DiskInfo", + "Failed to refresh disk information, failed to read disk entries: {}", + e + ); + return; + } + }; + for entry in entries { + let entry = match entry { + Ok(e) => e, + Err(e) => { + warning!("Gatherer::DiskInfo", "Failed to read disk entry: {}", e); + continue; + } + }; + let file_type = match entry.file_type() { + Ok(ft) => ft, + Err(e) => { + warning!( + "Gatherer::DiskInfo", + "Failed to read disk entry file type: {}", + e + ); + continue; + } + }; + + let dir_name = if file_type.is_symlink() { + let path = match entry.path().read_link() { + Err(e) => { + warning!( + "Gatherer::DiskInfo", + "Failed to read disk entry symlink: {}", + e + ); + continue; + } + Ok(p) => { + let path = std::path::Path::new("/sys/block").join(p); + if !path.is_dir() { + continue; + } + path + } + }; + + match path.file_name() { + None => continue, + Some(dir_name) => dir_name.to_string_lossy().into_owned(), + } + } else if file_type.is_dir() { + entry.file_name().to_string_lossy().into_owned() + } else { + continue; + }; + + let mut prev_disk_index = None; + for i in 0..prev_disks.len() { + if prev_disks[i].1.id.as_ref() == dir_name { + prev_disk_index = Some(i); + break; + } + } + + let stats = std::fs::read_to_string(format!("/sys/block/{}/stat", dir_name)); + + let stats = match stats.as_ref() { + Err(e) => { + warning!( + "MissionCenter::DiskInfo", + "Failed to read disk stat: {:?}", + e + ); + "" + } + Ok(stats) => stats.trim(), + }; + + let mut read_ios = 0; + let mut sectors_read = 0; + let mut read_ticks_weighted_ms = 0; + let mut write_ios = 0; + let mut sectors_written = 0; + let mut write_ticks_weighted_ms = 0; + let mut io_total_time_ms: u64 = 0; + let mut discard_ios = 0; + let mut discard_ticks_weighted_ms = 0; + let mut flush_ios = 0; + let mut flush_ticks_weighted_ms = 0; + + const IDX_READ_IOS: usize = 0; + const IDX_READ_SECTORS: usize = 2; + const IDX_READ_TICKS: usize = 3; + const IDX_WRITE_IOS: usize = 4; + const IDX_WRITE_SECTORS: usize = 6; + const IDX_WRITE_TICKS: usize = 7; + const IDX_IO_TICKS: usize = 9; + const IDX_DISCARD_IOS: usize = 11; + const IDX_DISCARD_TICKS: usize = 14; + const IDX_FLUSH_IOS: usize = 15; + const IDX_FLUSH_TICKS: usize = 16; + for (i, entry) in stats + .split_whitespace() + .enumerate() + .map(|(i, v)| (i, v.trim())) + { + match i { + IDX_READ_IOS => read_ios = entry.parse::().unwrap_or(0), + IDX_READ_SECTORS => sectors_read = entry.parse::().unwrap_or(0), + IDX_READ_TICKS => read_ticks_weighted_ms = entry.parse::().unwrap_or(0), + IDX_WRITE_IOS => write_ios = entry.parse::().unwrap_or(0), + IDX_WRITE_SECTORS => sectors_written = entry.parse::().unwrap_or(0), + IDX_WRITE_TICKS => write_ticks_weighted_ms = entry.parse::().unwrap_or(0), + IDX_IO_TICKS => { + io_total_time_ms = entry.parse::().unwrap_or(0); + } + IDX_DISCARD_IOS => discard_ios = entry.parse::().unwrap_or(0), + IDX_DISCARD_TICKS => { + discard_ticks_weighted_ms = entry.parse::().unwrap_or(0) + } + IDX_FLUSH_IOS => flush_ios = entry.parse::().unwrap_or(0), + IDX_FLUSH_TICKS => { + flush_ticks_weighted_ms = entry.parse::().unwrap_or(0); + break; + } + _ => (), + } + } + + if let Some((mut disk_stat, mut info)) = prev_disk_index.map(|i| prev_disks.remove(i)) { + let read_ticks_weighted_ms_prev = + if read_ticks_weighted_ms < disk_stat.read_ticks_weighted_ms { + read_ticks_weighted_ms + } else { + disk_stat.read_ticks_weighted_ms + }; + + let write_ticks_weighted_ms_prev = + if write_ticks_weighted_ms < disk_stat.write_ticks_weighted_ms { + write_ticks_weighted_ms + } else { + disk_stat.write_ticks_weighted_ms + }; + + let discard_ticks_weighted_ms_prev = + if discard_ticks_weighted_ms < disk_stat.discard_ticks_weighted_ms { + discard_ticks_weighted_ms + } else { + disk_stat.discard_ticks_weighted_ms + }; + + let flush_ticks_weighted_ms_prev = + if flush_ticks_weighted_ms < disk_stat.flush_ticks_weighted_ms { + flush_ticks_weighted_ms + } else { + disk_stat.flush_ticks_weighted_ms + }; + + let elapsed = disk_stat.read_time_ms.elapsed().as_secs_f32(); + + let delta_read_ticks_weighted_ms = + read_ticks_weighted_ms - read_ticks_weighted_ms_prev; + let delta_write_ticks_weighted_ms = + write_ticks_weighted_ms - write_ticks_weighted_ms_prev; + let delta_discard_ticks_weighted_ms = + discard_ticks_weighted_ms - discard_ticks_weighted_ms_prev; + let delta_flush_ticks_weighted_ms = + flush_ticks_weighted_ms - flush_ticks_weighted_ms_prev; + let delta_ticks_weighted_ms = delta_read_ticks_weighted_ms + + delta_write_ticks_weighted_ms + + delta_discard_ticks_weighted_ms + + delta_flush_ticks_weighted_ms; + + // Arbitrary math is arbitrary + let busy_percent = (delta_ticks_weighted_ms as f32 / (elapsed * 8.0)).min(100.); + + disk_stat.read_ticks_weighted_ms = read_ticks_weighted_ms; + disk_stat.write_ticks_weighted_ms = write_ticks_weighted_ms; + disk_stat.discard_ticks_weighted_ms = discard_ticks_weighted_ms; + disk_stat.flush_ticks_weighted_ms = flush_ticks_weighted_ms; + + let io_time_ms_prev = if io_total_time_ms < disk_stat.io_total_time_ms { + io_total_time_ms + } else { + disk_stat.io_total_time_ms + }; + + let read_ios_prev = if read_ios < disk_stat.read_ios { + read_ios + } else { + disk_stat.read_ios + }; + + let write_ios_prev = if write_ios < disk_stat.write_ios { + write_ios + } else { + disk_stat.write_ios + }; + + let discard_ios_prev = if discard_ios < disk_stat.discard_ios { + discard_ios + } else { + disk_stat.discard_ios + }; + + let flush_ios_prev = if flush_ios < disk_stat.flush_ios { + flush_ios + } else { + disk_stat.flush_ios + }; + + let delta_io_time_ms = io_total_time_ms - io_time_ms_prev; + let delta_read_ios = read_ios - read_ios_prev; + let delta_write_ios = write_ios - write_ios_prev; + let delta_discard_ios = discard_ios - discard_ios_prev; + let delta_flush_ios = flush_ios - flush_ios_prev; + + let delta_ios = + delta_read_ios + delta_write_ios + delta_discard_ios + delta_flush_ios; + let response_time_ms = if delta_ios > 0 { + delta_io_time_ms as f32 / delta_ios as f32 + } else { + 0. + }; + + disk_stat.read_ios = read_ios; + disk_stat.write_ios = write_ios; + disk_stat.discard_ios = discard_ios; + disk_stat.flush_ios = flush_ios; + disk_stat.io_total_time_ms = io_total_time_ms; + + let sectors_read_prev = if sectors_read < disk_stat.sectors_read { + sectors_read + } else { + disk_stat.sectors_read + }; + + let sectors_written_prev = if sectors_written < disk_stat.sectors_written { + sectors_written + } else { + disk_stat.sectors_written + }; + + let read_speed = ((sectors_read - sectors_read_prev) as f32 * 512.) / elapsed; + let write_speed = + ((sectors_written - sectors_written_prev) as f32 * 512.) / elapsed; + + let read_speed = read_speed.round() as u64; + let write_speed = write_speed.round() as u64; + + disk_stat.sectors_read = sectors_read; + disk_stat.sectors_written = sectors_written; + + disk_stat.read_time_ms = Instant::now(); + + info.busy_percent = busy_percent; + info.response_time_ms = response_time_ms; + info.read_speed = read_speed; + info.write_speed = write_speed; + + self.info.push((disk_stat, info)); + } else { + if dir_name.starts_with("loop") + || dir_name.starts_with("ram") + || dir_name.starts_with("zram") + || dir_name.starts_with("fd") + || dir_name.starts_with("md") + || dir_name.starts_with("dm") + || dir_name.starts_with("zd") + { + continue; + } + + let r#type = if let Ok(v) = + std::fs::read_to_string(format!("/sys/block/{}/queue/rotational", dir_name)) + { + let v = v.trim().parse::().ok().map_or(u8::MAX, |v| v); + if v == 0 { + if dir_name.starts_with("nvme") { + DiskType::NVMe + } else if dir_name.starts_with("mmc") { + Self::get_mmc_type(&dir_name) + } else { + DiskType::SSD + } + } else { + if dir_name.starts_with("sr") { + DiskType::Optical + } else { + match v { + 1 => DiskType::HDD, + _ => DiskType::Unknown, + } + } + } + } else { + DiskType::Unknown + }; + + let capacity = if let Ok(v) = + std::fs::read_to_string(format!("/sys/block/{}/size", dir_name)) + { + v.trim().parse::().ok().map_or(u64::MAX, |v| v * 512) + } else { + u64::MAX + }; + + let fs_info = Self::filesystem_info(&dir_name); + let (system_disk, formatted) = if let Some(v) = fs_info { v } else { (false, 0) }; + + let vendor = + std::fs::read_to_string(format!("/sys/block/{}/device/vendor", dir_name)) + .ok() + .unwrap_or("".to_string()); + + let model = + std::fs::read_to_string(format!("/sys/block/{}/device/model", dir_name)) + .ok() + .unwrap_or("".to_string()); + + let model = Arc::::from(format!("{} {}", vendor.trim(), model.trim())); + + self.info.push(( + DiskStats { + sectors_read, + sectors_written, + read_ios, + write_ios, + discard_ios, + flush_ios, + io_total_time_ms, + read_ticks_weighted_ms, + write_ticks_weighted_ms, + discard_ticks_weighted_ms, + flush_ticks_weighted_ms, + read_time_ms: Instant::now(), + }, + LinuxDiskInfo { + id: Arc::from(dir_name), + model, + r#type, + capacity, + formatted, + system_disk, + + busy_percent: 0., + response_time_ms: 0., + read_speed: 0, + write_speed: 0, + }, + )); + } + } + } + + fn info(&'a self) -> Self::Iter { + LinuxDiskInfoIter::new(self.info.iter()) + } +} + +impl LinuxDisksInfo { + pub fn new() -> Self { + Self { + info: vec![], + + refresh_timestamp: *INITIAL_REFRESH_TS, + } + } + + fn filesystem_info(device_name: &str) -> Option<(bool, u64)> { + use crate::{critical, warning}; + + let entries = match std::fs::read_dir(format!("/sys/block/{}", device_name)) { + Ok(e) => e, + Err(e) => { + critical!( + "Gatherer::DiskInfo", + "Failed to read filesystem information for '{}': {}", + device_name, + e + ); + + return None; + } + }; + + let is_root_device = Self::mount_points(&device_name) + .iter() + .map(|v| v.as_str()) + .any(|v| v == "/"); + let mut formatted_size = 0_u64; + for entry in entries { + let entry = match entry { + Ok(e) => e, + Err(e) => { + warning!( + "Gatherer::DiskInfo", + "Failed to read some filesystem information for '{}': {}", + device_name, + e + ); + continue; + } + }; + + let part_name = entry.file_name(); + let part_name = part_name.to_string_lossy(); + if !part_name.starts_with(device_name) { + continue; + } + std::fs::read_to_string(format!("/sys/block/{}/{}/size", &device_name, part_name)) + .ok() + .map(|v| v.trim().parse::().ok().map_or(0, |v| v * 512)) + .map(|v| { + formatted_size += v; + }); + } + + Some((is_root_device, formatted_size)) + } + + fn mount_points(device_name: &str) -> Vec { + use crate::critical; + + let mut cmd = std::process::Command::new("lsblk"); + cmd.arg("-o").arg("NAME,MOUNTPOINTS").arg("--json"); + + let lsblk_out = if let Ok(output) = cmd.output() { + if output.stderr.len() > 0 { + critical!( + "Gatherer::DiskInfo", + "Failed to refresh block device information, host command execution failed: {}", + std::str::from_utf8(output.stderr.as_slice()).unwrap_or("Unknown error") + ); + return vec![]; + } + + output.stdout + } else { + critical!( + "Gatherer::DiskInfo", + "Failed to refresh block device information, host command execution failed" + ); + return vec![]; + }; + + let mut lsblk_out = match serde_json::from_slice::(lsblk_out.as_slice()) { + Ok(v) => v, + Err(e) => { + critical!( + "MissionCenter::DiskInfo", + "Failed to refresh block device information, host command execution failed: {}", + e + ); + return vec![]; + } + }; + + let mut mount_points = vec![]; + for block_device in lsblk_out + .blockdevices + .iter_mut() + .filter_map(|bd| bd.as_mut()) + { + let block_device = core::mem::take(block_device); + if block_device.name != device_name { + continue; + } + + let children = match block_device.children { + None => break, + Some(c) => c, + }; + + fn find_mount_points( + mut block_devices: Vec>, + mount_points: &mut Vec, + ) { + for block_device in block_devices.iter_mut().filter_map(|bd| bd.as_mut()) { + let mut block_device = core::mem::take(block_device); + + for mountpoint in block_device + .mountpoints + .iter_mut() + .filter_map(|mp| mp.as_mut()) + { + mount_points.push(core::mem::take(mountpoint)); + } + + if let Some(children) = block_device.children { + find_mount_points(children, mount_points); + } + } + } + + find_mount_points(children, &mut mount_points); + break; + } + + mount_points + } + + fn get_mmc_type(dir_name: &String) -> DiskType { + let Some(hwmon_idx) = dir_name[6..].parse::().ok() else { + return DiskType::Unknown; + }; + + let globs = match glob(&format!( + "/sys/class/mmc_host/mmc{}/mmc{}*/type", + hwmon_idx, hwmon_idx + )) { + Ok(globs) => globs, + Err(e) => { + warning!("Gatherer::DiskInfo", "Failed to read mmc type entry: {}", e); + return DiskType::Unknown; + } + }; + + let mut res = DiskType::Unknown; + for entry in globs { + res = match entry { + Ok(path) => match std::fs::read_to_string(&path).ok() { + Some(typ) => match typ.trim() { + "SD" => DiskType::SD, + "MMC" => DiskType::eMMC, + _ => { + critical!("Gatherer::DiskInfo", "Unknown mmc type: '{}'", typ); + continue; + } + }, + _ => { + critical!( + "Gatherer::DiskInfo", + "Could not read mmc type: {}", + path.display() + ); + continue; + } + }, + _ => { + continue; + } + }; + } + res + } +} diff --git a/src/observatory-daemon/src/platform/linux/fan_info.rs b/src/observatory-daemon/src/platform/linux/fan_info.rs new file mode 100644 index 0000000..e00dee4 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/fan_info.rs @@ -0,0 +1,255 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/fan_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +use std::sync::Arc; +use std::time::Instant; + +use convert_case::{Case, Casing}; +use glob::glob; + +use super::{INITIAL_REFRESH_TS, MIN_DELTA_REFRESH}; +use crate::platform::fan_info::{FanInfoExt, FansInfoExt}; + +#[derive(Debug, Clone, PartialEq)] +pub struct LinuxFanInfo { + pub fan_label: Arc, + pub temp_name: Arc, + pub temp_amount: i64, + pub rpm: u64, + pub percent_vroomimg: f32, + + pub fan_index: u64, + pub hwmon_index: u64, + + pub max_speed: u64, +} + +impl Default for LinuxFanInfo { + fn default() -> Self { + Self { + fan_label: Arc::from(""), + temp_name: Arc::from(""), + temp_amount: 0, + rpm: 0, + percent_vroomimg: 0., + + fan_index: 0, + hwmon_index: 0, + + max_speed: 0, + } + } +} + +pub struct LinuxFanInfoIter<'a>( + pub std::iter::Map, fn(&'a LinuxFanInfo) -> &'a LinuxFanInfo>, +); + +impl<'a> LinuxFanInfoIter<'a> { + pub fn new(iter: std::slice::Iter<'a, LinuxFanInfo>) -> Self { + Self(iter.map(|di| di)) + } +} + +impl<'a> Iterator for LinuxFanInfoIter<'a> { + type Item = &'a LinuxFanInfo; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +impl FanInfoExt for LinuxFanInfo { + fn fan_label(&self) -> &str { + &self.fan_label + } + + fn temp_name(&self) -> &str { + &self.temp_name + } + + fn temp_amount(&self) -> i64 { + self.temp_amount + } + + fn rpm(&self) -> u64 { + self.rpm + } + + fn percent_vroomimg(&self) -> f32 { + self.percent_vroomimg + } + + fn fan_index(&self) -> u64 { + self.fan_index + } + + fn hwmon_index(&self) -> u64 { + self.hwmon_index + } + + fn max_speed(&self) -> u64 { + self.max_speed + } +} + +pub struct LinuxFansInfo { + info: Vec, + + refresh_timestamp: Instant, +} + +impl<'a> FansInfoExt<'a> for LinuxFansInfo { + type S = LinuxFanInfo; + type Iter = LinuxFanInfoIter<'a>; + + fn refresh_cache(&mut self) { + use crate::warning; + + let now = Instant::now(); + if now.duration_since(self.refresh_timestamp) < MIN_DELTA_REFRESH { + return; + } + self.refresh_timestamp = now; + + self.info.clear(); + + match glob("/sys/class/hwmon/hwmon[0-9]*/fan[0-9]*_input") { + Ok(globs) => { + for entry in globs { + match entry { + Ok(path) => { + // read the first glob result for hwmon location + let parent_dir = path.parent().unwrap(); + let parent_dir_str = path.parent().unwrap().to_str().unwrap(); + let hwmon_idx = + if let Some(hwmon_dir) = parent_dir.file_name().unwrap().to_str() { + hwmon_dir[5..].parse::().ok().unwrap_or(u64::MAX) + } else { + continue; + }; + + // read the second glob result for fan index + let findex = if let Some(hwmon_instance_dir) = + path.file_name().unwrap().to_str() + { + hwmon_instance_dir[3..(hwmon_instance_dir.len() - "_input".len())] + .parse::() + .ok() + .unwrap_or(u64::MAX) + } else { + continue; + }; + + let fan_label = if let Ok(label) = std::fs::read_to_string(format!( + "{}/fan{}_label", + parent_dir_str, findex + )) { + Arc::from(label.trim().to_case(Case::Title)) + } else { + Arc::from("") + }; + + let temp_label = if let Ok(label) = std::fs::read_to_string(format!( + "{}/temp{}_label", + parent_dir_str, findex + )) { + Arc::from(label.trim().to_case(Case::Title)) + } else { + // report no label as empty string + Arc::from("") + }; + + let percent_vrooming = if let Ok(v) = + std::fs::read_to_string(format!("{}/pwm{}", parent_dir_str, findex)) + { + v.trim() + .parse::() + .ok() + .map_or(-1.0, |v| v as f32 / 255.) + } else { + -1.0 + }; + + let rpm = if let Ok(v) = std::fs::read_to_string(format!( + "{}/fan{}_input", + parent_dir_str, findex + )) { + v.trim().parse::().ok().unwrap_or(u64::MAX) + } else { + u64::MAX + }; + + let temp = if let Ok(v) = std::fs::read_to_string(format!( + "{}/temp{}_input", + parent_dir_str, findex + )) { + v.trim().parse::().ok().unwrap_or(i64::MIN) + } else { + i64::MIN + }; + + let max_speed = if let Ok(v) = std::fs::read_to_string(format!( + "{}/fan{}_max", + parent_dir_str, findex + )) { + v.trim().parse::().ok().unwrap_or(0) + } else { + 0 + }; + + self.info.push(LinuxFanInfo { + fan_label, + temp_name: temp_label, + temp_amount: temp, + rpm, + percent_vroomimg: percent_vrooming, + + fan_index: findex, + hwmon_index: hwmon_idx, + + max_speed, + }) + } + Err(e) => { + warning!("Gatherer::FanInfo", "Failed to read hwmon entry: {}", e); + } + } + } + } + Err(e) => { + warning!("Gatherer::FanInfo", "Failed to read hwmon entry: {}", e); + } + }; + } + + fn info(&'a self) -> Self::Iter { + LinuxFanInfoIter::new(self.info.iter()) + } +} + +impl LinuxFansInfo { + pub fn new() -> Self { + Self { + info: vec![], + + refresh_timestamp: *INITIAL_REFRESH_TS, + } + } +} diff --git a/src/observatory-daemon/src/platform/linux/fork.rs b/src/observatory-daemon/src/platform/linux/fork.rs new file mode 100644 index 0000000..60d7967 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/fork.rs @@ -0,0 +1,97 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/fork.rs + * + * This file was originally part of the LACT project (https://github.com/ilya-zlobintsev/LACT) + * + * Copyright (c) 2023 Ilya Zlobintsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * SPDX-License-Identifier: MIT + */ + +use std::{ + fmt::Debug, + io::{BufReader, Read, Write}, + mem::size_of, + os::unix::net::UnixStream, +}; + +use anyhow::{anyhow, Context}; +use nix::{ + sys::wait::waitpid, + unistd::{fork, ForkResult}, +}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::debug; + +pub unsafe fn run_forked(f: F) -> anyhow::Result +where + T: Serialize + DeserializeOwned + Debug, + F: FnOnce() -> Result, +{ + let (rx, mut tx) = UnixStream::pair()?; + rx.set_read_timeout(Some(std::time::Duration::from_secs(1)))?; + tx.set_write_timeout(Some(std::time::Duration::from_secs(1)))?; + let mut rx = BufReader::new(rx); + + match fork()? { + ForkResult::Parent { child } => { + debug!("Gatherer::Fork", "Waiting for message from child"); + + let mut size_buf = [0u8; size_of::()]; + rx.read_exact(&mut size_buf)?; + let size = usize::from_ne_bytes(size_buf); + + let mut data_buf = vec![0u8; size]; + rx.read_exact(&mut data_buf)?; + + debug!( + "Gatherer::Fork", + "Received {} data bytes from child", + data_buf.len() + ); + + waitpid(child, None)?; + + let data: Result = bincode::deserialize(&data_buf) + .context("Could not deserialize response from child")?; + + data.map_err(|err| anyhow!("{err}")) + } + ForkResult::Child => { + let response = f(); + debug!("Gatherer::Fork", "Sending response to parent: {response:?}"); + + let send_result = (|| { + let data = bincode::serialize(&response)?; + tx.write_all(&data.len().to_ne_bytes())?; + tx.write_all(&data)?; + Ok::<_, anyhow::Error>(()) + })(); + + let exit_code = match send_result { + Ok(()) => 0, + Err(_) => 1, + }; + debug!("Gatherer::Fork", "Exiting child with code {exit_code}"); + std::process::exit(exit_code); + } + } +} diff --git a/src/observatory-daemon/src/platform/linux/gpu_info/mod.rs b/src/observatory-daemon/src/platform/linux/gpu_info/mod.rs new file mode 100644 index 0000000..2758d25 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/gpu_info/mod.rs @@ -0,0 +1,978 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/gpu_info/mod.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::{ + collections::HashMap, + fs, + sync::{Arc, RwLock}, + time::Instant, +}; + +use super::{INITIAL_REFRESH_TS, MIN_DELTA_REFRESH}; +use crate::{ + gpu_info_valid, + logging::{critical, debug, error, warning}, + platform::platform_impl::gpu_info::nvtop::GPUInfoDynamicInfoValid, + platform::{ + platform_impl::run_forked, ApiVersion, GpuDynamicInfoExt, GpuInfoExt, GpuStaticInfoExt, + OpenGLApiVersion, ProcessesExt, + }, +}; + +#[allow(unused)] +mod nvtop; +mod vulkan_info; + +#[derive(Debug, Clone)] +pub struct LinuxGpuStaticInfo { + id: Arc, + device_name: Arc, + vendor_id: u16, + device_id: u16, + total_memory: u64, + total_gtt: u64, + opengl_version: Option, + vulkan_version: Option, + pcie_gen: u8, + pcie_lanes: u8, +} + +impl LinuxGpuStaticInfo {} + +impl Default for LinuxGpuStaticInfo { + fn default() -> Self { + Self { + id: Arc::from(""), + device_name: Arc::from(""), + vendor_id: 0, + device_id: 0, + total_memory: 0, + total_gtt: 0, + opengl_version: None, + vulkan_version: None, + pcie_gen: 0, + pcie_lanes: 0, + } + } +} + +impl GpuStaticInfoExt for LinuxGpuStaticInfo { + fn id(&self) -> &str { + self.id.as_ref() + } + + fn device_name(&self) -> &str { + self.device_name.as_ref() + } + + fn vendor_id(&self) -> u16 { + self.vendor_id + } + + fn device_id(&self) -> u16 { + self.device_id + } + + fn total_memory(&self) -> u64 { + self.total_memory + } + + fn total_gtt(&self) -> u64 { + self.total_gtt + } + + fn opengl_version(&self) -> Option<&OpenGLApiVersion> { + self.opengl_version.as_ref() + } + + fn vulkan_version(&self) -> Option<&ApiVersion> { + self.vulkan_version.as_ref() + } + + fn metal_version(&self) -> Option<&ApiVersion> { + None + } + + fn direct3d_version(&self) -> Option<&ApiVersion> { + None + } + + fn pcie_gen(&self) -> u8 { + self.pcie_gen + } + + fn pcie_lanes(&self) -> u8 { + self.pcie_lanes + } +} + +#[derive(Debug, Clone)] +pub struct LinuxGpuDynamicInfo { + id: Arc, + temp_celsius: u32, + fan_speed_percent: u32, + util_percent: u32, + power_draw_watts: f32, + power_draw_max_watts: f32, + clock_speed_mhz: u32, + clock_speed_max_mhz: u32, + mem_speed_mhz: u32, + mem_speed_max_mhz: u32, + free_memory: u64, + used_memory: u64, + used_gtt: u64, + encoder_percent: u32, + decoder_percent: u32, +} + +impl Default for LinuxGpuDynamicInfo { + fn default() -> Self { + Self { + id: Arc::from(""), + temp_celsius: 0, + fan_speed_percent: 0, + util_percent: 0, + power_draw_watts: 0.0, + power_draw_max_watts: 0.0, + clock_speed_mhz: 0, + clock_speed_max_mhz: 0, + mem_speed_mhz: 0, + mem_speed_max_mhz: 0, + free_memory: 0, + used_memory: 0, + used_gtt: 0, + encoder_percent: 0, + decoder_percent: 0, + } + } +} + +impl LinuxGpuDynamicInfo { + pub fn new() -> Self { + Default::default() + } +} + +impl GpuDynamicInfoExt for LinuxGpuDynamicInfo { + fn id(&self) -> &str { + self.id.as_ref() + } + + fn temp_celsius(&self) -> u32 { + self.temp_celsius + } + + fn fan_speed_percent(&self) -> u32 { + self.fan_speed_percent + } + + fn util_percent(&self) -> u32 { + self.util_percent + } + + fn power_draw_watts(&self) -> f32 { + self.power_draw_watts + } + + fn power_draw_max_watts(&self) -> f32 { + self.power_draw_max_watts + } + + fn clock_speed_mhz(&self) -> u32 { + self.clock_speed_mhz + } + + fn clock_speed_max_mhz(&self) -> u32 { + self.clock_speed_max_mhz + } + + fn mem_speed_mhz(&self) -> u32 { + self.mem_speed_mhz + } + + fn mem_speed_max_mhz(&self) -> u32 { + self.mem_speed_max_mhz + } + + fn free_memory(&self) -> u64 { + self.free_memory + } + + fn used_memory(&self) -> u64 { + self.used_memory + } + + fn used_gtt(&self) -> u64 { + self.used_gtt + } + + fn encoder_percent(&self) -> u32 { + self.encoder_percent + } + + fn decoder_percent(&self) -> u32 { + self.decoder_percent + } +} + +pub struct LinuxGpuInfo { + gpu_list: Arc>, + static_info: HashMap, LinuxGpuStaticInfo>, + dynamic_info: HashMap, LinuxGpuDynamicInfo>, + + gpu_list_refreshed: bool, + + static_refresh_timestamp: Instant, + dynamic_refresh_timestamp: Instant, +} + +impl Drop for LinuxGpuInfo { + fn drop(&mut self) { + use std::ops::DerefMut; + + let mut gl = self.gpu_list.write().unwrap(); + unsafe { + nvtop::gpuinfo_shutdown_info_extraction(gl.deref_mut()); + } + } +} + +impl LinuxGpuInfo { + pub fn new() -> Self { + use std::ops::DerefMut; + + unsafe { + nvtop::init_extract_gpuinfo_intel(); + nvtop::init_extract_gpuinfo_amdgpu(); + nvtop::init_extract_gpuinfo_nvidia(); + } + + let gpu_list = Arc::new(RwLock::new(nvtop::ListHead { + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + })); + { + let mut gl = gpu_list.write().unwrap(); + gl.next = gl.deref_mut(); + gl.prev = gl.deref_mut(); + } + + let mut this = Self { + gpu_list, + + static_info: HashMap::new(), + dynamic_info: HashMap::new(), + + gpu_list_refreshed: false, + + static_refresh_timestamp: *INITIAL_REFRESH_TS, + dynamic_refresh_timestamp: *INITIAL_REFRESH_TS, + }; + + this.refresh_gpu_list(); + + this + } + + #[allow(non_snake_case)] + unsafe fn supported_opengl_version(dri_path: &str) -> Option { + use crate::platform::OpenGLApi; + use gbm::AsRaw; + use std::os::fd::*; + + type Void = std::ffi::c_void; + + pub struct DrmDevice(std::fs::File); + + impl AsFd for DrmDevice { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + } + + impl DrmDevice { + pub fn open(path: &str) -> std::io::Result { + let mut options = std::fs::OpenOptions::new(); + options.read(true); + options.write(true); + + Ok(Self(options.open(path)?)) + } + } + + impl drm::Device for DrmDevice {} + + let drm_device = match DrmDevice::open(dri_path) { + Err(e) => { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: {}", e + ); + return None; + } + Ok(drm_device) => drm_device, + }; + + let gbm_device = match gbm::Device::new(drm_device) { + Err(e) => { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: {}", e + ); + return None; + } + Ok(gbm_device) => gbm_device, + }; + + const EGL_CONTEXT_MAJOR_VERSION_KHR: egl::EGLint = 0x3098; + const EGL_CONTEXT_MINOR_VERSION_KHR: egl::EGLint = 0x30FB; + const EGL_PLATFORM_GBM_KHR: egl::EGLenum = 0x31D7; + const EGL_OPENGL_ES3_BIT: egl::EGLint = 0x0040; + + let eglGetPlatformDisplayEXT = + egl::get_proc_address("eglGetPlatformDisplayEXT") as *const Void; + let egl_display = if !eglGetPlatformDisplayEXT.is_null() { + let eglGetPlatformDisplayEXT: extern "C" fn( + egl::EGLenum, + *mut Void, + *const egl::EGLint, + ) -> egl::EGLDisplay = std::mem::transmute(eglGetPlatformDisplayEXT); + eglGetPlatformDisplayEXT( + EGL_PLATFORM_GBM_KHR, + gbm_device.as_raw() as *mut Void, + std::ptr::null(), + ) + } else { + let eglGetPlatformDisplay = + egl::get_proc_address("eglGetPlatformDisplay") as *const Void; + if !eglGetPlatformDisplay.is_null() { + let eglGetPlatformDisplay: extern "C" fn( + egl::EGLenum, + *mut Void, + *const egl::EGLint, + ) -> egl::EGLDisplay = std::mem::transmute(eglGetPlatformDisplay); + eglGetPlatformDisplay( + EGL_PLATFORM_GBM_KHR, + gbm_device.as_raw() as *mut Void, + std::ptr::null(), + ) + } else { + egl::get_display(gbm_device.as_raw() as *mut Void) + .map_or(std::ptr::null_mut(), |d| d) + } + }; + if egl_display.is_null() { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: Failed to initialize an EGL display ({:X})", + egl::get_error() + ); + return None; + } + + let mut egl_major = 0; + let mut egl_minor = 0; + if !egl::initialize(egl_display, &mut egl_major, &mut egl_minor) { + error!( + "Gathereer::GpuInfo", + "Failed to get OpenGL information: Failed to initialize an EGL display ({:X})", + egl::get_error() + ); + return None; + } + + if egl_major < 1 || (egl_major == 1 && egl_minor < 4) { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: EGL version 1.4 or higher is required to test OpenGL support" + ); + return None; + } + + let mut gl_api = egl::EGL_OPENGL_API; + if !egl::bind_api(gl_api) { + gl_api = egl::EGL_OPENGL_ES_API; + if !egl::bind_api(gl_api) { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: Failed to bind an EGL API ({:X})", + egl::get_error() + ); + return None; + } + } + + let egl_config = if gl_api == egl::EGL_OPENGL_ES_API { + let mut config_attribs = [ + egl::EGL_SURFACE_TYPE, + egl::EGL_WINDOW_BIT, + egl::EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES3_BIT, + egl::EGL_NONE, + ]; + + let mut egl_config = egl::choose_config(egl_display, &config_attribs, 1); + if egl_config.is_some() { + egl_config + } else { + config_attribs[3] = egl::EGL_OPENGL_ES2_BIT; + egl_config = egl::choose_config(egl_display, &config_attribs, 1); + if egl_config.is_some() { + egl_config + } else { + config_attribs[3] = egl::EGL_OPENGL_ES_BIT; + egl::choose_config(egl_display, &config_attribs, 1) + } + } + } else { + let config_attribs = [ + egl::EGL_SURFACE_TYPE, + egl::EGL_WINDOW_BIT, + egl::EGL_RENDERABLE_TYPE, + egl::EGL_OPENGL_BIT, + egl::EGL_NONE, + ]; + + egl::choose_config(egl_display, &config_attribs, 1) + }; + + if egl_config.is_none() { + return None; + } + let egl_config = match egl_config { + Some(ec) => ec, + None => { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: Failed to choose an EGL config ({:X})", + egl::get_error() + ); + return None; + } + }; + + let mut ver_major = if gl_api == egl::EGL_OPENGL_API { 4 } else { 3 }; + let mut ver_minor = if gl_api == egl::EGL_OPENGL_API { 6 } else { 0 }; + + let mut context_attribs = [ + EGL_CONTEXT_MAJOR_VERSION_KHR, + ver_major, + EGL_CONTEXT_MINOR_VERSION_KHR, + ver_minor, + egl::EGL_NONE, + ]; + + let mut egl_context; + loop { + egl_context = egl::create_context( + egl_display, + egl_config, + egl::EGL_NO_CONTEXT, + &context_attribs, + ); + + if egl_context.is_some() || (ver_major == 1 && ver_minor == 0) { + break; + } + + if ver_minor > 0 { + ver_minor -= 1; + } else { + ver_major -= 1; + ver_minor = 9; + } + + context_attribs[1] = ver_major; + context_attribs[3] = ver_minor; + } + + match egl_context { + Some(ec) => egl::destroy_context(egl_display, ec), + None => { + error!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: Failed to create an EGL context ({:X})", + egl::get_error() + ); + return None; + } + }; + + Some(OpenGLApiVersion { + major: ver_major as u8, + minor: ver_minor as u8, + api: if gl_api != egl::EGL_OPENGL_API { + OpenGLApi::OpenGLES + } else { + OpenGLApi::OpenGL + }, + }) + } +} + +impl<'a> GpuInfoExt<'a> for LinuxGpuInfo { + type S = LinuxGpuStaticInfo; + type D = LinuxGpuDynamicInfo; + type P = crate::platform::Processes; + type Iter = std::iter::Map< + std::collections::hash_map::Keys<'a, arrayvec::ArrayString<16>, LinuxGpuStaticInfo>, + fn(&arrayvec::ArrayString<16>) -> &str, + >; + + fn refresh_gpu_list(&mut self) { + use arrayvec::ArrayString; + use std::{io::Read, ops::DerefMut}; + + if self.gpu_list_refreshed { + return; + } + + self.gpu_list_refreshed = true; + + let mut gpu_list = self.gpu_list.write().unwrap(); + let gpu_list = gpu_list.deref_mut(); + + let mut gpu_count: u32 = 0; + let nvt_result = unsafe { nvtop::gpuinfo_init_info_extraction(&mut gpu_count, gpu_list) }; + if nvt_result == 0 { + critical!( + "Gatherer::GpuInfo", + "Unable to initialize GPU info extraction" + ); + return; + } + + let nvt_result = unsafe { nvtop::gpuinfo_populate_static_infos(gpu_list) }; + if nvt_result == 0 { + unsafe { nvtop::gpuinfo_shutdown_info_extraction(gpu_list) }; + + critical!("Gatherer::GPUInfo", "Unable to populate static GPU info"); + return; + } + + let result = unsafe { nvtop::gpuinfo_refresh_dynamic_info(gpu_list) }; + if result == 0 { + critical!("Gatherer::GpuInfo", "Unable to refresh dynamic GPU info"); + return; + } + + let result = unsafe { nvtop::gpuinfo_utilisation_rate(gpu_list) }; + if result == 0 { + critical!("Gatherer::GpuInfo", "Unable to refresh utilization rate"); + return; + } + + self.static_info.clear(); + self.dynamic_info.clear(); + + let mut buffer = String::new(); + + let mut device = gpu_list.next; + while device != gpu_list { + use std::fmt::Write; + + let dev: &nvtop::GPUInfo = unsafe { core::mem::transmute(device) }; + device = unsafe { (*device).next }; + + let pdev = unsafe { std::ffi::CStr::from_ptr(dev.pdev.as_ptr()) }; + let pdev = match pdev.to_str() { + Ok(pd) => pd, + Err(_) => { + warning!( + "Gatherer::GpuInfo", + "Unable to convert PCI ID to string: {:?}", + pdev + ); + continue; + } + }; + let mut pci_bus_id = ArrayString::<16>::new(); + match write!(pci_bus_id, "{}", pdev) { + Ok(_) => {} + Err(_) => { + warning!( + "Gatherer::GpuInfo", + "PCI ID exceeds 16 characters: {}", + pdev + ); + continue; + } + } + + let device_name = + unsafe { std::ffi::CStr::from_ptr(dev.static_info.device_name.as_ptr()) }; + let device_name = device_name.to_str().unwrap_or_else(|_| "Unknown"); + + let mut uevent_path = ArrayString::<64>::new(); + let _ = write!(uevent_path, "/sys/bus/pci/devices/{}/uevent", pdev); + let uevent_file = match std::fs::OpenOptions::new() + .read(true) + .open(uevent_path.as_str()) + { + Ok(f) => Some(f), + Err(_) => { + uevent_path.clear(); + let _ = write!( + uevent_path, + "/sys/bus/pci/devices/{}/uevent", + pdev.to_lowercase() + ); + match std::fs::OpenOptions::new() + .read(true) + .open(uevent_path.as_str()) + { + Ok(f) => Some(f), + Err(_) => { + warning!( + "Gatherer::GPUInfo", + "Unable to open `uevent` file for device {}", + pdev + ); + None + } + } + } + }; + + let total_gtt = match fs::read_to_string(format!( + "/sys/bus/pci/devices/{}/mem_info_gtt_total", + pdev.to_lowercase() + )) { + Ok(x) => match x.trim().parse::() { + Ok(x) => x, + Err(x) => { + debug!("Gatherer::GpuInfo", "Failed to parse total gtt: {}", x); + 0 + } + }, + Err(x) => { + debug!("Gatherer::GpuInfo", "Failed to read total gtt: {}", x); + 0 + } + }; + let ven_dev_id = if let Some(mut f) = uevent_file { + buffer.clear(); + match f.read_to_string(&mut buffer) { + Ok(_) => { + let mut vendor_id = 0; + let mut device_id = 0; + + for line in buffer.lines().map(|l| l.trim()) { + if line.starts_with("PCI_ID=") { + let mut ids = line[7..].split(':'); + vendor_id = ids + .next() + .and_then(|id| u16::from_str_radix(id, 16).ok()) + .unwrap_or(0); + device_id = ids + .next() + .and_then(|id| u16::from_str_radix(id, 16).ok()) + .unwrap_or(0); + break; + } + } + + (vendor_id, device_id) + } + Err(_) => { + warning!( + "Gatherer::GPUInfo", + "Unable to read `uevent` file content for device {}", + pdev + ); + (0, 0) + } + } + } else { + (0, 0) + }; + + let static_info = LinuxGpuStaticInfo { + id: Arc::from(pdev), + device_name: Arc::from(device_name), + vendor_id: ven_dev_id.0, + device_id: ven_dev_id.1, + + total_memory: dev.dynamic_info.total_memory, + total_gtt, + + pcie_gen: dev.dynamic_info.pcie_link_gen as _, + pcie_lanes: dev.dynamic_info.pcie_link_width as _, + + // Leave the rest for when static info is actually requested + ..Default::default() + }; + + self.static_info.insert(pci_bus_id.clone(), static_info); + self.dynamic_info + .insert(pci_bus_id, LinuxGpuDynamicInfo::new()); + } + } + + fn refresh_static_info_cache(&mut self) { + use arrayvec::ArrayString; + use std::fmt::Write; + + if !self.gpu_list_refreshed { + return; + } + + let now = Instant::now(); + if self.static_refresh_timestamp.elapsed() < MIN_DELTA_REFRESH { + return; + } + self.static_refresh_timestamp = now; + + let vulkan_versions = unsafe { + run_forked(|| { + if let Some(vulkan_info) = vulkan_info::VulkanInfo::new() { + Ok(vulkan_info + .supported_vulkan_versions() + .unwrap_or(HashMap::new())) + } else { + Ok(HashMap::new()) + } + }) + }; + let vulkan_versions = vulkan_versions.unwrap_or_else(|e| { + warning!( + "Gatherer::GpuInfo", + "Failed to get Vulkan information: {}", + e + ); + HashMap::new() + }); + + let mut dri_path = ArrayString::<64>::new_const(); + for (pci_id, static_info) in &mut self.static_info { + let _ = write!(dri_path, "/dev/dri/by-path/pci-{}-card", pci_id); + if !std::path::Path::new(dri_path.as_str()).exists() { + dri_path.clear(); + let _ = write!( + dri_path, + "/dev/dri/by-path/pci-{}-card", + pci_id.to_ascii_lowercase() + ); + } + static_info.opengl_version = unsafe { + run_forked(|| Ok(Self::supported_opengl_version(dri_path.as_str()))).unwrap_or_else( + |e| { + warning!( + "Gatherer::GpuInfo", + "Failed to get OpenGL information: {}", + e + ); + None + }, + ) + }; + + let device_id = ((static_info.vendor_id as u32) << 16) | static_info.device_id as u32; + if let Some(vulkan_version) = vulkan_versions.get(&device_id) { + static_info.vulkan_version = Some(*vulkan_version); + } + } + } + + fn refresh_dynamic_info_cache(&mut self, processes: &mut Self::P) { + use std::ops::DerefMut; + + if !self.gpu_list_refreshed { + return; + } + + let now = Instant::now(); + if self.dynamic_refresh_timestamp.elapsed() < MIN_DELTA_REFRESH { + return; + } + self.dynamic_refresh_timestamp = now; + + let mut gpu_list = self.gpu_list.write().unwrap(); + let gpu_list = gpu_list.deref_mut(); + + let result = unsafe { nvtop::gpuinfo_refresh_dynamic_info(gpu_list) }; + if result == 0 { + error!("Gatherer::GpuInfo", "Unable to refresh dynamic GPU info"); + return; + } + + let result = unsafe { nvtop::gpuinfo_refresh_processes(gpu_list) }; + if result == 0 { + error!("Gatherer::GpuInfo", "Unable to refresh GPU processes"); + return; + } + + let result = unsafe { nvtop::gpuinfo_utilisation_rate(gpu_list) }; + if result == 0 { + critical!("Gatherer::GpuInfo", "Unable to refresh utilization rate"); + return; + } + + let result = unsafe { nvtop::gpuinfo_fix_dynamic_info_from_process_info(gpu_list) }; + if result == 0 { + error!( + "Gatherer::GpuInfo", + "Unable to fix dynamic GPU info from process info" + ); + return; + } + + let processes = processes.process_list_mut(); + + let mut device: *mut nvtop::ListHead = gpu_list.next; + while device != gpu_list { + let dev: &nvtop::GPUInfo = unsafe { core::mem::transmute(device) }; + device = unsafe { (*device).next }; + + let pdev = unsafe { std::ffi::CStr::from_ptr(dev.pdev.as_ptr()) }; + let pdev = match pdev.to_str() { + Ok(pd) => pd, + Err(_) => { + warning!( + "Gatherer::GpuInfo", + "Unable to convert PCI ID to string: {:?}", + pdev + ); + continue; + } + }; + let pci_id = match arrayvec::ArrayString::<16>::from(pdev) { + Ok(id) => id, + Err(_) => { + warning!( + "Gatherer::GpuInfo", + "PCI ID exceeds 16 characters: {}", + pdev + ); + continue; + } + }; + + let used_gtt = match fs::read_to_string(format!( + "/sys/bus/pci/devices/{}/mem_info_gtt_used", + pdev.to_lowercase() + )) { + Ok(x) => match x.trim().parse::() { + Ok(x) => x, + Err(x) => { + debug!("Gatherer::GpuInfo", "Failed to parse used gtt: {}", x); + 0 + } + }, + Err(x) => { + debug!("Gatherer::GpuInfo", "Failed to read used gtt: {}", x); + 0 + } + }; + + let dynamic_info = self.dynamic_info.get_mut(&pci_id); + if dynamic_info.is_none() { + continue; + } + let dynamic_info = unsafe { dynamic_info.unwrap_unchecked() }; + dynamic_info.id = Arc::from(pdev); + dynamic_info.temp_celsius = dev.dynamic_info.gpu_temp; + dynamic_info.fan_speed_percent = dev.dynamic_info.fan_speed; + dynamic_info.util_percent = dev.dynamic_info.gpu_util_rate; + dynamic_info.power_draw_watts = dev.dynamic_info.power_draw as f32 / 1000.; + dynamic_info.power_draw_max_watts = dev.dynamic_info.power_draw_max as f32 / 1000.; + dynamic_info.clock_speed_mhz = dev.dynamic_info.gpu_clock_speed; + dynamic_info.clock_speed_max_mhz = dev.dynamic_info.gpu_clock_speed_max; + dynamic_info.mem_speed_mhz = dev.dynamic_info.mem_clock_speed; + dynamic_info.mem_speed_max_mhz = dev.dynamic_info.mem_clock_speed_max; + dynamic_info.free_memory = dev.dynamic_info.free_memory; + dynamic_info.used_memory = dev.dynamic_info.used_memory; + dynamic_info.used_gtt = used_gtt; + dynamic_info.encoder_percent = { + if gpu_info_valid!(dev.dynamic_info, GPUInfoDynamicInfoValid::EncoderRateValid) { + dev.dynamic_info.encoder_rate + } else { + 0 + } + }; + dynamic_info.decoder_percent = { + if gpu_info_valid!(dev.dynamic_info, GPUInfoDynamicInfoValid::DecoderRateValid) { + dev.dynamic_info.decoder_rate + } else { + 0 + } + }; + + for i in 0..dev.processes_count as usize { + let process = unsafe { &*dev.processes.add(i) }; + if let Some(proc) = processes.get_mut(&(process.pid as u32)) { + proc.usage_stats.gpu_usage = process.gpu_usage as f32; + proc.usage_stats.gpu_memory_usage = process.gpu_memory_usage as f32; + } + } + } + } + + fn enumerate(&'a self) -> Self::Iter { + self.static_info.keys().map(|k| k.as_str()) + } + + fn static_info(&self, id: &str) -> Option<&Self::S> { + use arrayvec::ArrayString; + + self.static_info + .get(&ArrayString::<16>::from(id).unwrap_or_default()) + } + + fn dynamic_info(&self, id: &str) -> Option<&Self::D> { + use arrayvec::ArrayString; + + self.dynamic_info + .get(&ArrayString::<16>::from(id).unwrap_or_default()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_gpu_info() { + use crate::platform::Processes; + + let mut gpu_info = LinuxGpuInfo::new(); + gpu_info.refresh_gpu_list(); + let pci_id = { + let pci_ids = gpu_info.enumerate(); + dbg!(&pci_ids); + + gpu_info.enumerate().next().unwrap_or("").to_owned() + }; + + gpu_info.refresh_static_info_cache(); + let static_info = gpu_info.static_info(&pci_id); + dbg!(&static_info); + + let mut p = Processes::default(); + gpu_info.refresh_dynamic_info_cache(&mut p); + let _ = gpu_info.dynamic_info(&pci_id); + + std::thread::sleep(std::time::Duration::from_millis(500)); + + gpu_info.refresh_dynamic_info_cache(&mut p); + let dynamic_info = gpu_info.dynamic_info(&pci_id); + dbg!(&dynamic_info); + } +} diff --git a/src/observatory-daemon/src/platform/linux/gpu_info/nvtop.rs b/src/observatory-daemon/src/platform/linux/gpu_info/nvtop.rs new file mode 100644 index 0000000..013e1c0 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/gpu_info/nvtop.rs @@ -0,0 +1,240 @@ +/* sys_info_v2/observatory-daemon/src/gpu/nvtop.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#[macro_export] +macro_rules! gpu_info_valid { + ($info: expr, $field: expr) => {{ + let field = $field as usize; + ((($info).valid)[field / 8] & (1 << (field % 8))) != 0 + }}; +} + +const MAX_DEVICE_NAME: usize = 128; +const PDEV_LEN: usize = 16; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ListHead { + pub next: *mut ListHead, + pub prev: *mut ListHead, +} + +unsafe impl Send for ListHead {} + +unsafe impl Sync for ListHead {} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GPUVendor { + pub list: ListHead, + + pub init: Option u8>, + pub shutdown: Option, + + pub last_error_string: Option *const i8>, + + pub get_device_handles: Option u8>, + + pub populate_static_info: Option, + pub refresh_dynamic_info: Option, + pub refresh_utilisation_rate: Option, + + pub refresh_running_processes: Option, + + pub name: *mut i8, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum GPUInfoStaticInfoValid { + DeviceNameValid = 0, + MaxPcieGenValid, + MaxPcieLinkWidthValid, + TemperatureShutdownThresholdValid, + TemperatureSlowdownThresholdValid, + NumberSharedCoresValid, + L2CacheSizeValid, + NumberExecEnginesValid, + StaticInfoCount, +} + +const GPU_INFO_STATIC_INFO_COUNT: usize = GPUInfoStaticInfoValid::StaticInfoCount as usize; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GPUInfoStaticInfo { + pub device_name: [libc::c_char; MAX_DEVICE_NAME], + pub max_pcie_gen: u32, + pub max_pcie_link_width: u32, + pub temperature_shutdown_threshold: u32, + pub temperature_slowdown_threshold: u32, + pub n_shared_cores: u32, + pub l2cache_size: u32, + pub n_exec_engines: u32, + pub integrated_graphics: u8, + pub encode_decode_shared: u8, + pub valid: [u8; (GPU_INFO_STATIC_INFO_COUNT + 7) / 8], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum GPUInfoDynamicInfoValid { + GpuClockSpeedValid = 0, + GpuClockSpeedMaxValid, + MemClockSpeedValid, + MemClockSpeedMaxValid, + GpuUtilRateValid, + MemUtilRateValid, + EncoderRateValid, + DecoderRateValid, + TotalMemoryValid, + FreeMemoryValid, + UsedMemoryValid, + PcieLinkGenValid, + PcieLinkWidthValid, + PcieRxValid, + PcieTxValid, + FanSpeedValid, + GpuTempValid, + PowerDrawValid, + PowerDrawMaxValid, + MultiInstanceModeValid, + DynamicInfoCount, +} + +const GPU_INFO_DYNAMIC_INFO_COUNT: usize = GPUInfoDynamicInfoValid::DynamicInfoCount as usize; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GPUInfoDynamicInfo { + pub gpu_clock_speed: u32, + pub gpu_clock_speed_max: u32, + pub mem_clock_speed: u32, + pub mem_clock_speed_max: u32, + pub gpu_util_rate: u32, + pub mem_util_rate: u32, + pub encoder_rate: u32, + pub decoder_rate: u32, + pub total_memory: u64, + pub free_memory: u64, + pub used_memory: u64, + pub pcie_link_gen: u32, + pub pcie_link_width: u32, + pub pcie_rx: u32, + pub pcie_tx: u32, + pub fan_speed: u32, + pub gpu_temp: u32, + pub power_draw: u32, + pub power_draw_max: u32, + pub multi_instance_mode: u8, + pub valid: [u8; (GPU_INFO_DYNAMIC_INFO_COUNT + 7) / 8], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum GPUProcessType { + Unknown = 0, + Graphical = 1, + Compute = 2, + GraphicalCompute = 3, + Count, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum GPUInfoProcessInfoValid { + CmdlineValid, + UserNameValid, + GfxEngineUsedValid, + ComputeEngineUsedValid, + EncEngineUsedValid, + DecEngineUsedValid, + GpuUsageValid, + EncodeUsageValid, + DecodeUsageValid, + GpuMemoryUsageValid, + GpuMemoryPercentageValid, + CpuUsageValid, + CpuMemoryVirtValid, + CpuMemoryResValid, + GpuCyclesValid, + SampleDeltaValid, + ProcessValidInfoCount, +} + +const GPU_PROCESS_INFO_VALID_INFO_COUNT: usize = + GPUInfoProcessInfoValid::ProcessValidInfoCount as usize; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GPUProcess { + pub r#type: GPUProcessType, + pub pid: i32, + pub cmdline: *mut libc::c_char, + pub user_name: *mut libc::c_char, + pub sample_delta: u64, // Time spent between two successive samples + pub gfx_engine_used: u64, + pub compute_engine_used: u64, + pub enc_engine_used: u64, + pub dec_engine_used: u64, + pub gpu_cycles: u64, // Number of GPU cycles spent in the GPU gfx engine + pub gpu_usage: u32, + pub encode_usage: u32, + pub decode_usage: u32, + pub gpu_memory_usage: libc::c_ulonglong, + pub gpu_memory_percentage: u32, + pub cpu_usage: u32, + pub cpu_memory_virt: libc::c_ulong, + pub cpu_memory_res: libc::c_ulong, + pub valid: [u8; (GPU_PROCESS_INFO_VALID_INFO_COUNT + 7) / 8], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct GPUInfo { + pub list: ListHead, + pub vendor: *mut GPUVendor, + pub static_info: GPUInfoStaticInfo, + pub dynamic_info: GPUInfoDynamicInfo, + pub processes_count: u32, + pub processes: *mut GPUProcess, + pub processes_array_size: u32, + pub pdev: [libc::c_char; PDEV_LEN], +} + +extern "C" { + pub fn gpuinfo_init_info_extraction( + monitored_dev_count: *mut u32, + devices: *mut ListHead, + ) -> u8; + + pub fn gpuinfo_shutdown_info_extraction(devices: *mut ListHead) -> u8; + + pub fn init_extract_gpuinfo_amdgpu(); + pub fn init_extract_gpuinfo_intel(); + pub fn init_extract_gpuinfo_msm(); + pub fn init_extract_gpuinfo_nvidia(); + + pub fn gpuinfo_populate_static_infos(devices: *mut ListHead) -> u8; + pub fn gpuinfo_refresh_dynamic_info(devices: *mut ListHead) -> u8; + pub fn gpuinfo_refresh_processes(devices: *mut ListHead) -> u8; + pub fn gpuinfo_utilisation_rate(devices: *mut ListHead) -> u8; + pub fn gpuinfo_fix_dynamic_info_from_process_info(devices: *mut ListHead) -> u8; +} diff --git a/src/observatory-daemon/src/platform/linux/gpu_info/vulkan_info.rs b/src/observatory-daemon/src/platform/linux/gpu_info/vulkan_info.rs new file mode 100644 index 0000000..7e1cd0e --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/gpu_info/vulkan_info.rs @@ -0,0 +1,127 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/gpu_info/vulkan_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +pub struct VulkanInfo { + _entry: ash::Entry, + vk_instance: ash::Instance, +} + +impl Drop for VulkanInfo { + fn drop(&mut self) { + unsafe { + self.vk_instance.destroy_instance(None); + } + } +} + +impl VulkanInfo { + pub fn new() -> Option { + use crate::{critical, debug}; + use ash::{vk, Entry}; + + let _entry = match unsafe { Entry::load() } { + Ok(e) => e, + Err(e) => { + critical!( + "Gatherer::GPU", + "Failed to get Vulkan information: Could not load 'libvulkan.so.1'; {}", + e + ); + return None; + } + }; + debug!("Gatherer::VkInfo", "Loaded Vulkan library"); + + let app_info = vk::ApplicationInfo { + api_version: vk::make_api_version(0, 1, 0, 0), + ..Default::default() + }; + let create_info = vk::InstanceCreateInfo { + p_application_info: &app_info, + ..Default::default() + }; + + let instance = match unsafe { _entry.create_instance(&create_info, None) } { + Ok(i) => i, + Err(e) => { + critical!( + "Gatherer::GPU", + "Failed to get Vulkan information: Could not create instance; {}", + e + ); + return None; + } + }; + debug!("Gatherer::VkInfo", "Created Vulkan instance"); + + Some(Self { + _entry: _entry, + vk_instance: instance, + }) + } + + pub unsafe fn supported_vulkan_versions( + &self, + ) -> Option> { + use crate::{debug, platform::ApiVersion, warning}; + + let physical_devices = match self.vk_instance.enumerate_physical_devices() { + Ok(pd) => pd, + Err(e) => { + warning!( + "Gatherer::GPU", + "Failed to get Vulkan information: No Vulkan capable devices found ({})", + e + ); + vec![] + } + }; + + let mut supported_versions = std::collections::HashMap::new(); + + for device in physical_devices { + let properties = self.vk_instance.get_physical_device_properties(device); + debug!( + "Gatherer::GPU", + "Found Vulkan device: {:?}", + std::ffi::CStr::from_ptr(properties.device_name.as_ptr()) + ); + + let version = properties.api_version; + let major = (version >> 22) as u16; + let minor = ((version >> 12) & 0x3ff) as u16; + let patch = (version & 0xfff) as u16; + + let vendor_id = properties.vendor_id & 0xffff; + let device_id = properties.device_id & 0xffff; + + supported_versions.insert( + (vendor_id << 16) | device_id, + ApiVersion { + major, + minor, + patch, + }, + ); + } + + Some(supported_versions) + } +} diff --git a/src/observatory-daemon/src/platform/linux/mod.rs b/src/observatory-daemon/src/platform/linux/mod.rs new file mode 100644 index 0000000..c3168ee --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/mod.rs @@ -0,0 +1,76 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/mod.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::time::{Duration, Instant}; + +use lazy_static::lazy_static; + +pub use apps::*; +pub use cpu_info::*; +pub use disk_info::*; +pub use fan_info::*; +use fork::run_forked; +pub use gpu_info::*; +pub use processes::*; +pub use services::*; +pub use utilities::*; + +mod apps; +mod cpu_info; +mod disk_info; +mod fan_info; +mod fork; +mod gpu_info; +mod openrc; +mod processes; +mod services; +mod systemd; +mod utilities; + +const MIN_DELTA_REFRESH: Duration = Duration::from_millis(200); + +lazy_static! { + static ref HZ: usize = unsafe { libc::sysconf(libc::_SC_CLK_TCK) as usize }; + static ref CPU_COUNT: usize = { + use crate::critical; + + let proc_stat = std::fs::read_to_string("/proc/stat").unwrap_or_else(|e| { + critical!("Gatherer::Linux", "Failed to read /proc/stat: {}", e); + "".to_owned() + }); + + proc_stat + .lines() + .map(|l| l.trim()) + .skip_while(|l| !l.starts_with("cpu")) + .filter(|l| l.starts_with("cpu")) + .count() + .max(2) + - 1 + }; + static ref INITIAL_REFRESH_TS: Instant = unsafe { + struct Ts { + _sec: i64, + _nsec: u32, + } + + std::mem::transmute(Ts { _sec: 0, _nsec: 0 }) + }; +} diff --git a/src/observatory-daemon/src/platform/linux/openrc/controller.rs b/src/observatory-daemon/src/platform/linux/openrc/controller.rs new file mode 100644 index 0000000..71c48f3 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/openrc/controller.rs @@ -0,0 +1,98 @@ +use std::process::Command; +use std::sync::Arc; + +use super::OpenRCError; + +pub struct Controller; + +impl Controller { + pub fn enable_service(&self, service: &str) -> Result<(), OpenRCError> { + let cmd = Command::new("pkexec") + .arg("rc-update") + .arg("add") + .arg(service) + .output()?; + if !cmd.status.success() { + let error_message = + Arc::::from(String::from_utf8_lossy(&cmd.stderr).as_ref().trim()); + return Err(OpenRCError::CommandExecutionError( + error_message, + cmd.status.code().unwrap_or(-1), + )); + } + + Ok(()) + } + + pub fn disable_service(&self, service: &str) -> Result<(), OpenRCError> { + let cmd = Command::new("pkexec") + .arg("rc-update") + .arg("del") + .arg(service) + .output()?; + if !cmd.status.success() { + let error_message = + Arc::::from(String::from_utf8_lossy(&cmd.stderr).as_ref().trim()); + return Err(OpenRCError::CommandExecutionError( + error_message, + cmd.status.code().unwrap_or(-1), + )); + } + + Ok(()) + } + + pub fn start_service(&self, service: &str) -> Result<(), OpenRCError> { + let cmd = Command::new("pkexec") + .arg("rc-service") + .arg(service) + .arg("start") + .output()?; + if !cmd.status.success() { + let error_message = + Arc::::from(String::from_utf8_lossy(&cmd.stderr).as_ref().trim()); + return Err(OpenRCError::CommandExecutionError( + error_message, + cmd.status.code().unwrap_or(-1), + )); + } + + Ok(()) + } + + pub fn stop_service(&self, service: &str) -> Result<(), OpenRCError> { + let cmd = Command::new("pkexec") + .arg("rc-service") + .arg(service) + .arg("stop") + .output()?; + if !cmd.status.success() { + let error_message = + Arc::::from(String::from_utf8_lossy(&cmd.stderr).as_ref().trim()); + return Err(OpenRCError::CommandExecutionError( + error_message, + cmd.status.code().unwrap_or(-1), + )); + } + + Ok(()) + } + + pub fn restart_service(&self, service: &str) -> Result<(), OpenRCError> { + let cmd = Command::new("pkexec") + .arg("rc-service") + .arg(service) + .arg("restart") + .output()?; + if !cmd.status.success() { + let error_message = + Arc::::from(String::from_utf8_lossy(&cmd.stderr).as_ref().trim()); + return Err(OpenRCError::CommandExecutionError( + error_message, + cmd.status.code().unwrap_or(-1), + )); + } + + Ok(()) + } +} diff --git a/src/observatory-daemon/src/platform/linux/openrc/mod.rs b/src/observatory-daemon/src/platform/linux/openrc/mod.rs new file mode 100644 index 0000000..6c8ef2a --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/openrc/mod.rs @@ -0,0 +1,295 @@ +use std::collections::HashMap; +use std::ffi::CStr; +use std::fs::OpenOptions; +use std::sync::Arc; + +use thiserror::Error; + +pub use controller::Controller; +pub use service::*; +use string_list::RC_STRINGLIST; + +use crate::warning; + +mod controller; +mod service; +mod string_list; + +type FnRcRunLevelList = unsafe extern "C" fn() -> *mut RC_STRINGLIST; +type FnRcServicesInRunlevel = unsafe extern "C" fn(*const libc::c_char) -> *mut RC_STRINGLIST; +type FnRcServiceState = unsafe extern "C" fn(*const libc::c_char) -> u32; +type FnRcServiceDescription = + unsafe extern "C" fn(*const libc::c_char, *const libc::c_char) -> *mut libc::c_char; +type FnRcServiceValueGet = + unsafe extern "C" fn(*const libc::c_char, *const libc::c_char) -> *mut libc::c_char; +type FnRCStringListFree = unsafe extern "C" fn(*mut RC_STRINGLIST); + +#[derive(Debug, Error)] +pub enum OpenRCError { + #[error("LibLoading error: {0}")] + LibLoadingError(#[from] libloading::Error), + #[error("Missing runlevels")] + MissingRunLevels, + #[error("Missing services")] + MissingServices, + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), + #[error("Command execution error: {0}. Exited with status code {1}")] + CommandExecutionError(Arc, i32), +} + +pub struct ServiceManager { + fn_rc_runlevel_list: libloading::Symbol<'static, FnRcRunLevelList>, + fn_rc_services_in_runlevel: libloading::Symbol<'static, FnRcServicesInRunlevel>, + fn_rc_service_state: libloading::Symbol<'static, FnRcServiceState>, + fn_rc_service_description: libloading::Symbol<'static, FnRcServiceDescription>, + fn_rc_service_value_get: libloading::Symbol<'static, FnRcServiceValueGet>, + + fn_rc_string_list_free: libloading::Symbol<'static, FnRCStringListFree>, +} + +impl ServiceManager { + pub fn new() -> Result { + let handle = Box::leak(Box::new(unsafe { libloading::Library::new("librc.so.1")? })); + + let fn_rc_runlevel_list = unsafe { handle.get::(b"rc_runlevel_list\0")? }; + + let fn_rc_services_in_runlevel = + unsafe { handle.get::(b"rc_services_in_runlevel\0")? }; + + let fn_rc_service_state = unsafe { handle.get::(b"rc_service_state\0")? }; + + let fn_rc_service_description = + unsafe { handle.get::(b"rc_service_description\0")? }; + + let fn_rc_service_value_get = + unsafe { handle.get::(b"rc_service_value_get\0")? }; + + let fn_rc_string_list_free = + unsafe { handle.get::(b"rc_stringlist_free\0")? }; + + Ok(Self { + fn_rc_runlevel_list, + fn_rc_services_in_runlevel, + fn_rc_service_state, + fn_rc_service_description, + fn_rc_service_value_get, + + fn_rc_string_list_free, + }) + } + + pub fn controller(&self) -> Controller { + Controller + } + + pub fn list_services(&self) -> Result, OpenRCError> { + let runlevels = unsafe { (self.fn_rc_runlevel_list)() }; + if runlevels.is_null() { + warning!( + "Gatherer::OpenRC", + "Empty runlevel list returned from OpenRC." + ); + return Err(OpenRCError::MissingRunLevels); + } + + let mut result = HashMap::new(); + + let empty_string = Arc::::from(""); + let mut buffer = String::new(); + + let runlevel_list = unsafe { &*runlevels }; + let mut current_rl = runlevel_list.tqh_first; + while !current_rl.is_null() { + let rl_rc_string = unsafe { &*current_rl }; + let rl_name = unsafe { CStr::from_ptr(rl_rc_string.value) }.to_string_lossy(); + let rl_name = Arc::::from(rl_name.as_ref()); + + let services_names = unsafe { (self.fn_rc_services_in_runlevel)(rl_rc_string.value) }; + if services_names.is_null() { + warning!( + "Gatherer::OpenRC", + "Empty service list returned for runlevel '{}'.", + rl_name.as_ref() + ); + continue; + } + + let rc_string_list = unsafe { &*services_names }; + let mut current = rc_string_list.tqh_first; + while !current.is_null() { + let rc_string = unsafe { &*current }; + let service_name = unsafe { CStr::from_ptr(rc_string.value) }; + + let description_cstr = + unsafe { (self.fn_rc_service_description)(rc_string.value, std::ptr::null()) }; + let description = if !description_cstr.is_null() { + let description = + Arc::from(unsafe { CStr::from_ptr(description_cstr) }.to_string_lossy()); + unsafe { + libc::free(description_cstr as *mut libc::c_void); + } + description + } else { + empty_string.clone() + }; + + let state = unsafe { (self.fn_rc_service_state)(rc_string.value) }; + + let pidfile = unsafe { + (self.fn_rc_service_value_get)( + rc_string.value, + b"pidfile\0".as_ptr() as *const libc::c_char, + ) + }; + let pid = if !pidfile.is_null() { + let pidfile_str = unsafe { CStr::from_ptr(pidfile) }.to_string_lossy(); + let pid = if let Some(mut pf) = OpenOptions::new() + .read(true) + .open(pidfile_str.as_ref()) + .ok() + { + buffer.clear(); + if let Ok(_) = std::io::Read::read_to_string(&mut pf, &mut buffer) { + if let Ok(pid) = buffer.trim().parse::() { + pid + } else { + 0 + } + } else { + 0 + } + } else { + 0 + }; + + unsafe { + libc::free(pidfile as *mut libc::c_void); + } + + pid + } else { + 0 + }; + + let service_name = Arc::::from(service_name.to_string_lossy().as_ref()); + result.insert( + service_name.clone(), + Service { + name: service_name, + description, + runlevel: rl_name.clone(), + state: unsafe { std::mem::transmute(state & 0xFF) }, + pid, + }, + ); + + current = rc_string.entries.tqe_next; + } + + unsafe { (self.fn_rc_string_list_free)(services_names) }; + + current_rl = rl_rc_string.entries.tqe_next; + } + + unsafe { (self.fn_rc_string_list_free)(runlevels) }; + + let services_names = unsafe { (self.fn_rc_services_in_runlevel)(std::ptr::null()) }; + if !services_names.is_null() { + let rc_string_list = unsafe { &*services_names }; + let mut current = rc_string_list.tqh_first; + while !current.is_null() { + let rc_string = unsafe { &*current }; + let service_name = unsafe { CStr::from_ptr(rc_string.value) }; + + let description_cstr = + unsafe { (self.fn_rc_service_description)(rc_string.value, std::ptr::null()) }; + let description = if !description_cstr.is_null() { + let description = + Arc::from(unsafe { CStr::from_ptr(description_cstr) }.to_string_lossy()); + unsafe { + libc::free(description_cstr as *mut libc::c_void); + } + description + } else { + empty_string.clone() + }; + + let state = unsafe { (self.fn_rc_service_state)(rc_string.value) }; + + let pidfile = unsafe { + (self.fn_rc_service_value_get)( + rc_string.value, + b"pidfile\0".as_ptr() as *const libc::c_char, + ) + }; + let pid = if !pidfile.is_null() { + let pidfile_str = unsafe { CStr::from_ptr(pidfile) }.to_string_lossy(); + let pid = if let Some(mut pf) = OpenOptions::new() + .read(true) + .open(pidfile_str.as_ref()) + .ok() + { + buffer.clear(); + if let Ok(_) = std::io::Read::read_to_string(&mut pf, &mut buffer) { + if let Ok(pid) = buffer.trim().parse::() { + pid + } else { + 0 + } + } else { + 0 + } + } else { + 0 + }; + + unsafe { + libc::free(pidfile as *mut libc::c_void); + } + + pid + } else { + 0 + }; + + let service_name = Arc::::from(service_name.to_string_lossy().as_ref()); + if !result.contains_key(&service_name) { + result.insert( + service_name.clone(), + Service { + name: service_name, + description, + runlevel: empty_string.clone(), + state: unsafe { std::mem::transmute(state & 0xFF) }, + pid, + }, + ); + } + + current = rc_string.entries.tqe_next; + } + + unsafe { (self.fn_rc_string_list_free)(services_names) }; + } + + if result.is_empty() { + return Err(OpenRCError::MissingServices); + } + + Ok(result.into_values().collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rc_services_in_runlevel() { + let openrc = ServiceManager::new().unwrap(); + let services = openrc.list_services().unwrap(); + assert!(!services.is_empty()); + dbg!(services); + } +} diff --git a/src/observatory-daemon/src/platform/linux/openrc/service.rs b/src/observatory-daemon/src/platform/linux/openrc/service.rs new file mode 100644 index 0000000..4aa45c3 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/openrc/service.rs @@ -0,0 +1,83 @@ +use std::num::NonZeroU32; +use std::sync::Arc; + +use crate::platform::platform_impl::openrc; + +#[derive(Debug, Clone, Eq, PartialEq)] +#[repr(C)] +pub enum State { + RcServiceStopped = 0x0001, + RcServiceStarted = 0x0002, + RcServiceStopping = 0x0004, + RcServiceStarting = 0x0008, + RcServiceInactive = 0x0010, + + /* Service may or may not have been hotplugged */ + RcServiceHotplugged = 0x0100, + + /* Optional states service could also be in */ + RcServiceFailed = 0x0200, + RcServiceScheduled = 0x0400, + RcServiceWasinactive = 0x0800, + RcServiceCrashed = 0x1000, +} + +impl From for u32 { + fn from(state: State) -> u32 { + state as u32 + } +} + +#[derive(Debug, Clone)] +pub struct Service { + pub name: Arc, + pub description: Arc, + pub runlevel: Arc, + pub state: State, + pub pid: u32, +} + +impl Service { + #[inline] + pub fn name(&self) -> &str { + self.name.as_ref() + } + + #[inline] + pub fn description(&self) -> &str { + self.description.as_ref() + } + + #[inline] + pub fn enabled(&self) -> bool { + !self.runlevel.as_ref().is_empty() + } + + #[inline] + pub fn running(&self) -> bool { + self.state == openrc::State::RcServiceStarted + } + + #[inline] + pub fn failed(&self) -> bool { + self.state == openrc::State::RcServiceFailed + } + + #[inline] + pub fn pid(&self) -> Option { + match self.pid { + 0 => None, + _ => NonZeroU32::new(self.pid), + } + } + + #[inline] + pub fn user(&self) -> Option<&str> { + Some("root") + } + + #[inline] + pub fn group(&self) -> Option<&str> { + Some("root") + } +} diff --git a/src/observatory-daemon/src/platform/linux/openrc/string_list.rs b/src/observatory-daemon/src/platform/linux/openrc/string_list.rs new file mode 100644 index 0000000..dbe0eff --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/openrc/string_list.rs @@ -0,0 +1,17 @@ +#[repr(C)] +pub struct RC_STRING_QUEUE { + pub tqe_next: *mut RC_STRING, + pub tqe_prev: *mut *mut RC_STRING, +} + +#[repr(C)] +pub struct RC_STRING { + pub value: *mut libc::c_char, + pub entries: RC_STRING_QUEUE, +} + +#[repr(C)] +pub struct RC_STRINGLIST { + pub tqh_first: *mut RC_STRING, + pub tqh_last: *mut *mut RC_STRING, +} diff --git a/src/observatory-daemon/src/platform/linux/processes.rs b/src/observatory-daemon/src/platform/linux/processes.rs new file mode 100644 index 0000000..d4c8d97 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/processes.rs @@ -0,0 +1,628 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/processes.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; +use std::time::Instant; + +use lazy_static::lazy_static; + +use crate::platform::processes::*; + +use super::{HZ, MIN_DELTA_REFRESH}; + +lazy_static! { + static ref PAGE_SIZE: usize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }; +} + +const PROC_PID_STAT_TCOMM: usize = 1; +const PROC_PID_STAT_STATE: usize = 2; +const PROC_PID_STAT_PPID: usize = 3; +const PROC_PID_STAT_UTIME: usize = 13; +const PROC_PID_STAT_STIME: usize = 14; + +#[allow(dead_code)] +const PROC_PID_STATM_VIRT: usize = 0; +const PROC_PID_STATM_RES: usize = 1; + +const PROC_PID_IO_READ_BYTES: usize = 4; +const PROC_PID_IO_WRITE_BYTES: usize = 5; + +#[allow(dead_code)] +const PROC_PID_NET_DEV_RECV_BYTES: usize = 0; +#[allow(dead_code)] +const PROC_PID_NET_DEV_SENT_BYTES: usize = 8; + +const STALE_DELTA: std::time::Duration = std::time::Duration::from_millis(1000); + +#[derive(Debug, Copy, Clone)] +struct RawStats { + pub user_jiffies: u64, + pub kernel_jiffies: u64, + + pub disk_read_bytes: u64, + pub disk_write_bytes: u64, + + pub net_bytes_sent: u64, + pub net_bytes_recv: u64, + + pub timestamp: Instant, +} + +impl Default for RawStats { + fn default() -> Self { + Self { + user_jiffies: 0, + kernel_jiffies: 0, + + disk_read_bytes: 0, + disk_write_bytes: 0, + + net_bytes_sent: 0, + net_bytes_recv: 0, + + timestamp: Instant::now(), + } + } +} + +#[derive(Debug, Clone)] +pub struct LinuxProcess { + name: Arc, + cmd: Vec>, + exe: Arc, + state: ProcessState, + pid: u32, + parent: u32, + // Needs to be pub to be accessible from GPU info + pub usage_stats: ProcessUsageStats, + task_count: usize, + + pub cgroup: Option>, + + raw_stats: RawStats, +} + +impl Default for LinuxProcess { + fn default() -> Self { + Self { + name: "".into(), + cmd: vec![], + exe: "".into(), + state: ProcessState::Unknown, + pid: 0, + parent: 0, + usage_stats: Default::default(), + task_count: 0, + cgroup: None, + raw_stats: Default::default(), + } + } +} + +impl<'a> ProcessExt<'a> for LinuxProcess { + type Iter = std::iter::Map>, fn(&'a Arc) -> &'a str>; + + fn name(&self) -> &str { + self.name.as_ref() + } + + fn cmd(&'a self) -> Self::Iter { + self.cmd.iter().map(|e| e.as_ref()) + } + + fn exe(&self) -> &str { + self.exe.as_ref() + } + + fn state(&self) -> ProcessState { + self.state + } + + fn pid(&self) -> u32 { + self.pid + } + + fn parent(&self) -> u32 { + self.parent + } + + fn usage_stats(&self) -> &ProcessUsageStats { + &self.usage_stats + } + + fn task_count(&self) -> usize { + self.task_count + } +} + +pub struct LinuxProcesses { + process_cache: std::collections::HashMap, + refresh_timestamp: Instant, +} + +impl LinuxProcesses { + pub fn new() -> Self { + Default::default() + } +} + +impl Default for LinuxProcesses { + fn default() -> Self { + Self { + process_cache: std::collections::HashMap::new(), + refresh_timestamp: std::time::Instant::now() + - (STALE_DELTA + std::time::Duration::from_millis(1)), + } + } +} + +impl<'a> ProcessesExt<'a> for LinuxProcesses { + type P = LinuxProcess; + + fn refresh_cache(&mut self) { + use crate::{critical, debug, warning}; + use std::io::Read; + + fn parse_stat_file<'a>(data: &'a str, output: &mut [&'a str; 52]) { + let mut part_index = 0; + + let mut split = data.split('(').filter(|x| !x.is_empty()); + output[part_index] = match split.next() { + Some(x) => x, + None => return, + }; + part_index += 1; + + let mut split = match split.next() { + Some(x) => x.split(')').filter(|x| !x.is_empty()), + None => return, + }; + + output[part_index] = match split.next() { + Some(x) => x, + None => return, + }; + part_index += 1; + + let split = match split.next() { + Some(x) => x, + None => return, + }; + for entry in split.split_whitespace() { + output[part_index] = entry; + part_index += 1; + } + } + + fn parse_statm_file(data: &str, output: &mut [u64; 7]) { + let mut part_index = 0; + + for entry in data.split_whitespace() { + output[part_index] = entry.trim().parse::().unwrap_or(0); + part_index += 1; + } + } + + fn parse_io_file(data: &str, output: &mut [u64; 7]) { + let mut part_index = 0; + + for entry in data.lines() { + let entry = entry.split_whitespace().last().unwrap_or(""); + output[part_index] = entry.trim().parse::().unwrap_or(0); + part_index += 1; + } + } + + fn stat_name(stat: &[&str; 52]) -> Arc { + stat[PROC_PID_STAT_TCOMM].into() + } + + fn stat_state(stat: &[&str; 52]) -> ProcessState { + match stat[PROC_PID_STAT_STATE] { + "R" => ProcessState::Running, + "S" => ProcessState::Sleeping, + "D" => ProcessState::SleepingUninterruptible, + "Z" => ProcessState::Zombie, + "T" => ProcessState::Stopped, + "t" => ProcessState::Tracing, + "X" | "x" => ProcessState::Dead, + "K" => ProcessState::WakeKill, + "W" => ProcessState::Waking, + "P" => ProcessState::Parked, + _ => ProcessState::Unknown, + } + } + + fn stat_parent_pid(stat: &[&str; 52]) -> u32 { + stat[PROC_PID_STAT_PPID].parse::().unwrap_or(0) + } + + fn stat_user_mode_jiffies(stat: &[&str; 52]) -> u64 { + stat[PROC_PID_STAT_UTIME].parse::().unwrap_or(0) + } + + fn stat_kernel_mode_jiffies(stat: &[&str; 52]) -> u64 { + stat[PROC_PID_STAT_STIME].parse::().unwrap_or(0) + } + + let now = Instant::now(); + if now.duration_since(self.refresh_timestamp) < MIN_DELTA_REFRESH { + return; + } + self.refresh_timestamp = now; + + let mut previous = std::mem::take(&mut self.process_cache); + let result = &mut self.process_cache; + result.reserve(previous.len()); + + let mut stat_file_content = String::new(); + stat_file_content.reserve(512); + + let mut read_buffer = String::new(); + read_buffer.reserve(512); + + let proc = match std::fs::read_dir("/proc") { + Ok(proc) => proc, + Err(e) => { + critical!( + "Gatherer::Processes", + "Failed to read /proc directory: {}", + e + ); + return; + } + }; + let proc_entries = proc + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false)); + for entry in proc_entries { + let pid = match entry.file_name().to_string_lossy().parse::() { + Ok(pid) => pid, + Err(_) => continue, + }; + + let entry_path = entry.path(); + + let mut stat_file = match std::fs::OpenOptions::new() + .read(true) + .open(entry_path.join("stat")) + { + Ok(f) => f, + Err(e) => { + critical!( + "Gatherer::Processes", + "Failed to read `stat` file for process {}, skipping: {}", + pid, + e, + ); + continue; + } + }; + stat_file_content.clear(); + match stat_file.read_to_string(&mut stat_file_content) { + Ok(sfc) => { + if sfc == 0 { + critical!( + "Gatherer::Processes", + "Failed to read stat information for process {}, skipping", + pid + ); + continue; + } + } + Err(e) => { + critical!( + "Gatherer::Processes", + "Failed to read stat information for process {}, skipping: {}", + pid, + e, + ); + continue; + } + }; + let mut stat_parsed = [""; 52]; + parse_stat_file(&stat_file_content, &mut stat_parsed); + + let utime = stat_user_mode_jiffies(&stat_parsed); + let stime = stat_kernel_mode_jiffies(&stat_parsed); + + let mut io_parsed = [0; 7]; + match std::fs::OpenOptions::new() + .read(true) + .open(entry_path.join("io")) + { + Ok(mut f) => { + read_buffer.clear(); + match f.read_to_string(&mut read_buffer) { + Ok(_) => { + parse_io_file(&read_buffer, &mut io_parsed); + } + _ => {} + } + } + Err(e) => { + debug!( + "Gatherer::Processes", + "Failed to read `io` file for process {}: {}", pid, e, + ); + } + }; + + let total_net_sent = 0_u64; + let total_net_recv = 0_u64; + + let mut process = match previous.remove(&pid) { + None => LinuxProcess::default(), + Some(mut process) => { + let delta_time = now - process.raw_stats.timestamp; + + let prev_utime = process.raw_stats.user_jiffies; + let prev_stime = process.raw_stats.kernel_jiffies; + + let delta_utime = + ((utime.saturating_sub(prev_utime) as f32) * 1000.) / *HZ as f32; + let delta_stime = + ((stime.saturating_sub(prev_stime) as f32) * 1000.) / *HZ as f32; + + process.usage_stats.cpu_usage = + (((delta_utime + delta_stime) / delta_time.as_millis() as f32) * 100.) + .min((*super::CPU_COUNT as f32) * 100.); + + let prev_read_bytes = process.raw_stats.disk_read_bytes; + let prev_write_bytes = process.raw_stats.disk_write_bytes; + + let read_speed = + io_parsed[PROC_PID_IO_READ_BYTES].saturating_sub(prev_read_bytes) as f32 + / delta_time.as_secs_f32(); + let write_speed = + io_parsed[PROC_PID_IO_WRITE_BYTES].saturating_sub(prev_write_bytes) as f32 + / delta_time.as_secs_f32(); + process.usage_stats.disk_usage = (read_speed + write_speed) / 2.; + + process + } + }; + + let cmd = match std::fs::OpenOptions::new() + .read(true) + .open(entry_path.join("cmdline")) + { + Ok(mut f) => { + read_buffer.clear(); + match f.read_to_string(&mut read_buffer) { + Ok(_) => read_buffer + .split('\0') + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .map(|s| Arc::::from(s)) + .collect::>(), + Err(e) => { + warning!( + "Gatherer::Processes", + "Failed to parse commandline for {}: {}", + pid, + e + ); + vec![] + } + } + } + Err(e) => { + warning!( + "Gatherer::Processes", + "Failed to read `cmdline` file for process {}: {}", + pid, + e, + ); + vec![] + } + }; + + let output = entry_path.join("exe").read_link(); + let exe = output + .map(|p| p.as_os_str().to_string_lossy().into()) + .unwrap_or("".into()); + + let mut statm_parsed = [0; 7]; + match std::fs::OpenOptions::new() + .read(true) + .open(entry_path.join("statm")) + { + Ok(mut f) => { + read_buffer.clear(); + match f.read_to_string(&mut read_buffer) { + Ok(_) => { + parse_statm_file(&read_buffer, &mut statm_parsed); + } + Err(e) => { + warning!( + "Gatherer::Processes", + "Failed to read memory information for {}: {}", + pid, + e + ); + } + }; + } + Err(e) => { + warning!( + "Gatherer::Processes", + "Failed to read `statm` file for process {}: {}", + pid, + e, + ); + } + }; + + let cgroup = match std::fs::OpenOptions::new() + .read(true) + .open(entry_path.join("cgroup")) + { + Ok(mut f) => { + read_buffer.clear(); + match f.read_to_string(&mut read_buffer) { + Ok(bytes_read) => { + if bytes_read == 0 { + warning!( + "Gatherer::Processes", + "Failed to read cgroup information for process {}: No cgroup associated with process", + pid + ); + None + } else { + let mut cgroup = None; + + let cfc = read_buffer + .trim() + .split(':') + .nth(2) + .unwrap_or("/") + .trim_start_matches('/') + .trim_end_matches(&format!("/{}", pid)); + + let cgroup_path = std::path::Path::new("/sys/fs/cgroup").join(cfc); + if !cfc.is_empty() && cgroup_path.exists() && cgroup_path.is_dir() { + let app_scope = cfc.split('/').last().unwrap_or(""); + if (app_scope.starts_with("app") + || app_scope.starts_with("snap")) + && app_scope.ends_with(".scope") + { + cgroup = Some(cgroup_path.to_string_lossy().into()); + } + } + + cgroup + } + } + Err(e) => { + warning!( + "Gatherer::Processes", + "Failed to read cgroup information for process {}: {}", + pid, + e + ); + None + } + } + } + Err(e) => { + warning!( + "Gatherer::Processes", + "Failed to read `cgroup` file for process {}: {}", + pid, + e, + ); + None + } + }; + + let mut task_count = 0_usize; + match std::fs::read_dir(entry_path.join("task")) { + Ok(tasks) => { + for task in tasks.filter_map(|t| t.ok()) { + match task.file_name().to_string_lossy().parse::() { + Err(_) => continue, + _ => {} + }; + task_count += 1; + } + } + Err(e) => { + warning!( + "Gatherer::Processes", + "Gatherer: Failed to read task directory for process {}: {}", + pid, + e + ); + } + } + + process.pid = pid; + process.name = stat_name(&stat_parsed); + process.cmd = cmd; + process.exe = exe; + process.state = stat_state(&stat_parsed); + process.parent = stat_parent_pid(&stat_parsed); + process.usage_stats.memory_usage = + (statm_parsed[PROC_PID_STATM_RES] * (*PAGE_SIZE) as u64) as f32; + process.task_count = task_count; + process.raw_stats.user_jiffies = utime; + process.raw_stats.kernel_jiffies = stime; + process.raw_stats.disk_read_bytes = io_parsed[PROC_PID_IO_READ_BYTES]; + process.raw_stats.disk_write_bytes = io_parsed[PROC_PID_IO_WRITE_BYTES]; + process.raw_stats.net_bytes_sent = total_net_sent; + process.raw_stats.net_bytes_recv = total_net_recv; + process.raw_stats.timestamp = now; + process.cgroup = cgroup; + + result.insert(pid, process); + } + + self.refresh_timestamp = Instant::now(); + } + + fn process_list(&'a self) -> &'a std::collections::HashMap { + &self.process_cache + } + + fn process_list_mut(&mut self) -> &mut std::collections::HashMap { + &mut self.process_cache + } + + fn terminate_process(&self, pid: u32) { + use libc::*; + + unsafe { + kill(pid as pid_t, SIGTERM); + } + } + + fn kill_process(&self, pid: u32) { + use libc::*; + + unsafe { + kill(pid as pid_t, SIGKILL); + } + } +} + +#[cfg(test)] +mod test { + use crate::platform::ProcessesExt; + + use super::*; + + #[test] + fn test_refresh_cache() { + let mut p = LinuxProcesses::new(); + assert!(p.process_cache.is_empty()); + + p.refresh_cache(); + assert!(!p.process_cache.is_empty()); + + let sample = p + .process_cache + .iter() + .filter(|(_pid, proc)| proc.raw_stats.user_jiffies > 0) + .map(|p| p.1) + .take(10); + dbg!(&sample); + } +} diff --git a/src/observatory-daemon/src/platform/linux/services/mod.rs b/src/observatory-daemon/src/platform/linux/services/mod.rs new file mode 100644 index 0000000..a13cb05 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/services/mod.rs @@ -0,0 +1,318 @@ +use std::{ + fmt::{Display, Formatter}, + num::NonZeroU32, + sync::Arc, + time::Duration, +}; + +use dbus::blocking::{stdintf::org_freedesktop_dbus::Peer, LocalConnection}; + +use crate::{ + logging::error, + platform::{ServiceControllerExt, ServiceExt, ServicesExt}, +}; + +mod openrc; +mod systemd; + +#[derive(Debug)] +pub enum LinuxServicesError { + UnsupportedServiceManager, + DBusError(dbus::Error), + TypeMismatchError(dbus::arg::TypeMismatchError), + LibLoadingError(libloading::Error), + MissingRunLevels, + MissingServices, + IoError(std::io::Error), + JournalError(Arc), + CommandExecutionError(Arc, i32), + MissingServiceController, +} + +impl Display for LinuxServicesError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + LinuxServicesError::UnsupportedServiceManager => { + write!(f, "Unsupported service manager") + } + LinuxServicesError::DBusError(e) => { + write!(f, "DBus error: {}", e) + } + LinuxServicesError::TypeMismatchError(e) => { + write!(f, "Type mismatch error: {}", e) + } + LinuxServicesError::LibLoadingError(e) => { + write!(f, "Library loading error: {}", e) + } + LinuxServicesError::MissingRunLevels => { + write!(f, "Missing run levels") + } + LinuxServicesError::MissingServices => { + write!(f, "Missing services") + } + LinuxServicesError::IoError(e) => { + write!(f, "IO error: {}", e) + } + LinuxServicesError::CommandExecutionError(stderr, exit_code) => { + write!( + f, + "Command execution error: {} (exit code: {})", + stderr, exit_code + ) + } + LinuxServicesError::MissingServiceController => { + write!(f, "Missing service controller") + } + LinuxServicesError::JournalError(e) => { + write!(f, "Journal error: {}", e) + } + } + } +} + +impl From for LinuxServicesError { + fn from(value: systemd::SystemDError) -> Self { + match value { + systemd::SystemDError::DBusError(e) => Self::DBusError(e), + systemd::SystemDError::TypeMismatchError(e) => Self::TypeMismatchError(e), + systemd::SystemDError::LibLoadingError(e) => Self::LibLoadingError(e), + systemd::SystemDError::IoError(e) => Self::IoError(e), + systemd::SystemDError::JournalOpenError(e) => Self::JournalError(e), + systemd::SystemDError::JournalSeekError(e) => Self::JournalError(e), + systemd::SystemDError::JournalAddMatchError(e) => Self::JournalError(e), + systemd::SystemDError::JournalAddDisjunctionError(e) => Self::JournalError(e), + systemd::SystemDError::JournalAddConjunctionError(e) => Self::JournalError(e), + systemd::SystemDError::JournalIterateError(e) => Self::JournalError(e), + } + } +} + +impl From for LinuxServicesError { + fn from(value: openrc::OpenRCError) -> Self { + match value { + openrc::OpenRCError::LibLoadingError(e) => Self::LibLoadingError(e), + openrc::OpenRCError::MissingRunLevels => Self::MissingRunLevels, + openrc::OpenRCError::MissingServices => Self::MissingServices, + openrc::OpenRCError::IoError(e) => Self::IoError(e), + openrc::OpenRCError::CommandExecutionError(err, exit_code) => { + Self::CommandExecutionError(err, exit_code) + } + } + } +} + +#[derive(Debug)] +pub enum LinuxService { + SystemD(systemd::Service), + OpenRC(openrc::Service), +} + +pub enum LinuxServiceController<'a> { + SystemD(systemd::SystemDController<'a>), + OpenRC(openrc::OpenRCController), +} + +pub enum LinuxServices<'a> { + Unimplemented, + SystemD(systemd::SystemD<'a>), + OpenRC(openrc::OpenRC), +} + +impl ServiceExt for LinuxService { + fn name(&self) -> &str { + match self { + LinuxService::SystemD(s) => s.name(), + LinuxService::OpenRC(s) => s.name(), + } + } + + fn description(&self) -> &str { + match self { + LinuxService::SystemD(s) => s.description(), + LinuxService::OpenRC(s) => s.description(), + } + } + + fn enabled(&self) -> bool { + match self { + LinuxService::SystemD(s) => s.enabled(), + LinuxService::OpenRC(s) => s.enabled(), + } + } + + fn running(&self) -> bool { + match self { + LinuxService::SystemD(s) => s.running(), + LinuxService::OpenRC(s) => s.running(), + } + } + + fn failed(&self) -> bool { + match self { + LinuxService::SystemD(s) => s.failed(), + LinuxService::OpenRC(s) => s.failed(), + } + } + + fn pid(&self) -> Option { + match self { + LinuxService::SystemD(s) => s.pid(), + LinuxService::OpenRC(s) => s.pid(), + } + } + + fn user(&self) -> Option<&str> { + match self { + LinuxService::SystemD(s) => s.user(), + LinuxService::OpenRC(s) => s.user(), + } + } + + fn group(&self) -> Option<&str> { + match self { + LinuxService::SystemD(s) => s.group(), + LinuxService::OpenRC(s) => s.group(), + } + } +} + +impl LinuxServices<'_> { + pub fn new() -> Self { + fn systemd_available() -> bool { + // If we're a Snap we can't access the Ping method on the org.freedesktop.DBus interface + // so fall back to checking if a standard SystemD specific path exists + if let Some(_) = std::env::var_os("SNAP_CONTEXT") { + return std::path::Path::new("/lib/systemd/systemd").exists(); + } + + let connection = match LocalConnection::new_system() { + Ok(c) => c, + Err(_) => { + return false; + } + }; + + let systemd1 = connection.with_proxy( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + Duration::from_millis(30_000), + ); + + systemd1.ping().is_ok() + } + + let librc_exists = std::path::Path::new("/lib/librc.so.1").exists() + || std::path::Path::new("/lib64/librc.so.1").exists(); + + if librc_exists && std::path::Path::new("/sbin/rc-service").exists() { + match openrc::OpenRC::new() { + Ok(openrc) => LinuxServices::OpenRC(openrc), + Err(e) => { + error!( + "Gatherer::ServiceManager", + "Failed to initialize OpenRC: {}", e + ); + LinuxServices::Unimplemented + } + } + } else if systemd_available() { + match systemd::SystemD::new() { + Ok(systemd) => LinuxServices::SystemD(systemd), + Err(e) => { + error!( + "Gatherer::ServiceManager", + "Failed to initialize SystemD: {}", e + ); + LinuxServices::Unimplemented + } + } + } else { + LinuxServices::Unimplemented + } + } +} + +impl<'a> ServiceControllerExt for LinuxServiceController<'a> { + type E = LinuxServicesError; + + fn enable_service(&self, name: &str) -> Result<(), LinuxServicesError> { + match self { + LinuxServiceController::SystemD(s) => s.enable_service(name).map_err(|e| e.into()), + LinuxServiceController::OpenRC(s) => s.enable_service(name).map_err(|e| e.into()), + } + } + + fn disable_service(&self, name: &str) -> Result<(), LinuxServicesError> { + match self { + LinuxServiceController::SystemD(s) => s.disable_service(name).map_err(|e| e.into()), + LinuxServiceController::OpenRC(s) => s.disable_service(name).map_err(|e| e.into()), + } + } + + fn start_service(&self, name: &str) -> Result<(), LinuxServicesError> { + match self { + LinuxServiceController::SystemD(s) => s.start_service(name).map_err(|e| e.into()), + LinuxServiceController::OpenRC(s) => s.start_service(name).map_err(|e| e.into()), + } + } + + fn stop_service(&self, name: &str) -> Result<(), LinuxServicesError> { + match self { + LinuxServiceController::SystemD(s) => s.stop_service(name).map_err(|e| e.into()), + LinuxServiceController::OpenRC(s) => s.stop_service(name).map_err(|e| e.into()), + } + } + + fn restart_service(&self, name: &str) -> Result<(), LinuxServicesError> { + match self { + LinuxServiceController::SystemD(s) => s.restart_service(name).map_err(|e| e.into()), + LinuxServiceController::OpenRC(s) => s.restart_service(name).map_err(|e| e.into()), + } + } +} + +impl<'a> ServicesExt<'a> for LinuxServices<'a> { + type S = LinuxService; + type C = LinuxServiceController<'a>; + type E = LinuxServicesError; + + fn refresh_cache(&mut self) -> Result<(), LinuxServicesError> { + match self { + LinuxServices::Unimplemented => Err(LinuxServicesError::UnsupportedServiceManager), + LinuxServices::SystemD(s) => s.refresh_cache().map_err(|e| e.into()), + LinuxServices::OpenRC(s) => s.refresh_cache().map_err(|e| e.into()), + } + } + + fn services(&'a self) -> Result, LinuxServicesError> { + match self { + LinuxServices::Unimplemented => Err(LinuxServicesError::UnsupportedServiceManager), + LinuxServices::SystemD(s) => Ok(s + .services()? + .iter() + .map(|s| LinuxService::SystemD(s.clone())) + .collect()), + LinuxServices::OpenRC(s) => Ok(s + .services()? + .iter() + .map(|s| LinuxService::OpenRC(s.clone())) + .collect()), + } + } + + fn controller(&self) -> Result, LinuxServicesError> { + match self { + LinuxServices::Unimplemented => Err(LinuxServicesError::UnsupportedServiceManager), + LinuxServices::SystemD(s) => Ok(LinuxServiceController::SystemD(s.controller()?)), + LinuxServices::OpenRC(s) => Ok(LinuxServiceController::OpenRC(s.controller()?)), + } + } + + fn service_logs(&self, name: &str, pid: Option) -> Result, Self::E> { + match self { + LinuxServices::Unimplemented => Err(LinuxServicesError::UnsupportedServiceManager), + LinuxServices::SystemD(s) => s.service_logs(name, pid).map_err(|e| e.into()), + LinuxServices::OpenRC(_) => Ok(Arc::::from("")), + } + } +} diff --git a/src/observatory-daemon/src/platform/linux/services/openrc.rs b/src/observatory-daemon/src/platform/linux/services/openrc.rs new file mode 100644 index 0000000..ccaa2c7 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/services/openrc.rs @@ -0,0 +1,134 @@ +pub use openrc::{OpenRCError, Service}; + +use crate::logging::error; + +use super::super::openrc; + +pub struct OpenRC { + manager: openrc::ServiceManager, + services: Vec, +} + +impl OpenRC { + pub fn new() -> Result { + Ok(OpenRC { + manager: openrc::ServiceManager::new()?, + services: Vec::new(), + }) + } +} + +pub type OpenRCController = openrc::Controller; + +impl<'a> OpenRC { + #[inline] + pub fn refresh_cache(&mut self) -> Result<(), OpenRCError> { + self.services = match self.manager.list_services() { + Ok(services) => services, + Err(e) => { + error!("Gatherer::OpenRC", "Failed to list services: {}", &e); + return Err(e); + } + }; + + Ok(()) + } + + #[inline] + pub fn services(&'a self) -> Result, OpenRCError> { + Ok(self.services.clone()) + } + + pub fn controller(&self) -> Result { + Ok(self.manager.controller()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rc_disable_enable_service() -> Result<(), OpenRCError> { + let openrc = openrc::ServiceManager::new().unwrap(); + let controller = openrc.controller(); + + let services = openrc.list_services().unwrap(); + assert!(!services.is_empty()); + + let service = services.iter().find(|s| s.name.as_ref() == "sshd").unwrap(); + + match controller.disable_service(service.name()) { + Ok(_) => { + println!("Service disabled successfully."); + } + Err(e) => { + return Err(e); + } + } + + match controller.enable_service(service.name()) { + Ok(_) => { + println!("Service enabled successfully."); + } + Err(e) => { + return Err(e); + } + } + + Ok(()) + } + + #[test] + fn test_rc_stop_start_service() -> Result<(), OpenRCError> { + let openrc = openrc::ServiceManager::new().unwrap(); + let controller = openrc.controller(); + + let services = openrc.list_services().unwrap(); + assert!(!services.is_empty()); + + let service = services.iter().find(|s| s.name.as_ref() == "sshd").unwrap(); + + match controller.stop_service(service.name()) { + Ok(_) => { + println!("Service stopped successfully."); + } + Err(e) => { + return Err(e); + } + } + + match controller.start_service(service.name()) { + Ok(_) => { + println!("Service started successfully."); + } + Err(e) => { + return Err(e); + } + } + + Ok(()) + } + + #[test] + fn test_rc_restart_service() -> Result<(), OpenRCError> { + let openrc = openrc::ServiceManager::new().unwrap(); + let controller = openrc.controller(); + + let services = openrc.list_services().unwrap(); + assert!(!services.is_empty()); + + let service = services.iter().find(|s| s.name.as_ref() == "sshd").unwrap(); + + match controller.restart_service(service.name()) { + Ok(_) => { + println!("Service restarted successfully."); + } + Err(e) => { + return Err(e); + } + } + + Ok(()) + } +} diff --git a/src/observatory-daemon/src/platform/linux/services/systemd.rs b/src/observatory-daemon/src/platform/linux/services/systemd.rs new file mode 100644 index 0000000..ad53f65 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/services/systemd.rs @@ -0,0 +1,56 @@ +use std::num::NonZeroU32; +use std::sync::Arc; + +pub use systemd::{Service, SystemDError}; + +use crate::logging::error; + +use super::super::systemd; + +pub struct SystemD<'a> { + manager: systemd::ServiceManager<'a>, + services: Vec, +} + +impl<'a> SystemD<'a> { + pub fn new() -> Result { + Ok(SystemD { + manager: systemd::ServiceManager::new()?, + services: Vec::new(), + }) + } +} + +pub type SystemDController<'a> = systemd::Controller<'a>; + +impl<'a> SystemD<'a> { + #[inline] + pub fn refresh_cache(&mut self) -> Result<(), SystemDError> { + self.services = match self.manager.list_services() { + Ok(services) => services, + Err(e) => { + error!("Gatherer::SystemD", "Failed to list services: {}", &e); + return Err(e); + } + }; + + Ok(()) + } + + #[inline] + pub fn services(&'a self) -> Result, SystemDError> { + Ok(self.services.clone()) + } + + pub fn controller(&self) -> Result, SystemDError> { + self.manager.controller() + } + + pub fn service_logs( + &self, + name: &str, + pid: Option, + ) -> Result, SystemDError> { + self.manager.service_logs(name, pid) + } +} diff --git a/src/observatory-daemon/src/platform/linux/systemd/controller.rs b/src/observatory-daemon/src/platform/linux/systemd/controller.rs new file mode 100644 index 0000000..ac18241 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/systemd/controller.rs @@ -0,0 +1,218 @@ +use std::sync::Arc; +use std::time::Duration; + +use dbus::blocking::{Proxy, SyncConnection}; + +use crate::logging::error; + +use super::{dbus_call_forget, dbus_call_wait_reply, SystemDError}; + +pub struct Controller<'a> { + _connection: Arc, + proxy: Proxy<'a, &'a SyncConnection>, +} + +impl<'a> Controller<'a> { + pub fn new(connection: Arc, proxy: Proxy<'a, &'a SyncConnection>) -> Self { + Self { + _connection: connection, + proxy, + } + } +} + +impl<'a> Controller<'a> { + pub fn enable_service<'i, 'm>(&self, service: &str) -> Result<(), SystemDError> { + rayon::spawn({ + let service = service.to_string(); + move || { + let Ok(connection) = SyncConnection::new_system() else { + error!( + "Gatherer::SystemD", + "Failed to create connection to system bus" + ); + return; + }; + + let proxy = connection.with_proxy( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + Duration::from_millis(30_000), + ); + + let r: Result<(bool, Vec<(String, String, String)>), dbus::Error> = + dbus_call_wait_reply( + &proxy, + "org.freedesktop.systemd1.Manager", + "EnableUnitFiles", + (vec![service.as_str()], false, true), + ); + if let Err(e) = r { + error!("Gatherer::SystemD", "Failed to enable service: {}", e); + return; + } + + let r: Result<(), dbus::Error> = + dbus_call_wait_reply(&proxy, "org.freedesktop.systemd1.Manager", "Reload", ()); + if let Err(e) = r { + error!( + "Gatherer::SystemD", + "Failed to reload Systemd daemon: {}", e + ); + return; + } + } + }); + + Ok(()) + } + + pub fn disable_service<'i, 'm>(&self, service: &str) -> Result<(), SystemDError> { + rayon::spawn({ + let service = service.to_string(); + move || { + let Ok(connection) = SyncConnection::new_system() else { + error!( + "Gatherer::SystemD", + "Failed to create connection to system bus" + ); + return; + }; + + let proxy = connection.with_proxy( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + Duration::from_millis(30_000), + ); + + let r: Result<(Vec<(String, String, String)>,), dbus::Error> = dbus_call_wait_reply( + &proxy, + "org.freedesktop.systemd1.Manager", + "DisableUnitFiles", + (vec![service], false), + ); + if let Err(e) = r { + error!("Gatherer::SystemD", "Failed to disable service: {}", e); + return; + } + + let r: Result<(), dbus::Error> = + dbus_call_wait_reply(&proxy, "org.freedesktop.systemd1.Manager", "Reload", ()); + if let Err(e) = r { + error!( + "Gatherer::SystemD", + "Failed to reload Systemd daemon: {}", e + ); + return; + } + } + }); + + Ok(()) + } + + pub fn start_service(&self, service: &str) -> Result<(), SystemDError> { + dbus_call_forget( + &self.proxy, + "org.freedesktop.systemd1.Manager", + "StartUnit", + (service, "fail"), + )?; + + Ok(()) + } + + pub fn stop_service(&self, service: &str) -> Result<(), SystemDError> { + dbus_call_forget( + &self.proxy, + "org.freedesktop.systemd1.Manager", + "StopUnit", + (service, "replace"), + )?; + + Ok(()) + } + + pub fn restart_service(&self, service: &str) -> Result<(), SystemDError> { + dbus_call_forget( + &self.proxy, + "org.freedesktop.systemd1.Manager", + "RestartUnit", + (service, "fail"), + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::super::ServiceManager; + use super::*; + + #[test] + fn test_disable_enable_service() -> Result<(), anyhow::Error> { + let svc_mgr = ServiceManager::new()?; + let controller = svc_mgr.controller()?; + let services = svc_mgr.list_services()?; + assert!(!services.is_empty()); + + let service = services + .iter() + .find(|s| s.name.as_ref() == "NetworkManager.service") + .unwrap(); + + eprintln!("{:?}", std::env::args()); + + let nm_service_path = std::path::Path::new( + "/etc/systemd/system/multi-user.target.wants/NetworkManager.service", + ); + + controller.disable_service(service.name())?; + assert!(!nm_service_path.exists()); + + controller.enable_service(service.name())?; + std::thread::sleep(Duration::from_secs(1)); + assert!(nm_service_path.exists()); + + Ok(()) + } + + #[test] + fn test_stop_start_service() -> Result<(), anyhow::Error> { + let svc_mgr = ServiceManager::new()?; + let controller = svc_mgr.controller()?; + let services = svc_mgr.list_services()?; + assert!(!services.is_empty()); + + let service = services + .iter() + .find(|s| s.name.as_ref() == "NetworkManager.service") + .unwrap(); + + controller.stop_service(service.name())?; + unsafe { + libc::sleep(5); + } + controller.start_service(service.name())?; + + Ok(()) + } + + #[test] + fn test_restart_service() -> Result<(), anyhow::Error> { + let svc_mgr = ServiceManager::new()?; + let controller = svc_mgr.controller()?; + let services = svc_mgr.list_services()?; + assert!(!services.is_empty()); + + let service = services + .iter() + .find(|s| s.name.as_ref() == "NetworkManager.service") + .unwrap(); + + controller.restart_service(service.name())?; + + Ok(()) + } +} diff --git a/src/observatory-daemon/src/platform/linux/systemd/mod.rs b/src/observatory-daemon/src/platform/linux/systemd/mod.rs new file mode 100644 index 0000000..95a665b --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/systemd/mod.rs @@ -0,0 +1,497 @@ +use std::num::NonZeroU32; +use std::{ + mem::{align_of, size_of}, + ops::Deref, + slice::from_raw_parts, + str::from_utf8_unchecked, + sync::Arc, + time::Duration, +}; + +use dbus::{ + arg::{AppendAll, IterAppend, ReadAll, RefArg}, + blocking::{stdintf::org_freedesktop_dbus::Properties, BlockingSender, Proxy, SyncConnection}, + channel::Sender, + strings::{Interface, Member}, + Error, Message, +}; +use libloading::Symbol; +use static_assertions::const_assert; +use thiserror::Error; + +pub use controller::Controller; +use service::ServiceVec; +pub use service::*; + +use crate::error; + +mod controller; +mod service; + +const_assert!(size_of::() == size_of::<*mut ()>()); +const_assert!(align_of::() == align_of::<*mut ()>()); + +#[allow(dead_code)] +mod ffi { + #[repr(C)] + pub struct DBusMessage { + _private: [u8; 0], + } + + #[repr(C)] + #[allow(non_camel_case_types)] + pub struct sd_journal { + _unused: [u8; 0], + } + + pub type FnSdJournalOpen = + unsafe extern "C" fn(ret: *mut *mut sd_journal, flags: i32) -> libc::c_int; + pub type FnSdJournalClose = unsafe extern "C" fn(j: *mut sd_journal); + + pub type FnSdJournalAddMatch = unsafe extern "C" fn( + j: *mut sd_journal, + match_: *const libc::c_void, + size: libc::size_t, + ) -> libc::c_int; + pub type FnSdJournalAddDisjunction = unsafe extern "C" fn(j: *mut sd_journal) -> libc::c_int; + pub type FnSdJournalAddConjunction = unsafe extern "C" fn(j: *mut sd_journal) -> libc::c_int; + + pub type FnSdJournalSeekTail = unsafe extern "C" fn(j: *mut sd_journal) -> libc::c_int; + pub type FnSdJournalPrevious = unsafe extern "C" fn(j: *mut sd_journal) -> libc::c_int; + + pub type FnSdJournalGetData = unsafe extern "C" fn( + j: *mut sd_journal, + field: *const libc::c_char, + data: *mut *const libc::c_void, + length: *mut libc::size_t, + ) -> libc::c_int; + + pub const SD_JOURNAL_LOCAL_ONLY: i32 = 1 << 0; + pub const SD_JOURNAL_RUNTIME_ONLY: i32 = 1 << 1; + pub const SD_JOURNAL_SYSTEM: i32 = 1 << 2; + pub const SD_JOURNAL_CURRENT_USER: i32 = 1 << 3; + pub const SD_JOURNAL_OS_ROOT: i32 = 1 << 4; + pub const SD_JOURNAL_ALL_NAMESPACES: i32 = 1 << 5; + pub const SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE: i32 = 1 << 6; + pub const SD_JOURNAL_TAKE_DIRECTORY_FD: i32 = 1 << 7; + pub const SD_JOURNAL_ASSUME_IMMUTABLE: i32 = 1 << 8; + + pub const SD_JOURNAL_NOP: i32 = 0; + pub const SD_JOURNAL_APPEND: i32 = 1; + pub const SD_JOURNAL_INVALIDATE: i32 = 2; + + extern "C" { + pub fn dbus_message_set_allow_interactive_authorization(msg: *mut DBusMessage, allow: u32); + } +} + +const MAX_LOG_MESSAGE_COUNT: usize = 5; + +pub fn dbus_call_wait_reply< + 'a, + 'i, + 'm, + R: ReadAll, + A: AppendAll, + I: Into>, + M: Into>, +>( + proxy: &Proxy<'a, &'a SyncConnection>, + i: I, + m: M, + args: A, +) -> Result { + let mut msg = Message::method_call(&proxy.destination, &proxy.path, &i.into(), &m.into()); + unsafe { + ffi::dbus_message_set_allow_interactive_authorization(std::mem::transmute_copy(&msg), 1); + } + args.append(&mut IterAppend::new(&mut msg)); + let r = proxy + .connection + .send_with_reply_and_block(msg, proxy.timeout)?; + Ok(R::read(&mut r.iter_init())?) +} + +fn dbus_call_forget( + proxy: &Proxy<&SyncConnection>, + i: &str, + m: &str, + args: A, +) -> Result<(), Error> { + let mut msg = Message::new_method_call(&proxy.destination, &proxy.path, i, m) + .map_err(|e| Error::new_failed(e.as_str()))?; + args.append(&mut IterAppend::new(&mut msg)); + unsafe { + ffi::dbus_message_set_allow_interactive_authorization(std::mem::transmute_copy(&msg), 1); + } + + proxy + .connection + .send(msg) + .map_err(|_| Error::new_failed(&format!("Failed to send message `{m}`")))?; + + Ok(()) +} + +#[derive(Debug, Error)] +pub enum SystemDError { + #[error("DBus error: {0}")] + DBusError(#[from] dbus::Error), + #[error("DBus error: {0}")] + TypeMismatchError(#[from] dbus::arg::TypeMismatchError), + #[error("Library loading error: {0}")] + LibLoadingError(#[from] libloading::Error), + #[error("IO error: {0}")] + IoError(#[from] std::io::Error), + #[error("Failed to open journal: {0}")] + JournalOpenError(Arc), + #[error("Seek failed: {0}")] + JournalSeekError(Arc), + #[error("Failed to add match: {0}")] + JournalAddMatchError(Arc), + #[error("Failed to add disjunction: {0}")] + JournalAddDisjunctionError(Arc), + #[error("Failed to add conjunction: {0}")] + JournalAddConjunctionError(Arc), + #[error("Failed to iterate journal entries: {0}")] + JournalIterateError(Arc), +} + +pub struct ServiceManager<'a> { + connection: Arc, + systemd1: Proxy<'a, &'a SyncConnection>, + + fn_sd_journal_open: Symbol<'static, ffi::FnSdJournalOpen>, + fn_sd_journal_close: Symbol<'static, ffi::FnSdJournalClose>, + fn_sd_journal_seek_tail: Symbol<'static, ffi::FnSdJournalSeekTail>, + fn_sd_journal_add_match: Symbol<'static, ffi::FnSdJournalAddMatch>, + fn_sd_journal_add_disjunction: Symbol<'static, ffi::FnSdJournalAddDisjunction>, + fn_sd_journal_add_conjunction: Symbol<'static, ffi::FnSdJournalAddConjunction>, + fn_sd_journal_previous: Symbol<'static, ffi::FnSdJournalPrevious>, + fn_sd_journal_get_data: Symbol<'static, ffi::FnSdJournalGetData>, + + boot_id: Arc, +} + +impl<'a> ServiceManager<'a> { + pub fn new() -> Result { + let connection = Arc::new(SyncConnection::new_system()?); + let connection_ptr = connection.deref() as *const SyncConnection; + + let systemd1 = unsafe { + (&*connection_ptr).with_proxy( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + Duration::from_millis(30_000), + ) + }; + + let handle = Box::leak(Box::new(unsafe { + libloading::Library::new("libsystemd.so.0")? + })); + + let fn_sd_journal_open = + unsafe { handle.get::(b"sd_journal_open\0")? }; + let fn_sd_journal_close = + unsafe { handle.get::(b"sd_journal_close\0")? }; + let fn_sd_journal_seek_tail = + unsafe { handle.get::(b"sd_journal_seek_tail\0")? }; + let fn_sd_journal_add_match = + unsafe { handle.get::(b"sd_journal_add_match\0")? }; + let fn_sd_journal_add_disjunction = unsafe { + handle.get::(b"sd_journal_add_disjunction\0")? + }; + let fn_sd_journal_add_conjunction = unsafe { + handle.get::(b"sd_journal_add_conjunction\0")? + }; + let fn_sd_journal_previous = + unsafe { handle.get::(b"sd_journal_previous\0")? }; + let fn_sd_journal_get_data = + unsafe { handle.get::(b"sd_journal_get_data\0")? }; + + let boot_id = Arc::::from( + std::fs::read_to_string("/proc/sys/kernel/random/boot_id")? + .trim() + .replace("-", ""), + ); + + Ok(Self { + connection, + systemd1, + + fn_sd_journal_open, + fn_sd_journal_close, + fn_sd_journal_seek_tail, + fn_sd_journal_add_match, + fn_sd_journal_add_conjunction, + fn_sd_journal_add_disjunction, + fn_sd_journal_previous, + fn_sd_journal_get_data, + + boot_id, + }) + } + + pub fn controller(&self) -> Result, SystemDError> { + Ok(Controller::new( + self.connection.clone(), + self.systemd1.clone(), + )) + } + + pub fn list_services(&self) -> Result, SystemDError> { + let (mut services,): (ServiceVec,) = dbus_call_wait_reply( + &self.systemd1, + "org.freedesktop.systemd1.Manager", + "ListUnits", + (), + )?; + + let mut services = services + .0 + .drain(..) + .filter(|s| s.load_state != LoadState::NotFound) + .collect::>(); + + for service in &mut services { + service.properties = self.service_properties(&service.unit_path)?; + } + + Ok(services) + } + + pub fn service_logs( + &self, + name: &str, + pid: Option, + ) -> Result, SystemDError> { + fn error_string(mut errno: i32) -> Arc { + if errno < 0 { + errno = -errno; + } + + unsafe { + let mut buf = [0; 1024]; + let _ = libc::strerror_r(errno, buf.as_mut_ptr(), buf.len()); + let c_str = std::ffi::CStr::from_ptr(buf.as_ptr()); + + c_str.to_string_lossy().into() + } + } + + struct JournalHandle { + j: *mut ffi::sd_journal, + close: Symbol<'static, ffi::FnSdJournalClose>, + } + + impl Drop for JournalHandle { + fn drop(&mut self) { + unsafe { (self.close)(self.j) }; + } + } + + let mut j: *mut ffi::sd_journal = std::ptr::null_mut(); + let ret = unsafe { (self.fn_sd_journal_open)(&mut j, ffi::SD_JOURNAL_SYSTEM) }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to open journal: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalOpenError(err_string)); + } + + let raii_handle = JournalHandle { + j, + close: self.fn_sd_journal_close.clone(), + }; + + let ret = unsafe { + (self.fn_sd_journal_add_match)(j, format!("UNIT={}\0", name).as_ptr() as _, 0) + }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to add match: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalAddMatchError(err_string)); + } + + let ret = unsafe { (self.fn_sd_journal_add_disjunction)(j) }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to add disjunction: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalAddDisjunctionError(err_string)); + } + + if let Some(pid) = pid { + let ret = unsafe { + (self.fn_sd_journal_add_match)(j, format!("_PID={}\0", pid).as_ptr() as _, 0) + }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to add match: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalAddMatchError(err_string)); + } + } + + let ret = unsafe { (self.fn_sd_journal_add_conjunction)(j) }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to add disjunction: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalAddConjunctionError(err_string)); + } + + let ret = unsafe { + (self.fn_sd_journal_add_match)( + j, + format!("_BOOT_ID={}\0", self.boot_id).as_ptr() as _, + 0, + ) + }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to add match: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalAddMatchError(err_string)); + } + + let ret = unsafe { (self.fn_sd_journal_seek_tail)(j) }; + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to seek to tail: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalSeekError(err_string)); + } + + let mut messages = Vec::with_capacity(MAX_LOG_MESSAGE_COUNT); + loop { + let ret = unsafe { (self.fn_sd_journal_previous)(j) }; + if ret == 0 { + break; + } + + if ret < 0 { + let err_string = error_string(ret); + error!( + "Gatherer::SystemD", + "Failed to iterate journal entries: {}", + err_string.as_ref() + ); + + return Err(SystemDError::JournalIterateError(err_string)); + } + + let mut data: *const libc::c_void = std::ptr::null_mut(); + let mut length: libc::size_t = 0; + + let ret = unsafe { + (self.fn_sd_journal_get_data)(j, "MESSAGE\0".as_ptr() as _, &mut data, &mut length) + }; + if ret == 0 { + if messages.len() >= MAX_LOG_MESSAGE_COUNT { + break; + } + + let message = Arc::::from( + &unsafe { from_utf8_unchecked(from_raw_parts(data as *const u8, length)) }[8..], + ); + messages.push(message); + } + } + + drop(raii_handle); + + messages.reverse(); + Ok(Arc::from(messages.join("\n"))) + } + + #[inline] + fn service_properties(&self, unit_path: &str) -> Result { + let unit_proxy = self.connection.with_proxy( + "org.freedesktop.systemd1", + unit_path, + Duration::from_millis(5000), + ); + + let enabled: Box = + unit_proxy.get("org.freedesktop.systemd1.Unit", "UnitFileState")?; + let enabled = enabled.as_str().unwrap_or_default(); + + let pid: Box = unit_proxy.get("org.freedesktop.systemd1.Service", "MainPID")?; + let pid = pid.as_u64().unwrap() as u32; + + let user: Box = unit_proxy.get("org.freedesktop.systemd1.Service", "User")?; + let user = user.as_str().unwrap(); + + let uid: Box = unit_proxy.get("org.freedesktop.systemd1.Service", "UID")?; + let uid = uid.as_u64().unwrap() as u32; + + let group: Box = unit_proxy.get("org.freedesktop.systemd1.Service", "Group")?; + let group = group.as_str().unwrap(); + + let gid: Box = unit_proxy.get("org.freedesktop.systemd1.Service", "GID")?; + let gid = gid.as_u64().unwrap() as u32; + + Ok(service::Properties { + enabled: enabled.to_ascii_lowercase() == "enabled", + pid, + user: Arc::from(user), + uid, + group: Arc::from(group), + gid, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_list_services() -> Result<(), anyhow::Error> { + let systemd = ServiceManager::new()?; + let services = systemd.list_services()?; + assert!(!services.is_empty()); + dbg!(services); + + Ok(()) + } + + #[test] + fn test_service_logs() -> Result<(), anyhow::Error> { + let systemd = ServiceManager::new()?; + let logs = systemd.service_logs("NetworkManager.service", NonZeroU32::new(883))?; + dbg!(logs); + + Ok(()) + } +} diff --git a/src/observatory-daemon/src/platform/linux/systemd/service.rs b/src/observatory-daemon/src/platform/linux/systemd/service.rs new file mode 100644 index 0000000..00aaf28 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/systemd/service.rs @@ -0,0 +1,435 @@ +use std::{num::NonZeroU32, sync::Arc}; + +use dbus::{ + arg::{Arg, ArgType, Get, Iter, RefArg}, + Signature, +}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum LoadState { + Loaded, + Error, + Masked, + NotFound, + Unknown, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ActiveState { + Active, + Reloading, + Inactive, + Failed, + Activating, + Deactivating, + Unknown, +} + +#[derive(Debug, Clone)] +pub struct Properties { + pub enabled: bool, + pub pid: u32, + pub user: Arc, + pub uid: u32, + pub group: Arc, + pub gid: u32, +} + +impl Default for Properties { + fn default() -> Self { + let empty_str: Arc = Arc::from(""); + Self { + enabled: false, + pid: 0, + user: empty_str.clone(), + uid: 0, + group: empty_str.clone(), + gid: 0, + } + } +} + +#[derive(Debug, Clone)] +pub struct Service { + pub name: Arc, + pub description: Arc, + pub load_state: LoadState, + pub active_state: ActiveState, + pub sub_state: Arc, + pub following: Arc, + pub unit_path: Arc, + pub job_id: Option, + pub job_type: Arc, + pub job_path: Arc, + + pub properties: Properties, +} + +impl Default for Service { + fn default() -> Self { + let empty_str: Arc = Arc::from(""); + Self { + name: empty_str.clone(), + description: empty_str.clone(), + load_state: LoadState::Unknown, + active_state: ActiveState::Unknown, + sub_state: empty_str.clone(), + following: empty_str.clone(), + unit_path: empty_str.clone(), + job_id: None, + job_type: empty_str.clone(), + job_path: empty_str.clone(), + + properties: Properties::default(), + } + } +} + +impl Service { + #[inline] + pub fn name(&self) -> &str { + self.name.as_ref() + } + + #[inline] + pub fn description(&self) -> &str { + self.description.as_ref() + } + + #[inline] + pub fn enabled(&self) -> bool { + self.properties.enabled + } + + #[inline] + pub fn running(&self) -> bool { + self.active_state == ActiveState::Active + } + + #[inline] + pub fn failed(&self) -> bool { + self.active_state == ActiveState::Failed + } + + #[inline] + pub fn pid(&self) -> Option { + match self.properties.pid { + 0 => None, + _ => NonZeroU32::new(self.properties.pid), + } + } + + #[inline] + pub fn user(&self) -> Option<&str> { + match self.properties.user.as_ref() { + "" => None, + _ => Some(self.properties.user.as_ref()), + } + } + + #[inline] + pub fn group(&self) -> Option<&str> { + match self.properties.group.as_ref() { + "" => None, + _ => Some(self.properties.group.as_ref()), + } + } +} + +pub struct ServiceVec(pub Vec); + +impl From for Vec { + fn from(v: ServiceVec) -> Self { + v.0 + } +} + +impl Arg for ServiceVec { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("a(ssssssouso)") + } +} + +impl<'a> Get<'a> for ServiceVec { + fn get(i: &mut Iter<'a>) -> Option { + use crate::critical; + + let mut result = vec![]; + + match Iterator::next(i) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get Vec: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get Vec: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for i in arr { + let mut this = Service::default(); + + let mut i = match i.as_iter() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '0: STRUCT', got None", + ); + continue; + } + Some(i) => i, + }; + let unit = i.as_mut(); + + this.name = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '0: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '0: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(n) => { + if !n.ends_with(".service") { + continue; + } + + Arc::from(n) + } + }, + }; + + this.description = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '1: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '1: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(d) => Arc::::from(d), + }, + }; + + this.load_state = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '2: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '2: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(ls) => match ls { + "loaded" => LoadState::Loaded, + "error" => LoadState::Error, + "masked" => LoadState::Masked, + "not-found" => LoadState::NotFound, + _ => LoadState::Unknown, + }, + }, + }; + + this.active_state = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '3: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '3: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(a) => match a { + "active" => ActiveState::Active, + "reloading" => ActiveState::Reloading, + "inactive" => ActiveState::Inactive, + "failed" => ActiveState::Failed, + "activating" => ActiveState::Activating, + "deactivating" => ActiveState::Deactivating, + _ => ActiveState::Unknown, + }, + }, + }; + + this.sub_state = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '4: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '4: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(ss) => Arc::from(ss), + }, + }; + + this.following = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '5: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '5: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(f) => Arc::from(f), + }, + }; + + this.unit_path = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '6: o', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '6: o', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(up) => Arc::from(up), + }, + }; + + this.job_id = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '7: u', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '7: u', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(ji) => match ji { + 0 => None, + ji => Some(ji as u32), + }, + }, + }; + + this.job_type = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '8: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '8: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(jt) => Arc::from(jt), + }, + }; + + this.job_path = match Iterator::next(unit) { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '9: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + critical!( + "Gatherer::SystemDServices", + "Failed to get SystemD Unit: Expected '9: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(jp) => Arc::from(jp), + }, + }; + + result.push(this); + } + } + }, + } + + Some(ServiceVec(result)) + } +} diff --git a/src/observatory-daemon/src/platform/linux/utilities.rs b/src/observatory-daemon/src/platform/linux/utilities.rs new file mode 100644 index 0000000..9624911 --- /dev/null +++ b/src/observatory-daemon/src/platform/linux/utilities.rs @@ -0,0 +1,104 @@ +/* sys_info_v2/observatory-daemon/src/platform/linux/utilities.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use dbus::arg::RefArg; + +use crate::platform::utilities::*; + +#[derive(Default)] +pub struct LinuxPlatformUtilities {} + +impl PlatformUtilitiesExt for LinuxPlatformUtilities { + fn on_main_app_exit(&self, mut callback: Box) { + use crate::critical; + use dbus::{blocking::Connection, channel::MatchingReceiver, message::MatchRule}; + use std::sync::{atomic::*, Arc}; + + std::thread::spawn(move || { + let c = match Connection::new_session() { + Ok(c) => c, + Err(e) => { + critical!( + "Gatherer::PlatformUtilities", + "Failed to connect to the D-Bus session bus, and set up monitoring: {}", + e + ); + return; + } + }; + + let mut rule = MatchRule::new(); + rule.strict_sender = true; + rule.sender = Some("org.freedesktop.DBus".into()); + rule.interface = Some("org.freedesktop.DBus".into()); + rule.path = Some("/org/freedesktop/DBus".into()); + rule.member = Some("NameLost".into()); + + let proxy = c.with_proxy( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + std::time::Duration::from_millis(5000), + ); + let result: Result<(), dbus::Error> = proxy.method_call( + "org.freedesktop.DBus.Monitoring", + "BecomeMonitor", + (vec![rule.match_str()], 0u32), + ); + match result { + Ok(_) => { + let done = Arc::new(AtomicBool::new(false)); + let d = Arc::clone(&done); + c.start_receive( + rule, + Box::new(move |msg, _: &Connection| { + if let Some(name) = msg + .iter_init() + .get_refarg() + .and_then(|a| a.as_str().and_then(|s| Some(s.to_string()))) + { + if name == "io.missioncenter.MissionCenter" { + d.store(true, Ordering::Release); + callback(); + false + } else { + true + } + } else { + true + } + }), + ); + + while !done.load(Ordering::Acquire) { + c.process(std::time::Duration::from_millis(1000)).unwrap(); + } + } + Err(e) => { + critical!( + "Gatherer::PlatformUtilities", + "Failed to connect to the D-Bus session bus, and set up monitoring: {}", + e + ); + return; + } + } + }); + } +} diff --git a/src/observatory-daemon/src/platform/mod.rs b/src/observatory-daemon/src/platform/mod.rs new file mode 100644 index 0000000..9228893 --- /dev/null +++ b/src/observatory-daemon/src/platform/mod.rs @@ -0,0 +1,71 @@ +/* sys_info_v2/observatory-daemon/src/platform/mod.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +pub use apps::*; +pub use cpu_info::*; +pub use disk_info::*; +pub use fan_info::*; +pub use gpu_info::*; +#[cfg(target_os = "linux")] +pub use linux::*; +pub use processes::*; +pub use services::*; +pub use utilities::*; + +#[cfg(target_os = "linux")] +#[path = "linux/mod.rs"] +mod platform_impl; + +#[cfg(target_os = "linux")] +#[allow(unused)] +mod linux { + use super::*; + + pub type Process = platform_impl::LinuxProcess; + pub type Processes = platform_impl::LinuxProcesses; + pub type App = platform_impl::LinuxApp; + pub type Apps = platform_impl::LinuxApps; + pub type CpuStaticInfo = platform_impl::LinuxCpuStaticInfo; + pub type CpuDynamicInfo = platform_impl::LinuxCpuDynamicInfo; + pub type DiskInfo = platform_impl::LinuxDiskInfo; + pub type DiskInfoIter<'a> = platform_impl::LinuxDiskInfoIter<'a>; + pub type DisksInfo = platform_impl::LinuxDisksInfo; + pub type FanInfo = platform_impl::LinuxFanInfo; + pub type FanInfoIter<'a> = platform_impl::LinuxFanInfoIter<'a>; + pub type FansInfo = platform_impl::LinuxFansInfo; + pub type CpuInfo = platform_impl::LinuxCpuInfo; + pub type GpuStaticInfo = platform_impl::LinuxGpuStaticInfo; + pub type GpuDynamicInfo = platform_impl::LinuxGpuDynamicInfo; + pub type GpuInfo = platform_impl::LinuxGpuInfo; + pub type Service = platform_impl::LinuxService; + pub type Services<'a> = platform_impl::LinuxServices<'a>; + pub type ServiceController<'a> = platform_impl::LinuxServiceController<'a>; + pub type ServicesError = platform_impl::LinuxServicesError; + pub type PlatformUtilities = platform_impl::LinuxPlatformUtilities; +} + +mod apps; +mod cpu_info; +mod disk_info; +mod fan_info; +mod gpu_info; +mod processes; +mod services; +mod utilities; diff --git a/src/observatory-daemon/src/platform/processes.rs b/src/observatory-daemon/src/platform/processes.rs new file mode 100644 index 0000000..b28a086 --- /dev/null +++ b/src/observatory-daemon/src/platform/processes.rs @@ -0,0 +1,133 @@ +/* sys_info_v2/observatory-daemon/src/platform/processes.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::collections::HashMap; + +use dbus::arg::{Append, Arg}; + +/// State of a running process +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum ProcessState { + Running = 0, + Sleeping = 1, + SleepingUninterruptible = 2, + Zombie = 3, + Stopped = 4, + Tracing = 5, + Dead = 6, + WakeKill = 7, + Waking = 8, + Parked = 9, + Unknown = 10, // Keep this last +} + +/// Statistics associated with a process +#[derive(Debug, Default, Copy, Clone)] +pub struct ProcessUsageStats { + pub cpu_usage: f32, + pub memory_usage: f32, + pub disk_usage: f32, + pub network_usage: f32, + pub gpu_usage: f32, + pub gpu_memory_usage: f32, +} + +/// High-level description of a process +pub trait ProcessExt<'a> { + type Iter: Iterator; + + fn name(&self) -> &str; + fn cmd(&'a self) -> Self::Iter; + fn exe(&self) -> &str; + fn state(&self) -> ProcessState; + fn pid(&self) -> u32; + fn parent(&self) -> u32; + fn usage_stats(&self) -> &ProcessUsageStats; + fn task_count(&self) -> usize; +} + +/// The public interface that describes how the list of running processes is obtained +pub trait ProcessesExt<'a>: Default + Append + Arg { + type P: ProcessExt<'a>; + + /// Refreshes the internal process cache + /// + /// It is expected that implementors of this trait cache the process list once obtained from + /// the underlying OS + fn refresh_cache(&mut self); + + /// Return the (cached) list of processes + fn process_list(&'a self) -> &'a HashMap; + + /// Return the (cached) mutable list of processes + fn process_list_mut(&'a mut self) -> &'a mut HashMap; + + /// Ask a process to terminate + /// + /// On Linux this would be the equivalent of sending a SIGTERM signal to the process + /// Optionally, a platform implementation can ask a user to authenticate if the process is not + /// owned by the current user + fn terminate_process(&self, pid: u32); + + /// Force a process to terminate + /// + /// On Linux this would be the equivalent of sending a SIGKILL signal to the process + /// Optionally, a platform implementation can ask a user to authenticate if the process is not + /// owned by the current user + fn kill_process(&self, pid: u32); +} + +impl Arg for crate::platform::Processes { + const ARG_TYPE: dbus::arg::ArgType = dbus::arg::ArgType::Array; + + fn signature() -> dbus::Signature<'static> { + dbus::Signature::from("a(sassyuu(dddddd)t)") + } +} + +impl Append for crate::platform::Processes { + fn append_by_ref(&self, ia: &mut dbus::arg::IterAppend) { + ia.append( + self.process_list() + .iter() + .map(|(_, p)| { + ( + p.name(), + p.cmd().clone().collect::>(), + p.exe(), + p.state() as u8, + p.pid(), + p.parent(), + ( + p.usage_stats().cpu_usage as f64, + p.usage_stats().memory_usage as f64, + p.usage_stats().disk_usage as f64, + p.usage_stats().network_usage as f64, + p.usage_stats().gpu_usage as f64, + p.usage_stats().gpu_memory_usage as f64, + ), + p.task_count() as u64, + ) + }) + .collect::>(), + ); + } +} diff --git a/src/observatory-daemon/src/platform/services.rs b/src/observatory-daemon/src/platform/services.rs new file mode 100644 index 0000000..da81763 --- /dev/null +++ b/src/observatory-daemon/src/platform/services.rs @@ -0,0 +1,121 @@ +/* sys_info_v2/observatory-daemon/src/platform/services.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::num::NonZeroU32; +use std::sync::Arc; + +use dbus::{ + arg::{Append, Arg, ArgType, IterAppend}, + Signature, +}; + +/// High-level description of a service +pub trait ServiceExt: Append + Arg { + /// The name of the service, also used as the unique identifier for the service + fn name(&self) -> &str; + + /// A human-readable description of the service + fn description(&self) -> &str; + + /// Whether the service is enabled to start at boot + fn enabled(&self) -> bool; + + /// Whether the service is currently running + fn running(&self) -> bool; + + /// If the service isn't running did it finish successfully + fn failed(&self) -> bool; + + /// The process id of the service + fn pid(&self) -> Option; + + /// The user that the service runs as + fn user(&self) -> Option<&str>; + + /// The group that the service runs as + fn group(&self) -> Option<&str>; +} + +impl Append for crate::platform::Service { + #[inline] + fn append_by_ref(&self, ia: &mut IterAppend) { + ia.append(( + self.name(), + self.description(), + self.enabled(), + self.running(), + self.failed(), + self.pid().map(|pid| pid.get()).unwrap_or(0), + self.user().unwrap_or(""), + self.group().unwrap_or(""), + )) + } +} + +impl Arg for crate::platform::Service { + const ARG_TYPE: ArgType = ArgType::Struct; + + #[inline] + fn signature() -> Signature<'static> { + Signature::from("(ssbbbuss)") + } +} + +/// An object that can control services and their state +pub trait ServiceControllerExt { + type E; + + /// Enable a service to start at boot + fn enable_service(&self, name: &str) -> Result<(), Self::E>; + + /// Disable a service from starting at boot + fn disable_service(&self, name: &str) -> Result<(), Self::E>; + + /// Start a service + fn start_service(&self, name: &str) -> Result<(), Self::E>; + + /// Stop a service + fn stop_service(&self, name: &str) -> Result<(), Self::E>; + + /// Restart a service + fn restart_service(&self, name: &str) -> Result<(), Self::E>; +} + +/// The public interface that describes how the list of running processes is obtained +pub trait ServicesExt<'a> { + type S: ServiceExt; + type C: ServiceControllerExt; + type E; + + /// Refreshes the internal service cache + /// + /// It is expected that implementors of this trait cache the list once obtained from + /// the underlying OS + fn refresh_cache(&mut self) -> Result<(), Self::E>; + + /// Return the (cached) list of services + fn services(&'a self) -> Result, Self::E>; + + /// An instance of a service controller + fn controller(&self) -> Result; + + /// The logs of a service + fn service_logs(&self, name: &str, pid: Option) -> Result, Self::E>; +} diff --git a/src/observatory-daemon/src/platform/utilities.rs b/src/observatory-daemon/src/platform/utilities.rs new file mode 100644 index 0000000..92ff70a --- /dev/null +++ b/src/observatory-daemon/src/platform/utilities.rs @@ -0,0 +1,25 @@ +/* sys_info_v2/observatory-daemon/src/platform/utilities.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/// This trait is used to provide platform specific behavior to the Gatherer +pub trait PlatformUtilitiesExt: Default { + /// Sets up a callback that should be called when the main app exits + fn on_main_app_exit(&self, callback: Box); +} diff --git a/src/observatory-daemon/src/utils.rs b/src/observatory-daemon/src/utils.rs new file mode 100644 index 0000000..007d8ca --- /dev/null +++ b/src/observatory-daemon/src/utils.rs @@ -0,0 +1,61 @@ +/* sys_info_v2/observatory-daemon/src/utils.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +pub mod arraystring { + // pub trait ToArrayStringLossy { + // fn to_array_string_lossy(&self) -> arrayvec::ArrayString; + // } + // + // impl ToArrayStringLossy for str { + // fn to_array_string_lossy(&self) -> arrayvec::ArrayString { + // let mut result = arrayvec::ArrayString::new(); + // if self.len() > CAPACITY { + // for i in (0..CAPACITY).rev() { + // if self.is_char_boundary(i) { + // result.push_str(&self[0..i]); + // break; + // } + // } + // } else { + // result.push_str(self); + // } + // + // result + // } + // } + // + // impl ToArrayStringLossy for std::borrow::Cow<'_, str> { + // fn to_array_string_lossy(&self) -> arrayvec::ArrayString { + // let mut result = arrayvec::ArrayString::new(); + // if self.len() > CAPACITY { + // for i in (0..CAPACITY).rev() { + // if self.is_char_boundary(i) { + // result.push_str(&self[0..i]); + // break; + // } + // } + // } else { + // result.push_str(self); + // } + // + // result + // } + // } +} diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 86ce541..3472634 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -1,7 +1,7 @@ pub mod page; pub mod overview; -pub mod processes; -pub mod resources; +//pub mod processes; +//pub mod resources; pub use page::Page; diff --git a/src/pages/overview.rs b/src/pages/overview.rs index 1b373f6..9fc28c6 100644 --- a/src/pages/overview.rs +++ b/src/pages/overview.rs @@ -1,58 +1,60 @@ -mod applications; mod statistic; +use std::collections::HashMap; +use std::sync::Arc; use crate::app::message::AppMessage; use crate::core::icons; use statistic::Statistic; -use cosmic::iced::alignment::Horizontal; -use cosmic::iced::Length; +use cosmic::iced::alignment::{Horizontal, Vertical}; +use cosmic::iced::{Background, Length}; use cosmic::{theme, widget, Element, Task}; +use crate::system_info::{App, SystemInfo}; pub struct OverviewPage { // nothing yet statistics: Vec, - applications: applications::ApplicationPage, + applications: HashMap, App>, + selected_app: Option, } impl super::Page for OverviewPage { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, - ) -> cosmic::Task> { + ) -> cosmic::app::Task { let mut tasks = Vec::new(); - tasks.push(self.applications.update(&sys, message.clone())); match message { AppMessage::Refresh => { self.statistics.clear(); self.statistics.push(Statistic::new( "CPU".into(), "processor-symbolic".into(), - sys.global_cpu_usage() / 100., + sys.cpu_dynamic_info().overall_utilization_percent / 100.0, )); + let mem_info = crate::system_info::mem_info::MemInfo::load().unwrap(); + let used = mem_info + .mem_total + .saturating_sub(mem_info.mem_available + mem_info.dirty) as f64 / mem_info.mem_total as f64; + self.statistics.push(Statistic::new( "RAM".into(), "memory-symbolic".into(), - sys.used_memory() as f32 / sys.total_memory() as f32, + used as f32, )); - let disks = sysinfo::Disks::new_with_refreshed_list(); - let mut i = 0; - for disk in disks.list().iter().filter(|disk| { - !disk.mount_point().to_string_lossy().contains("/boot") - && !disk.mount_point().to_string_lossy().contains("/recovery") - }) { - if i > 1 { - break; - } + let disks = sys.disks_info(); + for disk in disks.iter() { self.statistics.push(Statistic::new( - format!("Disk {}", i), + format!("Disk {}", disk.model), "harddisk-symbolic".into(), - (disk.total_space() - disk.available_space()) as f32 - / disk.total_space() as f32, + disk.busy_percent / 100.0, )); - i = i + 1; } + self.applications = sys.apps().into(); + } + AppMessage::ApplicationSelect(app) => { + self.selected_app = app; } _ => {} } @@ -63,6 +65,89 @@ impl super::Page for OverviewPage { let theme = theme::active(); let cosmic = theme.cosmic(); + let mut apps = widget::column() + .spacing(cosmic.space_xxs()); + let mut applications = self.applications.values().collect::>(); + applications.sort_by_key(|a| &a.name); + for app in applications.into_iter().collect::>() { + let is_selected = if let Some(selected_app) = self.selected_app { + selected_app == app.pids[0] + } else { + false + }; + apps = apps.push( + widget::button::custom( + widget::row::with_children(vec![ + widget::icon::from_name(app.icon.clone().unwrap()).size(24).into(), + widget::text::body(String::from(app.name.clone().as_ref())).into() + ]) + .align_y(Vertical::Center) + .padding([cosmic.space_xxxs(), cosmic.space_xs()]) + .spacing(cosmic.space_xs()) + .width(Length::Fill), + ).on_press(AppMessage::ApplicationSelect(Some(app.pids[0]))) + .class(cosmic::style::Button::Custom { + active: Box::new(move |_, theme| { + let cosmic = theme.cosmic(); + let mut appearance = widget::button::Style::new(); + if is_selected { + appearance.background = + Some(Background::Color(cosmic.accent.base.into())); + appearance.text_color = Some(cosmic.accent.on.into()); + } + appearance.border_radius = cosmic.radius_s().into(); + appearance + }), + + disabled: Box::new(move |theme| { + let cosmic = theme.cosmic(); + let mut appearance = widget::button::Style::new(); + if is_selected { + appearance.background = + Some(Background::Color(cosmic.accent.disabled.into())); + appearance.text_color = Some(cosmic.accent.on.into()); + } else { + appearance.background = + Some(Background::Color(cosmic.button.disabled.into())); + appearance.text_color = Some(cosmic.button.on_disabled.into()); + } + + appearance + }), + hovered: Box::new(move |_, theme| { + let cosmic = theme.cosmic(); + let mut appearance = widget::button::Style::new(); + if is_selected { + appearance.background = + Some(Background::Color(cosmic.accent.hover.into())); + appearance.text_color = Some(cosmic.accent.on.into()); + } else { + appearance.background = + Some(Background::Color(cosmic.button.hover.into())); + appearance.text_color = Some(cosmic.button.on.into()); + } + appearance.border_radius = cosmic.radius_s().into(); + appearance + }), + pressed: Box::new(move |_, theme| { + let cosmic = theme.cosmic(); + let mut appearance = widget::button::Style::new(); + if is_selected { + appearance.background = + Some(Background::Color(cosmic.accent.pressed.into())); + appearance.text_color = Some(cosmic.accent.on.into()); + } else { + appearance.background = + Some(Background::Color(cosmic.button.pressed.into())); + appearance.text_color = Some(cosmic.button.on.into()); + } + appearance.border_radius = cosmic.radius_s().into(); + appearance + }), + }) + ) + } + widget::column::with_children(vec![ widget::container( widget::row::with_children( @@ -99,22 +184,27 @@ impl super::Page for OverviewPage { ) .class(cosmic::style::Container::Primary) .into(), - self.applications.view(), + widget::layer_container( + widget::column() + .push(widget::text::heading("Applications")) + .push(cosmic::iced_widget::horizontal_rule(1)) + .push(widget::scrollable(apps)) + .spacing(cosmic.space_xs())) + .layer(cosmic::cosmic_theme::Layer::Primary) + .padding([cosmic.space_xs(), cosmic.space_s()]) + .into() ]) .spacing(cosmic.space_xs()) .into() } - - fn footer(&self) -> Option> { - self.applications.footer() - } } impl OverviewPage { pub fn new() -> Self { Self { statistics: Vec::new(), - applications: applications::ApplicationPage::new(), + applications: HashMap::new(), + selected_app: None, } } } diff --git a/src/pages/overview/applications.rs b/src/pages/overview/applications.rs index 389c6d0..b18fe7f 100644 --- a/src/pages/overview/applications.rs +++ b/src/pages/overview/applications.rs @@ -17,6 +17,7 @@ use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::keyboard::Key; use cosmic::iced::{Background, Length}; use cosmic::iced_widget::horizontal_rule; +use crate::system_info::SystemInfo; pub struct Applications { output_state: OutputState, @@ -35,7 +36,7 @@ pub struct ApplicationPage { impl Page for ApplicationPage { fn update( &mut self, - _sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task> { let mut tasks = Vec::new(); diff --git a/src/pages/page.rs b/src/pages/page.rs index 0004814..24c0fcb 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -1,7 +1,9 @@ +use crate::system_info::SystemInfo; + pub trait Page { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task>; diff --git a/src/pages/processes.rs b/src/pages/processes.rs index f0c5f3a..29ee9bf 100644 --- a/src/pages/processes.rs +++ b/src/pages/processes.rs @@ -9,7 +9,7 @@ use cosmic::{ iced::{alignment::Vertical, Length}, widget, Element, }; - +use crate::system_info::SystemInfo; pub use super::Page; pub struct ProcessPage { @@ -24,7 +24,7 @@ pub struct ProcessPage { impl Page for ProcessPage { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task> { let mut tasks = vec![]; diff --git a/src/pages/resources.rs b/src/pages/resources.rs index a7ed1b8..4bb5697 100644 --- a/src/pages/resources.rs +++ b/src/pages/resources.rs @@ -8,6 +8,7 @@ use crate::fl; use crate::pages::Page; use cosmic::{iced::Alignment, theme, widget, Element, Task}; +use crate::system_info::SystemInfo; pub struct ResourcePage { tab_model: widget::segmented_button::SingleSelectModel, @@ -16,7 +17,7 @@ pub struct ResourcePage { impl Page for ResourcePage { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task> { let mut tasks = Vec::new(); diff --git a/src/pages/resources/cpu.rs b/src/pages/resources/cpu.rs index c8ac2a2..6841036 100644 --- a/src/pages/resources/cpu.rs +++ b/src/pages/resources/cpu.rs @@ -9,6 +9,7 @@ use cosmic::iced::{ use cosmic::{theme, widget, Element, Task}; use std::collections::{HashMap, VecDeque}; use sysinfo::System; +use crate::system_info::SystemInfo; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ContextMenuAction { @@ -42,7 +43,7 @@ pub struct CpuResources { impl super::Page for CpuResources { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task> { match message { diff --git a/src/pages/resources/disk.rs b/src/pages/resources/disk.rs index 69ee8be..6950dfb 100644 --- a/src/pages/resources/disk.rs +++ b/src/pages/resources/disk.rs @@ -6,6 +6,7 @@ use cosmic::iced::Length; use cosmic::iced_widget::horizontal_rule; use cosmic::{theme, widget, Element, Task}; use std::collections::VecDeque; +use crate::system_info::SystemInfo; pub struct DiskResources { disk_write_history: VecDeque, @@ -20,7 +21,7 @@ pub struct DiskResources { impl super::Page for DiskResources { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task> { match message { diff --git a/src/pages/resources/mem.rs b/src/pages/resources/mem.rs index 257a1ff..c5b3798 100644 --- a/src/pages/resources/mem.rs +++ b/src/pages/resources/mem.rs @@ -6,6 +6,7 @@ use cosmic::iced::Length; use cosmic::iced_widget::horizontal_rule; use cosmic::{theme, widget, Element, Task}; use std::collections::VecDeque; +use crate::system_info::SystemInfo; pub struct MemResources { mem_usage_history: VecDeque, @@ -20,7 +21,7 @@ pub struct MemResources { impl super::Page for MemResources { fn update( &mut self, - sys: &sysinfo::System, + sys: &SystemInfo, message: crate::app::message::AppMessage, ) -> cosmic::Task> { match message { diff --git a/src/system_info/dbus_interface/apps.rs b/src/system_info/dbus_interface/apps.rs new file mode 100644 index 0000000..a5e265e --- /dev/null +++ b/src/system_info/dbus_interface/apps.rs @@ -0,0 +1,232 @@ +/* sys_info_v2/dbus-interface/apps.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::{collections::HashMap, sync::Arc}; + +use dbus::{arg::*, strings::*}; + +#[derive(Debug, Clone)] +pub struct App { + pub name: Arc, + pub icon: Option>, + pub id: Arc, + pub command: Arc, + pub pids: Vec, +} + +impl From<&dyn RefArg> for App { + fn from(value: &dyn RefArg) -> Self { + + let empty_string = Arc::::from(""); + + let mut this = App { + name: empty_string.clone(), + icon: None, + id: empty_string.clone(), + command: empty_string, + pids: vec![], + }; + + let mut app = match value.as_iter() { + None => { + log::error!( + "Failed to get App: Expected '0: STRUCT', got None, failed to iterate over fields", + ); + return this; + } + Some(i) => i, + }; + let app = app.as_mut(); + + this.name = match Iterator::next(app) { + None => { + log::error!( + "Failed to get App: Expected '0: s', got None", + ); + return this; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + "Failed to get App: Expected '0: s', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(n) => Arc::from(n), + }, + }; + + this.icon = match Iterator::next(app) { + None => { + log::error!( + "Failed to get App: Expected '1: s', got None", + ); + return this; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + "Failed to get App: Expected '1: s', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(icon) => { + if icon.is_empty() { + None + } else { + Some(Arc::from(icon)) + } + } + }, + }; + + this.id = match Iterator::next(app) { + None => { + log::error!( + "Failed to get App: Expected '2: s', got None", + ); + return this; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + "Failed to get App: Expected '2: s', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(id) => Arc::from(id), + }, + }; + + this.command = match Iterator::next(app) { + None => { + log::error!( + "Failed to get App: Expected '3: 2', got None", + ); + return this; + } + Some(arg) => match arg.as_str() { + None => { + log::error!("Failed to get App: Expected '3: s', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(c) => Arc::from(c), + }, + }; + + match Iterator::next(app) { + None => { + log::error!( + "Failed to get App: Expected '4: ARRAY', got None", + ); + return this; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!("Failed to get App: Expected '4: ARRAY', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(pids) => { + for p in pids { + if let Some(p) = p.as_u64() { + this.pids.push(p as u32); + } + } + } + }, + } + + this + } +} + +pub struct AppMap(HashMap, App>); + +impl From, App>> for AppMap { + fn from(value: HashMap, App>) -> Self { + Self(value) + } +} + +impl From for HashMap, App> { + fn from(value: AppMap) -> Self { + value.0 + } +} + +impl Arg for AppMap { + const ARG_TYPE: ArgType = ArgType::Array; + + fn signature() -> Signature<'static> { + Signature::from("a(ssssau(dddddd))") + } +} + +impl ReadAll for AppMap { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for AppMap { + fn get(i: &mut Iter<'a>) -> Option { + + let mut this = HashMap::new(); + + match Iterator::next(i) { + None => { + log::error!( + "Failed to get HashMap: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!("Failed to get HashMap: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for a in arr { + let a = App::from(a); + if a.name.as_ref().is_empty() { + continue; + } + this.insert(a.id.clone(), a); + } + } + }, + } + + Some(this.into()) + } +} diff --git a/src/system_info/dbus_interface/arc_str_vec.rs b/src/system_info/dbus_interface/arc_str_vec.rs new file mode 100644 index 0000000..2463166 --- /dev/null +++ b/src/system_info/dbus_interface/arc_str_vec.rs @@ -0,0 +1,91 @@ +/* sys_info_v2/dbus-interface/arc_str_vec.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; + +use dbus::{arg::*, strings::*}; + +pub struct ArcStrVec(Vec>); + +impl From>> for ArcStrVec { + fn from(value: Vec>) -> Self { + Self(value) + } +} + +impl From for Vec> { + fn from(value: ArcStrVec) -> Self { + value.0 + } +} + +impl Arg for ArcStrVec { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("as") + } +} + +impl ReadAll for ArcStrVec { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for ArcStrVec { + fn get(i: &mut Iter<'a>) -> Option { + + let mut this = vec![]; + + match Iterator::next(i) { + None => { + log::error!( + "MissionCenter::GathererDBusProxy: {}", + "Failed to get Vec>: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + "MissionCenter::GathererDBusProxy: {}", + format!("Failed to get Vec>: Expected '0: ARRAY', got {:?}", + arg.arg_type()), + ); + return None; + } + Some(arr) => { + for s in arr { + if let Some(s) = s.as_str() { + this.push(Arc::from(s)); + } + } + } + }, + } + + Some(this.into()) + } +} diff --git a/src/system_info/dbus_interface/cpu_dynamic_info.rs b/src/system_info/dbus_interface/cpu_dynamic_info.rs new file mode 100644 index 0000000..6b2747a --- /dev/null +++ b/src/system_info/dbus_interface/cpu_dynamic_info.rs @@ -0,0 +1,367 @@ +/* sys_info_v2/dbus_interface/cpu_dynamic_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; + +use dbus::{arg::*, strings::*}; + +#[derive(Debug, Default, Clone)] +pub struct CpuDynamicInfo { + pub overall_utilization_percent: f32, + pub overall_kernel_utilization_percent: f32, + pub per_logical_cpu_utilization_percent: Vec, + pub per_logical_cpu_kernel_utilization_percent: Vec, + pub current_frequency_mhz: u64, + pub temperature: Option, + pub process_count: u64, + pub thread_count: u64, + pub handle_count: u64, + pub uptime_seconds: u64, + pub cpufreq_driver: Option>, + pub cpufreq_governor: Option>, + pub energy_performance_preference: Option>, +} + +impl Arg for CpuDynamicInfo { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("(ddadadtdtttt)") + } +} + +impl ReadAll for CpuDynamicInfo { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for CpuDynamicInfo { + fn get(i: &mut Iter<'a>) -> Option { + + let mut this = CpuDynamicInfo { + overall_utilization_percent: 0.0, + overall_kernel_utilization_percent: 0.0, + per_logical_cpu_utilization_percent: vec![], + per_logical_cpu_kernel_utilization_percent: vec![], + current_frequency_mhz: 0, + temperature: None, + process_count: 0, + thread_count: 0, + handle_count: 0, + uptime_seconds: 0, + cpufreq_driver: None, + cpufreq_governor: None, + energy_performance_preference: None, + }; + + let dynamic_info = match Iterator::next(i) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '0: STRUCT', got None", + ); + return None; + } + Some(id) => id, + }; + + let mut dynamic_info = match dynamic_info.as_iter() { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '0: STRUCT', got None, failed to iterate over fields", + ); + return None; + } + Some(i) => i, + }; + let dynamic_info = dynamic_info.as_mut(); + + this.overall_utilization_percent = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '0: d', got None", + ); + return None; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!("Failed to get CpuDynamicInfo: Expected '0: d', got {:?}", + arg.arg_type() + ); + return None; + } + Some(u) => u as _, + }, + }; + + this.overall_kernel_utilization_percent = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '1: d', got None", + ); + return None; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!("Failed to get CpuDynamicInfo: Expected '1: d', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(u) => u as _, + }, + }; + + match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '2: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!("Failed to get CpuDynamicInfo: Expected '2: ARRAY', got {:?}", + arg.arg_type() + ); + return None; + } + Some(u) => { + for v in u { + this.per_logical_cpu_utilization_percent + .push(v.as_f64().unwrap_or(0.) as f32); + } + } + }, + } + + match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '4: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!("Failed to get CpuDynamicInfo: Expected '4: ARRAY', got {:?}", + arg.arg_type() + ); + return None; + } + Some(u) => { + for v in u { + this.per_logical_cpu_kernel_utilization_percent + .push(v.as_f64().unwrap_or(0.) as f32); + } + } + }, + } + + this.current_frequency_mhz = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '6: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!("Failed to get CpuDynamicInfo: Expected '6: t', got {:?}", + arg.arg_type() + ); + return None; + } + Some(f) => f, + }, + }; + + this.temperature = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '7: d', got None", + ); + return None; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!("Failed to get CpuDynamicInfo: Expected '7: d', got {:?}", + arg.arg_type() + ); + return None; + } + Some(u) => { + if u == 0. { + None + } else { + Some(u as f32) + } + } + }, + }; + + this.process_count = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '8: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '8: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(pc) => pc, + }, + }; + + this.thread_count = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '9: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '9: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(tc) => tc, + }, + }; + + this.handle_count = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '10: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '10: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(hc) => hc, + }, + }; + + this.uptime_seconds = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '11: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + "Failed to get CpuDynamicInfo: Expected '11: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(us) => us, + }, + }; + this.cpufreq_driver = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuStaticInfo: Expected '12: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + "Failed to get CpuStaticInfo: Expected '12: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ivs) => match ivs { + "" => None, + _ => Some(Arc::from(ivs)), + }, + }, + }; + + this.cpufreq_governor = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuStaticInfo: Expected '13: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + "Failed to get CpuStaticInfo: Expected '13: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ivs) => match ivs { + "" => None, + _ => Some(Arc::from(ivs)), + }, + }, + }; + + this.energy_performance_preference = match Iterator::next(dynamic_info) { + None => { + log::error!( + "Failed to get CpuStaticInfo: Expected '14: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + "Failed to get CpuStaticInfo: Expected '14: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ivs) => match ivs { + "" => None, + _ => Some(Arc::from(ivs)), + }, + }, + }; + + Some(this) + } +} diff --git a/src/system_info/dbus_interface/cpu_static_info.rs b/src/system_info/dbus_interface/cpu_static_info.rs new file mode 100644 index 0000000..b023ab1 --- /dev/null +++ b/src/system_info/dbus_interface/cpu_static_info.rs @@ -0,0 +1,357 @@ +/* sys_info_v2/dbus_interface/cpu_static_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; + +use dbus::{arg::*, strings::*}; + +#[derive(Debug, Clone)] +pub struct CpuStaticInfo { + pub name: Arc, + pub logical_cpu_count: u32, + pub socket_count: Option, + pub base_frequency_khz: Option, + pub virtualization_technology: Option>, + pub is_virtual_machine: Option, + pub l1_combined_cache: Option, + pub l2_cache: Option, + pub l3_cache: Option, + pub l4_cache: Option, +} + +impl Default for CpuStaticInfo { + fn default() -> Self { + Self { + name: Arc::from(""), + logical_cpu_count: 0, + socket_count: None, + base_frequency_khz: None, + virtualization_technology: None, + is_virtual_machine: None, + l1_combined_cache: None, + l2_cache: None, + l3_cache: None, + l4_cache: None, + } + } +} + +impl Arg for CpuStaticInfo { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("(suytyytttt)") + } +} + +impl ReadAll for CpuStaticInfo { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for CpuStaticInfo { + fn get(i: &mut Iter<'a>) -> Option { + + let mut this = Self::default(); + + let static_info = match Iterator::next(i) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '0: STRUCT', got None", + ); + return None; + } + Some(id) => id, + }; + + let mut static_info = match static_info.as_iter() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '0: STRUCT', got None, failed to iterate over fields", + ); + return None; + } + Some(i) => i, + }; + let static_info = static_info.as_mut(); + + this.name = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '0: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '0: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(n) => Arc::::from(n), + }, + }; + + this.logical_cpu_count = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '1: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '1: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(lcc) => lcc as _, + }, + }; + + this.socket_count = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '2: y', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '2: y', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(sc) => { + if sc == 0 { + None + } else { + Some(sc as _) + } + } + }, + }; + + this.base_frequency_khz = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '3: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '3: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(bf) => { + if bf == 0 { + None + } else { + Some(bf) + } + } + }, + }; + + this.virtualization_technology = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '4: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '4: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ivs) => match ivs { + "" => None, + _ => Some(Arc::from(ivs)), + }, + }, + }; + + this.is_virtual_machine = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '5: y', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '5: y', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ivm) => match ivm { + 0 => Some(false), + 1 => Some(true), + _ => None, + }, + }, + }; + + this.l1_combined_cache = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '6: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '6: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(l1) => { + if l1 == 0 { + None + } else { + Some(l1) as _ + } + } + }, + }; + + this.l2_cache = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '7: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '7: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(l2) => { + if l2 == 0 { + None + } else { + Some(l2) + } + } + }, + }; + + this.l3_cache = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '8: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '8: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(l3) => { + if l3 == 0 { + None + } else { + Some(l3) + } + } + }, + }; + + this.l4_cache = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '9: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get CpuStaticInfo: Expected '9: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(l4) => { + if l4 == 0 { + None + } else { + Some(l4) + } + } + }, + }; + + Some(this) + } +} diff --git a/src/system_info/dbus_interface/disk_info.rs b/src/system_info/dbus_interface/disk_info.rs new file mode 100644 index 0000000..708fd58 --- /dev/null +++ b/src/system_info/dbus_interface/disk_info.rs @@ -0,0 +1,392 @@ +/* sys_info_v2/dbus_interface/disk_static_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; + +use dbus::{arg::*, strings::*}; + +#[allow(non_camel_case_types)] +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum DiskType { + Unknown = 0, + HDD, + SSD, + NVMe, + eMMC, + SD, + iSCSI, + Optical, +} + +impl Default for DiskType { + fn default() -> Self { + Self::Unknown + } +} + +#[derive(Debug, Clone)] +pub struct DiskInfo { + pub id: Arc, + pub model: Arc, + pub r#type: DiskType, + pub capacity: u64, + pub formatted: u64, + pub system_disk: bool, + + pub busy_percent: f32, + pub response_time_ms: f32, + pub read_speed: u64, + pub write_speed: u64, +} + +impl Default for DiskInfo { + fn default() -> Self { + Self { + id: Arc::from(""), + model: Arc::from(""), + r#type: DiskType::default(), + capacity: 0, + formatted: 0, + system_disk: false, + + busy_percent: 0., + response_time_ms: 0., + read_speed: 0, + write_speed: 0, + } + } +} + +impl Eq for DiskInfo {} + +impl PartialEq for DiskInfo { + fn eq(&self, other: &Self) -> bool { + self.id.as_ref() == other.id.as_ref() + } +} + +impl PartialOrd for DiskInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.id.as_ref().cmp(other.id.as_ref())) + } +} + +impl Ord for DiskInfo { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.id.as_ref().cmp(other.id.as_ref()) + } +} + +pub struct DiskInfoVec(pub Vec); + +impl From for Vec { + fn from(v: DiskInfoVec) -> Self { + v.0 + } +} + +impl Arg for DiskInfoVec { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("a(ssyttbddtt)") + } +} + +impl ReadAll for DiskInfoVec { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for DiskInfoVec { + fn get(i: &mut Iter<'a>) -> Option { + + let mut result = vec![]; + + match Iterator::next(i) { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for i in arr { + let mut this = DiskInfo::default(); + + let mut i = match i.as_iter() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '0: STRUCT', got None", + ); + continue; + } + Some(i) => i, + }; + let disk_info = i.as_mut(); + + this.id = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '0: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '0: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(n) => Arc::::from(n), + }, + }; + + this.model = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '1: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '1: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(m) => Arc::::from(m), + }, + }; + + this.r#type = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '2: y', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '2: y', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(t) => match t { + 1 => DiskType::HDD, + 2 => DiskType::SSD, + 3 => DiskType::NVMe, + 4 => DiskType::eMMC, + 5 => DiskType::SD, + 6 => DiskType::iSCSI, + 7 => DiskType::Optical, + _ => DiskType::Unknown, + }, + }, + }; + + this.capacity = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '3: t', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '3: t', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(c) => c, + }, + }; + + this.formatted = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '4: t', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '4: t', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(f) => f, + }, + }; + + this.system_disk = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '5: b', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '5: b', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(ivm) => match ivm { + 1 => true, + _ => false, + }, + }, + }; + + this.busy_percent = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '6: d', got None", + ); + continue; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '6: d', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(bp) => bp as _, + }, + }; + + this.response_time_ms = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '7: d', got None", + ); + continue; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '7: d', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(rt) => rt as _, + }, + }; + + this.read_speed = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '8: t', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '8: t', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(rs) => rs, + }, + }; + + this.write_speed = match Iterator::next(disk_info) { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '9: t', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get DiskInfo: Expected '9: t', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(ws) => ws, + }, + }; + + result.push(this); + } + } + }, + } + + Some(DiskInfoVec(result)) + } +} diff --git a/src/system_info/dbus_interface/fan_info.rs b/src/system_info/dbus_interface/fan_info.rs new file mode 100644 index 0000000..3851771 --- /dev/null +++ b/src/system_info/dbus_interface/fan_info.rs @@ -0,0 +1,323 @@ +/* sys_info_v2/gatherer/src/platform/fan_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ +use dbus::arg::{Arg, ArgType, Get, Iter, ReadAll, RefArg, TypeMismatchError}; +use dbus::Signature; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct FanInfo { + pub fan_label: Arc, + pub temp_name: Arc, + pub temp_amount: i64, + pub rpm: u64, + pub percent_vroomimg: f32, + + pub fan_index: u64, + pub hwmon_index: u64, + + pub max_speed: u64, +} + +impl Default for FanInfo { + fn default() -> Self { + Self { + fan_label: Arc::from(""), + temp_name: Arc::from(""), + temp_amount: 0, + rpm: 0, + percent_vroomimg: 0.0, + + fan_index: 0, + hwmon_index: 0, + + max_speed: 0, + } + } +} + +impl Eq for FanInfo {} + +impl PartialEq for FanInfo { + fn eq(&self, other: &Self) -> bool { + self.fan_index == other.fan_index && self.hwmon_index == other.hwmon_index + } +} + +impl PartialOrd for FanInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(if self.hwmon_index == other.hwmon_index { + self.fan_index.cmp(&other.fan_index) + } else { + self.hwmon_index.cmp(&other.hwmon_index) + }) + } +} + +impl Ord for FanInfo { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if self.hwmon_index == other.hwmon_index { + self.fan_index.cmp(&other.fan_index) + } else { + self.hwmon_index.cmp(&other.hwmon_index) + } + } +} + +pub struct FanInfoVec(pub Vec); + +impl From for Vec { + fn from(v: FanInfoVec) -> Self { + v.0 + } +} + +impl Arg for FanInfoVec { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("a(ssxtdttt)") + } +} + +impl ReadAll for FanInfoVec { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for FanInfoVec { + fn get(i: &mut Iter<'a>) -> Option { + + let mut result = vec![]; + + match Iterator::next(i) { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for i in arr { + let mut this = FanInfo::default(); + + let mut i = match i.as_iter() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '0: STRUCT', got None", + ); + continue; + } + Some(i) => i, + }; + let fan_info = i.as_mut(); + + this.fan_label = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '0: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '0: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(n) => Arc::::from(n), + }, + }; + + this.temp_name = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '1: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '1: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(m) => Arc::::from(m), + }, + }; + + this.temp_amount = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '2: y', got None", + ); + continue; + } + Some(arg) => match arg.as_i64() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '2: y', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(s) => s, + }, + }; + + this.rpm = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '3: t', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '3: t', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(c) => c, + }, + }; + + this.percent_vroomimg = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '4: t', got None", + ); + continue; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '4: t', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(f) => f as f32, + }, + }; + + this.fan_index = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '5: b', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '5: b', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(i) => i, + }, + }; + + this.hwmon_index = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '6: d', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '6: d', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(bp) => bp, + }, + }; + + this.max_speed = match Iterator::next(fan_info) { + None => { + log::error!( + + "Failed to get FanInfo: Expected '7: d', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get FanInfo: Expected '7: d', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(bp) => bp, + }, + }; + + result.push(this); + } + } + }, + } + + Some(FanInfoVec(result)) + } +} diff --git a/src/system_info/dbus_interface/gpu_dynamic_info.rs b/src/system_info/dbus_interface/gpu_dynamic_info.rs new file mode 100644 index 0000000..20ec120 --- /dev/null +++ b/src/system_info/dbus_interface/gpu_dynamic_info.rs @@ -0,0 +1,474 @@ +/* sys_info_v2/dbus_interface/gpu_dynamic_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; + +use dbus::{arg::*, strings::*}; + +#[derive(Debug, Clone)] +pub struct GpuDynamicInfo { + pub id: Arc, + pub temp_celsius: u32, + pub fan_speed_percent: u32, + pub util_percent: u32, + pub power_draw_watts: f32, + pub power_draw_max_watts: f32, + pub clock_speed_mhz: u32, + pub clock_speed_max_mhz: u32, + pub mem_speed_mhz: u32, + pub mem_speed_max_mhz: u32, + pub free_memory: u64, + pub used_memory: u64, + pub used_gtt: u64, + pub encoder_percent: u32, + pub decoder_percent: u32, +} + +impl Default for GpuDynamicInfo { + fn default() -> Self { + Self { + id: Arc::from(""), + temp_celsius: 0, + fan_speed_percent: 0, + util_percent: 0, + power_draw_watts: 0.0, + power_draw_max_watts: 0.0, + clock_speed_mhz: 0, + clock_speed_max_mhz: 0, + mem_speed_mhz: 0, + mem_speed_max_mhz: 0, + free_memory: 0, + used_memory: 0, + used_gtt: 0, + encoder_percent: 0, + decoder_percent: 0, + } + } +} + +pub struct GpuDynamicInfoVec(pub Vec); + +impl From for Vec { + fn from(v: GpuDynamicInfoVec) -> Self { + v.0 + } +} + +impl From> for GpuDynamicInfoVec { + fn from(v: Vec) -> Self { + GpuDynamicInfoVec(v) + } +} + +impl Arg for GpuDynamicInfoVec { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + dbus::Signature::from("a(suuudduuuuttuu)") + } +} + +impl ReadAll for GpuDynamicInfoVec { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for GpuDynamicInfoVec { + fn get(i: &mut Iter<'a>) -> Option { + let mut result = vec![]; + + match Iterator::next(i) { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for dynamic_info in arr { + let mut this = GpuDynamicInfo { + id: Arc::from(""), + temp_celsius: 0, + fan_speed_percent: 0, + util_percent: 0, + power_draw_watts: 0.0, + power_draw_max_watts: 0.0, + clock_speed_mhz: 0, + clock_speed_max_mhz: 0, + mem_speed_mhz: 0, + mem_speed_max_mhz: 0, + free_memory: 0, + used_memory: 0, + used_gtt: 0, + encoder_percent: 0, + decoder_percent: 0, + }; + + let mut dynamic_info = match dynamic_info.as_iter() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '0: STRUCT', got None, failed to iterate over fields", + ); + return None; + } + Some(i) => i, + }; + let dynamic_info = dynamic_info.as_mut(); + + this.id = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '0: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '0: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(id) => Arc::::from(id), + }, + }; + + this.temp_celsius = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '1: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '1: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(temp) => temp as _, + }, + }; + + this.fan_speed_percent = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '2: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '2: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(fs) => fs as _, + }, + }; + + this.util_percent = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '3: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '3: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(up) => up as _, + }, + }; + + this.power_draw_watts = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '4: d', got None", + ); + return None; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '4: d', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(pd) => pd as _, + }, + }; + + this.power_draw_max_watts = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '5: d', got None", + ); + return None; + } + Some(arg) => match arg.as_f64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '5: d', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(pdm) => pdm as _, + }, + }; + + this.clock_speed_mhz = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '6: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '6: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(cs) => cs as _, + }, + }; + + this.clock_speed_max_mhz = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '7: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '7: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(csm) => csm as _, + }, + }; + + this.mem_speed_mhz = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '8: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '8: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ms) => ms as _, + }, + }; + + this.mem_speed_max_mhz = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '9: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '9: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(msm) => msm as _, + }, + }; + + this.free_memory = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '10: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '10: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(fm) => fm as _, + }, + }; + + this.used_memory = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '11: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '11: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(um) => um as _, + }, + }; + + this.used_gtt = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '12: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '12: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(fm) => fm as _, + }, + }; + + this.encoder_percent = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '13: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '13: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(ep) => ep as _, + }, + }; + + this.decoder_percent = match Iterator::next(dynamic_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '14: u', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '14: u', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(dp) => dp as _, + }, + }; + + result.push(this); + } + + Some(result.into()) + } + }, + } + } +} diff --git a/src/system_info/dbus_interface/gpu_static_info.rs b/src/system_info/dbus_interface/gpu_static_info.rs new file mode 100644 index 0000000..839bfb0 --- /dev/null +++ b/src/system_info/dbus_interface/gpu_static_info.rs @@ -0,0 +1,489 @@ +/* sys_info_v2/dbus_interface/gpu_static_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::sync::Arc; + +use dbus::{arg::*, strings::*}; + +#[repr(u8)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum OpenGLApi { + OpenGL, + OpenGLES, + Invalid = 255, +} + +#[derive(Debug, Copy, Clone)] +pub struct OpenGLApiVersion { + pub major: u8, + pub minor: u8, + pub api: OpenGLApi, +} + +#[derive(Default, Debug, Copy, Clone)] +pub struct ApiVersion { + pub major: u16, + pub minor: u16, + pub patch: u16, +} + +#[derive(Debug, Clone)] +pub struct GpuStaticInfo { + pub id: Arc, + pub device_name: Arc, + pub vendor_id: u16, + pub device_id: u16, + pub total_memory: u64, + pub total_gtt: u64, + pub opengl_version: Option, + pub vulkan_version: Option, + pub metal_version: Option, + pub direct3d_version: Option, + pub pcie_gen: u8, + pub pcie_lanes: u8, +} + +impl Default for GpuStaticInfo { + fn default() -> Self { + let empty = Arc::::from(""); + GpuStaticInfo { + id: empty.clone(), + device_name: empty, + vendor_id: 0, + device_id: 0, + total_memory: 0, + total_gtt: 0, + opengl_version: None, + vulkan_version: None, + metal_version: None, + direct3d_version: None, + pcie_gen: 0, + pcie_lanes: 0, + } + } +} + +pub struct GpuStaticInfoVec(pub Vec); + +impl From for Vec { + fn from(v: GpuStaticInfoVec) -> Self { + v.0 + } +} + +impl From> for GpuStaticInfoVec { + fn from(v: Vec) -> Self { + GpuStaticInfoVec(v) + } +} + +impl Arg for GpuStaticInfoVec { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + dbus::Signature::from("a(ssqqt(yyy)(qqq)(qqq)(qqq)yy)") + } +} + +impl ReadAll for GpuStaticInfoVec { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for GpuStaticInfoVec { + fn get(i: &mut Iter<'a>) -> Option { + let mut result = vec![]; + + match Iterator::next(i) { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for static_info in arr { + let empty_string = Arc::::from(""); + + let mut info = GpuStaticInfo { + id: empty_string.clone(), + device_name: empty_string, + vendor_id: 0, + device_id: 0, + total_memory: 0, + total_gtt: 0, + opengl_version: None, + vulkan_version: None, + metal_version: None, + direct3d_version: None, + pcie_gen: 0, + pcie_lanes: 0, + }; + + let mut static_info = match static_info.as_iter() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '0: STRUCT', got None, failed to iterate over fields", + ); + return None; + } + Some(i) => i, + }; + let static_info = static_info.as_mut(); + + info.id = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '0: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '0: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(id) => Arc::::from(id), + }, + }; + + info.device_name = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '1: s', got None", + ); + return None; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '1: s', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(id) => Arc::::from(id), + }, + }; + + info.vendor_id = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '2: q', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '2: q', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(vendor_id) => vendor_id as _, + }, + }; + + info.device_id = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '3: q', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '3: q', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(device_id) => device_id as _, + }, + }; + + info.total_memory = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '4: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '4: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(total_memory) => total_memory, + }, + }; + + info.total_gtt = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '5: t', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuDynamicInfo: Expected '5: t', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(um) => um as _, + }, + }; + info.opengl_version = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '6: STRUCT', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '6: STRUCT', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(mut it) => { + let major = if let Some(major) = Iterator::next(it.as_mut()) { + major.as_u64().unwrap_or(0) + } else { + log::error!( + + "Failed to get GpuStaticInfo(OpenGLVersion): Expected '6-0: y', got None", + ); + + 0 + }; + + let minor = if let Some(minor) = Iterator::next(it.as_mut()) { + minor.as_u64().unwrap_or(0) + } else { + log::error!( + + "Failed to get GpuStaticInfo(OpenGLVersion): Expected '6-1: y', got None", + ); + + 0 + }; + + let gl_api = if let Some(minor) = Iterator::next(it.as_mut()) { + match minor.as_u64().unwrap_or(OpenGLApi::Invalid as u64) { + 0 => OpenGLApi::OpenGL, + 1 => OpenGLApi::OpenGLES, + _ => OpenGLApi::Invalid, + } + } else { + log::error!( + + "Failed to get GpuStaticInfo(OpenGLVersion): Expected '6-2: y', got None", + ); + + OpenGLApi::Invalid + }; + + if major == 0 || minor == 0 || gl_api == OpenGLApi::Invalid { + None + } else { + Some(OpenGLApiVersion { + major: major as u8, + minor: minor as u8, + api: gl_api, + }) + } + } + }, + }; + + let mut api_versions = [None; 3]; + for i in 0..3 { + api_versions[i] = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '{}: STRUCT', got None", + i + 7, + ); + return None; + } + Some(id) => match id.as_iter() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '{}: STRUCT', got {:?}", + i + 7, + id.arg_type(), + ); + return None; + } + Some(mut it) => { + let major = if let Some(major) = Iterator::next(it.as_mut()) + { + major.as_u64().unwrap_or(0) + } else { + log::error!( + + "Failed to get GpuStaticInfo(ApiVersion): Expected '{}-0: y', got None", + i + 7 + ); + + 0 + }; + + let minor = if let Some(minor) = Iterator::next(it.as_mut()) + { + minor.as_u64().unwrap_or(0) + } else { + log::error!( + + "Failed to get GpuStaticInfo(ApiVersion): Expected '{}-1: y', got None", + i + 7 + ); + + 0 + }; + + let patch = if let Some(patch) = Iterator::next(it.as_mut()) + { + patch.as_u64().unwrap_or(0) + } else { + log::error!( + + "Failed to get GpuStaticInfo(ApiVersion): Expected '{}-1: y', got None", + i + 7 + ); + + 0 + }; + + if major == 0 { + None + } else { + Some(ApiVersion { + major: major as u16, + minor: minor as u16, + patch: patch as u16, + }) + } + } + }, + } + } + + info.vulkan_version = api_versions[0]; + info.metal_version = api_versions[1]; + info.direct3d_version = api_versions[2]; + + info.pcie_gen = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '10: y', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '10: y', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(pcie_gen) => pcie_gen as u8, + }, + }; + + info.pcie_lanes = match Iterator::next(static_info) { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '11: y', got None", + ); + return None; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get GpuStaticInfo: Expected '11: y', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(pcie_lanes) => pcie_lanes as u8, + }, + }; + + result.push(info); + } + + Some(result.into()) + } + }, + } + } +} diff --git a/src/system_info/dbus_interface/mod.rs b/src/system_info/dbus_interface/mod.rs new file mode 100644 index 0000000..55b3a8a --- /dev/null +++ b/src/system_info/dbus_interface/mod.rs @@ -0,0 +1,216 @@ +/* sys_info_v2/dbus_interface/mod.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::{ + collections::HashMap, + mem::{align_of, size_of}, + num::NonZeroU32, + rc::Rc, + sync::Arc, +}; + +use dbus::{ + arg::ArgType, + blocking::{LocalConnection, Proxy}, +}; +use static_assertions::const_assert; + +pub use apps::{App, AppMap}; +pub use arc_str_vec::ArcStrVec; +pub use cpu_dynamic_info::CpuDynamicInfo; +pub use cpu_static_info::CpuStaticInfo; +pub use disk_info::{DiskInfo, DiskInfoVec, DiskType}; +pub use fan_info::{FanInfo, FanInfoVec}; +pub use gpu_dynamic_info::{GpuDynamicInfo, GpuDynamicInfoVec}; +pub use gpu_static_info::{GpuStaticInfo, GpuStaticInfoVec, OpenGLApi}; +pub use processes::{Process, ProcessMap, ProcessUsageStats}; +pub use service::{Service, ServiceMap}; + +mod apps; +mod arc_str_vec; +mod cpu_dynamic_info; +mod cpu_static_info; +mod disk_info; +mod fan_info; +mod gpu_dynamic_info; +mod gpu_static_info; +mod processes; +mod service; + +pub const OD_OBJECT_PATH: &str = "/io/github/cosmic_utils/observatory_daemon"; +pub const OD_INTERFACE_NAME: &str = "io.github.cosmic_utils.observatory_daemon"; + +// I don't know how to create one of these, so I just copy the one from the `dbus` crate. +#[allow(unused)] +struct TypeMismatchError { + pub expected: ArgType, + pub found: ArgType, + pub position: u32, +} + +impl TypeMismatchError { + pub fn new(expected: ArgType, found: ArgType, position: u32) -> dbus::arg::TypeMismatchError { + unsafe { + std::mem::transmute(Self { + expected, + found, + position, + }) + } + } +} + +const_assert!(size_of::() == size_of::()); +const_assert!(align_of::() == align_of::()); + +pub trait Gatherer { + fn get_cpu_static_info(&self) -> Result; + fn get_cpu_dynamic_info(&self) -> Result; + fn get_disks_info(&self) -> Result, dbus::Error>; + fn get_fans_info(&self) -> Result, dbus::Error>; + fn get_gpu_list(&self) -> Result>, dbus::Error>; + fn get_gpu_static_info(&self) -> Result, dbus::Error>; + fn get_gpu_dynamic_info(&self) -> Result, dbus::Error>; + fn get_apps(&self) -> Result, App>, dbus::Error>; + fn get_processes(&self) -> Result, dbus::Error>; + fn get_services(&self) -> Result, Service>, dbus::Error>; + fn terminate_process(&self, process_id: u32) -> Result<(), dbus::Error>; + fn kill_process(&self, process_id: u32) -> Result<(), dbus::Error>; + fn enable_service(&self, service_name: &str) -> Result<(), dbus::Error>; + fn disable_service(&self, service_name: &str) -> Result<(), dbus::Error>; + fn start_service(&self, service_name: &str) -> Result<(), dbus::Error>; + fn stop_service(&self, service_name: &str) -> Result<(), dbus::Error>; + fn restart_service(&self, service_name: &str) -> Result<(), dbus::Error>; + fn get_service_logs( + &self, + service_name: &str, + pid: Option, + ) -> Result, dbus::Error>; +} + +impl<'a> Gatherer for Proxy<'a, Rc> { + fn get_cpu_static_info(&self) -> Result { + self.method_call(OD_INTERFACE_NAME, "GetCPUStaticInfo", ()) + } + + fn get_cpu_dynamic_info(&self) -> Result { + self.method_call(OD_INTERFACE_NAME, "GetCPUDynamicInfo", ()) + } + + fn get_disks_info(&self) -> Result, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetDisksInfo", ()); + res.map(|v| v.into()) + } + + fn get_fans_info(&self) -> Result, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetFansInfo", ()); + res.map(|v| v.into()) + } + + fn get_gpu_list(&self) -> Result>, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetGPUList", ()); + res.map(|v| v.into()) + } + + fn get_gpu_static_info(&self) -> Result, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetGPUStaticInfo", ()); + res.map(|v| v.into()) + } + + fn get_gpu_dynamic_info(&self) -> Result, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetGPUDynamicInfo", ()); + res.map(|v| v.into()) + } + + fn get_apps(&self) -> Result, App>, dbus::Error> { + let res: Result = self.method_call(OD_INTERFACE_NAME, "GetApps", ()); + res.map(|v| v.into()) + } + + fn get_processes(&self) -> Result, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetProcesses", ()); + res.map(|v| v.into()) + } + + fn get_services(&self) -> Result, Service>, dbus::Error> { + let res: Result = + self.method_call(OD_INTERFACE_NAME, "GetServices", ()); + res.map(|v| v.into()) + } + + fn terminate_process(&self, process_id: u32) -> Result<(), dbus::Error> { + self.method_call( + OD_INTERFACE_NAME, + "TerminateProcess", + (process_id,), + ) + } + + fn kill_process(&self, process_id: u32) -> Result<(), dbus::Error> { + self.method_call(OD_INTERFACE_NAME, "KillProcess", (process_id,)) + } + + fn enable_service(&self, service_name: &str) -> Result<(), dbus::Error> { + self.method_call(OD_INTERFACE_NAME, "EnableService", (service_name,)) + } + + fn disable_service(&self, service_name: &str) -> Result<(), dbus::Error> { + self.method_call( + OD_INTERFACE_NAME, + "DisableService", + (service_name,), + ) + } + + fn start_service(&self, service_name: &str) -> Result<(), dbus::Error> { + self.method_call(OD_INTERFACE_NAME, "StartService", (service_name,)) + } + + fn stop_service(&self, service_name: &str) -> Result<(), dbus::Error> { + self.method_call(OD_INTERFACE_NAME, "StopService", (service_name,)) + } + + fn restart_service(&self, service_name: &str) -> Result<(), dbus::Error> { + self.method_call( + OD_INTERFACE_NAME, + "RestartService", + (service_name,), + ) + } + + fn get_service_logs( + &self, + service_name: &str, + pid: Option, + ) -> Result, dbus::Error> { + let res: Result<(String,), _> = self.method_call( + OD_INTERFACE_NAME, + "GetServiceLogs", + (service_name, pid.map(|v| v.get()).unwrap_or(0)), + ); + res.map(|v| Arc::::from(v.0)) + } +} diff --git a/src/system_info/dbus_interface/processes.rs b/src/system_info/dbus_interface/processes.rs new file mode 100644 index 0000000..ad1a546 --- /dev/null +++ b/src/system_info/dbus_interface/processes.rs @@ -0,0 +1,379 @@ +/* sys_info_v2/dbus_interface/processes.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::{collections::HashMap, sync::Arc}; + +use dbus::{arg::*, strings::*}; + +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum ProcessState { + Running = 0, + Sleeping = 1, + SleepingUninterruptible = 2, + Zombie = 3, + Stopped = 4, + Tracing = 5, + Dead = 6, + WakeKill = 7, + Waking = 8, + Parked = 9, + Unknown = 10, // Keep this last and increase it +} + +#[derive(Debug, Default, Copy, Clone)] +pub struct ProcessUsageStats { + pub cpu_usage: f32, + pub memory_usage: f32, + pub disk_usage: f32, + pub network_usage: f32, + pub gpu_usage: f32, + pub gpu_memory_usage: f32, +} + +impl ProcessUsageStats { + pub fn merge(&mut self, other: &Self) { + self.cpu_usage += other.cpu_usage; + self.memory_usage += other.memory_usage; + self.disk_usage += other.disk_usage; + self.network_usage += other.network_usage; + self.gpu_usage += other.gpu_usage; + self.gpu_memory_usage += other.gpu_memory_usage; + } +} + +#[derive(Debug, Clone)] +pub struct Process { + pub name: Arc, + pub cmd: Vec>, + pub exe: Arc, + pub state: ProcessState, + pub pid: u32, + pub parent: u32, + pub usage_stats: ProcessUsageStats, + pub merged_usage_stats: ProcessUsageStats, + pub task_count: usize, + pub children: HashMap, +} + +impl Default for Process { + fn default() -> Self { + let empty_string = Arc::::from(""); + + Self { + name: empty_string.clone(), + cmd: vec![], + exe: empty_string, + state: ProcessState::Unknown, + pid: 0, + parent: 0, + usage_stats: Default::default(), + merged_usage_stats: Default::default(), + task_count: 0, + children: HashMap::new(), + } + } +} + +impl From<&dyn RefArg> for Process { + fn from(value: &dyn RefArg) -> Self { + + let mut this = Self::default(); + + let mut process = match value.as_iter() { + None => { + log::error!( + + "Failed to get Process: Expected '0: STRUCT', got None, failed to iterate over fields", + ); + return this; + } + Some(i) => i, + }; + let process = process.as_mut(); + + this.name = match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '0: s', got None", + ); + return this; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get Process: Expected '0: s', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(n) => Arc::from(n), + }, + }; + + match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '1: ARRAY', got None", + ); + return this; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Process: Expected '1: ARRAY', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(cmds) => { + for c in cmds { + if let Some(c) = c.as_str() { + this.cmd.push(Arc::from(c)); + } + } + } + }, + } + + this.exe = match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '3: s', got None", + ); + return this; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get Process: Expected '3: s', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(e) => Arc::from(e), + }, + }; + + this.state = match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '4: y', got None", + ); + return this; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get Process: Expected '4: y', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(u) => { + if u < ProcessState::Unknown as u64 { + unsafe { core::mem::transmute(u as u8) } + } else { + ProcessState::Unknown + } + } + }, + }; + + this.pid = match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '5: u', got None", + ); + return this; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get Process: Expected '5: u', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(p) => p as _, + }, + }; + + this.parent = match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '6: u', got None", + ); + return this; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get Process: Expected '6: u', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(p) => p as _, + }, + }; + + match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '7: STRUCT', got None", + ); + return this; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Process: Expected '7: STRUCT', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(stats) => { + let mut values = [0_f32; 6]; + + for (i, v) in stats.enumerate() { + values[i] = v.as_f64().unwrap_or(0.) as f32; + } + + this.usage_stats.cpu_usage = values[0]; + this.usage_stats.memory_usage = values[1]; + this.usage_stats.disk_usage = values[2]; + this.usage_stats.network_usage = values[3]; + this.usage_stats.gpu_usage = values[4]; + this.usage_stats.gpu_memory_usage = values[5]; + + this.merged_usage_stats = this.usage_stats; + } + }, + }; + + this.task_count = match Iterator::next(process) { + None => { + log::error!( + + "Failed to get Process: Expected '14: t', got None", + ); + return this; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get Process: Expected '14: t', got {:?}", + arg.arg_type(), + ); + return this; + } + Some(tc) => tc as _, + }, + }; + + this + } +} + +pub struct ProcessMap(HashMap); + +impl From> for ProcessMap { + fn from(value: HashMap) -> Self { + Self(value) + } +} + +impl From for HashMap { + fn from(value: ProcessMap) -> Self { + value.0 + } +} + +impl Arg for ProcessMap { + const ARG_TYPE: ArgType = ArgType::Array; + + fn signature() -> Signature<'static> { + Signature::from("a(sassyuu(dddddd)t)") + } +} + +impl ReadAll for ProcessMap { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for ProcessMap { + fn get(i: &mut Iter<'a>) -> Option { + + let mut this = HashMap::new(); + + match Iterator::next(i) { + None => { + log::error!( + + "Failed to get HashMap: Expected '0: ARRAY', got None", + ); + return None; + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get HashMap: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for p in arr { + let p = Process::from(p); + if p.pid == 0 { + continue; + } + this.insert(p.pid, p.clone()); + } + } + }, + } + + Some(this.into()) + } +} diff --git a/src/system_info/dbus_interface/service.rs b/src/system_info/dbus_interface/service.rs new file mode 100644 index 0000000..0bf6eaa --- /dev/null +++ b/src/system_info/dbus_interface/service.rs @@ -0,0 +1,315 @@ +/* sys_info_v2/dbus_interface/service.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::collections::HashMap; +use std::{num::NonZeroU32, sync::Arc}; + +use dbus::{ + arg::{Arg, ArgType, Get, Iter, ReadAll, RefArg, TypeMismatchError}, + Signature, +}; + +#[derive(Debug, Clone)] +pub struct Service { + pub name: Arc, + pub description: Arc, + pub enabled: bool, + pub running: bool, + pub failed: bool, + pub pid: Option, + pub user: Option>, + pub group: Option>, +} + +impl Default for Service { + fn default() -> Self { + let empty = Arc::::from(""); + Self { + name: empty.clone(), + description: empty.clone(), + enabled: false, + running: false, + failed: false, + pid: None, + user: None, + group: None, + } + } +} + +pub struct ServiceMap(HashMap, Service>); + +impl From, Service>> for ServiceMap { + fn from(value: HashMap, Service>) -> Self { + Self(value) + } +} + +impl From for HashMap, Service> { + fn from(value: ServiceMap) -> Self { + value.0 + } +} + +impl Arg for ServiceMap { + const ARG_TYPE: ArgType = ArgType::Struct; + + fn signature() -> Signature<'static> { + Signature::from("a(ssbbbuss)") + } +} + +impl ReadAll for ServiceMap { + fn read(i: &mut Iter) -> Result { + i.get().ok_or(super::TypeMismatchError::new( + ArgType::Invalid, + ArgType::Invalid, + 0, + )) + } +} + +impl<'a> Get<'a> for ServiceMap { + fn get(i: &mut Iter<'a>) -> Option { + + let mut result = HashMap::new(); + + match Iterator::next(i) { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got None", + ); + return Some(ServiceMap(result)); + } + Some(arg) => match arg.as_iter() { + None => { + log::error!( + + "Failed to get Vec: Expected '0: ARRAY', got {:?}", + arg.arg_type(), + ); + return None; + } + Some(arr) => { + for i in arr { + let mut this = Service::default(); + + let mut i = match i.as_iter() { + None => { + log::error!( + + "Failed to get Service: Expected '0: STRUCT', got None", + ); + continue; + } + Some(i) => i, + }; + let service = i.as_mut(); + + this.name = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '0: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get Service: Expected '0: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(n) => Arc::::from(n), + }, + }; + + this.description = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '1: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get Service: Expected '1: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(d) => Arc::::from(d), + }, + }; + + this.enabled = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '2: b', got None", + ); + continue; + } + Some(arg) => match arg.as_i64() { + None => { + log::error!( + + "Failed to get Service: Expected '2: b', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(e) => e != 0, + }, + }; + + this.running = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '3: b', got None", + ); + continue; + } + Some(arg) => match arg.as_i64() { + None => { + log::error!( + + "Failed to get Service: Expected '3: b', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(r) => r != 0, + }, + }; + + this.failed = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '4: b', got None", + ); + continue; + } + Some(arg) => match arg.as_i64() { + None => { + log::error!( + + "Failed to get Service: Expected '4: b', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(r) => r != 0, + }, + }; + + this.pid = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '5: u', got None", + ); + continue; + } + Some(arg) => match arg.as_u64() { + None => { + log::error!( + + "Failed to get Service: Expected '5: u', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(p) => NonZeroU32::new(p as u32), + }, + }; + + this.user = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '6: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get Service: Expected '6: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(u) => { + if u.is_empty() { + None + } else { + Some(Arc::::from(u)) + } + } + }, + }; + + this.group = match Iterator::next(service) { + None => { + log::error!( + + "Failed to get Service: Expected '7: s', got None", + ); + continue; + } + Some(arg) => match arg.as_str() { + None => { + log::error!( + + "Failed to get Service: Expected '7: s', got {:?}", + arg.arg_type(), + ); + continue; + } + Some(g) => { + if g.is_empty() { + None + } else { + Some(Arc::::from(g)) + } + } + }, + }; + + result.insert(this.name.clone(), this); + } + } + }, + } + + Some(ServiceMap(result)) + } +} diff --git a/src/system_info/mem_info.rs b/src/system_info/mem_info.rs new file mode 100644 index 0000000..1963f07 --- /dev/null +++ b/src/system_info/mem_info.rs @@ -0,0 +1,321 @@ +/* sys_info_v2/mem_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#[derive(Default, Debug, Eq, PartialEq)] +pub struct MemoryDevice { + pub size: usize, + pub form_factor: String, + pub locator: String, + pub bank_locator: String, + pub ram_type: String, + pub speed: usize, + pub rank: u8, +} + +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub struct MemInfo { + pub mem_total: usize, + pub mem_free: usize, + pub mem_available: usize, + pub buffers: usize, + pub cached: usize, + pub swap_cached: usize, + pub active: usize, + pub inactive: usize, + pub active_anon: usize, + pub inactive_anon: usize, + pub active_file: usize, + pub inactive_file: usize, + pub unevictable: usize, + pub m_locked: usize, + pub swap_total: usize, + pub swap_free: usize, + pub zswap: usize, + pub zswapped: usize, + pub dirty: usize, + pub writeback: usize, + pub anon_pages: usize, + pub mapped: usize, + pub sh_mem: usize, + pub k_reclaimable: usize, + pub slab: usize, + pub s_reclaimable: usize, + pub s_unreclaim: usize, + pub kernel_stack: usize, + pub page_tables: usize, + pub sec_page_tables: usize, + pub nfs_unstable: usize, + pub bounce: usize, + pub writeback_tmp: usize, + pub commit_limit: usize, + pub committed: usize, + pub vmalloc_total: usize, + pub vmalloc_used: usize, + pub vmalloc_chunk: usize, + pub percpu: usize, + pub hardware_corrupted: usize, + pub anon_huge_pages: usize, + pub shmem_huge_pages: usize, + pub shmem_pmd_mapped: usize, + pub file_huge_pages: usize, + pub file_pmd_mapped: usize, + pub cma_total: usize, + pub cma_free: usize, + pub huge_pages_total: usize, + pub huge_pages_free: usize, + pub huge_pages_rsvd: usize, + pub huge_pages_surp: usize, + pub hugepagesize: usize, + pub hugetlb: usize, + pub direct_map4k: usize, + pub direct_map2m: usize, + pub direct_map1g: usize, +} + +impl MemInfo { + pub fn load() -> Option { + + let meminfo = if let Ok(output) = std::process::Command::new("cat").arg("/proc/meminfo").output() { + if output.stderr.len() > 0 { + log::error!( + "Failed to refresh memory information, host command execution failed: {}", + String::from_utf8_lossy(output.stderr.as_slice()) + ); + return None; + } + + String::from_utf8_lossy(output.stdout.as_slice()).into_owned() + } else { + log::error!( + "Failed to refresh memory information, host command execution failed" + ); + + return None; + }; + + let mut this = Self::default(); + + for line in meminfo.trim().lines() { + let mut split = line.split_whitespace(); + let key = split.next().map_or("", |s| s); + let value = split + .next() + .map_or("", |s| s) + .parse::() + .map_or(0, |v| v) + * 1024; + + match key { + "MemTotal:" => this.mem_total = value, + "MemFree:" => this.mem_free = value, + "MemAvailable:" => this.mem_available = value, + "Buffers:" => this.buffers = value, + "Cached:" => this.cached = value, + "SwapCached:" => this.swap_cached = value, + "Active:" => this.active = value, + "Inactive:" => this.inactive = value, + "Active(anon):" => this.active_anon = value, + "Inactive(anon):" => this.inactive_anon = value, + "Active(file):" => this.active_file = value, + "Inactive(file):" => this.inactive_file = value, + "Unevictable:" => this.unevictable = value, + "Mlocked:" => this.m_locked = value, + "SwapTotal:" => this.swap_total = value, + "SwapFree:" => this.swap_free = value, + "ZSwap:" => this.zswap = value, + "ZSwapTotal:" => this.zswapped = value, + "Dirty:" => this.dirty = value, + "Writeback:" => this.writeback = value, + "AnonPages:" => this.anon_pages = value, + "Mapped:" => this.mapped = value, + "Shmem:" => this.sh_mem = value, + "KReclaimable:" => this.k_reclaimable = value, + "Slab:" => this.slab = value, + "SReclaimable:" => this.s_reclaimable = value, + "SUnreclaim:" => this.s_unreclaim = value, + "KernelStack:" => this.kernel_stack = value, + "PageTables:" => this.page_tables = value, + "SecMemTables:" => this.sec_page_tables = value, + "NFS_Unstable:" => this.nfs_unstable = value, + "Bounce:" => this.bounce = value, + "WritebackTmp:" => this.writeback_tmp = value, + "CommitLimit:" => this.commit_limit = value, + "Committed_AS:" => this.committed = value, + "VmallocTotal:" => this.vmalloc_total = value, + "VmallocUsed:" => this.vmalloc_used = value, + "VmallocChunk:" => this.vmalloc_chunk = value, + "Percpu:" => this.percpu = value, + "HardwareCorrupted:" => this.hardware_corrupted = value, + "AnonHugePages:" => this.anon_huge_pages = value, + "ShmemHugePages:" => this.shmem_huge_pages = value, + "ShmemPmdMapped:" => this.shmem_pmd_mapped = value, + "FileHugePages:" => this.file_huge_pages = value, + "FilePmdMapped:" => this.file_pmd_mapped = value, + "CmaTotal:" => this.cma_total = value, + "CmaFree:" => this.cma_free = value, + "HugePages_Total:" => this.huge_pages_total = value / 1024, + "HugePages_Free:" => this.huge_pages_free = value / 1024, + "HugePages_Rsvd:" => this.huge_pages_rsvd = value / 1024, + "HugePages_Surp:" => this.huge_pages_surp = value / 1024, + "Hugepagesize:" => this.hugepagesize = value, + "Hugetlb:" => this.hugetlb = value, + "DirectMap4k:" => this.direct_map4k = value, + "DirectMap2M:" => this.direct_map2m = value, + "DirectMap1G:" => this.direct_map1g = value, + _ => (), + } + } + + Some(this) + } + + pub fn load_memory_device_info() -> Option> { + use std::process::*; + + let is_flatpak = false; //*super::IS_FLATPAK; + let mut cmd = {//if !is_flatpak { + let mut cmd = Command::new("udevadm"); + cmd.arg("info") + .arg("-q") + .arg("property") + .arg("-p") + .arg("/sys/devices/virtual/dmi/id"); + cmd.env_remove("LD_PRELOAD"); + cmd + //} else { + // let mut cmd = + // cmd_flatpak_host!("udevadm info -q property -p /sys/devices/virtual/dmi/id"); + // cmd.env_remove("LD_PRELOAD"); + // cmd + }; + + let cmd_output = match cmd.output() { + Ok(output) => { + if output.stderr.len() > 0 { + log::error!( + "Failed to read memory device information, host command execution failed: {}", + std::str::from_utf8(output.stderr.as_slice()).unwrap_or("Unknown error") + ); + return None; + } + + match std::str::from_utf8(output.stdout.as_slice()) { + Ok(out) => out.to_owned(), + Err(err) => { + log::error!( + "Failed to read memory device information, host command execution failed: {:?}", + err + ); + return None; + } + } + } + Err(err) => { + log::error!( + "Failed to read memory device information, host command execution failed: {:?}", + err + ); + return None; + } + }; + + let mut result = vec![]; + + let mut cmd_output_str = cmd_output.as_str(); + let mut cmd_output_str_index = 0; // Index for what character we are at in the string + let mut module_index = 0; // Index for what RAM module we are working with + let mut speed_fallback = 0; // If CONFIGURED_SPEED_MTS is not available, use SPEED_MTS + + loop { + if cmd_output_str_index >= cmd_output_str.len() { + // We reached the end of the command output + break; + } + + let to_parse = cmd_output_str.trim(); + let mem_dev_string = format!("MEMORY_DEVICE_{}_", module_index); + let mem_dev_str = mem_dev_string.as_str(); + cmd_output_str_index = match to_parse.find(mem_dev_str) { + None => { + break; + } + Some(cmd_output_str_index) => cmd_output_str_index, + }; + cmd_output_str_index += mem_dev_str.len(); + module_index += 1; + if cmd_output_str_index < cmd_output_str.len() { + cmd_output_str = cmd_output_str[cmd_output_str_index..].trim(); + } + + let mut mem_dev = Some(MemoryDevice::default()); + + for line in to_parse[cmd_output_str_index..].trim().lines() { + let mut split = line.trim().split("="); + let mut key = split.next().map_or("", |s| s).trim(); + let value = split.next().map_or("", |s| s).trim(); + + key = key.strip_prefix(mem_dev_str).unwrap_or(key); + + let md = match mem_dev.as_mut() { + Some(mem_dev) => mem_dev, + None => { + break; + } + }; + + match key { + "PRESENT" => { + if value == "0" { + // Module does not actually exist; drop the module so it is not counted + // towards the installed module count and is not used to get values to + // display. + #[allow(dropping_references)] + drop(md); + mem_dev = None; + break; + } + } + "SIZE" => md.size = value.parse::().map_or(0, |s| s), + "FORM_FACTOR" => md.form_factor = value.to_owned(), + "LOCATOR" => md.locator = value.to_owned(), + "BANK_LOCATOR" => md.bank_locator = value.to_owned(), + "TYPE" => md.ram_type = value.to_owned(), + "SPEED_MTS" => speed_fallback = value.parse::().map_or(0, |s| s), + "CONFIGURED_SPEED_MTS" => md.speed = value.parse::().map_or(0, |s| s), + "RANK" => md.rank = value.parse::().map_or(0, |s| s), + _ => (), // Ignore unused values and other RAM modules we are not currently parsing + } + } + + match mem_dev { + Some(mut mem_dev) => { + if mem_dev.speed == 0 { + // If CONFIGURED_SPEED_MTS is not available, + mem_dev.speed = speed_fallback; // then use SPEED_MTS instead + } + result.push(mem_dev); + } + _ => {} + } + } + + Some(result) + } +} diff --git a/src/system_info/mod.rs b/src/system_info/mod.rs new file mode 100644 index 0000000..ed77b70 --- /dev/null +++ b/src/system_info/mod.rs @@ -0,0 +1,308 @@ +pub mod mem_info; +//mod net_info; +mod proc_info; +pub mod dbus_interface; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::num::NonZeroU32; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Duration; +use dbus::blocking::{ + stdintf::{org_freedesktop_dbus::Peer, org_freedesktop_dbus::Properties}, + LocalConnection, Proxy, +}; + +pub use dbus_interface::*; +use lazy_static::lazy_static; + + +macro_rules! dbus_call { + ($self: ident, $method: tt, $dbus_method_name: literal $(,$args:ident)*) => {{ + use dbus_interface::Gatherer; + + const RETRY_COUNT: i32 = 10; + + for i in 1..=RETRY_COUNT { + match $self.proxy.$method($($args,)*) { + Ok(reply) => { + return reply; + } + Err(e) => { + match $self.is_running() { + Ok(()) => { + if e.name() == Some("org.freedesktop.DBus.Error.NoReply") { + log::error!( + "DBus call '{}' timed out, on try {}", + $dbus_method_name, i, + ); + + if i == RETRY_COUNT - 1 { + log::error!("Restarting Daemon..."); + $self.stop(); + $self.start(); + } else { + std::thread::sleep(Duration::from_millis(100)); + } + } else { + log::error!( + "DBus call '{}' failed on try {}: {}", + $dbus_method_name, i, e, + ); + + std::thread::sleep(Duration::from_millis(100)); + } + } + Err(exit_code) => { + log::error!( + "Child failed, on try {}, with exit code {}. Restarting Gatherer...", + i, exit_code, + ); + $self.start(); + } + } + } + } + } + + panic!(); + }}; +} + + +pub struct SystemInfo { + #[allow(dead_code)] + connection: Rc, + proxy: Proxy<'static, Rc>, + + child: RefCell>, + +} + +impl SystemInfo { + pub fn new() -> Self { + let connection = Rc::new(LocalConnection::new_session().unwrap_or_else(|e| { + log::error!("Failed to connect to D-Bus: {}", e.to_string()); + panic!(); + })); + let proxy = Proxy::new(OD_INTERFACE_NAME, OD_OBJECT_PATH, Duration::from_millis(1000), connection.clone()); + + Self { + connection, proxy, + child: RefCell::new(None), + } + } + + pub fn start(&self) { + let mut command = { + let mut cmd = std::process::Command::new(Self::executable()); + cmd.env_remove("LD_PRELOAD"); + + if let Some(mut appdir) = std::env::var_os("APPDIR") { + appdir.push("/runtime/default"); + cmd.current_dir(appdir); + } + + cmd + }; + command + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + + self.child.borrow_mut().replace(match command.spawn() { + Ok(c) => c, + Err(e) => { + log::error!( + "Failed to spawn Gatherer process: {}", + &e + ); + panic!(); + } + }); + + const START_WAIT_TIME_MS: u64 = 300; + const RETRY_COUNT: i32 = 50; + + // Let the child process start up + for i in 0..RETRY_COUNT { + std::thread::sleep(Duration::from_millis(START_WAIT_TIME_MS / 2)); + match self.proxy.ping() { + Ok(()) => return, + Err(e) => { + log::error!( + "Call to Gatherer Ping method failed on try {}: {}", + i, + e, + ); + } + } + std::thread::sleep(Duration::from_millis(START_WAIT_TIME_MS / 2)); + } + + panic!("Failed to spawn Gatherer process: Did not respond to Ping"); + } + + pub fn stop(&self) { + let child = self.child.borrow_mut().take(); + if let Some(mut child) = child { + // Try to get the child to wake up in case it's stuck + #[cfg(target_family = "unix")] + unsafe { + libc::kill(child.id() as _, libc::SIGCONT); + } + + let _ = child.kill(); + for _ in 0..2 { + match child.try_wait() { + Ok(Some(_)) => return, + Ok(None) => { + // Wait a bit and try again, the child process might just be slow to stop + std::thread::sleep(Duration::from_millis(20)); + continue; + } + Err(e) => { + log::error!( + "Failed to wait for Gatherer process to stop: {}", + &e + ); + + panic!(); + } + } + } + } + } + + pub fn is_running(&self) -> Result<(), i32> { + let mut lock = self.child.borrow_mut(); + + let child = match lock.as_mut() { + Some(child) => child, + None => return Err(-1), + }; + + let status = match child.try_wait() { + Ok(None) => return Ok(()), + Ok(Some(status)) => status, + Err(_) => { + return Err(-1); + } + }; + + match status.code() { + Some(status_code) => Err(status_code), + None => Err(-1), + } + } + + fn executable() -> String { + + let exe_simple = "observatory-daemon".to_owned(); + + log::debug!( + "Gatherer executable name: {}", + &exe_simple + ); + + exe_simple + } +} + +impl SystemInfo { + pub fn set_refresh_interval(&self, interval: u64) { + if let Err(e) = self + .proxy + .set(OD_INTERFACE_NAME, "RefreshInterval", interval) + { + log::error!( + "Failed to set RefreshInterval property: {e}" + ); + } + } + + pub fn set_core_count_affects_percentages(&self, v: bool) { + if let Err(e) = self + .proxy + .set(OD_INTERFACE_NAME, "CoreCountAffectsPercentages", v) + { + log::error!( + "Failed to set CoreCountAffectsPercentages property: {e}" + ); + } + } + + pub fn cpu_static_info(&self) -> CpuStaticInfo { + dbus_call!(self, get_cpu_static_info, "GetCPUStaticInfo"); + } + + pub fn cpu_dynamic_info(&self) -> CpuDynamicInfo { + dbus_call!(self, get_cpu_dynamic_info, "GetCPUDynamicInfo"); + } + + pub fn disks_info(&self) -> Vec { + dbus_call!(self, get_disks_info, "GetDisksInfo"); + } + + pub fn fans_info(&self) -> Vec { + dbus_call!(self, get_fans_info, "GetFansInfo"); + } + + #[allow(unused)] + pub fn gpu_list(&self) -> Vec> { + dbus_call!(self, get_gpu_list, "GetGPUList"); + } + + pub fn gpu_static_info(&self) -> Vec { + dbus_call!(self, get_gpu_static_info, "GetGPUStaticInfo"); + } + + pub fn gpu_dynamic_info(&self) -> Vec { + dbus_call!(self, get_gpu_dynamic_info, "GetGPUDynamicInfo"); + } + + pub fn processes(&self) -> HashMap { + dbus_call!(self, get_processes, "GetProcesses"); + } + + pub fn apps(&self) -> HashMap, App> { + dbus_call!(self, get_apps, "GetApps"); + } + + pub fn services(&self) -> HashMap, Service> { + dbus_call!(self, get_services, "GetServices"); + } + + pub fn terminate_process(&self, pid: u32) { + dbus_call!(self, terminate_process, "TerminateProcess", pid); + } + + pub fn kill_process(&self, pid: u32) { + dbus_call!(self, kill_process, "KillProcess", pid); + } + + pub fn start_service(&self, service_name: &str) { + dbus_call!(self, start_service, "StartService", service_name); + } + + pub fn stop_service(&self, service_name: &str) { + dbus_call!(self, stop_service, "StopService", service_name); + } + + pub fn restart_service(&self, service_name: &str) { + dbus_call!(self, restart_service, "RestartService", service_name); + } + + pub fn enable_service(&self, service_name: &str) { + dbus_call!(self, enable_service, "EnableService", service_name); + } + + pub fn disable_service(&self, service_name: &str) { + dbus_call!(self, disable_service, "DisableService", service_name); + } + + pub fn get_service_logs(&self, service_name: &str, pid: Option) -> Arc { + dbus_call!(self, get_service_logs, "GetServiceLogs", service_name, pid); + } + +} \ No newline at end of file diff --git a/src/system_info/net_info.rs b/src/system_info/net_info.rs new file mode 100644 index 0000000..0d18fc9 --- /dev/null +++ b/src/system_info/net_info.rs @@ -0,0 +1,1098 @@ +/* sys_info_v2/net_info.rs + * + * Copyright 2023 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use std::path::Path; + +use crate::i18n::*; + +#[allow(non_camel_case_types)] +mod if_nameindex { + #[derive(Debug, Copy, Clone)] + #[repr(C)] + pub struct if_nameindex { + pub if_index: u32, + pub if_name: *mut libc::c_char, + } + + extern "C" { + #[link_name = "if_freenameindex"] + pub fn free(ptr: *mut if_nameindex); + #[link_name = "if_nameindex"] + pub fn new() -> *mut if_nameindex; + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum NetDeviceType { + Bluetooth, + Bridge, + Docker, + InfiniBand, + Multipass, + Virtual, + VPN, + Wired, + Wireless, + WWAN, + Other = 255, +} + +impl ToString for NetDeviceType { + fn to_string(&self) -> String { + match self { + NetDeviceType::Bluetooth => i18n("Bluetooth"), + NetDeviceType::Bridge => i18n("Bridge"), + NetDeviceType::Docker => i18n("Docker"), + NetDeviceType::InfiniBand => i18n("InfiniBand"), + NetDeviceType::Multipass => i18n("Multipass"), + NetDeviceType::Virtual => i18n("Virtual"), + NetDeviceType::VPN => i18n("VPN"), + NetDeviceType::Wired => i18n("Ethernet"), + NetDeviceType::Wireless => i18n("Wi-Fi"), + NetDeviceType::WWAN => i18n("WWAN"), + NetDeviceType::Other => i18n("Other"), + } + } +} + +impl From for NetDeviceType { + fn from(v: u8) -> Self { + match v { + 0 => NetDeviceType::Bluetooth, + 1 => NetDeviceType::Bridge, + 2 => NetDeviceType::Docker, + 3 => NetDeviceType::InfiniBand, + 4 => NetDeviceType::Multipass, + 5 => NetDeviceType::Virtual, + 6 => NetDeviceType::VPN, + 7 => NetDeviceType::Wired, + 8 => NetDeviceType::Wireless, + 9 => NetDeviceType::WWAN, + _ => NetDeviceType::Other, + } + } +} + +impl From for u8 { + fn from(v: NetDeviceType) -> Self { + match v { + NetDeviceType::Bluetooth => 0, + NetDeviceType::Bridge => 1, + NetDeviceType::Docker => 2, + NetDeviceType::InfiniBand => 3, + NetDeviceType::Multipass => 4, + NetDeviceType::Virtual => 5, + NetDeviceType::VPN => 6, + NetDeviceType::Wired => 7, + NetDeviceType::Wireless => 8, + NetDeviceType::WWAN => 9, + NetDeviceType::Other => 255, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NetworkDeviceDescriptor { + pub kind: NetDeviceType, + pub if_name: String, + pub adapter_name: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NetworkAddress { + pub hw_address: Option<[u8; 6]>, + pub ip4_address: Option, + pub ip6_address: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WirelessInfo { + pub ssid: Option, + pub frequency_mhz: Option, + pub bitrate_kbps: Option, + pub signal_strength_percent: Option, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct NetworkDevice { + pub descriptor: NetworkDeviceDescriptor, + pub address: NetworkAddress, + pub wireless_info: Option, + + pub send_bps: f32, + pub sent_bytes: u64, + pub recv_bps: f32, + pub recv_bytes: u64, + + pub max_speed: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct NetworkDeviceCacheEntry { + pub descriptor: NetworkDeviceDescriptor, + pub hw_address: Option<[u8; 6]>, + + pub tx_bytes: u64, + pub rx_bytes: u64, + + pub update_timestamp: std::time::Instant, +} + +pub struct NetInfo { + udev: *mut libudev_sys::udev, + nm_proxy: *mut gtk::gio::ffi::GDBusProxy, + + device_cache: std::collections::HashMap, + + hwdb_conn: Option, + device_name_cache: std::collections::HashMap, +} + +unsafe impl Send for NetInfo {} +unsafe impl Sync for NetInfo {} + +impl Drop for NetInfo { + fn drop(&mut self) { + use gtk::glib::gobject_ffi::*; + use libudev_sys::*; + + unsafe { + if self.nm_proxy != std::ptr::null_mut() { + g_object_unref(self.nm_proxy as _); + } + + if self.udev != std::ptr::null_mut() { + udev_unref(self.udev); + } + } + } +} + +impl NetInfo { + pub fn new() -> Option { + use gtk::gio::ffi::*; + use gtk::glib::{ffi::*, translate::from_glib_full, *}; + use libudev_sys::*; + use std::{collections::*, path::*}; + + let mut error: *mut GError = std::ptr::null_mut(); + + let nm_proxy = unsafe { + g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + std::ptr::null_mut(), + b"org.freedesktop.NetworkManager\0".as_ptr() as _, + b"/org/freedesktop/NetworkManager\0".as_ptr() as _, + b"org.freedesktop.NetworkManager\0".as_ptr() as _, + std::ptr::null_mut(), + &mut error, + ) + }; + if nm_proxy.is_null() { + if !error.is_null() { + let error: Error = unsafe { from_glib_full(error) }; + g_critical!( + "MissionCenter::NetInfo", + "Failed to connect to NetworkManager: {}", + error.message() + ); + } else { + g_critical!( + "MissionCenter::NetInfo", + "Failed to connect to NetworkManager: Unknown error" + ); + } + return None; + } + + let udev = unsafe { udev_new() }; + if nm_proxy == std::ptr::null_mut() { + g_critical!("MissionCenter::NetInfo", "Failed to create udev context"); + return None; + } + + let conn = if let Ok(conn) = + rusqlite::Connection::open(Path::new(crate::HW_DB_DIR.as_str()).join("hw.db")) + { + Some(conn) + } else { + g_warning!( + "MissionCenter::NetInfo", + "Failed to load hardware database, network devices will (probably) have missing names", + ); + + None + }; + + Some(Self { + udev, + nm_proxy, + + device_cache: HashMap::new(), + + hwdb_conn: conn, + device_name_cache: HashMap::new(), + }) + } + + pub fn load_devices(&mut self) -> Vec { + use gtk::glib::{gobject_ffi::*, *}; + + let mut result = vec![]; + + let if_list = unsafe { if_nameindex::new() }; + if if_list.is_null() { + g_warning!( + "MissionCenter::NetInfo", + "Failed to list network interfaces" + ); + + return result; + } + + let mut if_list_it = unsafe { if_list.offset(-1) }; + loop { + unsafe { + if_list_it = if_list_it.offset(1); + } + + let if_entry = unsafe { &*if_list_it }; + if if_entry.if_index == 0 || if_entry.if_name.is_null() { + break; + } + + let if_name = unsafe { std::ffi::CStr::from_ptr(if_entry.if_name) }; + + let if_name_str = if_name.to_string_lossy(); + if if_name_str.starts_with("lo") { + continue; + } + + let device_path = match unsafe { self.nm_device_obj_path_new(if_name) } { + None => { + g_debug!( + "MissionCenter::NetInfo", + "Failed to get device path for {}", + if_name_str + ); + continue; + } + Some(dp) => dp, + }; + + let device_proxy = unsafe { + Self::create_nm_dbus_proxy( + device_path.as_bytes_with_nul(), + b"org.freedesktop.NetworkManager.Device\0", + ) + }; + if device_proxy.is_null() { + g_critical!( + "MissionCenter::NetInfo", + "Failed to create dbus proxy for {}", + if_name_str + ); + + continue; + } + + let if_name = if_name_str.to_string(); + + let max_speed = Self::get_max_speed(&if_name); + + let (tx_bytes, rx_bytes) = Self::tx_rx_bytes(&if_name); + + let (descriptor, hw_address, send_bps, send_bytes, recv_bps, rec_bytes) = + if let Some(cached_device) = self.device_cache.get_mut(&if_name) { + let prev_tx_bytes = if cached_device.tx_bytes > tx_bytes { + tx_bytes + } else { + cached_device.tx_bytes + }; + + let prev_rx_bytes = if cached_device.rx_bytes > rx_bytes { + rx_bytes + } else { + cached_device.rx_bytes + }; + + let elapsed = cached_device.update_timestamp.elapsed().as_secs_f32(); + let send_bps = (tx_bytes - prev_tx_bytes) as f32 / elapsed; + let recv_bps = (rx_bytes - prev_rx_bytes) as f32 / elapsed; + + cached_device.tx_bytes = tx_bytes; + cached_device.rx_bytes = rx_bytes; + cached_device.update_timestamp = std::time::Instant::now(); + + ( + cached_device.descriptor.clone(), + cached_device.hw_address.clone(), + send_bps, + tx_bytes, + recv_bps, + rx_bytes, + ) + } else { + let kind = Self::device_kind(&if_name); + let adapter_name = unsafe { self.adapter_name(device_proxy) }; + let hw_address = Self::hw_address(device_proxy); + + self.device_cache.insert( + if_name.clone(), + NetworkDeviceCacheEntry { + descriptor: NetworkDeviceDescriptor { + kind, + if_name: if_name.clone(), + adapter_name: adapter_name.clone(), + }, + hw_address: hw_address.clone(), + + tx_bytes, + rx_bytes, + + update_timestamp: std::time::Instant::now(), + }, + ); + + ( + NetworkDeviceDescriptor { + kind, + if_name, + adapter_name, + }, + hw_address, + 0., + 0, + 0., + 0, + ) + }; + + let ip4_address = unsafe { Self::ip4_address(device_proxy) }; + let ip6_address = unsafe { Self::ip6_address(device_proxy) }; + + let address = NetworkAddress { + hw_address, + ip4_address, + ip6_address, + }; + + let wireless_info = if descriptor.kind == NetDeviceType::Wireless { + unsafe { Self::wireless_info(device_proxy) } + } else { + None + }; + + unsafe { g_object_unref(device_proxy as _) }; + + result.push(NetworkDevice { + descriptor, + address, + wireless_info, + + send_bps, + sent_bytes: send_bytes, + recv_bps, + recv_bytes: rec_bytes, + + max_speed, + }); + } + + unsafe { + if_nameindex::free(if_list); + } + + result + } + + fn device_kind(device_if: &str) -> NetDeviceType { + if device_if.starts_with("bn") { + NetDeviceType::Bluetooth + } else if device_if.starts_with("br") || device_if.starts_with("virbr") { + NetDeviceType::Bridge + } else if device_if.starts_with("docker") { + NetDeviceType::Docker + } else if device_if.starts_with("eth") || device_if.starts_with("en") { + NetDeviceType::Wired + } else if device_if.starts_with("ib") { + NetDeviceType::InfiniBand + } else if device_if.starts_with("mp") { + NetDeviceType::Multipass + } else if device_if.starts_with("veth") { + NetDeviceType::Virtual + } else if device_if.starts_with("vpn") || device_if.starts_with("wg") { + NetDeviceType::VPN + } else if device_if.starts_with("wl") || device_if.starts_with("ww") { + NetDeviceType::Wireless + } else if device_if.starts_with("mlan") { + let path = Path::new("/sys/class/net").join(device_if).join("wireless"); + if path.exists() { + NetDeviceType::Wireless + } else { + NetDeviceType::Other + } + } else { + NetDeviceType::Other + } + } + + fn hw_address(dbus_proxy: *mut gtk::gio::ffi::GDBusProxy) -> Option<[u8; 6]> { + if let Some(hw_address_variant) = + unsafe { Self::nm_device_property(dbus_proxy, b"HwAddress\0") } + { + if let Some(hw_address_str) = hw_address_variant.str() { + let mut hw_address = [0; 6]; + + hw_address_str + .split(':') + .take(6) + .enumerate() + .map(|(i, s)| (i, u8::from_str_radix(s, 16).map_or(0, |v| v))) + .for_each(|(i, v)| hw_address[i] = v); + + Some(hw_address) + } else { + None + } + } else { + None + } + } + + unsafe fn ip4_address(dbus_proxy: *mut gtk::gio::ffi::GDBusProxy) -> Option { + use gtk::glib::gobject_ffi::*; + + if let Some(ip4_address_obj_path) = Self::nm_device_property(dbus_proxy, b"Ip4Config\0") { + if let Some(ip4_address_obj_path_str) = ip4_address_obj_path.str() { + let ip4_config_proxy = Self::create_nm_dbus_proxy( + ip4_address_obj_path_str.as_bytes(), + b"org.freedesktop.NetworkManager.IP4Config\0", + ); + if ip4_config_proxy.is_null() { + return None; + } + + let result = if let Some(ip4_address_variant) = + Self::nm_device_property(ip4_config_proxy, b"Addresses\0") + { + // Just take the first entry in the list of lists + if let Some(ip4_address_info) = ip4_address_variant.iter().next() { + // The first entry in the inner list is the IP address + ip4_address_info + .iter() + .next() + .map_or(None, |v| v.get::()) + } else { + None + } + } else { + None + }; + + g_object_unref(ip4_config_proxy as _); + + result + } else { + None + } + } else { + None + } + } + + unsafe fn ip6_address(dbus_proxy: *mut gtk::gio::ffi::GDBusProxy) -> Option { + use gtk::glib::gobject_ffi::*; + + // The space for link-local addresses is fe80::/10 + const LL_PREFIX: u128 = 0xfe80 << 112; + // Using this mask, we check if the first 10 bit are equal to the prefix above + const LL_MASK: u128 = 0xffc0 << 112; + + if let Some(ip6_address_obj_path) = Self::nm_device_property(dbus_proxy, b"Ip6Config\0") { + if let Some(ip6_address_obj_path_str) = ip6_address_obj_path.str() { + let ip6_config_proxy = Self::create_nm_dbus_proxy( + ip6_address_obj_path_str.as_bytes(), + b"org.freedesktop.NetworkManager.IP6Config\0", + ); + if ip6_config_proxy.is_null() { + return None; + } + + let result = if let Some(ip6_address_variant) = + Self::nm_device_property(ip6_config_proxy, b"Addresses\0") + { + let parsed_ips: Vec = ip6_address_variant + .iter() + .map(|ip6_with_mask| { + if let Some(ip6_bytes) = ip6_with_mask.iter().next() { + let mut ip6_address = [0; 16]; + ip6_bytes.iter().enumerate().for_each(|(i, v)| { + ip6_address[i] = v.get::().unwrap_or(0); + }); + + Some(u128::from_be_bytes(ip6_address)) + } else { + None + } + }) + .flatten() + .collect(); + + let first_non_ll = parsed_ips + .iter() + .filter(|ip| (*ip & LL_MASK) != LL_PREFIX) + .next() + .copied(); + + first_non_ll.or(parsed_ips.first().copied()) + } else { + None + }; + + g_object_unref(ip6_config_proxy as _); + + result + } else { + None + } + } else { + None + } + } + + unsafe fn wireless_info(dbus_proxy: *mut gtk::gio::ffi::GDBusProxy) -> Option { + use gtk::{gio::ffi::*, glib::gobject_ffi::*}; + + use std::ffi::CStr; + + let wireless_obj_path = CStr::from_ptr(g_dbus_proxy_get_object_path(dbus_proxy)); + + let wireless_info_proxy = Self::create_nm_dbus_proxy( + wireless_obj_path.to_bytes_with_nul(), + b"org.freedesktop.NetworkManager.Device.Wireless\0", + ); + if wireless_info_proxy.is_null() { + return None; + } + + let result = if let Some(wireless_info_variant) = + Self::nm_device_property(wireless_info_proxy, b"ActiveAccessPoint\0") + { + if let Some(wireless_info_obj_path) = wireless_info_variant.str() { + let wireless_info_proxy = Self::create_nm_dbus_proxy( + wireless_info_obj_path.as_bytes(), + b"org.freedesktop.NetworkManager.AccessPoint\0", + ); + if wireless_info_proxy.is_null() { + return None; + } + + let ssid = if let Some(ssid_variant) = + Self::nm_device_property(wireless_info_proxy, b"Ssid\0") + { + let ssid = ssid_variant + .iter() + .filter_map(|v| v.get::()) + .collect::>(); + + String::from_utf8(ssid).ok() + } else { + None + }; + + let frequency = if let Some(frequency) = + Self::nm_device_property(wireless_info_proxy, b"Frequency\0") + { + frequency.get::() + } else { + None + }; + + let bitrate = if let Some(bitrate) = + Self::nm_device_property(wireless_info_proxy, b"MaxBitrate\0") + { + bitrate.get::() + } else { + None + }; + + let signal_strength = if let Some(signal_strength) = + Self::nm_device_property(wireless_info_proxy, b"Strength\0") + { + signal_strength.get::() + } else { + None + }; + + g_object_unref(wireless_info_proxy as _); + Some(WirelessInfo { + ssid, + frequency_mhz: frequency, + bitrate_kbps: bitrate, + signal_strength_percent: signal_strength, + }) + } else { + None + } + } else { + None + }; + + g_object_unref(wireless_info_proxy as _); + + result + } + + fn get_max_speed(if_name: &str) -> u64 { + let speed = std::fs::read_to_string(format!("/sys/class/net/{}/speed", if_name)); + + if let Ok(str) = speed { + // Convert from megabits to bytes + (str.trim().parse::().unwrap_or(0) / 8) * 1_000_000 + } else { + 0 + } + } + + fn tx_rx_bytes(if_name: &str) -> (u64, u64) { + let tx_bytes = + std::fs::read_to_string(format!("/sys/class/net/{}/statistics/tx_bytes", if_name)); + let rx_bytes = + std::fs::read_to_string(format!("/sys/class/net/{}/statistics/rx_bytes", if_name)); + + let tx_bytes = if let Ok(str) = tx_bytes { + str.trim().parse::().unwrap_or(0) + } else { + 0 + }; + + let rx_bytes = if let Ok(str) = rx_bytes { + str.trim().parse::().unwrap_or(0) + } else { + 0 + }; + + (tx_bytes, rx_bytes) + } + + fn device_name_from_hw_db(&mut self, udi: &str) -> Option { + use gtk::glib::*; + use std::{fs::*, io::*, path::*}; + + let device_name_cache = &mut self.device_name_cache; + if let Some(device_name) = device_name_cache.get(udi) { + return Some(device_name.clone()); + } + + let conn = match self.hwdb_conn.as_ref() { + None => return None, + Some(c) => c, + }; + + let mut stmt = match conn.prepare("SELECT value FROM key_len WHERE key = 'min'") { + Ok(s) => s, + Err(e) => { + g_critical!( + "MissionCenter::NetInfo", + "Failed to extract min key length from {}/hw.db: Prepare query failed: {}", + crate::HW_DB_DIR.as_str(), + e, + ); + return None; + } + }; + let mut query_result = match stmt.query_map([], |row| row.get::(0)) { + Ok(qr) => qr, + Err(e) => { + g_critical!( + "MissionCenter::NetInfo", + "Failed to extract min key length from {}/hw.db: Query map failed: {}", + crate::HW_DB_DIR.as_str(), + e, + ); + return None; + } + }; + let min_key_len = if let Some(min_len) = query_result.next() { + min_len.unwrap_or(0) + } else { + 0 + }; + + let mut stmt = match conn.prepare("SELECT value FROM key_len WHERE key = 'max'") { + Ok(s) => s, + Err(e) => { + g_critical!( + "MissionCenter::NetInfo", + "Failed to extract max key length from {}/hw.db: Prepare query failed: {}", + crate::HW_DB_DIR.as_str(), + e, + ); + return None; + } + }; + let mut query_result = match stmt.query_map([], |row| row.get::(0)) { + Ok(qr) => qr, + Err(e) => { + g_critical!( + "MissionCenter::NetInfo", + "Failed to extract max key length from {}/hw.db: Query map failed: {}", + crate::HW_DB_DIR.as_str(), + e, + ); + return None; + } + }; + let mut max_key_len = if let Some(max_len) = query_result.next() { + max_len.unwrap_or(i32::MAX) + } else { + i32::MAX + }; + + let device_id = format!("{}/device", udi); + let mut sys_device_path = Path::new(&device_id); + let mut modalias = String::new(); + for _ in 0..4 { + if let Some(p) = sys_device_path.parent() { + sys_device_path = p; + } else { + break; + } + + let modalias_path = sys_device_path.join("modalias"); + if modalias_path.exists() { + if let Ok(mut modalias_file) = File::options() + .create(false) + .read(true) + .write(false) + .open(modalias_path) + { + modalias.clear(); + + if let Ok(_) = modalias_file.read_to_string(&mut modalias) { + modalias = modalias.trim().to_owned(); + if max_key_len == i32::MAX { + max_key_len = modalias.len() as i32; + } + + for i in (min_key_len..max_key_len).rev() { + modalias.truncate(i as usize); + let mut stmt = match conn.prepare( + "SELECT value FROM models WHERE key LIKE ?1 || '%' LIMIT 1", + ) { + Ok(s) => s, + Err(e) => { + g_warning!( + "MissionCenter::NetInfo", + "Failed to find model in {}/hw.db: Prepare query failed: {}", + crate::HW_DB_DIR.as_str(), + e, + ); + continue; + } + }; + let mut query_result = match stmt + .query_map([modalias.trim()], |row| row.get::(0)) + { + Ok(qr) => qr, + Err(e) => { + g_warning!( + "MissionCenter::NetInfo", + "Failed to find model in {}/hw.db: Query map failed: {}", + crate::HW_DB_DIR.as_str(), + e, + ); + continue; + } + }; + + let model_name = if let Some(model) = query_result.next() { + model.ok() + } else { + None + }; + + if let Some(model_name) = model_name { + let device_name_cache = &mut self.device_name_cache; + device_name_cache.insert(udi.to_owned(), model_name.clone()); + return Some(model_name); + } + } + } + } + } + } + + None + } + + unsafe fn adapter_name( + &mut self, + dbus_proxy: *mut gtk::gio::ffi::GDBusProxy, + ) -> Option { + use errno_sys::errno_location; + use gtk::glib::*; + use libudev_sys::*; + + use std::ffi::CStr; + + if let Some(udi_variant) = Self::nm_device_property(dbus_proxy, b"Udi\0") { + if let Some(udi) = udi_variant.str() { + if let Some(device_name) = self.device_name_from_hw_db(udi) { + return Some(device_name); + } + + let udev_device = udev_device_new_from_syspath(self.udev, udi.as_ptr() as _); + if udev_device.is_null() { + let err = *errno_location(); + let error_message = CStr::from_ptr(libc::strerror(err)) + .to_str() + .map_or("Unknown error", |s| s) + .to_owned(); + + g_debug!( + "MissionCenter::NetInfo", + "Failed to create udev device from {:?}. {}", + udi, + error_message + ); + return None; + } + + let dev_name = + Self::get_udev_property(udev_device, b"ID_MODEL_ENC\0".as_ptr() as _); + + udev_device_unref(udev_device); + + dev_name + } else { + g_critical!( + "MissionCenter::NetInfo", + "Failed to get udev device path, cannot extract device sys path from variant: Unknown error" + ); + None + } + } else { + None + } + } + + unsafe fn create_nm_dbus_proxy( + path: &[u8], + interface: &[u8], + ) -> *mut gtk::gio::ffi::GDBusProxy { + use gtk::gio::ffi::*; + use gtk::glib::{ffi::*, translate::from_glib_full, *}; + use std::ffi::CStr; + + let mut error: *mut GError = std::ptr::null_mut(); + + let proxy = g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + std::ptr::null_mut(), + b"org.freedesktop.NetworkManager\0".as_ptr() as _, + path.as_ptr() as _, + interface.as_ptr() as _, + std::ptr::null_mut(), + &mut error, + ); + if proxy.is_null() { + if !error.is_null() { + let error: Error = from_glib_full(error); + g_critical!( + "MissionCenter::NetInfo", + "Failed to create dbus proxy for interface '{:?}': {}", + CStr::from_ptr(interface.as_ptr() as _), + error.message() + ); + } else { + g_critical!( + "MissionCenter::NetInfo", + "Failed to create dbus proxy for interface '{:?}': Unknown error", + CStr::from_ptr(interface.as_ptr() as _), + ); + } + } + + proxy + } + + unsafe fn nm_device_obj_path_new( + &self, + device_if: &std::ffi::CStr, + ) -> Option { + use gtk::gio::ffi::*; + use gtk::glib::{ffi::*, translate::from_glib_full, *}; + use std::ffi::CStr; + + let mut error: *mut GError = std::ptr::null_mut(); + + let device_path_variant = unsafe { + g_dbus_proxy_call_sync( + self.nm_proxy, + b"GetDeviceByIpIface\0".as_ptr() as _, + g_variant_new(b"(s)\0".as_ptr() as _, device_if.as_ptr()), + G_DBUS_CALL_FLAGS_NONE, + -1, + std::ptr::null_mut(), + &mut error, + ) + }; + if device_path_variant.is_null() { + if !error.is_null() { + let error: Error = unsafe { from_glib_full(error) }; + g_debug!( + "MissionCenter::NetInfo", + "Failed to get device info for {:?}: {}", + device_if, + error.message() + ); + } else { + g_critical!( + "MissionCenter::NetInfo", + "Failed to get device info for {:?}: Unknown error", + device_if, + ); + } + + return None; + } + + let mut device_path: *mut libc::c_char = std::ptr::null_mut(); + unsafe { + g_variant_get( + device_path_variant, + b"(&o)\0".as_ptr() as _, + &mut device_path, + ) + }; + if device_path.is_null() { + g_critical!( + "MissionCenter::NetInfo", + "Failed to get device info for {:?}: Variant error", + device_if, + ); + return None; + } + + let device_path = CStr::from_ptr(device_path).to_owned(); + let _: Variant = from_glib_full(device_path_variant); + + Some(device_path) + } + + unsafe fn nm_device_property( + dbus_proxy: *mut gtk::gio::ffi::GDBusProxy, + property: &[u8], + ) -> Option { + use gtk::gio::ffi::*; + use gtk::glib::{ffi::*, translate::from_glib_full, *}; + use std::ffi::CStr; + + let mut error: *mut GError = std::ptr::null_mut(); + + let variant = g_dbus_proxy_call_sync( + dbus_proxy, + b"org.freedesktop.DBus.Properties.Get\0".as_ptr() as _, + g_variant_new( + b"(ss)\0".as_ptr() as _, + g_dbus_proxy_get_interface_name(dbus_proxy), + property.as_ptr() as *const i8, + ), + G_DBUS_CALL_FLAGS_NONE, + -1, + std::ptr::null_mut(), + &mut error, + ); + if variant.is_null() { + if !error.is_null() { + let error: Error = from_glib_full(error); + g_debug!( + "MissionCenter::NetInfo", + "Failed to get property {:?}: {}", + CStr::from_ptr(property.as_ptr() as _), + error.message() + ); + } else { + g_critical!( + "MissionCenter::NetInfo", + "Failed to get property {:?}: Unknown error", + CStr::from_ptr(property.as_ptr() as _), + ); + } + + return None; + } + + let mut inner: *mut GVariant = std::ptr::null_mut(); + g_variant_get(variant, b"(v)\0".as_ptr() as _, &mut inner); + if inner.is_null() { + g_variant_unref(variant); + + g_critical!( + "MissionCenter::NetInfo", + "Failed to get property {:?}, cannot extract inner variant: Unknown error", + CStr::from_ptr(property.as_ptr() as _), + ); + + return None; + } + + g_variant_ref_sink(inner); + g_variant_unref(variant); + + from_glib_full(inner) + } + + // Yanked from NetworkManager: src/libnm-client-impl/nm-device.c: _get_udev_property() + unsafe fn get_udev_property( + device: *mut libudev_sys::udev_device, + property: *const libc::c_char, + ) -> Option { + use libudev_sys::*; + use std::ffi::CStr; + + let mut value: *const libc::c_char = std::ptr::null_mut(); + let mut tmpdev: *mut udev_device = device; + + let mut count = 0; + while (count < 3) && !tmpdev.is_null() && value.is_null() { + count += 1; + + if value.is_null() { + value = udev_device_get_property_value(tmpdev, property); + } + + tmpdev = udev_device_get_parent(tmpdev); + } + + if !value.is_null() { + CStr::from_ptr(value) + .to_str() + .map_or(None, |s| Some(s.to_owned())) + } else { + None + } + } +} diff --git a/src/system_info/proc_info.rs b/src/system_info/proc_info.rs new file mode 100644 index 0000000..4ddf8fb --- /dev/null +++ b/src/system_info/proc_info.rs @@ -0,0 +1,141 @@ +/* sys_info_v2/proc_info.rs + * + * Copyright 2024 Romeo Calota + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +use super::{Process, ProcessUsageStats}; + +pub fn process_hierarchy(processes: &std::collections::HashMap) -> Option { + use std::collections::*; + + let now = std::time::Instant::now(); + + let pids = processes.keys().map(|pid| *pid).collect::>(); + let root_pid = match pids.first() { + None => return None, + Some(pid) => *pid, + }; + + let mut root_process = match processes.get(&root_pid).map_or(None, |p| Some(p.clone())) { + None => return None, + Some(p) => p, + }; + + let mut process_tree = BTreeMap::new(); + process_tree.insert(root_process.pid, 0_usize); + + let mut children = Vec::with_capacity(pids.len()); + children.push(HashMap::new()); + + let mut visited = HashSet::new(); + visited.insert(root_process.pid); + + for pid in pids.iter().skip(1).rev() { + if visited.contains(pid) { + continue; + } + + let process = match processes.get(pid) { + None => continue, + Some(p) => p, + }; + + let mut stack = vec![process]; + let mut parent = process.parent; + while parent != 0 { + let parent_process = match processes.get(&parent) { + None => break, + Some(pp) => pp, + }; + + if visited.contains(&parent_process.pid) { + let mut index = match process_tree.get(&parent_process.pid) { + None => { + // TODO: Fully understand if this could happen, and what to do if it does. + log::error!( + "Process {} has been visited, but it's not in the process_tree?", + process.pid + ); + break; + } + Some(index) => *index, + }; + while let Some(ancestor) = stack.pop() { + let p = ancestor.clone(); + children[index].insert(p.pid, p); + + visited.insert(ancestor.pid); + + index = children.len(); + process_tree.insert(ancestor.pid, index); + children.push(HashMap::new()); + } + + break; + } + + stack.push(parent_process); + parent = parent_process.parent; + } + } + + fn gather_descendants( + process: &mut Process, + process_tree: &BTreeMap, + children: &mut Vec>, + ) { + let pid = process.pid; + + let index = match process_tree.get(&pid) { + Some(index) => *index, + None => return, + }; + + if children[index].is_empty() { + return; + } + + std::mem::swap(&mut process.children, &mut children[index]); + + let mut merged_stats = ProcessUsageStats::default(); + for (_, child) in &mut process.children { + gather_descendants(child, process_tree, children); + merged_stats.merge(&child.merged_usage_stats); + } + process.merged_usage_stats.merge(&merged_stats); + } + + let process = &mut root_process; + std::mem::swap(&mut process.children, &mut children[0]); + + let mut merged_stats = ProcessUsageStats::default(); + for (_, child) in &mut process.children { + gather_descendants(child, &process_tree, &mut children); + merged_stats.merge(&child.merged_usage_stats); + } + process.merged_usage_stats.merge(&merged_stats); + + log::debug!( + "[{}:{}] Loading process hierarchy took {}ms", + file!(), + line!(), + now.elapsed().as_millis() + ); + + Some(root_process) +}