pub mod ffi;
use crate::CpuError;
use ffi::{KPC_CLASS_CONFIGURABLE, KPC_CLASS_FIXED};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Counter {
Cycles = 0,
Instructions = 1,
Branches = 2,
BranchMisses = 3,
L1dMisses = 4,
L1iMisses = 5,
L2Misses = 6,
}
impl Counter {
pub const COUNT: usize = 7;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Snapshot {
pub counters: [u64; 16],
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Counts {
pub cycles: u64,
pub instructions: u64,
pub branches: u64,
pub branch_misses: u64,
pub l1d_misses: u64,
pub l1i_misses: u64,
pub l2_misses: u64,
}
fn event_selector(c: Counter) -> u64 {
match c {
Counter::Cycles => 0x02, Counter::Instructions => 0x8c, Counter::Branches => 0x90, Counter::BranchMisses => 0x91, Counter::L1dMisses => 0xa3, Counter::L1iMisses => 0xa1, Counter::L2Misses => 0xa8, }
}
pub struct Counters {
classes: u32,
counter_count: u32,
active: Vec<Counter>,
hw_indices: Vec<usize>,
}
impl Counters {
pub fn new(counters: &[Counter]) -> crate::Result<Self> {
let vt = ffi::vtable().map_err(|_| CpuError::PmuNotAvailable)?;
let classes = KPC_CLASS_FIXED | KPC_CLASS_CONFIGURABLE;
let n_counters = unsafe { (vt.get_counter_count)(classes) };
let n_config = unsafe { (vt.get_config_count)(classes) };
let mut config = vec![0u64; n_config as usize];
let n_fixed = unsafe { (vt.get_counter_count)(KPC_CLASS_FIXED) } as usize;
let mut cfg_slot = 0usize;
let mut active = Vec::new();
let mut hw_indices = Vec::new();
for &c in counters {
match c {
Counter::Cycles => {
active.push(c);
hw_indices.push(0);
}
Counter::Instructions => {
active.push(c);
hw_indices.push(1);
}
_ => {
if n_fixed + cfg_slot >= n_counters as usize {
return Err(CpuError::PmuConfigFailed(
"not enough configurable PMU slots".into(),
));
}
if cfg_slot < config.len() {
config[cfg_slot] = event_selector(c);
}
active.push(c);
hw_indices.push(n_fixed + cfg_slot);
cfg_slot += 1;
}
}
}
let rc = unsafe { (vt.set_config)(classes, config.as_ptr()) };
if rc != 0 {
return Err(CpuError::PmuConfigFailed(format!(
"kpc_set_config returned {rc}"
)));
}
Ok(Self {
classes,
counter_count: n_counters,
active,
hw_indices,
})
}
pub fn start(&mut self) {
let vt = ffi::vtable().expect("vtable already resolved");
unsafe {
(vt.set_counting)(self.classes);
(vt.set_thread_counting)(self.classes);
}
}
pub fn read(&self) -> Snapshot {
let vt = ffi::vtable().expect("vtable already resolved");
let mut snap = Snapshot::default();
unsafe {
(vt.get_thread_counters)(
0, self.counter_count.min(16),
snap.counters.as_mut_ptr(),
);
}
snap
}
pub fn stop(&mut self) {
let vt = ffi::vtable().expect("vtable already resolved");
unsafe {
(vt.set_thread_counting)(0);
(vt.set_counting)(0);
}
}
pub fn elapsed(&self, a: &Snapshot, b: &Snapshot) -> Counts {
let mut counts = Counts::default();
for (i, &c) in self.active.iter().enumerate() {
let idx = self.hw_indices[i];
let delta = b.counters[idx].wrapping_sub(a.counters[idx]);
match c {
Counter::Cycles => counts.cycles = delta,
Counter::Instructions => counts.instructions = delta,
Counter::Branches => counts.branches = delta,
Counter::BranchMisses => counts.branch_misses = delta,
Counter::L1dMisses => counts.l1d_misses = delta,
Counter::L1iMisses => counts.l1i_misses = delta,
Counter::L2Misses => counts.l2_misses = delta,
}
}
counts
}
}