use std::{collections::HashSet, io::ErrorKind};
use log::debug;
use perf_event::{Builder, Group, ReadFormat, events::Software};
use crate::{
Result,
domain_type::RaplDomainType,
error::{PerfParanoidError, RaplError},
perf::{
event::{RaplEvent, discover_supported_domain_types},
socket::{SocketInfo, discover_socket_topology},
},
};
#[derive(Debug)]
pub struct PerfRaplDomain {
pub domain_type: RaplDomainType,
pub event_counter: RaplEvent,
}
impl PerfRaplDomain {
pub fn new(domain_type: RaplDomainType, event_counter: RaplEvent) -> Self {
Self {
domain_type,
event_counter,
}
}
#[allow(clippy::cast_precision_loss)]
pub fn compute_scale(&self, value: u64) -> f64 {
value as f64 * self.event_counter.scale
}
pub fn get_name(&self, socket: u32) -> String {
self.domain_type.to_string_socket(socket)
}
}
pub fn build_group_for_socket(socket: &SocketInfo) -> Result<Group> {
for cpu in &socket.cpus {
debug!("Trying to build perf group on CPU {cpu}");
match Builder::new(Software::DUMMY)
.read_format(ReadFormat::GROUP)
.one_cpu(*cpu as usize)
.any_pid()
.exclude_kernel(false)
.exclude_hv(false)
.build_group()
{
Ok(group) => {
debug!("Perf group successfully built on CPU {cpu}");
return Ok(group);
}
Err(err) => {
debug!("Failed to build group on CPU {cpu}: {err:?}");
if err.kind() == ErrorKind::PermissionDenied {
return Err(PerfParanoidError::IoError(err).into());
}
}
}
}
Err(RaplError::FailToOpenDomainCounter(format!(
"unable to find a CPU associate to the {} socket's PMU",
socket.id
)))
}
pub fn discover_domains(domains_to_discover: Option<&HashSet<u32>>) -> Result<Vec<SocketInfo>> {
let mut socket_topology = discover_socket_topology(domains_to_discover)?;
debug!("Discovered {} socket(s)", socket_topology.len());
for socket in &mut socket_topology {
socket.domain_types = discover_supported_domain_types(socket.id)?;
}
Ok(socket_topology)
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
use crate::domain_type::RaplDomainType;
use crate::perf::event::RaplEvent;
use std::mem::ManuallyDrop;
fn mock_domain(domain_type: RaplDomainType, scale: f64) -> ManuallyDrop<PerfRaplDomain> {
let event = RaplEvent {
scale,
counter: unsafe { std::mem::zeroed() },
};
ManuallyDrop::new(PerfRaplDomain::new(domain_type, event))
}
#[test]
fn compute_scale_multiplies_raw_value_by_scale() {
let d = mock_domain(RaplDomainType::Package, 2.5);
assert_eq!(d.compute_scale(4), 10.0);
}
#[test]
fn compute_scale_zero_value_returns_zero() {
let d = mock_domain(RaplDomainType::Package, 2.5);
assert_eq!(d.compute_scale(0), 0.0);
}
#[test]
fn compute_scale_scale_of_one_is_identity() {
let d = mock_domain(RaplDomainType::Dram, 1.0);
assert_eq!(d.compute_scale(42), 42.0);
}
#[test]
fn get_name_includes_socket_for_regular_domains() {
assert_eq!(
mock_domain(RaplDomainType::Package, 1.0).get_name(0),
"PACKAGE-0"
);
assert_eq!(
mock_domain(RaplDomainType::Package, 1.0).get_name(1),
"PACKAGE-1"
);
assert_eq!(mock_domain(RaplDomainType::Core, 1.0).get_name(0), "CORE-0");
assert_eq!(mock_domain(RaplDomainType::Dram, 1.0).get_name(2), "DRAM-2");
assert_eq!(
mock_domain(RaplDomainType::Uncore, 1.0).get_name(0),
"UNCORE-0"
);
}
#[test]
fn get_name_psys_omits_socket_number() {
assert_eq!(mock_domain(RaplDomainType::Psys, 1.0).get_name(0), "PSYS");
assert_eq!(mock_domain(RaplDomainType::Psys, 1.0).get_name(99), "PSYS");
}
}