use super::*;
#[derive(Debug)]
pub struct PerTaskCounter {
scheduler_id: ax_runtime::task::thread::ThreadId,
pub(super) counter: Counter,
event: u16,
pub(super) exclude_user: bool,
pub(super) exclude_kernel: bool,
read_format: u64,
pub(super) enable_on_exec: bool,
pub(super) cpu_filter: Option<PerfCpuId>,
pub(super) enabled: AtomicBool,
pub(super) run_state: IrqMutex<PmuRunState>,
pub(super) accumulated: AtomicU64,
pub(super) time_enabled_ns: AtomicU64,
pub(super) time_running_ns: AtomicU64,
pub(super) last_in_ns: AtomicU64,
pub(super) enabled_at_ns: AtomicU64,
pub(super) is_sampling: bool,
pub(super) sample_period: u32,
pub(super) sample_type: u64,
pub(super) freq: bool,
pub(super) freq_target: u32,
pub(super) sample_id: AtomicU64,
pub(super) want_comm: bool,
pub(super) want_mmap2: bool,
pub(super) want_task: bool,
pub(super) sample_id_all: bool,
inherit: bool,
pub(super) observer: PidNamespaceId,
family: IrqMutex<Option<FamilyBinding>>,
pub(super) resources: PmuResourceRelease,
rdpmc: RdpmcMapping,
pub(super) output: IrqMutex<PerfOutputRoute>,
inherited_output_wake: AtomicBool,
anchors: IrqMutex<Option<SamplingAnchors>>,
}
#[derive(Clone, Debug)]
struct FamilyBinding {
family: PerfInheritanceFamilyWeak,
root: bool,
}
#[derive(Clone)]
pub(crate) struct SamplingAnchors {
notify: Arc<IrqNotify>,
poll_ready: Arc<axpoll_set::PollSet>,
poll_alive: Arc<AtomicBool>,
}
impl SamplingAnchors {
pub(crate) fn new(
notify: Arc<IrqNotify>,
poll_ready: Arc<axpoll_set::PollSet>,
poll_alive: Arc<AtomicBool>,
) -> Self {
Self {
notify,
poll_ready,
poll_alive,
}
}
pub(crate) fn stop(&self) {
self.poll_alive.store(false, Ordering::Release);
self.notify.notify();
}
}
impl core::fmt::Debug for SamplingAnchors {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SamplingAnchors").finish_non_exhaustive()
}
}
pub(in crate::perf) struct PerTaskConfig {
pub(in crate::perf) scheduler_id: ax_runtime::task::thread::ThreadId,
pub(in crate::perf) counter: Counter,
pub(in crate::perf) event: u16,
pub(in crate::perf) exclude_user: bool,
pub(in crate::perf) exclude_kernel: bool,
pub(in crate::perf) read_format: u64,
pub(in crate::perf) enabled: bool,
pub(in crate::perf) enable_on_exec: bool,
pub(in crate::perf) cpu_filter: Option<PerfCpuId>,
pub(in crate::perf) sample_period: u32,
pub(in crate::perf) sample_type: u64,
pub(in crate::perf) freq: bool,
pub(in crate::perf) target_freq: u32,
pub(in crate::perf) want_comm: bool,
pub(in crate::perf) want_mmap2: bool,
pub(in crate::perf) want_task: bool,
pub(in crate::perf) sample_id_all: bool,
pub(in crate::perf) inherit: bool,
pub(in crate::perf) observer: PidNamespaceId,
}
impl PerTaskCounter {
pub(in crate::perf) fn new(cfg: PerTaskConfig) -> Self {
PerTaskCounter {
scheduler_id: cfg.scheduler_id,
counter: cfg.counter,
event: cfg.event,
exclude_user: cfg.exclude_user,
exclude_kernel: cfg.exclude_kernel,
read_format: cfg.read_format,
enable_on_exec: cfg.enable_on_exec,
cpu_filter: cfg.cpu_filter,
enabled: AtomicBool::new(cfg.enabled),
run_state: IrqMutex::new(PmuRunState::new()),
accumulated: AtomicU64::new(0),
time_enabled_ns: AtomicU64::new(0),
time_running_ns: AtomicU64::new(0),
last_in_ns: AtomicU64::new(0),
enabled_at_ns: AtomicU64::new(0),
is_sampling: cfg.sample_period > 0,
sample_period: cfg.sample_period,
sample_type: cfg.sample_type,
freq: cfg.freq,
freq_target: cfg.target_freq,
sample_id: AtomicU64::new(0),
want_comm: cfg.want_comm,
want_mmap2: cfg.want_mmap2,
want_task: cfg.want_task,
sample_id_all: cfg.sample_id_all,
inherit: cfg.inherit,
observer: cfg.observer,
family: IrqMutex::new(None),
resources: PmuResourceRelease::new(),
rdpmc: RdpmcMapping::new(),
output: IrqMutex::new(PerfOutputRoute::new()),
inherited_output_wake: AtomicBool::new(false),
anchors: IrqMutex::new(None),
}
}
pub fn read_format(&self) -> u64 {
self.read_format
}
pub fn set_sample_id(&self, id: u64) {
self.sample_id.store(id, Ordering::Relaxed);
}
pub(in crate::perf) fn inherited_config(
&self,
scheduler_id: ax_runtime::task::thread::ThreadId,
counter: Counter,
) -> PerTaskConfig {
PerTaskConfig {
scheduler_id,
counter,
event: self.event,
exclude_user: self.exclude_user,
exclude_kernel: self.exclude_kernel,
read_format: self.read_format,
enabled: false,
enable_on_exec: false,
cpu_filter: self.cpu_filter,
sample_period: self.sample_period,
sample_type: self.sample_type,
freq: self.freq,
target_freq: self.freq_target,
want_comm: self.want_comm,
want_mmap2: self.want_mmap2,
want_task: self.want_task,
sample_id_all: self.sample_id_all,
inherit: true,
observer: self.observer,
}
}
pub(super) fn programmed_event(&self) -> Option<u16> {
self.counter.programmable_index().map(|_| self.event)
}
pub(super) fn programmable_index(&self) -> usize {
self.counter
.programmable_index()
.expect("sampling events are validated onto programmable counters")
}
pub(in crate::perf) fn synchronize_context(&self) -> crate::StarryResult<()> {
let handle = match ax_runtime::task::thread::ThreadHandle::lookup(self.scheduler_id) {
Ok(handle) => handle,
Err(ax_runtime::task::thread::TaskError::StaleThreadId) => return Ok(()),
Err(_) => return Err(crate::StarryError::BadState),
};
if handle.state() == ax_runtime::task::thread::ThreadState::Exited {
return Ok(());
}
let Some(cpu) = handle.scheduler_fence_cpu() else {
return Ok(());
};
cpu_worker::synchronize_task_context(PerfCpuId::new(cpu.as_u32() as usize))
}
pub(super) fn rdpmc_snapshot(&self) -> RdpmcSnapshot {
RdpmcSnapshot {
offset: self.accumulated.load(Ordering::Acquire),
time_enabled: self.time_enabled_ns.load(Ordering::Acquire),
time_running: self.time_running_ns.load(Ordering::Acquire),
}
}
pub(super) fn publish_rdpmc_active(&self) {
if !self.is_sampling {
self.rdpmc.publish_active(self.rdpmc_snapshot());
}
}
pub(super) fn publish_rdpmc_inactive(&self) {
if !self.is_sampling {
self.rdpmc.publish_inactive(self.rdpmc_snapshot());
}
}
pub(in crate::perf) fn device_mmap_rdpmc(
&self,
len: usize,
) -> crate::StarryResult<(PhysAddr, Arc<dyn Any + Send + Sync>)> {
if self.is_sampling {
return Err(crate::StarryError::InvalidInput);
}
let page = self
.rdpmc
.install(len, self.rdpmc_snapshot())?;
self.publish_rdpmc_inactive();
if let Err(error) = self.synchronize_context() {
self.rdpmc.withdraw(&page);
return Err(error);
}
Ok(mapping_result(page))
}
pub fn set_enabled(&self) {
if !self.enabled.swap(true, Ordering::AcqRel) {
self.enabled_at_ns.store(now_ns(), Ordering::Relaxed);
}
}
pub(crate) fn set_enabled_state(&self, enabled: bool) {
if enabled {
self.set_enabled();
} else {
self.enabled.store(false, Ordering::Release);
}
}
pub(crate) fn bind_family(&self, family: PerfInheritanceFamilyWeak, root: bool) {
let old = self.family.lock().replace(FamilyBinding { family, root });
assert!(old.is_none(), "a task perf counter joined two families");
}
pub(crate) fn family(&self) -> Option<Arc<PerfInheritanceFamily>> {
self.family.lock().as_ref()?.family.upgrade()
}
pub(super) fn is_family_root(&self) -> bool {
self.family
.lock()
.as_ref()
.is_some_and(|binding| binding.root)
}
pub(in crate::perf) fn resources_released(&self) -> bool {
self.resources.is_released()
}
pub(in crate::perf) fn publish_scheduler_registration(&self) -> bool {
self.resources.publish()
}
pub(crate) fn retired_values(&self) -> (u64, u64, u64) {
debug_assert!(
self.resources_released(),
"only a quiescent task event may be folded into family totals"
);
(
self.accumulated.load(Ordering::Acquire),
self.time_enabled_ns.load(Ordering::Acquire),
self.time_running_ns.load(Ordering::Acquire),
)
}
pub fn is_sampling(&self) -> bool {
self.is_sampling
}
pub(in crate::perf) fn wants_comm(&self) -> bool {
self.want_comm
}
pub(in crate::perf) fn wants_mmap2(&self) -> bool {
self.want_mmap2
}
pub(in crate::perf) fn wants_task(&self) -> bool {
self.want_task
}
pub(in crate::perf) fn inheritable(&self) -> bool {
self.inherit && !self.run_state.lock().is_stopping()
}
pub(in crate::perf) fn sample_id(&self) -> u64 {
self.sample_id.load(Ordering::Relaxed)
}
pub(crate) fn install_root_output(&self, output: &PerfRingOutput, anchors: SamplingAnchors) {
*self.anchors.lock() = Some(anchors);
self.inherited_output_wake.store(false, Ordering::Release);
self.output.lock().publish_owned(output);
}
pub(crate) fn install_family_output(
&self,
output: PerfRingOutput,
anchors: Option<SamplingAnchors>,
) {
self.inherited_output_wake
.store(anchors.is_some(), Ordering::Release);
*self.anchors.lock() = anchors;
self.output.lock().redirect(output);
}
pub(crate) fn clear_family_output(&self) {
self.inherited_output_wake.store(false, Ordering::Release);
self.anchors.lock().take();
self.output.lock().clear();
}
pub fn ring_mapped(&self) -> bool {
self.output.lock().owned().is_some()
}
pub(crate) fn output_ring(&self) -> Option<PerfRingOutput> {
self.output.lock().owned()
}
pub(crate) fn set_redirect_ring(&self, output: PerfRingOutput) {
self.inherited_output_wake.store(false, Ordering::Release);
self.output.lock().redirect(output);
}
pub(crate) fn detach_redirect(&self) {
self.inherited_output_wake.store(false, Ordering::Release);
self.output.lock().detach();
}
pub(super) fn sample_output(&self) -> Option<SampleOutput> {
let (ring, redirected) = self.output.lock().effective()?;
let notify = if redirected && !self.inherited_output_wake.load(Ordering::Acquire) {
None
} else {
self.anchors
.lock()
.as_ref()
.map(|anchors| Arc::clone(&anchors.notify))
};
Some(SampleOutput::new(Some(ring), notify))
}
pub fn ring_has_data(&self) -> bool {
let Some(ring) = self.output.lock().owned() else {
return false;
};
let header = ring.ring_vaddr() as *const kbpf_basic::linux_bpf::perf_event_mmap_page;
let (head, tail) = unsafe {
(
core::ptr::addr_of!((*header).data_head).read_volatile(),
core::ptr::addr_of!((*header).data_tail).read_volatile(),
)
};
head != tail
}
pub unsafe fn register_poll_shared(&self, sink: &mut dyn axpoll::SharedRegistrationSink) {
let guard = self.anchors.lock();
if let Some(anchors) = guard.as_ref() {
unsafe { sink.register_shared(&anchors.poll_ready, axpoll::IoEvents::IN) };
}
}
pub unsafe fn register_poll_exclusive(&self, sink: &mut dyn axpoll::ExclusiveRegistrationSink) {
let guard = self.anchors.lock();
if let Some(anchors) = guard.as_ref() {
unsafe { sink.register_exclusive(&anchors.poll_ready, axpoll::IoEvents::IN) };
}
}
}