use core::{array, fmt, marker::PhantomData, ptr::NonNull};
use darwin_kperf_sys::{kperf::KPC_MAX_COUNTERS, kperfdata::kpep_config};
use super::{
Sampler,
error::{SamplerError, try_kpc},
};
use crate::utils::DropGuard;
pub struct ThreadSampler<'sampler, const N: usize> {
running: bool,
sampler: &'sampler Sampler,
config: NonNull<kpep_config>,
classes: u32,
counter_map: [usize; N],
_marker: PhantomData<*mut ()>,
}
impl<'sampler, const N: usize> ThreadSampler<'sampler, N> {
pub(crate) const fn new(
sampler: &'sampler Sampler,
config: NonNull<kpep_config>,
classes: u32,
counter_map: [usize; N],
) -> Self {
Self {
running: false,
sampler,
config,
classes,
counter_map,
_marker: PhantomData,
}
}
#[must_use]
pub const fn is_running(&self) -> bool {
self.running
}
pub fn start(&mut self) -> Result<(), SamplerError> {
if self.running {
return Ok(());
}
let kpc_vt = self.sampler.kperf.vtable();
try_kpc(
unsafe { (kpc_vt.kpc_set_counting)(self.classes) },
SamplerError::UnableToStartCounting,
)?;
let counting_guard = DropGuard::new((), |()| {
let _res = unsafe { (kpc_vt.kpc_set_counting)(0) };
});
try_kpc(
unsafe { (kpc_vt.kpc_set_thread_counting)(self.classes) },
SamplerError::UnableToStartThreadCounting,
)?;
self.running = true;
DropGuard::dismiss(counting_guard);
Ok(())
}
#[expect(clippy::cast_possible_truncation, clippy::indexing_slicing)]
pub fn sample(&self) -> Result<[u64; N], SamplerError> {
if !self.running {
return Err(SamplerError::SamplerNotRunning);
}
let kpc_vt = self.sampler.kperf.vtable();
let mut counters = [0; KPC_MAX_COUNTERS];
try_kpc(
unsafe {
(kpc_vt.kpc_get_thread_counters)(0, KPC_MAX_COUNTERS as u32, counters.as_mut_ptr())
},
SamplerError::UnableToReadCounters,
)?;
let output = array::from_fn(|index| {
let counter_index = self.counter_map[index];
counters[counter_index]
});
Ok(output)
}
pub fn stop(&mut self) -> Result<(), SamplerError> {
if !self.running {
return Ok(());
}
let kpc_vt = self.sampler.kperf.vtable();
let ret_thread_counting = unsafe { (kpc_vt.kpc_set_thread_counting)(0) };
let ret_counting = unsafe { (kpc_vt.kpc_set_counting)(0) };
self.running = false;
try_kpc(
ret_thread_counting,
SamplerError::UnableToStopThreadCounting,
)?;
try_kpc(ret_counting, SamplerError::UnableToStopCounting)?;
Ok(())
}
}
impl<const N: usize> Drop for ThreadSampler<'_, N> {
fn drop(&mut self) {
let _result = self.stop();
let kpep_vt = self.sampler.kperfdata.vtable();
unsafe {
(kpep_vt.kpep_config_free)(self.config.as_ptr());
}
}
}
impl<const N: usize> fmt::Debug for ThreadSampler<'_, N> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("ThreadSampler")
.field("running", &self.running)
.field("classes", &self.classes)
.field("counter_map", &self.counter_map)
.finish_non_exhaustive()
}
}