mod info;
mod record_profiling;
mod signal_stats;
mod stage_metrics;
pub use info::{SignalStatsInfo, StageProfilingInfo};
pub use record_profiling::{RecordProfilingMetrics, SignalGauge, StageEntry};
pub use signal_stats::SignalStats;
pub use stage_metrics::StageMetrics;
use alloc::{boxed::Box, sync::Arc};
use core::sync::atomic::Ordering;
use core::task::{Context, Poll};
use portable_atomic::AtomicU64;
use crate::buffer::BufferReader;
use crate::DbError;
pub(crate) type Clock = Arc<dyn Fn() -> u64 + Send + Sync>;
pub(crate) fn make_clock(rt: Arc<dyn crate::executor::RuntimeOps>) -> Clock {
let epoch = rt.now_nanos();
Arc::new(move || rt.now_nanos().saturating_sub(epoch))
}
const NO_PREV: u64 = u64::MAX;
pub(crate) struct ProducerProfilingState {
metrics: Arc<StageMetrics>,
clock: Clock,
last_produce_ns: AtomicU64,
}
impl ProducerProfilingState {
pub(crate) fn new(metrics: Arc<StageMetrics>, clock: Clock) -> Self {
Self {
metrics,
clock,
last_produce_ns: AtomicU64::new(NO_PREV),
}
}
pub(crate) fn record_produce(&self) {
let now = (self.clock)();
let prev = self.last_produce_ns.swap(now, Ordering::Relaxed);
if prev != NO_PREV {
self.metrics.record(now.saturating_sub(prev));
}
}
}
pub(crate) struct ProfilingBufferReader<T: Clone + Send> {
inner: Box<dyn BufferReader<T> + Send>,
metrics: Arc<StageMetrics>,
clock: Clock,
last_yield_ns: Option<u64>,
pending_since: Option<u64>,
}
impl<T: Clone + Send> ProfilingBufferReader<T> {
pub(crate) fn new(
inner: Box<dyn BufferReader<T> + Send>,
metrics: Arc<StageMetrics>,
clock: Clock,
) -> Self {
Self {
inner,
metrics,
clock,
last_yield_ns: None,
pending_since: None,
}
}
fn on_yield(&mut self, started_ns: u64) {
if let Some(prev) = self.last_yield_ns {
self.metrics.record(started_ns.saturating_sub(prev));
}
self.last_yield_ns = Some((self.clock)());
}
}
impl<T: Clone + Send> BufferReader<T> for ProfilingBufferReader<T> {
fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Result<T, DbError>> {
let started_ns = match self.pending_since {
Some(t) if self.last_yield_ns.is_none_or(|ly| t >= ly) => t,
_ => {
let now = (self.clock)();
self.pending_since = Some(now);
now
}
};
let result = self.inner.poll_recv(cx);
if result.is_ready() {
self.pending_since = None;
if matches!(result, Poll::Ready(Ok(_))) {
self.on_yield(started_ns);
}
}
result
}
fn try_recv(&mut self) -> Result<T, DbError> {
let started_ns = (self.clock)();
let result = self.inner.try_recv();
if result.is_ok() {
self.pending_since = None;
self.on_yield(started_ns);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::task::Waker;
struct StepReader {
slot: Arc<AtomicU64>,
}
impl BufferReader<u32> for StepReader {
fn poll_recv(&mut self, _cx: &mut Context<'_>) -> Poll<Result<u32, DbError>> {
match self.slot.swap(NO_PREV, Ordering::AcqRel) {
NO_PREV => Poll::Pending,
v => Poll::Ready(Ok(v as u32)),
}
}
fn try_recv(&mut self) -> Result<u32, DbError> {
match self.slot.swap(NO_PREV, Ordering::AcqRel) {
NO_PREV => Err(DbError::BufferEmpty),
v => Ok(v as u32),
}
}
}
fn mock_clock() -> (Clock, Arc<AtomicU64>) {
let t = Arc::new(AtomicU64::new(0));
let clock_t = Arc::clone(&t);
(Arc::new(move || clock_t.load(Ordering::Relaxed)), t)
}
#[test]
fn cancelled_poll_then_try_recv_does_not_leak_into_next_sample() {
let (clock, t) = mock_clock();
let metrics = Arc::new(StageMetrics::new());
let slot = Arc::new(AtomicU64::new(NO_PREV));
let inner = StepReader {
slot: Arc::clone(&slot),
};
let mut reader: ProfilingBufferReader<u32> =
ProfilingBufferReader::new(Box::new(inner), Arc::clone(&metrics), clock);
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
assert!(reader.poll_recv(&mut cx).is_pending());
assert_eq!(reader.pending_since, Some(0));
t.store(1_000_000, Ordering::Relaxed);
slot.store(7, Ordering::Relaxed);
assert_eq!(reader.try_recv().unwrap(), 7);
assert_eq!(
reader.pending_since, None,
"try_recv must clear pending_since on Ok"
);
t.store(1_100_000, Ordering::Relaxed);
assert!(reader.poll_recv(&mut cx).is_pending());
assert_eq!(
reader.pending_since,
Some(1_100_000),
"poll_recv must resample, not reuse the stale pre-cancellation timestamp"
);
t.store(1_150_000, Ordering::Relaxed);
slot.store(9, Ordering::Relaxed);
match reader.poll_recv(&mut cx) {
Poll::Ready(Ok(v)) => assert_eq!(v, 9),
other => panic!("expected Ready(Ok(9)), got {other:?}"),
}
assert_eq!(metrics.call_count(), 1);
assert_eq!(metrics.total_time_ns(), 100_000);
}
}