async_inspect/instrument/
mod.rs

1//! Code instrumentation utilities
2//!
3//! This module provides macros and helpers for instrumenting async code.
4
5use crate::inspector::Inspector;
6use crate::task::TaskId;
7use std::time::Instant;
8
9/// Context for tracking async operations
10pub struct InspectContext {
11    /// Task ID being tracked
12    pub task_id: TaskId,
13    /// Start time of current operation
14    pub start_time: Instant,
15}
16
17impl InspectContext {
18    /// Create a new inspect context
19    #[must_use]
20    pub fn new(task_id: TaskId) -> Self {
21        Self {
22            task_id,
23            start_time: Instant::now(),
24        }
25    }
26
27    /// Get elapsed time since context creation
28    #[must_use]
29    pub fn elapsed(&self) -> std::time::Duration {
30        self.start_time.elapsed()
31    }
32}
33
34/// Helper for tracking poll operations
35pub struct PollGuard {
36    task_id: TaskId,
37    start: Instant,
38}
39
40impl PollGuard {
41    /// Create a new poll guard
42    #[must_use]
43    pub fn new(task_id: TaskId) -> Self {
44        Inspector::global().poll_started(task_id);
45        Self {
46            task_id,
47            start: Instant::now(),
48        }
49    }
50}
51
52impl Drop for PollGuard {
53    fn drop(&mut self) {
54        let duration = self.start.elapsed();
55        Inspector::global().poll_ended(self.task_id, duration);
56    }
57}
58
59/// Helper for tracking await operations
60pub struct AwaitGuard {
61    task_id: TaskId,
62    await_point: String,
63    start: Instant,
64}
65
66impl AwaitGuard {
67    /// Create a new await guard
68    #[must_use]
69    pub fn new(task_id: TaskId, await_point: String) -> Self {
70        Inspector::global().await_started(task_id, await_point.clone(), None);
71        Self {
72            task_id,
73            await_point,
74            start: Instant::now(),
75        }
76    }
77}
78
79impl Drop for AwaitGuard {
80    fn drop(&mut self) {
81        let duration = self.start.elapsed();
82        Inspector::global().await_ended(self.task_id, self.await_point.clone(), duration);
83    }
84}
85
86/// Record an inspection point in async code
87///
88/// # Examples
89///
90/// ```ignore
91/// use async_inspect::inspect_point;
92///
93/// async fn my_function() {
94///     inspect_point!("start");
95///
96///     let data = fetch_data().await;
97///
98///     inspect_point!("data_fetched", format!("Got {} items", data.len()));
99///
100///     process(data).await;
101///
102///     inspect_point!("done");
103/// }
104/// ```
105#[macro_export]
106macro_rules! inspect_point {
107    ($label:expr) => {{
108        if let Some(task_id) = $crate::instrument::current_task_id() {
109            $crate::inspector::Inspector::global().inspection_point(
110                task_id,
111                $label.to_string(),
112                None,
113            );
114        }
115    }};
116    ($label:expr, $message:expr) => {{
117        if let Some(task_id) = $crate::instrument::current_task_id() {
118            $crate::inspector::Inspector::global().inspection_point(
119                task_id,
120                $label.to_string(),
121                Some($message.to_string()),
122            );
123        }
124    }};
125}
126
127/// Begin tracking an async task
128///
129/// # Examples
130///
131/// ```ignore
132/// use async_inspect::inspect_task_start;
133///
134/// async fn my_task() {
135///     let task_id = inspect_task_start!("my_task");
136///
137///     // Your async code here
138///
139///     // Task will be marked as completed when task_id is dropped
140/// }
141/// ```
142#[macro_export]
143macro_rules! inspect_task_start {
144    ($name:expr) => {{
145        let task_id = $crate::inspector::Inspector::global().register_task($name.to_string());
146        $crate::instrument::set_current_task_id(task_id);
147        task_id
148    }};
149}
150
151/// Mark current task as completed
152#[macro_export]
153macro_rules! inspect_task_complete {
154    ($task_id:expr) => {{
155        $crate::inspector::Inspector::global().task_completed($task_id);
156    }};
157}
158
159/// Mark current task as failed
160#[macro_export]
161macro_rules! inspect_task_failed {
162    ($task_id:expr) => {{
163        $crate::inspector::Inspector::global().task_failed($task_id, None);
164    }};
165    ($task_id:expr, $error:expr) => {{
166        $crate::inspector::Inspector::global().task_failed($task_id, Some($error.to_string()));
167    }};
168}
169
170// Thread-local storage for current task ID
171thread_local! {
172    static CURRENT_TASK_ID: std::cell::RefCell<Option<TaskId>> = const { std::cell::RefCell::new(None) };
173}
174
175/// Get the current task ID
176#[must_use]
177pub fn current_task_id() -> Option<TaskId> {
178    CURRENT_TASK_ID.with(|id| *id.borrow())
179}
180
181/// Set the current task ID
182pub fn set_current_task_id(task_id: TaskId) {
183    CURRENT_TASK_ID.with(|id| *id.borrow_mut() = Some(task_id));
184}
185
186/// Clear the current task ID
187pub fn clear_current_task_id() {
188    CURRENT_TASK_ID.with(|id| *id.borrow_mut() = None);
189}
190
191/// RAII guard for task tracking
192pub struct TaskGuard {
193    task_id: TaskId,
194}
195
196impl TaskGuard {
197    /// Create a new task guard
198    #[must_use]
199    pub fn new(name: String) -> Self {
200        let task_id = Inspector::global().register_task(name);
201        set_current_task_id(task_id);
202        Self { task_id }
203    }
204
205    /// Get the task ID
206    #[must_use]
207    pub fn task_id(&self) -> TaskId {
208        self.task_id
209    }
210}
211
212impl Drop for TaskGuard {
213    fn drop(&mut self) {
214        Inspector::global().task_completed(self.task_id);
215        clear_current_task_id();
216    }
217}
218
219/// Helper function for await point instrumentation
220pub fn inspect_await_start(label: impl Into<String>, location: Option<String>) {
221    if let Some(task_id) = current_task_id() {
222        Inspector::global().add_event(
223            task_id,
224            crate::timeline::EventKind::AwaitStarted {
225                await_point: label.into(),
226                location,
227            },
228        );
229    }
230}
231
232/// Helper function for await point completion
233pub fn inspect_await_end(label: impl Into<String>) {
234    if let Some(task_id) = current_task_id() {
235        // Calculate duration would require storing start time
236        // For now, just record completion
237        Inspector::global().add_event(
238            task_id,
239            crate::timeline::EventKind::AwaitEnded {
240                await_point: label.into(),
241                duration: std::time::Duration::from_micros(0), // TODO: track actual duration
242            },
243        );
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_current_task_id() {
253        let task_id = TaskId::new();
254        set_current_task_id(task_id);
255        assert_eq!(current_task_id(), Some(task_id));
256        clear_current_task_id();
257        assert_eq!(current_task_id(), None);
258    }
259
260    #[test]
261    fn test_task_guard() {
262        let guard = TaskGuard::new("test".to_string());
263        let task_id = guard.task_id();
264        assert_eq!(current_task_id(), Some(task_id));
265        drop(guard);
266        assert_eq!(current_task_id(), None);
267    }
268}