use crate::error::{CoreError, CoreResult};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::time::Instant;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum HardwareCounterError {
#[error("Performance counters not available on this platform")]
NotAvailable,
#[error("Permission denied to access performance counters: {0}")]
PermissionDenied(String),
#[error("Performance counter not found: {0}")]
CounterNotFound(String),
#[error("Invalid counter configuration: {0}")]
InvalidConfiguration(String),
#[error("System error: {0}")]
SystemError(String),
}
impl From<HardwareCounterError> for CoreError {
fn from(err: HardwareCounterError) -> Self {
CoreError::ComputationError(crate::error::ErrorContext::new(err.to_string()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CounterType {
CpuCycles,
Instructions,
CacheReferences,
CacheMisses,
BranchInstructions,
BranchMisses,
BusCycles,
StalledCyclesFrontend,
StalledCyclesBackend,
L1DCacheLoads,
L1DCacheLoadMisses,
L1DCacheStores,
L1ICacheLoads,
L1ICacheLoadMisses,
L2CacheLoads,
L2CacheLoadMisses,
L3CacheLoads,
L3CacheLoadMisses,
DtlbLoads,
DtlbLoadMisses,
ItlbLoads,
ItlbLoadMisses,
CpuPower,
CpuTemperature,
CpuFrequency,
Custom(String),
}
impl CounterType {
pub const fn description(&self) -> &'static str {
match self {
CounterType::CpuCycles => "CPU cycles",
CounterType::Instructions => "Instructions retired",
CounterType::CacheReferences => "Cache references",
CounterType::CacheMisses => "Cache misses",
CounterType::BranchInstructions => "Branch instructions",
CounterType::BranchMisses => "Branch mispredictions",
CounterType::BusCycles => "Bus cycles",
CounterType::StalledCyclesFrontend => "Stalled cycles frontend",
CounterType::StalledCyclesBackend => "Stalled cycles backend",
CounterType::L1DCacheLoads => "L1 data cache loads",
CounterType::L1DCacheLoadMisses => "L1 data cache load misses",
CounterType::L1DCacheStores => "L1 data cache stores",
CounterType::L1ICacheLoads => "L1 instruction cache loads",
CounterType::L1ICacheLoadMisses => "L1 instruction cache load misses",
CounterType::L2CacheLoads => "L2 cache loads",
CounterType::L2CacheLoadMisses => "L2 cache load misses",
CounterType::L3CacheLoads => "L3 cache loads",
CounterType::L3CacheLoadMisses => "L3 cache load misses",
CounterType::DtlbLoads => "Data TLB loads",
CounterType::DtlbLoadMisses => "Data TLB load misses",
CounterType::ItlbLoads => "Instruction TLB loads",
CounterType::ItlbLoadMisses => "Instruction TLB load misses",
CounterType::CpuPower => "CPU power consumption",
CounterType::CpuTemperature => "CPU temperature",
CounterType::CpuFrequency => "CPU frequency",
CounterType::Custom(_) => "Custom counter",
}
}
pub const fn unit(&self) -> &'static str {
match self {
CounterType::CpuCycles
| CounterType::Instructions
| CounterType::CacheReferences
| CounterType::CacheMisses
| CounterType::BranchInstructions
| CounterType::BranchMisses
| CounterType::BusCycles
| CounterType::StalledCyclesFrontend
| CounterType::StalledCyclesBackend
| CounterType::L1DCacheLoads
| CounterType::L1DCacheLoadMisses
| CounterType::L1DCacheStores
| CounterType::L1ICacheLoads
| CounterType::L1ICacheLoadMisses
| CounterType::L2CacheLoads
| CounterType::L2CacheLoadMisses
| CounterType::L3CacheLoads
| CounterType::L3CacheLoadMisses
| CounterType::DtlbLoads
| CounterType::DtlbLoadMisses
| CounterType::ItlbLoads
| CounterType::ItlbLoadMisses => "count",
CounterType::CpuPower => "watts",
CounterType::CpuTemperature => "celsius",
CounterType::CpuFrequency => "hertz",
CounterType::Custom(_) => "unknown",
}
}
}
#[derive(Debug, Clone)]
pub struct CounterValue {
pub countertype: CounterType,
pub value: u64,
pub timestamp: Instant,
pub enabled: bool,
pub scaling_factor: f64,
}
impl CounterValue {
pub fn new(countertype: CounterType, value: u64) -> Self {
Self {
countertype,
value,
timestamp: Instant::now(),
enabled: true,
scaling_factor: 1.0,
}
}
pub fn scaled_value(&self) -> f64 {
self.value as f64 * self.scaling_factor
}
}
pub trait PerformanceCounter: Send + Sync {
fn available_counters(&self) -> Vec<CounterType>;
fn is_available(&self, countertype: &CounterType) -> bool;
fn start_counter(&self, countertype: &CounterType) -> CoreResult<()>;
fn stop_counter(&self, countertype: &CounterType) -> CoreResult<()>;
fn read_counter(&self, countertype: &CounterType) -> CoreResult<CounterValue>;
fn read_counters(&self, countertypes: &[CounterType]) -> CoreResult<Vec<CounterValue>>;
fn reset_counter(&self, countertype: &CounterType) -> CoreResult<()>;
fn is_overflowed(&self, countertype: &CounterType) -> CoreResult<bool>;
}
#[cfg(target_os = "linux")]
pub struct LinuxPerfCounter {
active_counters: RwLock<HashMap<CounterType, i32>>, }
#[cfg(target_os = "linux")]
impl LinuxPerfCounter {
pub fn new() -> Self {
Self {
active_counters: RwLock::new(HashMap::new()),
}
}
fn counter_to_perf_config(&self, countertype: &CounterType) -> Option<(u32, u64)> {
match countertype {
CounterType::CpuCycles => Some((0, 0)), CounterType::Instructions => Some((0, 1)), CounterType::CacheReferences => Some((0, 2)), CounterType::CacheMisses => Some((0, 3)), CounterType::BranchInstructions => Some((0, 4)), CounterType::BranchMisses => Some((0, 5)), CounterType::BusCycles => Some((0, 6)), CounterType::StalledCyclesFrontend => Some((0, 7)), CounterType::StalledCyclesBackend => Some((0, 8)), _ => None, }
}
}
#[cfg(target_os = "linux")]
impl Default for LinuxPerfCounter {
fn default() -> Self {
Self::new()
}
}
#[cfg(target_os = "linux")]
impl PerformanceCounter for LinuxPerfCounter {
fn available_counters(&self) -> Vec<CounterType> {
vec![
CounterType::CpuCycles,
CounterType::Instructions,
CounterType::CacheReferences,
CounterType::CacheMisses,
CounterType::BranchInstructions,
CounterType::BranchMisses,
CounterType::BusCycles,
CounterType::StalledCyclesFrontend,
CounterType::StalledCyclesBackend,
]
}
fn is_available(&self, countertype: &CounterType) -> bool {
self.counter_to_perf_config(countertype).is_some()
}
fn start_counter(&self, countertype: &CounterType) -> CoreResult<()> {
if let Some(_event_type_config) = self.counter_to_perf_config(countertype) {
let fd = 42;
let mut counters = self.active_counters.write().expect("Operation failed");
counters.insert(countertype.clone(), fd);
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn stop_counter(&self, countertype: &CounterType) -> CoreResult<()> {
let mut counters = self.active_counters.write().expect("Operation failed");
if let Some(fd) = counters.remove(countertype) {
let _ = fd;
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn read_counter(&self, countertype: &CounterType) -> CoreResult<CounterValue> {
let counters = self.active_counters.read().expect("Operation failed");
if let Some(_fd) = counters.get(countertype) {
let mock_value = match countertype {
CounterType::CpuCycles => 1_000_000,
CounterType::Instructions => 500_000,
CounterType::CacheReferences => 10_000,
CounterType::CacheMisses => 1_000,
CounterType::BranchInstructions => 100_000,
CounterType::BranchMisses => 5_000,
CounterType::BusCycles => 50_000,
CounterType::StalledCyclesFrontend => 10_000,
CounterType::StalledCyclesBackend => 20_000,
_ => 0,
};
Ok(CounterValue::new(countertype.clone(), mock_value))
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn read_counters(&self, countertypes: &[CounterType]) -> CoreResult<Vec<CounterValue>> {
let mut results = Vec::new();
for countertype in countertypes {
results.push(self.read_counter(countertype)?);
}
Ok(results)
}
fn reset_counter(&self, countertype: &CounterType) -> CoreResult<()> {
let counters = self.active_counters.read().expect("Operation failed");
if counters.contains_key(countertype) {
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn is_overflowed(&self, countertype: &CounterType) -> CoreResult<bool> {
let counters = self.active_counters.read().expect("Operation failed");
if counters.contains_key(countertype) {
Ok(false)
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
}
#[cfg(target_os = "windows")]
pub struct WindowsPdhCounter {
active_counters: RwLock<HashMap<CounterType, String>>, }
#[cfg(target_os = "windows")]
impl WindowsPdhCounter {
pub fn new() -> Self {
Self {
active_counters: RwLock::new(HashMap::new()),
}
}
fn counter_to_path(countertype: &CounterType) -> Option<String> {
match countertype {
CounterType::CpuCycles => Some("\\Processor(_Total)\\% Processor Time".to_string()),
CounterType::CpuFrequency => {
Some("\\Processor Information(_Total)\\Processor Frequency".to_string())
}
CounterType::CpuPower => Some("\\Power Meter(*)\\Power".to_string()),
_ => None,
}
}
}
#[cfg(target_os = "windows")]
impl Default for WindowsPdhCounter {
fn default() -> Self {
Self::new()
}
}
#[cfg(target_os = "windows")]
impl PerformanceCounter for WindowsPdhCounter {
fn available_counters(&self) -> Vec<CounterType> {
vec![
CounterType::CpuCycles,
CounterType::CpuFrequency,
CounterType::CpuPower,
]
}
fn is_available(&self, countertype: &CounterType) -> bool {
Self::counter_to_path(countertype).is_some()
}
fn start_counter(&self, countertype: &CounterType) -> CoreResult<()> {
if let Some(path) = Self::counter_to_path(countertype) {
let mut counters = self.active_counters.write().expect("Operation failed");
counters.insert(countertype.clone(), path);
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn stop_counter(&self, countertype: &CounterType) -> CoreResult<()> {
let mut counters = self.active_counters.write().expect("Operation failed");
if counters.remove(countertype).is_some() {
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn read_counter(&self, countertype: &CounterType) -> CoreResult<CounterValue> {
let counters = self.active_counters.read().expect("Operation failed");
if counters.contains_key(countertype) {
let mock_value = match countertype {
CounterType::CpuCycles => 85, CounterType::CpuFrequency => 2_400_000_000, CounterType::CpuPower => 45, _ => 0,
};
Ok(CounterValue::new(countertype.clone(), mock_value))
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn read_counters(&self, countertypes: &[CounterType]) -> CoreResult<Vec<CounterValue>> {
let mut results = Vec::new();
for countertype in countertypes {
results.push(self.read_counter(countertype)?);
}
Ok(results)
}
fn reset_counter(&self, countertype: &CounterType) -> CoreResult<()> {
Err(
HardwareCounterError::InvalidConfiguration("PDH counters cannot be reset".to_string())
.into(),
)
}
fn is_overflowed(&self, _countertype: &CounterType) -> CoreResult<bool> {
Ok(false)
}
}
#[cfg(target_os = "macos")]
pub struct MacOSCounter {
active_counters: RwLock<HashMap<CounterType, bool>>,
}
#[cfg(target_os = "macos")]
impl MacOSCounter {
pub fn new() -> Self {
Self {
active_counters: RwLock::new(HashMap::new()),
}
}
}
#[cfg(target_os = "macos")]
impl Default for MacOSCounter {
fn default() -> Self {
Self::new()
}
}
#[cfg(target_os = "macos")]
impl PerformanceCounter for MacOSCounter {
fn available_counters(&self) -> Vec<CounterType> {
vec![
CounterType::CpuCycles,
CounterType::Instructions,
CounterType::CpuFrequency,
CounterType::CpuTemperature,
]
}
fn is_available(&self, countertype: &CounterType) -> bool {
matches!(
countertype,
CounterType::CpuCycles
| CounterType::Instructions
| CounterType::CpuFrequency
| CounterType::CpuTemperature
)
}
fn start_counter(&self, countertype: &CounterType) -> CoreResult<()> {
if self.is_available(countertype) {
let mut counters = self.active_counters.write().expect("Operation failed");
counters.insert(countertype.clone(), true);
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn stop_counter(&self, countertype: &CounterType) -> CoreResult<()> {
let mut counters = self.active_counters.write().expect("Operation failed");
if counters.remove(countertype).is_some() {
Ok(())
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn read_counter(&self, countertype: &CounterType) -> CoreResult<CounterValue> {
let counters = self.active_counters.read().expect("Operation failed");
if counters.contains_key(countertype) {
let mock_value = match countertype {
CounterType::CpuCycles => 2_000_000,
CounterType::Instructions => 1_000_000,
CounterType::CpuFrequency => 3_200_000_000, CounterType::CpuTemperature => 65, _ => 0,
};
Ok(CounterValue::new(countertype.clone(), mock_value))
} else {
Err(HardwareCounterError::CounterNotFound(format!("{countertype:?}")).into())
}
}
fn read_counters(&self, countertypes: &[CounterType]) -> CoreResult<Vec<CounterValue>> {
let mut results = Vec::new();
for countertype in countertypes {
results.push(self.read_counter(countertype)?);
}
Ok(results)
}
fn reset_counter(&self, countertype: &CounterType) -> CoreResult<()> {
Ok(())
}
fn is_overflowed(&self, _countertype: &CounterType) -> CoreResult<bool> {
Ok(false)
}
}
pub struct HardwareCounterManager {
backend: Box<dyn PerformanceCounter>,
session_counters: RwLock<HashMap<String, Vec<CounterType>>>,
counter_history: RwLock<HashMap<CounterType, Vec<CounterValue>>>,
max_history_size: usize,
}
impl HardwareCounterManager {
pub fn new() -> CoreResult<Self> {
let backend = Self::create_platform_backend()?;
Ok(Self {
backend,
session_counters: RwLock::new(HashMap::new()),
counter_history: RwLock::new(HashMap::new()),
max_history_size: 1000,
})
}
fn create_platform_backend() -> CoreResult<Box<dyn PerformanceCounter>> {
#[cfg(target_os = "linux")]
{
Ok(Box::new(LinuxPerfCounter::new()))
}
#[cfg(target_os = "windows")]
{
Ok(Box::new(WindowsPdhCounter::new()))
}
#[cfg(target_os = "macos")]
{
Ok(Box::new(MacOSCounter::new()))
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
{
Err(HardwareCounterError::NotAvailable.into())
}
}
pub fn available_counters(&self) -> Vec<CounterType> {
self.backend.available_counters()
}
pub fn start_session(&self, sessionname: &str, counters: Vec<CounterType>) -> CoreResult<()> {
for counter in &counters {
self.backend.start_counter(counter)?;
}
let mut sessions = self.session_counters.write().expect("Operation failed");
sessions.insert(sessionname.to_string(), counters);
Ok(())
}
pub fn stop_session(&self, sessionname: &str) -> CoreResult<()> {
let mut sessions = self.session_counters.write().expect("Operation failed");
if let Some(counters) = sessions.remove(sessionname) {
for counter in &counters {
self.backend.stop_counter(counter)?;
}
Ok(())
} else {
Err(HardwareCounterError::InvalidConfiguration(format!(
"Session not found: {sessionname}"
))
.into())
}
}
pub fn sample_counters(&self) -> CoreResult<HashMap<CounterType, CounterValue>> {
let sessions = self.session_counters.read().expect("Operation failed");
let active_counters: Vec<CounterType> = sessions
.values()
.flat_map(|counters| counters.iter())
.cloned()
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
let values = self.backend.read_counters(&active_counters)?;
let mut history = self.counter_history.write().expect("Operation failed");
for value in &values {
let counter_history = history.entry(value.countertype.clone()).or_default();
counter_history.push(value.clone());
if counter_history.len() > self.max_history_size {
counter_history.drain(0..counter_history.len() - self.max_history_size);
}
}
let result = values
.into_iter()
.map(|value| (value.countertype.clone(), value))
.collect();
Ok(result)
}
pub fn get_counter_history(&self, countertype: &CounterType) -> Vec<CounterValue> {
let history = self.counter_history.read().expect("Operation failed");
history.get(countertype).cloned().unwrap_or_default()
}
pub fn calculate_derived_metrics(
&self,
counters: &HashMap<CounterType, CounterValue>,
) -> DerivedMetrics {
let mut metrics = DerivedMetrics::default();
if let (Some(instructions), Some(cycles)) = (
counters.get(&CounterType::Instructions),
counters.get(&CounterType::CpuCycles),
) {
if cycles.value > 0 {
metrics.instructions_per_cycle = instructions.value as f64 / cycles.value as f64;
}
}
if let (Some(references), Some(misses)) = (
counters.get(&CounterType::CacheReferences),
counters.get(&CounterType::CacheMisses),
) {
if references.value > 0 {
metrics.cache_hit_rate = 1.0 - (misses.value as f64 / references.value as f64);
}
}
if let (Some(instructions), Some(misses)) = (
counters.get(&CounterType::BranchInstructions),
counters.get(&CounterType::BranchMisses),
) {
if instructions.value > 0 {
metrics.branch_prediction_accuracy =
1.0 - (misses.value as f64 / instructions.value as f64);
}
}
if let Some(cycles) = counters.get(&CounterType::CpuCycles) {
metrics.cpu_utilization = cycles.value as f64 / 1_000_000.0; }
metrics
}
pub fn generate_report(&self, sessionname: &str) -> PerformanceReport {
let sessions = self.session_counters.read().expect("Operation failed");
let counters = sessions.get(sessionname).cloned().unwrap_or_default();
let current_values = self.sample_counters().unwrap_or_default();
let derived_metrics = self.calculate_derived_metrics(¤t_values);
PerformanceReport {
session_name: sessionname.to_string(),
timestamp: Instant::now(),
counter_values: current_values,
derived_metrics,
countersmonitored: counters,
}
}
}
impl Default for HardwareCounterManager {
fn default() -> Self {
Self::new().unwrap_or_else(|_| {
Self {
backend: Box::new(NoOpCounter),
session_counters: RwLock::new(HashMap::new()),
counter_history: RwLock::new(HashMap::new()),
max_history_size: 1000,
}
})
}
}
pub struct NoOpCounter;
impl PerformanceCounter for NoOpCounter {
fn available_counters(&self) -> Vec<CounterType> {
Vec::new()
}
fn is_available(&self, _countertype: &CounterType) -> bool {
false
}
fn start_counter(&self, _countertype: &CounterType) -> CoreResult<()> {
Err(HardwareCounterError::NotAvailable.into())
}
fn stop_counter(&self, _countertype: &CounterType) -> CoreResult<()> {
Err(HardwareCounterError::NotAvailable.into())
}
fn read_counter(&self, _countertype: &CounterType) -> CoreResult<CounterValue> {
Err(HardwareCounterError::NotAvailable.into())
}
fn read_counters(&self, _countertypes: &[CounterType]) -> CoreResult<Vec<CounterValue>> {
Err(HardwareCounterError::NotAvailable.into())
}
fn reset_counter(&self, _countertype: &CounterType) -> CoreResult<()> {
Err(HardwareCounterError::NotAvailable.into())
}
fn is_overflowed(&self, _countertype: &CounterType) -> CoreResult<bool> {
Err(HardwareCounterError::NotAvailable.into())
}
}
#[derive(Debug, Clone, Default)]
pub struct DerivedMetrics {
pub instructions_per_cycle: f64,
pub cache_hit_rate: f64,
pub branch_prediction_accuracy: f64,
pub cpu_utilization: f64,
pub memorybandwidth: f64,
pub power_efficiency: f64,
}
#[derive(Debug, Clone)]
pub struct PerformanceReport {
pub session_name: String,
pub timestamp: Instant,
pub counter_values: HashMap<CounterType, CounterValue>,
pub derived_metrics: DerivedMetrics,
pub countersmonitored: Vec<CounterType>,
}
impl PerformanceReport {
pub fn formattext(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"Performance Report: {session_name}\n",
session_name = self.session_name
));
output.push_str(&format!("Timestamp: {:?}\n\n", self.timestamp));
output.push_str("Raw Counters:\n");
for (countertype, value) in &self.counter_values {
output.push_str(&format!(
" {}: {} {}\n",
countertype.description(),
value.scaled_value(),
countertype.unit()
));
}
output.push_str("\nDerived Metrics:\n");
let metrics = &self.derived_metrics;
output.push_str(&format!(
" Instructions per Cycle: {:.2}\n",
metrics.instructions_per_cycle
));
output.push_str(&format!(
" Cache Hit Rate: {:.2}%\n",
metrics.cache_hit_rate * 100.0
));
output.push_str(&format!(
" Branch Prediction Accuracy: {:.2}%\n",
metrics.branch_prediction_accuracy * 100.0
));
output.push_str(&format!(
" CPU Utilization: {:.2}%\n",
metrics.cpu_utilization
));
output
}
pub fn to_json(&self) -> String {
format!(
r#"{{"session":"{}","timestamp":"{}","metrics":{{"ipc":{:.2},"cache_hit_rate":{:.2},"branch_accuracy":{:.2}}}}}"#,
self.session_name,
self.timestamp.elapsed().as_secs(),
self.derived_metrics.instructions_per_cycle,
self.derived_metrics.cache_hit_rate,
self.derived_metrics.branch_prediction_accuracy
)
}
}
static GLOBAL_MANAGER: std::sync::OnceLock<Arc<Mutex<HardwareCounterManager>>> =
std::sync::OnceLock::new();
#[allow(dead_code)]
pub fn global_manager() -> Arc<Mutex<HardwareCounterManager>> {
GLOBAL_MANAGER
.get_or_init(|| Arc::new(Mutex::new(HardwareCounterManager::default())))
.clone()
}
pub mod utils {
use super::*;
pub fn start_basic_cpumonitoring(sessionname: &str) -> CoreResult<()> {
let manager = global_manager();
let manager = manager.lock().expect("Operation failed");
let counters = vec![
CounterType::CpuCycles,
CounterType::Instructions,
CounterType::CacheReferences,
CounterType::CacheMisses,
];
manager.start_session(sessionname, counters)
}
pub fn start_cachemonitoring(sessionname: &str) -> CoreResult<()> {
let manager = global_manager();
let manager = manager.lock().expect("Operation failed");
let counters = vec![
CounterType::L1DCacheLoads,
CounterType::L1DCacheLoadMisses,
CounterType::L2CacheLoads,
CounterType::L2CacheLoadMisses,
CounterType::L3CacheLoads,
CounterType::L3CacheLoadMisses,
];
manager.start_session(sessionname, counters)
}
pub fn get_performance_snapshot() -> CoreResult<HashMap<CounterType, CounterValue>> {
let manager = global_manager();
let manager = manager.lock().expect("Operation failed");
manager.sample_counters()
}
pub fn counters_available() -> bool {
let manager = global_manager();
let manager = manager.lock().expect("Operation failed");
!manager.available_counters().is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_countertype_properties() {
let counter = CounterType::CpuCycles;
assert_eq!(counter.description(), "CPU cycles");
assert_eq!(counter.unit(), "count");
let custom = CounterType::Custom("test".to_string());
assert_eq!(custom.description(), "Custom counter");
assert_eq!(custom.unit(), "unknown");
}
#[test]
fn test_counter_value() {
let counter = CounterType::Instructions;
let value = CounterValue::new(counter.clone(), 1000);
assert_eq!(value.countertype, counter);
assert_eq!(value.value, 1000);
assert_eq!(value.scaled_value(), 1000.0);
assert!(value.enabled);
}
#[test]
fn test_derived_metrics() {
let metrics = DerivedMetrics {
instructions_per_cycle: 2.5,
cache_hit_rate: 0.95,
branch_prediction_accuracy: 0.98,
..Default::default()
};
assert_eq!(metrics.instructions_per_cycle, 2.5);
assert_eq!(metrics.cache_hit_rate, 0.95);
assert_eq!(metrics.branch_prediction_accuracy, 0.98);
}
#[test]
fn test_performance_report() {
let mut counter_values = HashMap::new();
counter_values.insert(
CounterType::CpuCycles,
CounterValue::new(CounterType::CpuCycles, 1000000),
);
let report = PerformanceReport {
session_name: "test_session".to_string(),
timestamp: Instant::now(),
counter_values,
derived_metrics: DerivedMetrics::default(),
countersmonitored: vec![CounterType::CpuCycles],
};
let text = report.formattext();
assert!(text.contains("Performance Report: test_session"));
assert!(text.contains("CPU cycles"));
let json = report.to_json();
assert!(json.contains("test_session"));
}
#[test]
fn test_no_op_counter() {
let counter = NoOpCounter;
assert!(counter.available_counters().is_empty());
assert!(!counter.is_available(&CounterType::CpuCycles));
assert!(counter.start_counter(&CounterType::CpuCycles).is_err());
}
#[test]
fn test_global_manager() {
let manager = global_manager();
let manager2 = global_manager();
assert!(Arc::ptr_eq(&manager, &manager2));
}
#[test]
fn test_utils_functions() {
let available = utils::counters_available();
let result = utils::start_basic_cpumonitoring("test");
assert!(result.is_ok() || result.is_err());
}
}