use std::fmt;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::pal::PlatformFacade;
use crate::{ERR_POISONED_LOCK, OperationMetrics, ProcessSpan, ThreadSpan};
#[derive(Debug)]
pub struct Operation {
metrics: Arc<Mutex<OperationMetrics>>,
platform: PlatformFacade,
}
impl Operation {
#[must_use]
pub(crate) fn new(
_name: String,
operation_data: Arc<Mutex<OperationMetrics>>,
platform: PlatformFacade,
) -> Self {
Self {
metrics: operation_data,
platform,
}
}
#[must_use]
pub(crate) fn platform(&self) -> &PlatformFacade {
&self.platform
}
#[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)
}
#[must_use]
pub fn mean(&self) -> Duration {
let data = self.metrics.lock().expect(ERR_POISONED_LOCK);
if data.total_iterations == 0 {
Duration::ZERO
} else {
Duration::from_nanos(
data.total_processor_time
.as_nanos()
.checked_div(u128::from(data.total_iterations))
.expect("guarded by if condition")
.try_into()
.expect("all realistic values fit in u64"),
)
}
}
#[must_use]
#[cfg(test)]
pub(crate) fn total_iterations(&self) -> u64 {
let data = self.metrics.lock().expect(ERR_POISONED_LOCK);
data.total_iterations
}
#[must_use]
#[cfg(test)]
pub(crate) fn total_processor_time(&self) -> Duration {
let data = self.metrics.lock().expect(ERR_POISONED_LOCK);
data.total_processor_time
}
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} (mean)", self.mean())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::hint::black_box;
use std::panic::RefUnwindSafe;
use std::panic::UnwindSafe;
use super::*;
use crate::Session;
fn create_test_session() -> Session {
use crate::pal::{FakePlatform, PlatformFacade};
let fake_platform = FakePlatform::new();
let platform_facade = PlatformFacade::fake(fake_platform);
Session::with_platform(platform_facade)
}
#[test]
fn starts_with_zero_values() {
let session = create_test_session();
let operation = session.operation("test");
assert_eq!(operation.mean(), Duration::ZERO);
assert_eq!(operation.total_iterations(), 0);
assert_eq!(operation.total_processor_time(), Duration::ZERO);
}
#[test]
fn tracks_single_duration() {
let session = create_test_session();
let operation = session.operation("test");
let mut metrics = operation.metrics.lock().expect(ERR_POISONED_LOCK);
metrics.add_iterations(Duration::from_millis(100), 1);
drop(metrics);
assert_eq!(operation.mean(), Duration::from_millis(100));
assert_eq!(operation.total_iterations(), 1);
assert_eq!(operation.total_processor_time(), Duration::from_millis(100));
}
#[test]
fn calculates_mean_of_multiple_durations() {
let session = create_test_session();
let operation = session.operation("test");
{
let mut metrics = operation.metrics.lock().expect(ERR_POISONED_LOCK);
metrics.add_iterations(Duration::from_millis(100), 1);
metrics.add_iterations(Duration::from_millis(200), 1);
metrics.add_iterations(Duration::from_millis(300), 1);
}
assert_eq!(operation.mean(), Duration::from_millis(200)); assert_eq!(operation.total_iterations(), 3);
assert_eq!(operation.total_processor_time(), Duration::from_millis(600));
}
#[test]
fn handles_zero_durations() {
let session = create_test_session();
let operation = session.operation("test");
{
let mut metrics = operation.metrics.lock().expect(ERR_POISONED_LOCK);
metrics.add_iterations(Duration::ZERO, 1);
metrics.add_iterations(Duration::ZERO, 1);
}
assert_eq!(operation.mean(), Duration::ZERO);
assert_eq!(operation.total_iterations(), 2);
assert_eq!(operation.total_processor_time(), Duration::ZERO);
}
#[test]
fn integrates_with_span_api() {
let session = create_test_session();
let operation = session.operation("test");
{
let _span = operation.measure_thread();
let mut sum = 0;
for i in 0..1000 {
sum += i;
}
black_box(sum);
}
assert_eq!(operation.total_iterations(), 1);
assert!(operation.total_processor_time() >= Duration::ZERO);
}
#[test]
fn handles_batch_iterations() {
let session = create_test_session();
let operation = session.operation("test");
{
let _span = operation.measure_thread().iterations(10);
for i in 0..10 {
let mut sum = 0;
for j in 0..100 {
sum += i * j;
}
black_box(sum);
}
}
assert_eq!(operation.total_iterations(), 10);
assert!(operation.total_processor_time() >= Duration::ZERO);
}
static_assertions::assert_impl_all!(Operation: Send, Sync);
static_assertions::assert_impl_all!(
Operation: UnwindSafe, RefUnwindSafe
);
#[test]
fn display_shows_mean() {
let session = create_test_session();
let operation = session.operation("test");
{
let mut metrics = operation.metrics.lock().expect(ERR_POISONED_LOCK);
metrics.add_iterations(Duration::from_millis(100), 2);
}
let display = operation.to_string();
assert!(display.contains("mean"), "Display should mention 'mean'");
assert!(
display.contains("100"),
"Display should show the mean duration containing '100' (for 100ms): got {display}"
);
}
}