use std::io::ErrorKind;
use log::{debug, info, warn};
use perf_event::{Builder, Counter, Group, events::Dynamic};
use crate::{
RaplError, Result,
domain_type::RaplDomainType,
perf::{
domain::{PerfRaplDomain, build_group_for_socket},
socket::{Socket, SocketInfo},
},
};
#[derive(Debug)]
pub struct RaplEvent {
pub counter: Counter,
pub scale: f64,
}
impl RaplEvent {
pub fn new(
domain_type: RaplDomainType,
socket: &SocketInfo,
group: &mut Group,
) -> Result<Self> {
let mut builder = Dynamic::builder("power")?;
if let Err(err) = builder.event(domain_type.to_perf_event()) {
return Err(match err.kind() {
std::io::ErrorKind::NotFound => RaplError::DomainNotSupported(domain_type),
_ => err.into(),
});
}
let scale = builder.scale()?.ok_or(RaplError::RetrieveScaleError)?;
for cpu in &socket.cpus {
debug!("Trying to build RAPL event {domain_type:?} on CPU {cpu}");
let counter_result = Builder::new(
builder
.build()
.map_err(|err| RaplError::FailToOpenDomainCounter(err.to_string()))?,
)
.one_cpu(*cpu as usize)
.any_pid()
.include_hv()
.include_kernel()
.build_with_group(&mut *group);
match counter_result {
Ok(counter) => {
debug!("RAPL event {domain_type:?} successfully built on CPU {cpu}");
return Ok(Self { counter, scale });
}
Err(err) => {
debug!("Failed to build RAPL event {domain_type:?} on CPU {cpu}: {err:?}");
}
}
}
Err(RaplError::FailToOpenDomainCounter(format!(
"unable to find a CPU associated with the {} socket's PMU for domain {:?}",
socket.id, domain_type
)))
}
}
pub fn open_counters(socket_topology: &[SocketInfo]) -> Result<Vec<Socket>> {
let mut sockets = Vec::new();
for socket_info in socket_topology {
debug!(
"Processing socket {} (cpus={:?})",
socket_info.id, socket_info.cpus
);
if socket_info.cpus.is_empty() {
warn!("Socket {} has no CPUs, skipping it", socket_info.id);
continue;
}
let mut group = build_group_for_socket(socket_info)?;
let domains = open_counters_for_socket(socket_info, &mut group)?;
group.enable().map_err(RaplError::from)?;
info!(
"Opened {} RAPL domains for socket {}",
domains.len(),
socket_info.id
);
sockets.push(Socket {
id: socket_info.id,
group,
domains,
});
}
Ok(sockets)
}
static PER_SOCKET_DOMAIN_TYPES: &[RaplDomainType] = &[
RaplDomainType::Package,
RaplDomainType::Core,
RaplDomainType::Uncore,
RaplDomainType::Dram,
];
fn is_domain_supported(domain_type: RaplDomainType) -> Result<bool> {
let mut builder = Dynamic::builder("power")?;
match builder.event(domain_type.to_perf_event()) {
Ok(_) => Ok(true),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
Err(err) => Err(err.into()),
}
}
pub(crate) fn discover_supported_domain_types(socket_id: u32) -> Result<Vec<RaplDomainType>> {
let mut domain_types = Vec::new();
for domain_type in PER_SOCKET_DOMAIN_TYPES {
if is_domain_supported(*domain_type)? {
domain_types.push(*domain_type);
}
}
if socket_id == 0 && is_domain_supported(RaplDomainType::Psys)? {
domain_types.push(RaplDomainType::Psys);
}
Ok(domain_types)
}
pub fn open_counters_for_socket(
socket: &SocketInfo,
group: &mut Group,
) -> Result<Vec<PerfRaplDomain>> {
let mut domains = Vec::new();
for domain_type in PER_SOCKET_DOMAIN_TYPES {
add_counter_to_domain_if_supported(*domain_type, socket, group, &mut domains)?;
}
if socket.id == 0
&& let Some(psys_domain) = open_psys_counter(socket, group)?
{
domains.push(psys_domain);
}
Ok(domains)
}
pub fn open_psys_counter(socket: &SocketInfo, group: &mut Group) -> Result<Option<PerfRaplDomain>> {
match RaplEvent::new(RaplDomainType::Psys, socket, group) {
Ok(counter) => {
let domain = PerfRaplDomain::new(RaplDomainType::Psys, counter);
Ok(Some(domain))
}
Err(RaplError::DomainNotSupported(_)) => {
debug!("PSYS domain not supported on this system");
Ok(None)
}
Err(err) => Err(err),
}
}
pub fn add_counter_to_domain_if_supported(
domain_type: RaplDomainType,
socket: &SocketInfo,
group: &mut Group,
domains: &mut Vec<PerfRaplDomain>,
) -> Result<()> {
let counter = match RaplEvent::new(domain_type, socket, group) {
Ok(counter) => counter,
Err(err) => match err {
RaplError::DomainNotSupported(_) => return Ok(()),
_ => {
return Err(err);
}
},
};
let domain = PerfRaplDomain::new(domain_type, counter);
domains.push(domain);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn socket(id: u32, cpus: Vec<u32>) -> SocketInfo {
SocketInfo {
id,
cpus,
domain_types: Vec::new(),
}
}
#[test]
fn open_counters_empty_topology_returns_empty() {
let result = open_counters(&[]).unwrap();
assert!(result.is_empty());
}
#[test]
fn open_counters_skips_socket_with_no_cpus() {
let result = open_counters(&[socket(0, vec![])]).unwrap();
assert!(result.is_empty());
}
}