use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex};
use crate::{ERR_POISONED_LOCK, Operation, OperationMetrics, Report};
#[derive(Debug)]
pub struct Session {
operations: Arc<Mutex<HashMap<String, Arc<Mutex<OperationMetrics>>>>>,
}
impl Session {
#[expect(
clippy::new_without_default,
reason = "to avoid ambiguity with the notion of a 'default session' that is not actually a default session"
)]
#[must_use]
pub fn new() -> Self {
Self {
operations: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn operation(&self, name: impl Into<String>) -> Operation {
let name = name.into();
let operation_data = {
let mut operations = self.operations.lock().expect(ERR_POISONED_LOCK);
Arc::clone(
operations
.entry(name.clone())
.or_insert_with(|| Arc::new(Mutex::new(OperationMetrics::default()))),
)
};
Operation::new(name, operation_data)
}
#[must_use]
pub fn to_report(&self) -> Report {
let operations = self.operations.lock().expect(ERR_POISONED_LOCK);
let operation_data: HashMap<String, OperationMetrics> = operations
.iter()
.map(|(name, data_ref)| {
(
name.clone(),
data_ref.lock().expect(ERR_POISONED_LOCK).clone(),
)
})
.collect();
Report::from_operation_data(&operation_data)
}
#[cfg_attr(test, mutants::skip)] pub fn print_to_stdout(&self) {
self.to_report().print_to_stdout();
}
#[must_use]
pub fn is_empty(&self) -> bool {
let operations = self.operations.lock().expect(ERR_POISONED_LOCK);
operations.is_empty()
|| operations
.values()
.all(|op| op.lock().expect(ERR_POISONED_LOCK).total_iterations == 0)
}
}
impl fmt::Display for Session {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_report())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use std::panic::RefUnwindSafe;
use std::panic::UnwindSafe;
static_assertions::assert_impl_all!(Session: Send, Sync);
static_assertions::assert_impl_all!(Session: UnwindSafe, RefUnwindSafe);
}