feat(src/commands): implement dynamic CPU TDP limit control and status reporting

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
This commit is contained in:
sankalp committed 2026-03-12 00:11:29 +05:45
1 parent 0327885bef
commit 569775f549
2 files changed
+153

No files matched your search

+94
View File
@@ -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.");
}
}
}
}
+59
View File
@@ -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::<u64>() {
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::<u64>() {
println!("CPU TDP Limit: {}W (AMD hwmon)", limit / 1_000_000);
tdp_found = true;
break;
}
}
}
}
}
}
}
}
}
if !tdp_found {
println!("CPU TDP Limit: Hardware Managed");
}
}