Add AMD RAPL power tracking interface

This commit is contained in:
NGnius (Graham) 2024-09-06 11:12:58 -04:00
parent c1ebbd1ce8
commit 95e52020f9
4 changed files with 204 additions and 18 deletions

View file

@ -8,3 +8,6 @@ description = "Power toolbox for processors"
[dependencies]
powerbox = { version = "0.1", path = "../core" }
sysfuss = { version = "0.4", path = "../../../sysfs-nav" }
[target.'cfg(any(target_arch = "x86_64", target_arch = "x86"))'.dependencies]
x86 = "0.52"

View file

@ -0,0 +1,146 @@
use std::io::{Seek, SeekFrom, Read};
use powerbox::{Power, PowerOp};
use powerbox::primitives::Value;
use crate::cpu::{CpuPower, CpuPowerOp};
use super::BasicCpu;
const CPUDEV_ROOT: &str = "/dev/cpu/";
const CPUDEV_MSR: &str = "msr";
const MSR_RAPL_POWER_UNIT: u64 = 0xC0010299;
pub struct AmdCpu {
basic: BasicCpu,
}
impl AmdCpu {
fn msr_path(&self) -> Option<std::path::PathBuf> {
self.basic.index.map(|index| std::path::PathBuf::from(CPUDEV_ROOT).join(index.to_string()).join(CPUDEV_MSR))
}
fn is_compatible() -> bool {
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
if let Some(vendor_info) = x86::cpuid::CpuId::new().get_vendor_info() {
vendor_info.as_str() == "AuthenticAMD"
} else {
false
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
{false}
}
}
impl core::ops::Deref for AmdCpu {
type Target = BasicCpu;
fn deref(&self) -> &Self::Target {
&self.basic
}
}
#[derive(Debug)]
pub struct NotAmd;
impl core::fmt::Display for NotAmd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "non-AMD CPU")
}
}
impl std::error::Error for NotAmd {}
impl std::convert::TryFrom<BasicCpu> for AmdCpu {
type Error = NotAmd;
fn try_from(basic: BasicCpu) -> Result<Self, Self::Error> {
if Self::is_compatible() {
Ok(Self { basic })
} else {
Err(NotAmd)
}
}
}
pub enum AmdPowerUsage {
Get(GetAmdPowerUsage),
}
impl PowerOp for AmdPowerUsage {
fn is_eq_op(&self, other: &Self) -> bool {
match self {
Self::Get(_) => matches!(other, Self::Get(_)),
}
}
}
impl CpuPowerOp for AmdPowerUsage {}
impl Power<AmdPowerUsage> for AmdCpu {
fn is_on(&self) -> bool {
self.basic.is_online()
}
fn is_available(&self) -> bool {
self.basic.is_exists()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=AmdPowerUsage>> {
Box::new(
Power::<GetAmdPowerUsage, _>::supported_operations(self).map(core::convert::Into::into)
)
}
fn act(&self, op: AmdPowerUsage) -> Result<Value, powerbox::PowerError> {
match op {
AmdPowerUsage::Get(get) => self.act(get).map(|x| Value::UInt(x as _)),
}
}
}
impl CpuPower<AmdPowerUsage> for AmdCpu {}
pub struct GetAmdPowerUsage; // number
impl PowerOp for GetAmdPowerUsage {
fn is_eq_op(&self, _: &Self) -> bool {
true
}
}
impl CpuPowerOp for GetAmdPowerUsage {}
impl Power<GetAmdPowerUsage, u64> for AmdCpu {
fn is_on(&self) -> bool {
self.basic.is_online()
}
fn is_available(&self) -> bool {
self.basic.is_exists()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=GetAmdPowerUsage>> {
if let Some(msr_path) = self.msr_path() {
if Self::is_compatible() && msr_path.exists() { return Box::new(core::iter::once(GetAmdPowerUsage)); }
}
Box::new(core::iter::empty())
}
fn act(&self, _: GetAmdPowerUsage) -> Result<u64, powerbox::PowerError> {
let mut buf = [0u8; 8];
let path = self.msr_path().ok_or(powerbox::PowerError::InvalidInput)?;
let mut file = std::fs::File::open(path).map_err(powerbox::PowerError::Io)?;
file.seek(SeekFrom::Start(MSR_RAPL_POWER_UNIT)).map_err(powerbox::PowerError::Io)?;
file.read(&mut buf).map_err(powerbox::PowerError::Io)?;
Ok(u64::from_le_bytes(buf))
}
}
impl CpuPower<GetAmdPowerUsage, u64> for AmdCpu {}
impl core::convert::Into<AmdPowerUsage> for GetAmdPowerUsage {
fn into(self) -> AmdPowerUsage {
AmdPowerUsage::Get(self)
}
}

View file

@ -22,31 +22,55 @@ const CPUFREQ_SCALING_DRIVER: &str = "cpufreq/scaling_driver";
/// General single CPU functionality
pub struct BasicCpu {
sysfs: BasicEntityPath,
pub(super) sysfs: BasicEntityPath,
pub(super) index: Option<usize>,
}
impl BasicCpu {
fn is_online(&self) -> bool {
pub(super) fn is_online(&self) -> bool {
self.sysfs.attribute::<u8>(CPU_ONLINE).is_ok_and(|num| num == 1)
}
fn is_exists(&self) -> bool {
pub(super) fn is_exists(&self) -> bool {
self.sysfs.as_ref().exists()
}
pub fn new(cpu: usize) -> Self {
Self {
sysfs: BasicEntityPath::new(std::path::PathBuf::from(super::basic_general::DEFAULT_CPU_ROOT).join(format!("cpu{}", cpu))),
index: Some(cpu),
}
}
pub fn with_root(p: impl AsRef<std::path::Path>) -> Self {
let index = p.as_ref().to_str().and_then(|s| parse_cpu_index(s));
Self {
sysfs: BasicEntityPath::new(p),
index,
}
}
}
fn parse_cpu_index(path: &str) -> Option<usize> {
// usually "/sys/devices/system/cpu/cpu<index>" with optional ending /
if let Some(stripped) = path.strip_prefix("/sys/devices/system/cpu/") {
let mut iter = stripped.char_indices();
if iter.next().is_some_and(|c| c.1 == 'c')
&& iter.next().is_some_and(|c| c.1 == 'p')
&& iter.next().is_some_and(|c| c.1 == 'u') {
let first_valid = 3;
let mut last_valid = first_valid;
while let Some(maybe_digit) = iter.next() {
if maybe_digit.1.is_ascii_digit() {
last_valid = maybe_digit.0;
}
}
return Some(stripped[first_valid..last_valid+1].parse().unwrap()); // a string of digits is always a valid number
}
}
None
}
impl Power<BasicCpuOp> for BasicCpu {
fn is_on(&self) -> bool {
self.is_online()
@ -57,11 +81,15 @@ impl Power<BasicCpuOp> for BasicCpu {
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=BasicCpuOp>> {
todo!()
Box::new(Power::<Online, _>::supported_operations(self).map(core::convert::Into::<BasicCpuOp>::into)
.chain(Power::<CpuFreq, _>::supported_operations(self).map(core::convert::Into::<BasicCpuOp>::into)))
}
fn act(&self, _op: BasicCpuOp) -> Result<Value, powerbox::PowerError> {
todo!()
fn act(&self, op: BasicCpuOp) -> Result<Value, powerbox::PowerError> {
match op {
BasicCpuOp::Online(online) => Power::<Online, _>::act(self, online),
BasicCpuOp::CpuFreq(cpu_freq) => Power::<CpuFreq, _>::act(self, cpu_freq),
}
}
}
@ -303,7 +331,7 @@ impl PowerOp for CpuFreq {
impl CpuPowerOp for CpuFreq {}
impl Power<CpuFreq, Value<RangeList<usize>>> for BasicCpu {
impl Power<CpuFreq> for BasicCpu {
fn is_on(&self) -> bool {
self.is_online()
}
@ -316,24 +344,29 @@ impl Power<CpuFreq, Value<RangeList<usize>>> for BasicCpu {
Box::new(
Power::<GetAffectedCpus, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into)
.chain(Power::<GetRelatedCpus, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
//.chain(Power::<CpuInfo, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
//.chain(Power::<EnergyPerf, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
//.chain(Power::<Scaling, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
.chain(Power::<CpuInfo, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
.chain(Power::<EnergyPerf, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
.chain(Power::<Scaling, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
)
}
fn act(&self, op: CpuFreq) -> Result<Value<RangeList<usize>>, powerbox::PowerError> {
fn act(&self, op: CpuFreq) -> Result<Value, powerbox::PowerError> {
match op {
CpuFreq::AffectedCpus(affected) => Power::<GetAffectedCpus, _>::act(self, affected).map(Value::Custom),
CpuFreq::RelatedCpus(related) => Power::<GetRelatedCpus, _>::act(self, related).map(Value::Custom),
//CpuFreq::CpuInfo(cpu_info) => if let Self::CpuInfo(other) = other { cpu_info.is_eq_op(other) } else { false },
//CpuFreq::Energy(en) => if let Self::Energy(other) = other { en.is_eq_op(other) } else { false },
//CpuFreq::Scaling(scaling) => if let Self::Scaling(other) = other { scaling.is_eq_op(other) } else { false },
_ => todo!(),
CpuFreq::AffectedCpus(affected) => Power::<GetAffectedCpus, _>::act(self, affected).map(|x| Value::Custom(Box::new(x) as _)),
CpuFreq::RelatedCpus(related) => Power::<GetRelatedCpus, _>::act(self, related).map(|x| Value::Custom(Box::new(x) as _)),
CpuFreq::CpuInfo(cpu_info) => Power::<CpuInfo, _>::act(self, cpu_info).map(|int| Value::UInt(int as _)),
CpuFreq::Energy(en) => Power::<EnergyPerf, _>::act(self, en).map(Value::into_any),
CpuFreq::Scaling(scaling) => Power::<Scaling, _>::act(self, scaling).map(Value::into_any),
}
}
}
impl core::convert::Into<BasicCpuOp> for CpuFreq {
fn into(self) -> BasicCpuOp {
BasicCpuOp::CpuFreq(self)
}
}
pub struct GetCpuInfoMinFreq; // number
impl PowerOp for GetCpuInfoMinFreq {
@ -529,7 +562,7 @@ impl Power<EnergyPerf> for BasicCpu {
fn act(&self, op: EnergyPerf) -> Result<Value, powerbox::PowerError> {
match op {
EnergyPerf::AvailablePreferences(avail) => self.act(avail).map(|x| Value::Custom(Box::new(x) as _)),
EnergyPerf::Preference(pref) => self.act(pref).map(Value::into_any),
EnergyPerf::Preference(pref) => self.act(pref),
}
}
}

View file

@ -9,3 +9,7 @@ pub use basic_general::BasicCpus;
#[allow(missing_docs)]
pub mod basic_single;
pub use basic_single::BasicCpu;
#[allow(missing_docs)]
pub mod amd;
pub use amd::AmdCpu;