From 96a30c045af1df633e5b3ab4b187b7c5afd744b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Jul 2026 05:53:50 +0000 Subject: [PATCH 1/8] fix: only refresh own process in sysinfo to avoid AppArmor DENIED --- dragonfly-client-util/src/sysinfo/cpu.rs | 17 +++++++++++++---- dragonfly-client-util/src/sysinfo/disk.rs | 16 +++++++++++----- dragonfly-client-util/src/sysinfo/memory.rs | 16 +++++++++++----- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/dragonfly-client-util/src/sysinfo/cpu.rs b/dragonfly-client-util/src/sysinfo/cpu.rs index 9195380d..f5b7f52a 100644 --- a/dragonfly-client-util/src/sysinfo/cpu.rs +++ b/dragonfly-client-util/src/sysinfo/cpu.rs @@ -124,12 +124,21 @@ impl CPU { // Lock the mutex to ensure exclusive access to cpu stats. let _guard = self.mutex.lock().await; - let mut sys = System::new_with_specifics( - RefreshKind::new().with_processes(ProcessRefreshKind::new().with_cpu()), + // Only refresh the given process to avoid reading other processes' + // `/proc/` entries, which is denied by the default AppArmor + // profile when running in a container. + let mut sys = System::new(); + sys.refresh_processes_specifics( + ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), + false, + ProcessRefreshKind::new().with_cpu(), ); - sys.refresh_processes(ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false); sleep(Self::DEFAULT_CPU_REFRESH_INTERVAL).await; - sys.refresh_processes(ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false); + sys.refresh_processes_specifics( + ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), + false, + ProcessRefreshKind::new().with_cpu(), + ); let cpu_usage = sys.process(Pid::from_u32(pid)).unwrap().cpu_usage(); let used_percent = cpu_usage as f64 / self.logical_core_count as f64; diff --git a/dragonfly-client-util/src/sysinfo/disk.rs b/dragonfly-client-util/src/sysinfo/disk.rs index 3e8a4fa7..69a5d554 100644 --- a/dragonfly-client-util/src/sysinfo/disk.rs +++ b/dragonfly-client-util/src/sysinfo/disk.rs @@ -18,7 +18,7 @@ use dragonfly_client_core::Result; use std::path::Path; use std::sync::Arc; use std::time::Duration; -use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System}; +use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System}; use tokio::sync::Mutex; use tracing::debug; @@ -118,8 +118,14 @@ impl Disk { pub async fn get_process_stats(&self, pid: u32) -> ProcessDiskStats { // Lock the mutex to ensure exclusive access to disk stats. let _guard = self.mutex.lock().await; - let mut sys = System::new_with_specifics( - RefreshKind::new().with_processes(ProcessRefreshKind::new().with_disk_usage()), + // Only refresh the given process to avoid reading other processes' + // `/proc/` entries, which is denied by the default AppArmor + // profile when running in a container. + let mut sys = System::new(); + sys.refresh_processes_specifics( + ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), + false, + ProcessRefreshKind::new().with_disk_usage(), ); // Sleep to calculate the disk traffic difference over @@ -127,8 +133,8 @@ impl Disk { tokio::time::sleep(Self::DEFAULT_DISK_REFRESH_INTERVAL).await; sys.refresh_processes_specifics( - ProcessesToUpdate::All, - true, + ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), + false, ProcessRefreshKind::new().with_disk_usage(), ); diff --git a/dragonfly-client-util/src/sysinfo/memory.rs b/dragonfly-client-util/src/sysinfo/memory.rs index 149828ed..72f748fe 100644 --- a/dragonfly-client-util/src/sysinfo/memory.rs +++ b/dragonfly-client-util/src/sysinfo/memory.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System}; +use sysinfo::{MemoryRefreshKind, Pid, ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System}; use tracing::debug; /// Represents system-wide memory statistics. @@ -106,10 +106,16 @@ impl Memory { /// # Returns /// ProcessMemoryStats containing the process's memory usage percentage. pub fn get_process_stats(&self, pid: u32) -> ProcessMemoryStats { - let sys = System::new_with_specifics( - RefreshKind::new() - .with_memory(MemoryRefreshKind::new().with_ram()) - .with_processes(ProcessRefreshKind::new().with_memory()), + // Only refresh the given process to avoid reading other processes' + // `/proc/` entries, which is denied by the default AppArmor + // profile when running in a container. + let mut sys = System::new_with_specifics( + RefreshKind::new().with_memory(MemoryRefreshKind::new().with_ram()), + ); + sys.refresh_processes_specifics( + ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), + false, + ProcessRefreshKind::new().with_memory(), ); let memory_usage = sys.process(Pid::from_u32(pid)).unwrap().memory(); let total_memory = sys.total_memory(); From 6624a85158affb584f987dc82206824a0c870fa6 Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 18:55:46 +0800 Subject: [PATCH 2/8] build(deps): upgrade sysinfo from 0.32.1 to 0.38.0 - Bump `sysinfo` dependency from 0.32.1 to 0.38.0 - Remove outdated comments about AppArmor container restrictions - No functional logic changes, only version and comment cleanup Signed-off-by: Gaius --- Cargo.lock | 153 +++++++++++++------- Cargo.toml | 2 +- dragonfly-client-util/src/sysinfo/cpu.rs | 4 +- dragonfly-client-util/src/sysinfo/disk.rs | 4 +- dragonfly-client-util/src/sysinfo/memory.rs | 3 - 5 files changed, 104 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb9e3a52..cfe94ffe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3640,6 +3640,25 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.32.2" @@ -5847,15 +5866,16 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.1" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "windows 0.57.0", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.1", ] [[package]] @@ -6996,25 +7016,27 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.57.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", ] [[package]] name = "windows" -version = "0.61.3" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "49e6c4a1f363c8210c6f77ba24f645c61c6fb941eccf013da691f7e09515b8ac" dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", + "windows-collections 0.3.1", + "windows-core 0.62.1", + "windows-future 0.3.1", + "windows-numerics 0.3.0", ] [[package]] @@ -7027,23 +7049,20 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-collections" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "123e712f464a8a60ce1a13f4c446d2d43ab06464cb5842ff68f5c71b6fb7852e" dependencies = [ - "windows-targets 0.52.6", + "windows-core 0.62.1", ] [[package]] name = "windows-core" -version = "0.57.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -7053,11 +7072,24 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link 0.1.3", "windows-result 0.3.4", - "windows-strings", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.0", + "windows-result 0.4.0", + "windows-strings 0.5.0", ] [[package]] @@ -7068,18 +7100,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "68f3db6b24b120200d649cd4811b4947188ed3a8d2626f7075146c5d178a9a4a" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "windows-core 0.62.1", + "windows-link 0.2.0", + "windows-threading 0.2.0", ] [[package]] @@ -7093,17 +7125,6 @@ dependencies = [ "syn 2.0.117", ] -[[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.117", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -7137,6 +7158,16 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce3498fe0aba81e62e477408383196b4b0363db5e0c27646f932676283b43d8" +dependencies = [ + "windows-core 0.62.1", + "windows-link 0.2.0", +] + [[package]] name = "windows-registry" version = "0.5.3" @@ -7145,25 +7176,25 @@ checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", "windows-result 0.3.4", - "windows-strings", + "windows-strings 0.4.2", ] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link 0.1.3", ] [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ - "windows-link 0.1.3", + "windows-link 0.2.0", ] [[package]] @@ -7175,6 +7206,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -7300,6 +7340,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab47f085ad6932defa48855254c758cdd0e2f2d48e62a34118a268d8f345e118" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index 4670aa30..6de43fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ serde_json = "1.0.150" serde_regex = "1.2.0" serde_yaml = "0.9" sha2 = "0.10" -sysinfo = { version = "0.32.1", default-features = false, features = [ +sysinfo = { version = "0.38.0", default-features = false, features = [ "component", "disk", "network", diff --git a/dragonfly-client-util/src/sysinfo/cpu.rs b/dragonfly-client-util/src/sysinfo/cpu.rs index f5b7f52a..68b88f14 100644 --- a/dragonfly-client-util/src/sysinfo/cpu.rs +++ b/dragonfly-client-util/src/sysinfo/cpu.rs @@ -124,15 +124,13 @@ impl CPU { // Lock the mutex to ensure exclusive access to cpu stats. let _guard = self.mutex.lock().await; - // Only refresh the given process to avoid reading other processes' - // `/proc/` entries, which is denied by the default AppArmor - // profile when running in a container. let mut sys = System::new(); sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false, ProcessRefreshKind::new().with_cpu(), ); + sleep(Self::DEFAULT_CPU_REFRESH_INTERVAL).await; sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), diff --git a/dragonfly-client-util/src/sysinfo/disk.rs b/dragonfly-client-util/src/sysinfo/disk.rs index 69a5d554..a21418ef 100644 --- a/dragonfly-client-util/src/sysinfo/disk.rs +++ b/dragonfly-client-util/src/sysinfo/disk.rs @@ -118,9 +118,7 @@ impl Disk { pub async fn get_process_stats(&self, pid: u32) -> ProcessDiskStats { // Lock the mutex to ensure exclusive access to disk stats. let _guard = self.mutex.lock().await; - // Only refresh the given process to avoid reading other processes' - // `/proc/` entries, which is denied by the default AppArmor - // profile when running in a container. + let mut sys = System::new(); sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), diff --git a/dragonfly-client-util/src/sysinfo/memory.rs b/dragonfly-client-util/src/sysinfo/memory.rs index 72f748fe..b3ea61c7 100644 --- a/dragonfly-client-util/src/sysinfo/memory.rs +++ b/dragonfly-client-util/src/sysinfo/memory.rs @@ -106,9 +106,6 @@ impl Memory { /// # Returns /// ProcessMemoryStats containing the process's memory usage percentage. pub fn get_process_stats(&self, pid: u32) -> ProcessMemoryStats { - // Only refresh the given process to avoid reading other processes' - // `/proc/` entries, which is denied by the default AppArmor - // profile when running in a container. let mut sys = System::new_with_specifics( RefreshKind::new().with_memory(MemoryRefreshKind::new().with_ram()), ); From f63da13022366150d6a8598e2ed502c45cf280e3 Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 19:05:23 +0800 Subject: [PATCH 3/8] fix(sysinfo): replace `new()` with `nothing()` for refresh kinds - Replace `new()` with `nothing()` for all refresh kind constructors to avoid enabling unwanted metrics by default - Add explicit `.with_ram()` to `MemoryRefreshKind` in `get_stats()` to ensure RAM data is actually collected - Update `networks.refresh()` to `networks.refresh(true)` to match updated API signature Signed-off-by: Gaius --- dragonfly-client-util/src/sysinfo/cpu.rs | 6 +++--- dragonfly-client-util/src/sysinfo/disk.rs | 4 ++-- dragonfly-client-util/src/sysinfo/memory.rs | 9 +++++---- dragonfly-client-util/src/sysinfo/network.rs | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/dragonfly-client-util/src/sysinfo/cpu.rs b/dragonfly-client-util/src/sysinfo/cpu.rs index 68b88f14..c543e0bd 100644 --- a/dragonfly-client-util/src/sysinfo/cpu.rs +++ b/dragonfly-client-util/src/sysinfo/cpu.rs @@ -96,7 +96,7 @@ impl CPU { let _guard = self.mutex.lock().await; let mut sys = System::new_with_specifics( - RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()), + RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing().with_cpu_usage()), ); sys.refresh_cpu_usage(); sleep(Self::DEFAULT_CPU_REFRESH_INTERVAL).await; @@ -128,14 +128,14 @@ impl CPU { sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false, - ProcessRefreshKind::new().with_cpu(), + ProcessRefreshKind::nothing().with_cpu(), ); sleep(Self::DEFAULT_CPU_REFRESH_INTERVAL).await; sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false, - ProcessRefreshKind::new().with_cpu(), + ProcessRefreshKind::nothing().with_cpu(), ); let cpu_usage = sys.process(Pid::from_u32(pid)).unwrap().cpu_usage(); diff --git a/dragonfly-client-util/src/sysinfo/disk.rs b/dragonfly-client-util/src/sysinfo/disk.rs index a21418ef..7aa7478e 100644 --- a/dragonfly-client-util/src/sysinfo/disk.rs +++ b/dragonfly-client-util/src/sysinfo/disk.rs @@ -123,7 +123,7 @@ impl Disk { sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false, - ProcessRefreshKind::new().with_disk_usage(), + ProcessRefreshKind::nothing().with_disk_usage(), ); // Sleep to calculate the disk traffic difference over @@ -133,7 +133,7 @@ impl Disk { sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false, - ProcessRefreshKind::new().with_disk_usage(), + ProcessRefreshKind::nothing().with_disk_usage(), ); let disk_usage = sys.process(Pid::from_u32(pid)).unwrap().disk_usage(); diff --git a/dragonfly-client-util/src/sysinfo/memory.rs b/dragonfly-client-util/src/sysinfo/memory.rs index b3ea61c7..977a1008 100644 --- a/dragonfly-client-util/src/sysinfo/memory.rs +++ b/dragonfly-client-util/src/sysinfo/memory.rs @@ -72,8 +72,9 @@ impl Memory { /// # Returns /// MemoryStats containing total, free, available, used memory and usage percentage. pub fn get_stats(&self) -> MemoryStats { - let sys = - System::new_with_specifics(RefreshKind::new().with_memory(MemoryRefreshKind::new())); + let sys = System::new_with_specifics( + RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()), + ); let total_memory = sys.total_memory(); let free_memory = sys.free_memory(); let available_memory = sys.available_memory(); @@ -107,12 +108,12 @@ impl Memory { /// ProcessMemoryStats containing the process's memory usage percentage. pub fn get_process_stats(&self, pid: u32) -> ProcessMemoryStats { let mut sys = System::new_with_specifics( - RefreshKind::new().with_memory(MemoryRefreshKind::new().with_ram()), + RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()), ); sys.refresh_processes_specifics( ProcessesToUpdate::Some(&[Pid::from_u32(pid)]), false, - ProcessRefreshKind::new().with_memory(), + ProcessRefreshKind::nothing().with_memory(), ); let memory_usage = sys.process(Pid::from_u32(pid)).unwrap().memory(); let total_memory = sys.total_memory(); diff --git a/dragonfly-client-util/src/sysinfo/network.rs b/dragonfly-client-util/src/sysinfo/network.rs index 68408fd1..255cb2da 100644 --- a/dragonfly-client-util/src/sysinfo/network.rs +++ b/dragonfly-client-util/src/sysinfo/network.rs @@ -131,7 +131,7 @@ impl Network { tokio::time::sleep(Self::DEFAULT_NETWORK_REFRESH_INTERVAL).await; // Refresh network information to get updated statistics. - networks.refresh(); + networks.refresh(true); let Some(network_stats) = networks.get(self.interface_name.as_str()) else { warn!( "can not find network data for interface {}", From 87b5b5220aeea1e3a0c497f2e3d2a0ab533c1f6e Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 20:12:28 +0800 Subject: [PATCH 4/8] fix(sysinfo): use anon pages for cgroup v2 memory usage calculation - `rss` is absent in cgroup v2 `memory.stat`; use `inactive_anon + active_anon` instead - Both metrics exclude page cache, keeping semantics consistent across cgroup versions - Update debug log and returned `CgroupMemoryStats` to use the unified `memory_usage` value Signed-off-by: Gaius --- dragonfly-client-util/src/sysinfo/memory.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/dragonfly-client-util/src/sysinfo/memory.rs b/dragonfly-client-util/src/sysinfo/memory.rs index 977a1008..dfc527d3 100644 --- a/dragonfly-client-util/src/sysinfo/memory.rs +++ b/dragonfly-client-util/src/sysinfo/memory.rs @@ -149,24 +149,29 @@ impl Memory { Ok(cgroup) => { if let Some(memory_controller) = cgroup.controller_of::() { let memory_stats = memory_controller.memory_stat(); + // The `rss` key only exists in cgroup v1's memory.stat. In cgroup v2, + // sum the anonymous LRU pages instead, which also excludes the page + // cache. + let memory_usage = if cgroup.v2() { + memory_stats.stat.inactive_anon + memory_stats.stat.active_anon + } else { + memory_stats.stat.rss + }; + let used_percent = if memory_stats.limit_in_bytes > 0 { - (memory_stats.stat.rss as f64 / memory_stats.limit_in_bytes as f64) - * 100.0 + (memory_usage as f64 / memory_stats.limit_in_bytes as f64) * 100.0 } else { - (memory_stats.stat.rss as f64 / self.get_stats().total as f64) * 100.0 + (memory_usage as f64 / self.get_stats().total as f64) * 100.0 }; debug!( "process {} cgroup memory limit: {} bytes, memory usage: {} bytes, used percent: {}%", - pid, - memory_stats.limit_in_bytes, - memory_stats.stat.rss, - used_percent, + pid, memory_stats.limit_in_bytes, memory_usage, used_percent, ); return Some(CgroupMemoryStats { limit: memory_stats.limit_in_bytes, - usage: memory_stats.stat.rss, + usage: memory_usage, used_percent: used_percent.clamp(0.0, 100.0), }); } From 542a4a749595991350e82b983f597c98e65d5638 Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 20:13:50 +0800 Subject: [PATCH 5/8] fix(sysinfo): use cgroup v2 `anon` counter for memory usage calculation - Replace `inactive_anon + active_anon` sum with the `anon` raw counter (NR_ANON_MAPPED) for accurate cgroup v2 memory usage - Fall back to the LRU-based sum only when `anon` key is absent - Improve comment explaining why the LRU approach is inaccurate (shmem/tmpfs inclusion, mlocked/MADV_FREE exclusion) Signed-off-by: Gaius --- dragonfly-client-util/src/sysinfo/memory.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dragonfly-client-util/src/sysinfo/memory.rs b/dragonfly-client-util/src/sysinfo/memory.rs index dfc527d3..91a012bf 100644 --- a/dragonfly-client-util/src/sysinfo/memory.rs +++ b/dragonfly-client-util/src/sysinfo/memory.rs @@ -149,11 +149,15 @@ impl Memory { Ok(cgroup) => { if let Some(memory_controller) = cgroup.controller_of::() { let memory_stats = memory_controller.memory_stat(); - // The `rss` key only exists in cgroup v1's memory.stat. In cgroup v2, - // sum the anonymous LRU pages instead, which also excludes the page - // cache. + // The `rss` key only exists in cgroup v1's memory.stat. In cgroup v2 the + // equivalent counter (NR_ANON_MAPPED) is exported as `anon`. Note that + // `inactive_anon + active_anon` is NOT equivalent: the anon LRU lists + // include shmem/tmpfs pages and exclude mlocked (unevictable) and + // MADV_FREE'd pages, so only use it as a last-resort fallback. let memory_usage = if cgroup.v2() { - memory_stats.stat.inactive_anon + memory_stats.stat.active_anon + memory_stats.stat.raw.get("anon").copied().unwrap_or( + memory_stats.stat.inactive_anon + memory_stats.stat.active_anon, + ) } else { memory_stats.stat.rss }; From 966c708095ec12c52c7ebff758eb964f134e21a0 Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 20:52:55 +0800 Subject: [PATCH 6/8] feat(sysinfo): add cgroup disk I/O stats and expose via announcer - Add `CgroupDiskStats` struct to track cgroup-level block-device I/O bandwidth - Implement `get_cgroup_stats` measuring read/write bandwidth via cgroup v1 blkio or cgroup v2 io.stat over a sampling interval (Linux only) - Add parsers for both cgroup v1 IoService entries and v2 io.stat format, with unit tests covering both - Expose cgroup disk stats via `CgroupDisk` proto field in announcer when running inside a container - Bump `dragonfly-api` to 2.2.31 for the new `CgroupDisk` type Signed-off-by: Gaius --- Cargo.lock | 42 ++-- Cargo.toml | 2 +- dragonfly-client-util/src/sysinfo/disk.rs | 266 +++++++++++++++++++- dragonfly-client-util/src/sysinfo/memory.rs | 9 +- dragonfly-client/src/announcer/mod.rs | 20 +- 5 files changed, 302 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfe94ffe..b99cc38f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -797,7 +797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8599749b6667e2f0c910c1d0dff6901163ff698a52d5a39720f61b5be4b20d3" dependencies = [ "futures-core", - "prost 0.14.3", + "prost 0.14.4", "prost-types", "tonic", "tonic-prost", @@ -817,7 +817,7 @@ dependencies = [ "hdrhistogram", "humantime", "hyper-util", - "prost 0.14.3", + "prost 0.14.4", "prost-types", "serde", "serde_json", @@ -1285,11 +1285,11 @@ dependencies = [ [[package]] name = "dragonfly-api" -version = "2.2.30" +version = "2.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc71e0b2ce9c6315d54945d1c64de28a210a93aed009e981e87f35f2895482e7" +checksum = "cf76fa86d7330ac30c27189315fe5f4692bfd80c68a7616100f994c6d2699651" dependencies = [ - "prost 0.14.3", + "prost 0.14.4", "prost-types", "prost-wkt-types", "serde", @@ -3362,7 +3362,7 @@ dependencies = [ "http-body-util", "hyper 1.9.0", "hyper-util", - "prost 0.14.3", + "prost 0.14.4", "rand 0.9.4", "serde", "serde_json", @@ -3874,7 +3874,7 @@ dependencies = [ "opentelemetry-http", "opentelemetry-proto", "opentelemetry_sdk", - "prost 0.14.3", + "prost 0.14.4", "reqwest 0.12.28", "thiserror 2.0.18", "tokio", @@ -3889,7 +3889,7 @@ checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ "opentelemetry", "opentelemetry_sdk", - "prost 0.14.3", + "prost 0.14.4", "tonic", "tonic-prost", ] @@ -4490,12 +4490,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", - "prost-derive 0.14.3", + "prost-derive 0.14.4", ] [[package]] @@ -4510,7 +4510,7 @@ dependencies = [ "multimap", "petgraph", "prettyplease", - "prost 0.14.3", + "prost 0.14.4", "prost-types", "pulldown-cmark", "pulldown-cmark-to-cmark", @@ -4534,9 +4534,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools", @@ -4551,7 +4551,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ - "prost 0.14.3", + "prost 0.14.4", ] [[package]] @@ -4562,7 +4562,7 @@ checksum = "cd3de5e9c9e84fcb5efa204b8e283d23e615a8bc8c777bf1d6622bb01dc61445" dependencies = [ "chrono", "inventory", - "prost 0.14.3", + "prost 0.14.4", "serde", "serde_derive", "serde_json", @@ -4576,7 +4576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe500dc80e757a75e1e8fb7290e448d62dfba3105ece1d058579cb00b58151cd" dependencies = [ "heck", - "prost 0.14.3", + "prost 0.14.4", "prost-build", "prost-types", "quote", @@ -4589,7 +4589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13807eaa7e15833d06e899008371926201cdcd11d74b6d490f49130cdb3f415e" dependencies = [ "chrono", - "prost 0.14.3", + "prost 0.14.4", "prost-build", "prost-types", "prost-wkt", @@ -6313,7 +6313,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4ff0636fef47afb3ec02818f5bceb4377b8abb9d6a386aeade18bd6212f8eb7" dependencies = [ - "prost 0.14.3", + "prost 0.14.4", "tokio", "tokio-stream", "tonic", @@ -6327,7 +6327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", - "prost 0.14.3", + "prost 0.14.4", "tonic", ] @@ -6353,7 +6353,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaf0685a51e6d02b502ba0764002e766b7f3042aed13d9234925b6ffbfa3fca7" dependencies = [ - "prost 0.14.3", + "prost 0.14.4", "prost-types", "tokio", "tokio-stream", diff --git a/Cargo.toml b/Cargo.toml index 6de43fa7..7ec45f89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ chrono = { version = "0.4.45", features = ["clock", "serde"] } clap = { version = "4.6.1", features = ["derive", "env"] } crc32fast = "1.5.0" dashmap = "6.1.0" -dragonfly-api = "=2.2.30" +dragonfly-api = "=2.2.31" dragonfly-client = { path = "dragonfly-client", version = "1.4.0" } dragonfly-client-backend = { path = "dragonfly-client-backend", version = "1.4.0" } dragonfly-client-config = { path = "dragonfly-client-config", version = "1.4.0" } diff --git a/dragonfly-client-util/src/sysinfo/disk.rs b/dragonfly-client-util/src/sysinfo/disk.rs index 7aa7478e..d2d62fbe 100644 --- a/dragonfly-client-util/src/sysinfo/disk.rs +++ b/dragonfly-client-util/src/sysinfo/disk.rs @@ -48,6 +48,16 @@ pub struct ProcessDiskStats { pub read_bandwidth: u64, } +/// Represents disk I/O statistics from cgroup (Linux containers/resource limits). +#[derive(Debug, Clone, Default)] +pub struct CgroupDiskStats { + /// The write bandwidth of the cgroup at the block-device level in bytes per second. + pub write_bandwidth: u64, + + /// The read bandwidth of the cgroup at the block-device level in bytes per second. + pub read_bandwidth: u64, +} + /// Represents a disk interface for monitoring disk statistics. #[derive(Debug, Clone, Default)] pub struct Disk { @@ -57,8 +67,10 @@ pub struct Disk { /// Implementation of disk monitoring functionality. /// -/// Provides methods to retrieve disk space information and process-specific -/// disk I/O statistics, including read/write bandwidth measurements. +/// Provides methods to retrieve disk statistics at three different levels: +/// - Path-level: Total, free and used space of the filesystem. +/// - Process-level: I/O bandwidth of a specific process. +/// - Cgroup-level: Block-device I/O bandwidth of the cgroup (Linux only). impl Disk { /// Default interval for refreshing disk statistics. const DEFAULT_DISK_REFRESH_INTERVAL: Duration = Duration::from_secs(1); @@ -104,11 +116,12 @@ impl Disk { }) } - /// Retrieves the disk I/O statistics for a specific process. + /// Retrieves the disk I/O statistics for the process with the given PID. /// - /// This method measures disk I/O activity over a time interval - /// (DEFAULT_DISK_REFRESH_INTERVAL) to calculate read and write bandwidth - /// for the specified process. + /// This method measures the process's /proc I/O counters over a time + /// interval (DEFAULT_DISK_REFRESH_INTERVAL) to calculate read and write + /// bandwidth. Writes are accounted at page-dirtying time rather than at + /// the block device. /// /// # Arguments /// * `pid` - The process ID to monitor for disk I/O statistics. @@ -151,4 +164,245 @@ impl Disk { read_bandwidth, } } + + /// Retrieves disk I/O statistics from the cgroup (Linux only). + /// + /// This method measures the cumulative block-device I/O counters of the + /// cgroup the process belongs to over a time interval + /// (DEFAULT_DISK_REFRESH_INTERVAL) to calculate device-level read and + /// write bandwidth. cgroup v2 supports writeback attribution, so flushed + /// page cache writes are charged to the cgroup that dirtied the pages, + /// while cgroup v1 only accounts direct and synchronous I/O. + /// + /// # Arguments + /// * `pid` - Process ID used to determine which cgroup to query. + /// + /// # Returns + /// Some(CgroupDiskStats) if cgroup I/O statistics are available and accessible, + /// None otherwise or on non-Linux platforms. + #[allow(unused_variables)] + pub async fn get_cgroup_stats(&self, pid: u32) -> Option { + #[cfg(target_os = "linux")] + { + // Lock the mutex to ensure exclusive access to disk stats. + let _guard = self.mutex.lock().await; + + // Take a baseline of the cumulative block-device I/O counters. + let (baseline_read_bytes, baseline_written_bytes) = Self::get_cgroup_io_counters(pid)?; + + // Sleep to calculate the disk traffic difference over + // the DEFAULT_DISK_REFRESH_INTERVAL. + tokio::time::sleep(Self::DEFAULT_DISK_REFRESH_INTERVAL).await; + + let (read_bytes, written_bytes) = Self::get_cgroup_io_counters(pid)?; + + // Calculate the write bandwidth in bytes per second. + let write_bandwidth = (written_bytes.saturating_sub(baseline_written_bytes) as f64 + / Self::DEFAULT_DISK_REFRESH_INTERVAL.as_secs_f64()) + .round() as u64; + + // Calculate the read bandwidth in bytes per second. + let read_bandwidth = (read_bytes.saturating_sub(baseline_read_bytes) as f64 + / Self::DEFAULT_DISK_REFRESH_INTERVAL.as_secs_f64()) + .round() as u64; + + debug!( + "process {} cgroup disk write bandwidth: {} bytes/s, read bandwidth: {} bytes/s", + pid, write_bandwidth, read_bandwidth + ); + + return Some(CgroupDiskStats { + write_bandwidth, + read_bandwidth, + }); + } + + #[cfg(not(target_os = "linux"))] + None + } + + /// Retrieves the cumulative bytes read from and written to the block + /// devices by the cgroup the process belongs to. + /// + /// # Arguments + /// * `pid` - The process ID used to determine which cgroup to read. + /// + /// # Returns + /// Some((read_bytes, written_bytes)) summed across all block devices if + /// the cgroup I/O statistics are accessible, None otherwise. + #[cfg(target_os = "linux")] + fn get_cgroup_io_counters(pid: u32) -> Option<(u64, u64)> { + use cgroups_rs::fs::hierarchies; + + if hierarchies::auto().v2() { + Self::get_cgroup_v2_io_counters(pid) + } else { + Self::get_cgroup_v1_io_counters(pid) + } + } + + /// Retrieves the cumulative block-device I/O counters from the cgroup v2 + /// io.stat file. + /// + /// # Arguments + /// * `pid` - The process ID used to determine which cgroup to read. + /// + /// # Returns + /// Some((read_bytes, written_bytes)) summed across all block devices, + /// None if the io.stat file is unavailable. + #[cfg(target_os = "linux")] + fn get_cgroup_v2_io_counters(pid: u32) -> Option<(u64, u64)> { + use crate::cgroups::get_cgroup_v2_path_by_pid; + use tracing::error; + + let path = match get_cgroup_v2_path_by_pid(pid) { + Ok(path) => path.join("io.stat"), + Err(err) => { + error!("failed to get cgroup v2 path for pid {}: {}", pid, err); + return None; + } + }; + + match std::fs::read_to_string(&path) { + Ok(content) => Some(Self::parse_cgroup_v2_io_stat(&content)), + Err(err) => { + error!("failed to read {}: {}", path.display(), err); + None + } + } + } + + /// Parses the content of a cgroup v2 io.stat file and sums the read and + /// written bytes across all block devices. + /// + /// Each line is `MAJ:MIN rbytes=N wbytes=N rios=N wios=N dbytes=N dios=N`. + /// + /// # Arguments + /// * `content` - The content of the io.stat file. + /// + /// # Returns + /// (read_bytes, written_bytes) summed across all block devices. + #[cfg(target_os = "linux")] + fn parse_cgroup_v2_io_stat(content: &str) -> (u64, u64) { + let (mut read_bytes, mut written_bytes) = (0u64, 0u64); + for part in content.split_whitespace() { + if let Some(value) = part.strip_prefix("rbytes=") { + read_bytes = read_bytes.saturating_add(value.parse().unwrap_or(0)); + } else if let Some(value) = part.strip_prefix("wbytes=") { + written_bytes = written_bytes.saturating_add(value.parse().unwrap_or(0)); + } + } + + (read_bytes, written_bytes) + } + + /// Retrieves the cumulative block-device I/O counters from the cgroup v1 + /// blkio controller. + /// + /// cgroup v1 charges page cache writeback to the root cgroup, so only + /// direct and synchronous I/O are accounted here. + /// + /// # Arguments + /// * `pid` - The process ID used to determine which cgroup to read. + /// + /// # Returns + /// Some((read_bytes, written_bytes)) summed across all block devices, + /// None if the blkio controller is unavailable. + #[cfg(target_os = "linux")] + fn get_cgroup_v1_io_counters(pid: u32) -> Option<(u64, u64)> { + use crate::cgroups::get_cgroup_by_pid; + use cgroups_rs::fs::blkio::BlkIoController; + use tracing::error; + + let cgroup = match get_cgroup_by_pid(pid) { + Ok(cgroup) => cgroup, + Err(err) => { + error!("failed to get cgroup for pid {}: {}", pid, err); + return None; + } + }; + + let Some(blkio_controller) = cgroup.controller_of::() else { + error!("no blkio controller found for pid {}", pid); + return None; + }; + + // blkio.throttle.io_service_bytes is maintained by any I/O scheduler, + // while blkio.io_service_bytes is only maintained by CFQ. + let blkio = blkio_controller.blkio(); + let io_service_bytes = if !blkio.throttle.io_service_bytes.is_empty() { + blkio.throttle.io_service_bytes + } else { + blkio.io_service_bytes + }; + + Some(Self::parse_cgroup_v1_io_stat(&io_service_bytes)) + } + + /// Parses the cgroup v1 blkio IoService entries and sums the read and + /// written bytes across all block devices. + /// + /// # Arguments + /// * `io_service_bytes` - The per-device IoService entries from + /// blkio.throttle.io_service_bytes or blkio.io_service_bytes. + /// + /// # Returns + /// (read_bytes, written_bytes) summed across all block devices. + #[cfg(target_os = "linux")] + fn parse_cgroup_v1_io_stat( + io_service_bytes: &[cgroups_rs::fs::blkio::IoService], + ) -> (u64, u64) { + io_service_bytes + .iter() + .fold((0u64, 0u64), |(read_bytes, written_bytes), io_service| { + ( + read_bytes.saturating_add(io_service.read), + written_bytes.saturating_add(io_service.write), + ) + }) + } +} + +#[cfg(all(test, target_os = "linux"))] +mod tests { + use super::*; + + #[test] + fn test_parse_cgroup_v2_io_stat() { + let content = "8:0 rbytes=90430464 wbytes=299008000 rios=8950 wios=1252 dbytes=50331648 dios=3021\n253:0 rbytes=1459200 wbytes=314773504 rios=192 wios=353 dbytes=0 dios=0"; + assert_eq!( + Disk::parse_cgroup_v2_io_stat(content), + (90430464 + 1459200, 299008000 + 314773504) + ); + + assert_eq!(Disk::parse_cgroup_v2_io_stat(""), (0, 0)); + } + + #[test] + fn test_parse_cgroup_v1_io_stat() { + use cgroups_rs::fs::blkio::IoService; + + let io_service_bytes = vec![ + IoService { + major: 8, + minor: 0, + read: 90430464, + write: 299008000, + ..Default::default() + }, + IoService { + major: 253, + minor: 0, + read: 1459200, + write: 314773504, + ..Default::default() + }, + ]; + assert_eq!( + Disk::parse_cgroup_v1_io_stat(&io_service_bytes), + (90430464 + 1459200, 299008000 + 314773504) + ); + + assert_eq!(Disk::parse_cgroup_v1_io_stat(&[]), (0, 0)); + } } diff --git a/dragonfly-client-util/src/sysinfo/memory.rs b/dragonfly-client-util/src/sysinfo/memory.rs index 91a012bf..74221b19 100644 --- a/dragonfly-client-util/src/sysinfo/memory.rs +++ b/dragonfly-client-util/src/sysinfo/memory.rs @@ -150,14 +150,9 @@ impl Memory { if let Some(memory_controller) = cgroup.controller_of::() { let memory_stats = memory_controller.memory_stat(); // The `rss` key only exists in cgroup v1's memory.stat. In cgroup v2 the - // equivalent counter (NR_ANON_MAPPED) is exported as `anon`. Note that - // `inactive_anon + active_anon` is NOT equivalent: the anon LRU lists - // include shmem/tmpfs pages and exclude mlocked (unevictable) and - // MADV_FREE'd pages, so only use it as a last-resort fallback. + // equivalent counter (NR_ANON_MAPPED) is exported as `anon`. let memory_usage = if cgroup.v2() { - memory_stats.stat.raw.get("anon").copied().unwrap_or( - memory_stats.stat.inactive_anon + memory_stats.stat.active_anon, - ) + memory_stats.stat.raw.get("anon").copied().unwrap_or(0) } else { memory_stats.stat.rss }; diff --git a/dragonfly-client/src/announcer/mod.rs b/dragonfly-client/src/announcer/mod.rs index d8e97d0d..f0d0e2bd 100644 --- a/dragonfly-client/src/announcer/mod.rs +++ b/dragonfly-client/src/announcer/mod.rs @@ -15,7 +15,9 @@ */ use crate::grpc::scheduler::SchedulerClient; -use dragonfly_api::common::v2::{Build, CgroupCpu, CgroupMemory, Cpu, Disk, Host, Memory, Network}; +use dragonfly_api::common::v2::{ + Build, CgroupCpu, CgroupDisk, CgroupMemory, Cpu, Disk, Host, Memory, Network, +}; use dragonfly_api::scheduler::v2::{AnnounceHostRequest, DeleteHostRequest}; use dragonfly_client_config::{ dfdaemon::{Config, HostType}, @@ -212,13 +214,14 @@ impl SchedulerAnnouncer { .disk .get_stats(self.config.storage.dir.as_path())?; let process_disk_stats = self.system_monitor.disk.get_process_stats(pid).await; - let disk = Disk { + let mut disk = Disk { total: disk_stats.total, free: disk_stats.free, used: disk_stats.usage, used_percent: disk_stats.used_percent, write_bandwidth: process_disk_stats.write_bandwidth, read_bandwidth: process_disk_stats.read_bandwidth, + cgroup: None, // TODO: Get the disk inodes information. inodes_total: 0, @@ -227,6 +230,19 @@ impl SchedulerAnnouncer { inodes_used_percent: 0.0, }; + // Get the cgroup disk information if running in a container environment. + if self.is_running_in_container { + disk.cgroup = self + .system_monitor + .disk + .get_cgroup_stats(pid) + .await + .map(|stats| CgroupDisk { + read_bandwidth: stats.read_bandwidth, + write_bandwidth: stats.write_bandwidth, + }); + } + // Get the build information. let build = Build { git_version: CARGO_PKG_VERSION.to_string(), From 8237f6d1c6c3778fbfe88b5810269f9e0dda3eb9 Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 21:13:23 +0800 Subject: [PATCH 7/8] style(sysinfo): remove explicit return in get_cgroup_disk_stats - Replace explicit `return` with implicit return expression - Follows Rust idiomatic style for function tail expressions Signed-off-by: Gaius --- dragonfly-client-util/src/sysinfo/disk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dragonfly-client-util/src/sysinfo/disk.rs b/dragonfly-client-util/src/sysinfo/disk.rs index d2d62fbe..390e89da 100644 --- a/dragonfly-client-util/src/sysinfo/disk.rs +++ b/dragonfly-client-util/src/sysinfo/disk.rs @@ -211,7 +211,7 @@ impl Disk { pid, write_bandwidth, read_bandwidth ); - return Some(CgroupDiskStats { + Some(CgroupDiskStats { write_bandwidth, read_bandwidth, }); From adc23a2e4a57bd85be9f8848f7128c56fdbd8126 Mon Sep 17 00:00:00 2001 From: Gaius Date: Mon, 13 Jul 2026 21:19:13 +0800 Subject: [PATCH 8/8] fix(sysinfo): remove semicolon from CgroupDiskStats return expression Fixes implicit unit return `()` instead of `Option` by removing the erroneous semicolon from the `Some(...)` expression. Signed-off-by: Gaius --- dragonfly-client-util/src/sysinfo/disk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dragonfly-client-util/src/sysinfo/disk.rs b/dragonfly-client-util/src/sysinfo/disk.rs index 390e89da..671acb71 100644 --- a/dragonfly-client-util/src/sysinfo/disk.rs +++ b/dragonfly-client-util/src/sysinfo/disk.rs @@ -214,7 +214,7 @@ impl Disk { Some(CgroupDiskStats { write_bandwidth, read_bandwidth, - }); + }) } #[cfg(not(target_os = "linux"))]