11//! `TaskDumped<F>` wraps a future and captures async backtraces at yield
2- //! points using geometric ( Poisson) sampling keyed on idle duration.
2+ //! points using Poisson sampling keyed on idle duration.
33//!
44//! This wrapper is intentionally separate from [`crate::traced::Traced`]: the
55//! wake-event capture in `Traced` runs on every instrumented spawn regardless
@@ -83,7 +83,7 @@ impl SplitMix64 {
8383
8484pin_project ! {
8585 /// Future wrapper that captures async backtraces at yield points using
86- /// geometric ( Poisson) sampling keyed on idle duration.
86+ /// Poisson sampling keyed on idle duration.
8787 pub ( crate ) struct TaskDumped <F > {
8888 #[ pin]
8989 inner: F ,
@@ -93,7 +93,7 @@ pin_project! {
9393 // Monotonic nanoseconds when the frames in `frames` were captured.
9494 // Only meaningful when `frames.has_data()`.
9595 pending_capture_ts: Option <NonZeroU64 >,
96- // Geometric sampling state: remaining nanoseconds of idle time before
96+ // Sampling state: remaining nanoseconds of idle time before
9797 // the next sample triggers. Signed so subtracting a large idle from a
9898 // small remaining value goes negative rather than wrapping.
9999 next_sample_ns: i64 ,
@@ -107,15 +107,14 @@ pin_project! {
107107impl < F > TaskDumped < F > {
108108 pub ( crate ) fn new ( inner : F , shared : Arc < SharedState > , task_id : TaskId ) -> Self {
109109 let sample_mean_ns = shared. task_dump_idle_threshold_ns . load ( Ordering :: Relaxed ) ;
110- let base_seed = shared. task_dump_rng_seed . load ( Ordering :: Relaxed ) ;
111- // When a fixed seed is configured (non-zero), use it directly for
112- // deterministic tests. Otherwise use task_id + timestamp for
113- // production uniqueness across tasks.
114- let seed = if base_seed != 0 {
115- base_seed
116- } else {
117- ( task_id. to_u64 ( ) ) . wrapping_mul ( 0x517cc1b727220a95 )
118- ^ crate :: telemetry:: events:: clock_monotonic_ns ( )
110+ // When a fixed seed is configured, use it directly for deterministic
111+ // tests. Otherwise use task_id + timestamp for production uniqueness.
112+ let seed = match shared. task_dump_rng_seed {
113+ Some ( s) => s,
114+ None => {
115+ ( task_id. to_u64 ( ) ) . wrapping_mul ( 0x517cc1b727220a95 )
116+ ^ crate :: telemetry:: events:: clock_monotonic_ns ( )
117+ }
119118 } ;
120119 let mut rng = SplitMix64 :: new ( seed) ;
121120 let next_sample_ns = rng. draw_exponential_ns ( sample_mean_ns) ;
@@ -148,7 +147,7 @@ impl<F: Future> Future for TaskDumped<F> {
148147 return this. inner . poll ( cx) ;
149148 }
150149
151- // Geometric sampling: subtract the idle duration from the counter.
150+ // Poisson sampling: subtract the idle duration from the counter.
152151 // If it goes to zero or below, we should emit.
153152 let poll_start = crate :: telemetry:: recorder:: poll_start_ts_or_now ( ) ;
154153 let should_emit = match * this. pending_capture_ts {
@@ -289,3 +288,43 @@ impl Encodable for TaskDumpData<'_> {
289288 } ) ;
290289 }
291290}
291+
292+ #[ cfg( test) ]
293+ mod tests {
294+ use super :: SplitMix64 ;
295+
296+ #[ test]
297+ fn splitmix64_deterministic ( ) {
298+ let mut rng = SplitMix64 :: new ( 42 ) ;
299+ let a = rng. next_u64 ( ) ;
300+ let b = rng. next_u64 ( ) ;
301+
302+ let mut rng2 = SplitMix64 :: new ( 42 ) ;
303+ assert_eq ! ( a, rng2. next_u64( ) ) ;
304+ assert_eq ! ( b, rng2. next_u64( ) ) ;
305+ }
306+
307+ #[ test]
308+ fn draw_exponential_ns_mean_is_reasonable ( ) {
309+ let mut rng = SplitMix64 :: new ( 123 ) ;
310+ let mean_ns: u64 = 10_000_000 ; // 10ms
311+ let n = 10_000 ;
312+ let sum: f64 = ( 0 ..n)
313+ . map ( |_| rng. draw_exponential_ns ( mean_ns) as f64 )
314+ . sum ( ) ;
315+ let observed_mean = sum / n as f64 ;
316+ // Within 10% of the configured mean.
317+ assert ! (
318+ ( observed_mean - mean_ns as f64 ) . abs( ) < mean_ns as f64 * 0.1 ,
319+ "observed mean {observed_mean} too far from expected {mean_ns}"
320+ ) ;
321+ }
322+
323+ #[ test]
324+ fn draw_exponential_ns_always_positive ( ) {
325+ let mut rng = SplitMix64 :: new ( 0 ) ;
326+ for _ in 0 ..10_000 {
327+ assert ! ( rng. draw_exponential_ns( 1_000_000 ) >= 1 ) ;
328+ }
329+ }
330+ }
0 commit comments