Add AMD RAPL power tracking interface
This commit is contained in:
parent
c1ebbd1ce8
commit
95e52020f9
4 changed files with 204 additions and 18 deletions
|
@ -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"
|
||||
|
|
146
crates/procbox/src/cpu/amd.rs
Normal file
146
crates/procbox/src/cpu/amd.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue