Skip to main content

trueno/brick/
async_profiler.rs

1//! Async task profiler for measuring poll efficiency.
2
3use super::exec_graph::ExecutionNode;
4use super::profiling::{cached_nanos_or_now, cpu_cycles};
5
6// ============================================================================
7// Async Task Profiler (Pattern 3 from actix-web)
8// ============================================================================
9
10/// Async task profiler for measuring poll efficiency (Phase 11, E.9.4).
11///
12/// Tracks how many times a future is polled before completion.
13/// High poll counts indicate inefficient async code or spurious wakeups.
14///
15/// # Example
16/// ```rust,ignore
17/// let mut profiler = AsyncTaskProfiler::new("inference_request");
18///
19/// profiler.on_poll_start();
20/// // ... poll the future ...
21/// profiler.on_poll_end(is_ready);
22///
23/// println!("Poll efficiency: {:.1}%", profiler.efficiency() * 100.0);
24/// ```
25#[derive(Debug, Clone)]
26pub struct AsyncTaskProfiler {
27    /// Task name for identification
28    pub name: String,
29    /// Number of times poll() was called
30    pub poll_count: u64,
31    /// Number of times poll() returned Pending
32    pub yield_count: u64,
33    /// Total time spent in poll() (nanoseconds)
34    pub total_poll_ns: u64,
35    /// Start time of current poll
36    last_poll_start: u64,
37    /// CPU cycles at poll start
38    last_poll_cycles: u64,
39    /// Total CPU cycles in poll()
40    pub total_poll_cycles: u64,
41}
42
43impl AsyncTaskProfiler {
44    /// Create a new async task profiler.
45    pub fn new(name: impl Into<String>) -> Self {
46        Self {
47            name: name.into(),
48            poll_count: 0,
49            yield_count: 0,
50            total_poll_ns: 0,
51            last_poll_start: 0,
52            last_poll_cycles: 0,
53            total_poll_cycles: 0,
54        }
55    }
56
57    /// Call at the start of each poll() invocation.
58    #[inline]
59    pub fn on_poll_start(&mut self) {
60        self.poll_count += 1;
61        self.last_poll_start = cached_nanos_or_now();
62        self.last_poll_cycles = cpu_cycles();
63    }
64
65    /// Call at the end of each poll() invocation.
66    ///
67    /// # Arguments
68    /// - `is_ready`: true if the future returned Poll::Ready
69    #[inline]
70    pub fn on_poll_end(&mut self, is_ready: bool) {
71        let now = cached_nanos_or_now();
72        let cycles = cpu_cycles();
73
74        self.total_poll_ns += now.saturating_sub(self.last_poll_start);
75        self.total_poll_cycles += cycles.saturating_sub(self.last_poll_cycles);
76
77        if !is_ready {
78            self.yield_count += 1;
79        }
80    }
81
82    /// Poll efficiency ratio (0.0 to 1.0).
83    ///
84    /// - 1.0 = Perfect (ready on first poll)
85    /// - 0.5 = 2 polls required
86    /// - Lower = more wakeups/polls needed
87    #[must_use]
88    pub fn efficiency(&self) -> f64 {
89        if self.poll_count == 0 {
90            0.0
91        } else {
92            1.0 / self.poll_count as f64
93        }
94    }
95
96    /// Average time per poll in microseconds.
97    #[must_use]
98    pub fn avg_poll_us(&self) -> f64 {
99        if self.poll_count == 0 {
100            0.0
101        } else {
102            self.total_poll_ns as f64 / self.poll_count as f64 / 1000.0
103        }
104    }
105
106    /// Yield ratio (Pending / total polls).
107    ///
108    /// High yield ratio indicates the task is often not ready when polled.
109    #[must_use]
110    pub fn yield_ratio(&self) -> f64 {
111        if self.poll_count == 0 {
112            0.0
113        } else {
114            self.yield_count as f64 / self.poll_count as f64
115        }
116    }
117
118    /// Convert to ExecutionNode for graph integration.
119    pub fn to_execution_node(&self) -> ExecutionNode {
120        ExecutionNode::AsyncTask {
121            name: self.name.clone(),
122            poll_count: self.poll_count,
123            yield_count: self.yield_count,
124            total_poll_ns: self.total_poll_ns,
125        }
126    }
127}
128
129impl Default for AsyncTaskProfiler {
130    fn default() -> Self {
131        Self::new("unnamed")
132    }
133}