use std::cell::Cell;
use std::marker::PhantomData;
use std::panic::RefUnwindSafe;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::pal::{Platform, PlatformFacade};
use crate::{ERR_POISONED_LOCK, Operation, OperationMetrics};
#[derive(Debug)]
#[must_use = "Measurements are taken between creation and drop"]
pub struct ProcessSpan {
metrics: Arc<Mutex<OperationMetrics>>,
platform: PlatformFacade,
start_time: Duration,
iterations: u64,
_not_sync: PhantomData<Cell<()>>,
}
impl RefUnwindSafe for ProcessSpan {}
impl ProcessSpan {
pub(crate) fn new(operation: &Operation, iterations: u64) -> Self {
assert!(iterations != 0, "Iterations cannot be zero");
let platform = operation.platform().clone();
let start_time = platform.process_time();
Self {
metrics: operation.metrics(),
platform,
start_time,
iterations,
_not_sync: PhantomData,
}
}
pub fn iterations(mut self, iterations: u64) -> Self {
assert!(iterations != 0, "Iterations cannot be zero");
self.iterations = iterations;
self
}
#[must_use]
#[cfg_attr(test, mutants::skip)] fn to_duration(&self) -> Duration {
let current_time = self.platform.process_time();
let total_duration = current_time.saturating_sub(self.start_time);
if self.iterations > 1 {
Duration::from_nanos(
total_duration
.as_nanos()
.checked_div(u128::from(self.iterations))
.expect("guarded by if condition")
.try_into()
.expect("all realistic values fit in u64"),
)
} else {
total_duration
}
}
}
impl Drop for ProcessSpan {
fn drop(&mut self) {
let duration = self.to_duration();
let mut data = self.metrics.lock().expect(ERR_POISONED_LOCK);
data.add_iterations(duration, self.iterations);
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::time::Duration;
use crate::Session;
use crate::pal::{FakePlatform, PlatformFacade};
fn create_test_session() -> Session {
let fake_platform = FakePlatform::new();
let platform_facade = PlatformFacade::fake(fake_platform);
Session::with_platform(platform_facade)
}
fn create_test_session_with_time(process_time: Duration) -> Session {
let fake_platform = FakePlatform::new();
fake_platform.set_process_time(process_time);
let platform_facade = PlatformFacade::fake(fake_platform);
Session::with_platform(platform_facade)
}
#[test]
fn creates_span_with_iterations() {
let session = create_test_session();
let operation = session.operation("test");
let span = operation.measure_process().iterations(5);
assert_eq!(span.iterations, 5);
}
#[test]
#[should_panic]
fn panics_on_zero_iterations() {
let session = create_test_session();
let operation = session.operation("test");
let _span = operation.measure_process().iterations(0);
}
#[test]
fn records_cpu_time_measurements() {
let session = create_test_session();
let operation = session.operation("test");
{
let _span = operation.measure_process();
let mut sum = 0;
for i in 0..1000 {
sum += i;
}
std::hint::black_box(sum);
}
assert!(operation.total_iterations() > 0);
}
#[test]
fn extracts_time_from_pal() {
let session = create_test_session_with_time(Duration::ZERO);
let operation = session.operation("test");
{
let _span = operation.measure_process();
}
assert_eq!(operation.total_iterations(), 1);
assert_eq!(operation.total_processor_time(), Duration::ZERO);
}
#[test]
fn uses_process_time_from_pal() {
let fake_platform = FakePlatform::new();
fake_platform.set_process_time(Duration::from_millis(300));
fake_platform.set_thread_time(Duration::from_millis(100));
let platform_facade = PlatformFacade::fake(fake_platform);
let session = Session::with_platform(platform_facade);
let operation = session.operation("test");
{
let _span = operation.measure_process();
}
assert_eq!(operation.total_iterations(), 1);
}
#[test]
fn records_one_span_per_iteration() {
let session = create_test_session();
let operation = session.operation("test");
{
let _span = operation.measure_process().iterations(7);
}
assert_eq!(operation.total_iterations(), 7);
}
#[test]
fn accumulates_multiple_spans() {
let session = create_test_session();
let operation = session.operation("test");
for i in 1..=3 {
let _span = operation.measure_process().iterations(i);
}
assert_eq!(operation.total_iterations(), 6);
}
static_assertions::assert_impl_all!(super::ProcessSpan: Send, UnwindSafe, RefUnwindSafe);
static_assertions::assert_not_impl_any!(super::ProcessSpan: Sync);
}