use core::sync::atomic::{AtomicU64, Ordering};
use patina::component::service::{IntoService, perf_timer::ArchTimerFunctionality};
#[derive(IntoService)]
#[service(dyn ArchTimerFunctionality)]
pub(crate) struct PerfTimer {
frequency: AtomicU64,
}
impl ArchTimerFunctionality for PerfTimer {
#[coverage(off)]
fn cpu_count(&self) -> u64 {
arch_cpu_count()
}
fn perf_frequency(&self) -> u64 {
if self.frequency.load(Ordering::Relaxed) == 0 {
self.frequency.store(arch_perf_frequency(), Ordering::Relaxed);
}
self.frequency.load(Ordering::Relaxed)
}
}
impl PerfTimer {
pub fn new() -> Self {
Self { frequency: AtomicU64::new(0) }
}
pub fn with_frequency(frequency: u64) -> Self {
Self { frequency: AtomicU64::new(frequency) }
}
}
impl Default for PerfTimer {
fn default() -> Self {
Self::new()
}
}
#[coverage(off)]
fn arch_cpu_count() -> u64 {
#[cfg(target_arch = "x86_64")]
{
use core::arch::x86_64;
unsafe { x86_64::_rdtsc() }
}
#[cfg(target_arch = "aarch64")]
{
use aarch64_cpu::registers::{self, Readable};
registers::CNTPCT_EL0.get()
}
}
#[coverage(off)]
pub(crate) fn arch_perf_frequency() -> u64 {
#[cfg(target_arch = "x86_64")]
{
use core::arch::{x86_64, x86_64::CpuidResult};
let CpuidResult { eax, ebx, ecx, .. } = x86_64::__cpuid(0x15);
if eax != 0 && ebx != 0 && ecx != 0 {
return (ecx as u64 * ebx as u64) / eax as u64;
}
let CpuidResult { eax, .. } = x86_64::__cpuid(0x16);
if eax != 0 {
return (eax * 1_000_000) as u64;
}
0
}
#[cfg(target_arch = "aarch64")]
{
use patina::read_sysreg;
read_sysreg!(CNTFRQ_EL0)
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
0
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use super::*;
#[test]
fn test_set_non_zero_frequency_forces_that_frequency() {
let frequency = 19191919;
let timer = PerfTimer::with_frequency(frequency);
assert_eq!(timer.perf_frequency(), frequency);
}
#[test]
fn test_zero_frequency_forces_arch_perf_frequency() {
let timer = PerfTimer::default();
assert_eq!(timer.perf_frequency(), arch_perf_frequency());
let timer = PerfTimer::new();
assert_eq!(timer.perf_frequency(), arch_perf_frequency());
let timer = PerfTimer::with_frequency(0);
assert_eq!(timer.perf_frequency(), arch_perf_frequency());
}
}