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:
2 files changed
+153
No files matched your search
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user