use std::marker::PhantomData;
use std::sync::{Arc, Mutex};
use crate::allocator::get_or_init_thread_counters;
use crate::{ERR_POISONED_LOCK, Operation, OperationMetrics};
#[derive(Debug)]
#[must_use = "a span must be held across the measured work and given a count with `.iterations(n)`; it records when dropped and panics if the count is missing"]
pub struct ThreadSpan {
metrics: Arc<Mutex<OperationMetrics>>,
start_bytes: u64,
start_count: u64,
iterations: Option<u64>,
_single_threaded: PhantomData<*const ()>,
}
impl ThreadSpan {
pub(crate) fn new(operation: &Operation) -> Self {
let counters = get_or_init_thread_counters();
Self {
metrics: operation.metrics(),
start_bytes: counters.bytes(),
start_count: counters.count(),
iterations: None,
_single_threaded: PhantomData,
}
}
pub fn iterations(mut self, iterations: u64) -> Self {
self.iterations = Some(iterations);
self
}
}
impl Drop for ThreadSpan {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
let iterations = self.iterations.expect(
"the span was dropped without an iteration count; call `.iterations(1)` \
if the measured region is a single iteration",
);
let (bytes_delta, count_delta) = thread_deltas(self.start_bytes, self.start_count);
let mut data = self.metrics.lock().expect(ERR_POISONED_LOCK);
data.add_span(iterations, bytes_delta, count_delta);
}
}
fn thread_deltas(start_bytes: u64, start_count: u64) -> (u64, u64) {
let counters = get_or_init_thread_counters();
let bytes_delta = counters
.bytes()
.checked_sub(start_bytes)
.expect("thread bytes allocated could not possibly decrease");
let count_delta = counters
.count()
.checked_sub(start_count)
.expect("thread allocations count could not possibly decrease");
(bytes_delta, count_delta)
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
use crate::Session;
static_assertions::assert_not_impl_all!(ThreadSpan: Send);
static_assertions::assert_not_impl_all!(ThreadSpan: Sync);
static_assertions::assert_impl_all!(ThreadSpan: UnwindSafe, RefUnwindSafe);
#[test]
fn iterations_zero_is_accepted() {
let session = Session::new().no_stdout().no_file();
let operation = session.operation("test");
drop(operation.measure_thread().iterations(0));
assert_eq!(operation.total_iterations(), 0);
}
#[test]
fn records_span_via_post_hoc_iterations() {
let session = Session::new().no_stdout().no_file();
let operation = session.operation("test");
drop(operation.measure_thread().iterations(5));
assert_eq!(operation.total_iterations(), 5);
}
#[test]
fn records_span_via_iterations_guard() {
let session = Session::new().no_stdout().no_file();
let operation = session.operation("test");
{
let _span = operation.measure_thread().iterations(3);
}
assert_eq!(operation.total_iterations(), 3);
}
#[test]
#[should_panic]
fn dropping_span_without_iterations_panics() {
let session = Session::new().no_stdout().no_file();
let operation = session.operation("test");
drop(operation.measure_thread());
}
#[test]
fn panic_while_held_records_nothing() {
let session = Session::new().no_stdout().no_file();
let operation = session.operation("test");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _span = operation.measure_thread().iterations(1);
panic!("boom");
}));
assert!(result.is_err());
assert_eq!(operation.total_iterations(), 0);
}
}