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]
|
[dependencies]
|
||||||
powerbox = { version = "0.1", path = "../core" }
|
powerbox = { version = "0.1", path = "../core" }
|
||||||
sysfuss = { version = "0.4", path = "../../../sysfs-nav" }
|
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
|
/// General single CPU functionality
|
||||||
pub struct BasicCpu {
|
pub struct BasicCpu {
|
||||||
sysfs: BasicEntityPath,
|
pub(super) sysfs: BasicEntityPath,
|
||||||
|
pub(super) index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicCpu {
|
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)
|
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()
|
self.sysfs.as_ref().exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(cpu: usize) -> Self {
|
pub fn new(cpu: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sysfs: BasicEntityPath::new(std::path::PathBuf::from(super::basic_general::DEFAULT_CPU_ROOT).join(format!("cpu{}", cpu))),
|
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 {
|
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 {
|
Self {
|
||||||
sysfs: BasicEntityPath::new(p),
|
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 {
|
impl Power<BasicCpuOp> for BasicCpu {
|
||||||
fn is_on(&self) -> bool {
|
fn is_on(&self) -> bool {
|
||||||
self.is_online()
|
self.is_online()
|
||||||
|
@ -57,11 +81,15 @@ impl Power<BasicCpuOp> for BasicCpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=BasicCpuOp>> {
|
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> {
|
fn act(&self, op: BasicCpuOp) -> Result<Value, powerbox::PowerError> {
|
||||||
todo!()
|
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 CpuPowerOp for CpuFreq {}
|
||||||
|
|
||||||
impl Power<CpuFreq, Value<RangeList<usize>>> for BasicCpu {
|
impl Power<CpuFreq> for BasicCpu {
|
||||||
fn is_on(&self) -> bool {
|
fn is_on(&self) -> bool {
|
||||||
self.is_online()
|
self.is_online()
|
||||||
}
|
}
|
||||||
|
@ -316,24 +344,29 @@ impl Power<CpuFreq, Value<RangeList<usize>>> for BasicCpu {
|
||||||
Box::new(
|
Box::new(
|
||||||
Power::<GetAffectedCpus, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into)
|
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::<GetRelatedCpus, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
|
||||||
//.chain(Power::<CpuInfo, _>::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::<EnergyPerf, _>::supported_operations(self).map(core::convert::Into::<CpuFreq>::into))
|
||||||
//.chain(Power::<Scaling, _>::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 {
|
match op {
|
||||||
CpuFreq::AffectedCpus(affected) => Power::<GetAffectedCpus, _>::act(self, affected).map(Value::Custom),
|
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(Value::Custom),
|
CpuFreq::RelatedCpus(related) => Power::<GetRelatedCpus, _>::act(self, related).map(|x| Value::Custom(Box::new(x) as _)),
|
||||||
//CpuFreq::CpuInfo(cpu_info) => if let Self::CpuInfo(other) = other { cpu_info.is_eq_op(other) } else { false },
|
CpuFreq::CpuInfo(cpu_info) => Power::<CpuInfo, _>::act(self, cpu_info).map(|int| Value::UInt(int as _)),
|
||||||
//CpuFreq::Energy(en) => if let Self::Energy(other) = other { en.is_eq_op(other) } else { false },
|
CpuFreq::Energy(en) => Power::<EnergyPerf, _>::act(self, en).map(Value::into_any),
|
||||||
//CpuFreq::Scaling(scaling) => if let Self::Scaling(other) = other { scaling.is_eq_op(other) } else { false },
|
CpuFreq::Scaling(scaling) => Power::<Scaling, _>::act(self, scaling).map(Value::into_any),
|
||||||
_ => todo!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl core::convert::Into<BasicCpuOp> for CpuFreq {
|
||||||
|
fn into(self) -> BasicCpuOp {
|
||||||
|
BasicCpuOp::CpuFreq(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GetCpuInfoMinFreq; // number
|
pub struct GetCpuInfoMinFreq; // number
|
||||||
|
|
||||||
impl PowerOp for GetCpuInfoMinFreq {
|
impl PowerOp for GetCpuInfoMinFreq {
|
||||||
|
@ -529,7 +562,7 @@ impl Power<EnergyPerf> for BasicCpu {
|
||||||
fn act(&self, op: EnergyPerf) -> Result<Value, powerbox::PowerError> {
|
fn act(&self, op: EnergyPerf) -> Result<Value, powerbox::PowerError> {
|
||||||
match op {
|
match op {
|
||||||
EnergyPerf::AvailablePreferences(avail) => self.act(avail).map(|x| Value::Custom(Box::new(x) as _)),
|
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)]
|
#[allow(missing_docs)]
|
||||||
pub mod basic_single;
|
pub mod basic_single;
|
||||||
pub use basic_single::BasicCpu;
|
pub use basic_single::BasicCpu;
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub mod amd;
|
||||||
|
pub use amd::AmdCpu;
|
||||||
|
|
Loading…
Reference in a new issue