From 569775f549bcf38e2cb612166e556be818c38849 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Thu, 12 Mar 2026 00:11:29 +0545 Subject: [PATCH] feat(src/commands): implement dynamic CPU TDP limit control and status reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Features - Add per‑CPU power limit commands using Intel RAPL (PL1/PL2) and AMD hwmon constraints - Expose current TDP limits in status output, distinguishing RAPL, AMD hwmon, or hardware‑managed - Gracefully handle absence of required sysfs entries Enhancements - Extend the power command set with a `LimitTdp` sub‑command - Add debug logging for limit application attempts Bug Fixes - Ensure the command degrades gracefully when hardware does not support dynamic power caps --- src/commands/power.rs | 94 ++++++++++++++++++++++++++++++++++++++++++ src/commands/status.rs | 59 ++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/src/commands/power.rs b/src/commands/power.rs index 8d11fa4..56e7252 100644 --- a/src/commands/power.rs +++ b/src/commands/power.rs @@ -61,7 +61,82 @@ fn set_energy_performance_preference(epp: &str) { info!("Set Energy Performance Preference to '{}' on {} cores", epp, success_count); } } +fn set_intel_rapl_limit(watts: u32) -> bool { + let mut success = false; + let microwatts = watts * 1_000_000; + + // Intel RAPL + let rapl_dir = Path::new("/sys/class/powercap/intel-rapl"); + if rapl_dir.exists() { + if let Ok(entries) = fs::read_dir(rapl_dir) { + for entry in entries.flatten() { + let path = entry.path(); + let name = path.file_name().unwrap_or_default().to_string_lossy(); + + // Set limits for package-0 (entire CPU package) + if name.starts_with("intel-rapl:") { + let constraint_0 = path.join("constraint_0_power_limit_uw"); // PL1 + let constraint_1 = path.join("constraint_1_power_limit_uw"); // PL2 + + if constraint_0.exists() { + match fs::write(&constraint_0, microwatts.to_string()) { + Ok(_) => { + log::debug!("Set RAPL constraint 0 to {}uW", microwatts); + success = true; + } + Err(e) => log::debug!("Failed to write RAPL 0: {}", e), + } + } + if constraint_1.exists() { + // Usually PL2 (Boost limit) is set ~20% higher than PL1 (Sustained limit) + // But for a strict override we clamp BOTH to the requested static wattage limit. + match fs::write(&constraint_1, microwatts.to_string()) { + Ok(_) => { + log::debug!("Set RAPL constraint 1 to {}uW", microwatts); + success = true; + } + Err(e) => log::debug!("Failed to write RAPL 1: {}", e), + } + } + } + } + } + } + success +} +fn set_amd_hwmon_limit(watts: u32) -> bool { + let mut success = false; + let microwatts = watts * 1_000_000; + + // AMD hwmon (Typically exposed via hwmon platform drivers like k10temp or amd_pmc) + let hwmon_dir = Path::new("/sys/class/hwmon"); + if hwmon_dir.exists() { + if let Ok(entries) = fs::read_dir(hwmon_dir) { + for entry in entries.flatten() { + let path = entry.path(); + let power1_cap = path.join("power1_cap"); + let name_path = path.join("name"); + + if power1_cap.exists() { + // Try to verify this is actually the CPU and not a generic sensor + if let Ok(name) = fs::read_to_string(&name_path) { + if name.trim().contains("amd") { + match fs::write(&power1_cap, microwatts.to_string()) { + Ok(_) => { + log::debug!("Set AMD hwmon power1_cap to {}uW", microwatts); + success = true; + } + Err(e) => log::debug!("Failed to write AMD hwmon: {}", e), + } + } + } + } + } + } + } + success +} fn set_platform_profile(profile: &str) { let platform_profile_path = Path::new("/sys/firmware/acpi/platform_profile"); if platform_profile_path.exists() { @@ -101,5 +176,24 @@ pub fn execute(command: &PowerCommands) { set_energy_performance_preference("power"); println!("Operation completed successfully."); } + PowerCommands::LimitTdp { watts } => { + println!("Setting CPU Package TDP Limit to {} Watts...", watts); + + let mut applied = false; + if set_intel_rapl_limit(*watts) { + applied = true; + println!("Successfully applied Intel RAPL boundaries (PL1/PL2)."); + } + if set_amd_hwmon_limit(*watts) { + applied = true; + println!("Successfully applied AMD HWMon power capping."); + } + + if applied { + println!("Operation completed successfully."); + } else { + error!("Hardware does not support dynamic powercap limits via intel-rapl or standard hwmon."); + } + } } } diff --git a/src/commands/status.rs b/src/commands/status.rs index a6e592c..c122194 100644 --- a/src/commands/status.rs +++ b/src/commands/status.rs @@ -93,4 +93,63 @@ pub fn execute() { if !thermal_found { println!("Cooling Profile: Unknown / Firmware managed"); } + + // TDP Limit Status + let mut tdp_found = false; + + // Check Intel RAPL + let rapl_dir = Path::new("/sys/class/powercap/intel-rapl"); + if rapl_dir.exists() { + if let Ok(entries) = fs::read_dir(rapl_dir) { + for entry in entries.flatten() { + let path = entry.path(); + let name = path.file_name().unwrap_or_default().to_string_lossy(); + + if name.starts_with("intel-rapl:") { + let constraint_0 = path.join("constraint_0_power_limit_uw"); // PL1 + if constraint_0.exists() { + if let Ok(limit_uw) = fs::read_to_string(&constraint_0) { + if let Ok(limit) = limit_uw.trim().parse::() { + println!("CPU TDP Limit: {}W (Intel RAPL PL1)", limit / 1_000_000); + tdp_found = true; + break; + } + } + } + } + } + } + } + + // Check AMD HWMon + if !tdp_found { + let hwmon_dir = Path::new("/sys/class/hwmon"); + if hwmon_dir.exists() { + if let Ok(entries) = fs::read_dir(hwmon_dir) { + for entry in entries.flatten() { + let path = entry.path(); + let power1_cap = path.join("power1_cap"); + let name_path = path.join("name"); + + if power1_cap.exists() { + if let Ok(name) = fs::read_to_string(&name_path) { + if name.trim().contains("amd") { + if let Ok(limit_uw) = fs::read_to_string(&power1_cap) { + if let Ok(limit) = limit_uw.trim().parse::() { + println!("CPU TDP Limit: {}W (AMD hwmon)", limit / 1_000_000); + tdp_found = true; + break; + } + } + } + } + } + } + } + } + } + + if !tdp_found { + println!("CPU TDP Limit: Hardware Managed"); + } }