use std::fmt;
use std::sync::{Arc, Mutex};
use crate::{ERR_POISONED_LOCK, OperationMetrics, ProcessSpan, ThreadSpan};
#[derive(Debug)]
pub struct Operation {
metrics: Arc<Mutex<OperationMetrics>>,
}
impl Operation {
#[must_use]
pub(crate) fn new(_name: String, operation_data: Arc<Mutex<OperationMetrics>>) -> Self {
Self {
metrics: operation_data,
}
}
#[must_use]
pub(crate) fn metrics(&self) -> Arc<Mutex<OperationMetrics>> {
Arc::clone(&self.metrics)
}
pub fn measure_thread(&self) -> ThreadSpan {
ThreadSpan::new(self, 1)
}
pub fn measure_process(&self) -> ProcessSpan {
ProcessSpan::new(self, 1)
}
#[expect(clippy::integer_division, reason = "we accept loss of precision")]
#[expect(
clippy::arithmetic_side_effects,
reason = "division by zero excluded via if-else"
)]
#[must_use]
pub fn mean(&self) -> u64 {
let data = self.metrics.lock().expect(ERR_POISONED_LOCK);
if data.total_iterations == 0 {
0
} else {
data.total_bytes_allocated / data.total_iterations
}
}
#[must_use]
#[cfg(test)]
fn total_iterations(&self) -> u64 {
let data = self.metrics.lock().unwrap();
data.total_iterations
}
#[must_use]
pub fn total_bytes_allocated(&self) -> u64 {
let data = self.metrics.lock().expect(ERR_POISONED_LOCK);
data.total_bytes_allocated
}
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} bytes (mean)", self.mean())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::panic::RefUnwindSafe;
use std::panic::UnwindSafe;
use super::*;
use crate::Session;
use crate::allocator::register_fake_allocation;
fn create_test_operation() -> Operation {
let session = Session::new();
session.operation("test")
}
#[test]
fn operation_new() {
let operation = create_test_operation();
assert_eq!(operation.mean(), 0);
assert_eq!(operation.total_iterations(), 0);
assert_eq!(operation.total_bytes_allocated(), 0);
}
#[test]
fn operation_add_single() {
let operation = create_test_operation();
{
let mut metrics = operation.metrics.lock().unwrap();
metrics.add_iterations(100, 1, 1);
}
assert_eq!(operation.mean(), 100);
assert_eq!(operation.total_iterations(), 1);
assert_eq!(operation.total_bytes_allocated(), 100);
}
#[test]
fn operation_add_multiple() {
let operation = create_test_operation();
{
let mut metrics = operation.metrics.lock().unwrap();
metrics.add_iterations(100, 1, 1); metrics.add_iterations(200, 2, 1); metrics.add_iterations(300, 3, 1); }
assert_eq!(operation.mean(), 200); assert_eq!(operation.total_iterations(), 3);
assert_eq!(operation.total_bytes_allocated(), 600);
}
#[test]
fn operation_add_zero() {
let operation = create_test_operation();
{
let mut metrics = operation.metrics.lock().unwrap();
metrics.add_iterations(0, 0, 1);
metrics.add_iterations(0, 0, 1);
}
assert_eq!(operation.mean(), 0);
assert_eq!(operation.total_iterations(), 2);
assert_eq!(operation.total_bytes_allocated(), 0);
}
#[test]
fn operation_span_drop() {
let operation = create_test_operation();
{
let _span = operation.measure_thread();
register_fake_allocation(75, 1);
}
assert_eq!(operation.mean(), 75);
assert_eq!(operation.total_iterations(), 1);
assert_eq!(operation.total_bytes_allocated(), 75);
}
#[test]
fn operation_multiple_spans() {
let operation = create_test_operation();
{
let _span = operation.measure_thread();
register_fake_allocation(100, 1);
}
{
let _span = operation.measure_thread();
register_fake_allocation(200, 1);
}
assert_eq!(operation.mean(), 150); assert_eq!(operation.total_iterations(), 2);
assert_eq!(operation.total_bytes_allocated(), 300);
}
#[test]
fn operation_thread_span_drop() {
let operation = create_test_operation();
{
let _span = operation.measure_thread();
register_fake_allocation(50, 1);
}
assert_eq!(operation.mean(), 50);
assert_eq!(operation.total_iterations(), 1);
assert_eq!(operation.total_bytes_allocated(), 50);
}
#[test]
fn operation_mixed_spans() {
let operation = create_test_operation();
{
let _span = operation.measure_thread();
register_fake_allocation(100, 1);
}
{
let _span = operation.measure_thread();
register_fake_allocation(200, 1);
}
assert_eq!(operation.mean(), 150); assert_eq!(operation.total_iterations(), 2);
assert_eq!(operation.total_bytes_allocated(), 300);
}
#[test]
fn operation_thread_span_no_allocation() {
let operation = create_test_operation();
{
let _span = operation.measure_thread();
}
assert_eq!(operation.mean(), 0);
assert_eq!(operation.total_iterations(), 1);
assert_eq!(operation.total_bytes_allocated(), 0);
}
#[test]
fn operation_batch_iterations() {
let operation = create_test_operation();
{
let _span = operation.measure_thread().iterations(10);
register_fake_allocation(1000, 10);
}
assert_eq!(operation.total_iterations(), 10);
assert_eq!(operation.total_bytes_allocated(), 1000);
assert_eq!(operation.mean(), 100); }
#[test]
fn operation_drop_merges_data() {
let session = Session::new();
{
let operation = session.operation("test");
{
let mut metrics = operation.metrics.lock().unwrap();
metrics.add_iterations(100, 2, 5);
}
}
let report = session.to_report();
assert!(!report.is_empty());
let session_display = format!("{session}");
println!("Actual session display: '{session_display}'");
assert!(session_display.contains("| test | 100 | 2 |")); }
#[test]
fn multiple_operations_concurrent() {
let session = Session::new();
let op1 = session.operation("test");
let op2 = session.operation("test");
{
let mut metrics = op1.metrics.lock().unwrap();
metrics.add_iterations(100, 1, 2); metrics.add_iterations(200, 2, 3); }
assert_eq!(op1.mean(), 160);
assert_eq!(op2.mean(), 160);
drop(op1);
drop(op2);
let session_display = format!("{session}");
assert!(session_display.contains("| test | 160 | 1 |"));
}
static_assertions::assert_impl_all!(Operation: Send, Sync);
static_assertions::assert_impl_all!(
Operation: UnwindSafe, RefUnwindSafe
);
#[test]
fn operation_display_shows_mean_bytes() {
let operation = create_test_operation();
{
let mut metrics = operation.metrics.lock().unwrap();
metrics.add_iterations(250, 5, 2); }
let display_output = operation.to_string();
assert!(display_output.contains("bytes (mean)"));
assert!(display_output.contains("250")); }
}