use crate::sensors::units::Unit::MicroJoule;
use crate::sensors::utils::current_system_time_since_epoch;
use crate::sensors::{CPUSocket, Domain, Record, RecordReader, Sensor, Topology};
use procfs::{modules, KernelModule};
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use std::{env, fs};
use super::units::Unit;
pub const DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES: u16 = 1;
pub const DEFAULT_BUFFER_PER_DOMAIN_MAX_KBYTES: u16 = 1;
pub struct PowercapRAPLSensor {
base_path: String,
buffer_per_socket_max_kbytes: u16,
buffer_per_domain_max_kbytes: u16,
virtual_machine: bool,
}
impl PowercapRAPLSensor {
pub fn new(
buffer_per_socket_max_kbytes: u16,
buffer_per_domain_max_kbytes: u16,
virtual_machine: bool,
) -> PowercapRAPLSensor {
let mut powercap_path = String::from("/sys/class/powercap");
if virtual_machine {
powercap_path = String::from("/var/scaphandre");
if let Ok(val) = env::var("SCAPHANDRE_POWERCAP_PATH") {
powercap_path = val;
}
info!("Powercap_rapl path is: {}", powercap_path);
}
PowercapRAPLSensor {
base_path: powercap_path,
buffer_per_socket_max_kbytes,
buffer_per_domain_max_kbytes,
virtual_machine,
}
}
pub fn check_module() -> Result<String, String> {
let modules = modules().unwrap();
let rapl_modules = modules
.iter()
.filter(|(_, v)| {
v.name == "intel_rapl"
|| v.name == "intel_rapl_msr"
|| v.name == "intel_rapl_common"
})
.collect::<HashMap<&String, &KernelModule>>();
if !rapl_modules.is_empty() {
Ok(String::from(
"intel_rapl or intel_rapl_msr+intel_rapl_common modules found.",
))
} else {
Err(String::from(
"None of intel_rapl, intel_rapl_common or intel_rapl_msr kernel modules found.",
))
}
}
}
impl RecordReader for Topology {
fn read_record(&self) -> Result<Record, Box<dyn Error>> {
if let Some(psys_record) = self.get_rapl_psys_energy_microjoules() {
debug!("Using PSYS metric");
Ok(psys_record)
} else {
let mut total: i128 = 0;
debug!("Suming socket PKG and DRAM metrics to get host metric");
for s in &self.sockets {
if let Ok(r) = s.read_record() {
match r.value.trim().parse::<i128>() {
Ok(val) => {
total += val;
}
Err(e) => {
warn!("could'nt convert {} to i128: {}", r.value.trim(), e);
}
}
}
for d in &s.domains {
if d.name == "dram" {
if let Ok(dr) = d.read_record() {
match dr.value.trim().parse::<i128>() {
Ok(val) => {
total += val;
}
Err(e) => {
warn!("could'nt convert {} to i128: {}", dr.value.trim(), e);
}
}
}
}
}
}
Ok(Record::new(
current_system_time_since_epoch(),
total.to_string(),
Unit::MicroJoule,
))
}
}
}
impl RecordReader for CPUSocket {
fn read_record(&self) -> Result<Record, Box<dyn Error>> {
let source_file = self.sensor_data.get("source_file").unwrap();
match fs::read_to_string(source_file) {
Ok(result) => Ok(Record::new(
current_system_time_since_epoch(),
result,
MicroJoule,
)),
Err(error) => Err(Box::new(error)),
}
}
}
impl RecordReader for Domain {
fn read_record(&self) -> Result<Record, Box<dyn Error>> {
let source_file = self.sensor_data.get("source_file").unwrap();
match fs::read_to_string(source_file) {
Ok(result) => Ok(Record {
timestamp: current_system_time_since_epoch(),
unit: MicroJoule,
value: result,
}),
Err(error) => Err(Box::new(error)),
}
}
}
impl Sensor for PowercapRAPLSensor {
fn generate_topology(&self) -> Result<Topology, Box<dyn Error>> {
let modules_state = PowercapRAPLSensor::check_module();
if modules_state.is_err() && !self.virtual_machine {
warn!("Couldn't find intel_rapl modules.");
}
let mut topo = Topology::new(HashMap::new());
let re_socket = Regex::new(r"^.*/intel-rapl:\d+$").unwrap();
let re_domain = Regex::new(r"^.*/intel-rapl:\d+:\d+$").unwrap();
let re_socket_mmio = Regex::new(r"^.*/intel-rapl-mmio:\d+$").unwrap();
let re_domain_mmio = Regex::new(r"^.*/intel-rapl-mmio:\d+:\d+$").unwrap();
let mut re_domain_matched = false;
for folder in fs::read_dir(&self.base_path).unwrap() {
let folder_name = String::from(folder.unwrap().path().to_str().unwrap());
info!("working on {folder_name}");
if re_domain.is_match(&folder_name) {
re_domain_matched = true;
let mut splitted = folder_name.split(':');
let _ = splitted.next();
let socket_id = String::from(splitted.next().unwrap()).parse().unwrap();
let domain_id = String::from(splitted.next().unwrap()).parse().unwrap();
let mut sensor_data_for_socket = HashMap::new();
sensor_data_for_socket.insert(
String::from("source_file"),
format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id),
);
topo.safe_add_socket(
socket_id,
vec![],
vec![],
format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id),
self.buffer_per_socket_max_kbytes,
sensor_data_for_socket,
);
let mut sensor_data_for_domain = HashMap::new();
sensor_data_for_domain.insert(
String::from("source_file"),
format!(
"{}/intel-rapl:{}:{}/energy_uj",
self.base_path, socket_id, domain_id
),
);
if let Ok(domain_name) = &fs::read_to_string(format!("{folder_name}/name")) {
topo.safe_add_domain_to_socket(
socket_id,
domain_id,
domain_name.trim(),
&format!(
"{}/intel-rapl:{}:{}/energy_uj",
self.base_path, socket_id, domain_id
),
self.buffer_per_domain_max_kbytes,
sensor_data_for_domain,
);
}
} else if re_socket_mmio.is_match(&folder_name) {
info!("matched {folder_name}");
let mut splitted = folder_name.split(':');
let _ = splitted.next();
let socket_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap();
for s in topo.get_sockets() {
if socket_id == s.id {
s.sensor_data.insert(
String::from("mmio"),
format!("{}/intel-rapl-mmio:{}/energy_uj", self.base_path, socket_id),
);
}
}
} else if re_domain_mmio.is_match(&folder_name) {
debug!("matched {folder_name}");
let mut splitted = folder_name.split(':');
let _ = splitted.next();
let socket_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap();
for s in topo.get_sockets() {
if socket_id == s.id {
let mmio_file = format!("{}/energy_uj", folder_name);
for d in s.get_domains() {
let name_in_folder =
fs::read_to_string(format!("{folder_name}/name")).unwrap();
if d.name.trim() == name_in_folder.trim() {
d.sensor_data
.insert(String::from("mmio"), mmio_file.clone());
}
}
}
}
}
}
if !re_domain_matched {
warn!("Couldn't find domain folders from powercap. Fallback on socket folders.");
warn!("Scaphandre will not be able to provide per-domain data.");
let mut found = false;
for folder in fs::read_dir(&self.base_path).unwrap() {
let folder_name = String::from(folder.unwrap().path().to_str().unwrap());
if let Ok(domain_name) = &fs::read_to_string(format!("{folder_name}/name")) {
if domain_name != "psys" && re_socket.is_match(&folder_name) {
let mut splitted = folder_name.split(':');
let _ = splitted.next();
let socket_id = String::from(splitted.next().unwrap()).parse().unwrap();
let mut sensor_data_for_socket = HashMap::new();
sensor_data_for_socket.insert(
String::from("source_file"),
format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id),
);
topo.safe_add_socket(
socket_id,
vec![],
vec![],
format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id),
self.buffer_per_socket_max_kbytes,
sensor_data_for_socket,
);
found = true;
}
} else {
warn!("Couldn't read RAPL folder name : {folder_name}");
}
}
if !found {
warn!("Could'nt find any RAPL PKG domain (nor psys).");
}
}
for folder in fs::read_dir(&self.base_path).unwrap() {
let folder_name = String::from(folder.unwrap().path().to_str().unwrap());
match &fs::read_to_string(format!("{folder_name}/name")) {
Ok(domain_name) => {
let domain_name_trimed = domain_name.trim();
if domain_name_trimed == "psys" {
debug!("Found PSYS domain RAPL folder.");
topo._sensor_data.insert(String::from("psys"), folder_name);
}
}
Err(e) => {
debug!("Got error while reading {folder_name}: {e}");
}
}
}
topo.add_cpu_cores();
Ok(topo)
}
fn get_topology(&self) -> Box<Option<Topology>> {
let topology = self.generate_topology().ok();
if topology.is_none() {
panic!("Couldn't generate the topology !");
}
Box::new(topology)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::any::type_name;
fn type_of<T>(_: T) -> &'static str {
type_name::<T>()
}
#[test]
fn get_topology_returns_topology_type() {
let sensor = PowercapRAPLSensor::new(1, 1, false);
let topology = sensor.get_topology();
assert_eq!(
"alloc::boxed::Box<core::option::Option<scaphandre::sensors::Topology>>",
type_of(topology)
)
}
}