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}