async_inspect/
config.rs

1//! Production configuration and settings
2//!
3//! This module provides configuration options for using async-inspect
4//! in production environments with minimal overhead.
5
6use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
7use std::sync::Arc;
8
9/// Global configuration instance
10static CONFIG: once_cell::sync::Lazy<Config> = once_cell::sync::Lazy::new(Config::default);
11
12/// Production configuration for async-inspect
13#[derive(Clone)]
14pub struct Config {
15    inner: Arc<ConfigInner>,
16}
17
18struct ConfigInner {
19    /// Sampling rate: track 1 in N tasks (1 = track all)
20    sampling_rate: AtomicUsize,
21
22    /// Maximum number of events to retain (0 = unlimited)
23    max_events: AtomicUsize,
24
25    /// Maximum number of tasks to track (0 = unlimited)
26    max_tasks: AtomicUsize,
27
28    /// Counter for sampling decisions
29    sample_counter: AtomicU64,
30
31    /// Whether to track await points
32    track_awaits: AtomicUsize,
33
34    /// Whether to track poll counts
35    track_polls: AtomicUsize,
36
37    /// Whether to generate HTML reports
38    enable_html: AtomicUsize,
39
40    /// Overhead tracking: total time spent in instrumentation (nanoseconds)
41    overhead_ns: AtomicU64,
42
43    /// Number of instrumentation calls
44    instrumentation_calls: AtomicU64,
45}
46
47impl Config {
48    /// Get the global configuration instance
49    #[must_use]
50    pub fn global() -> &'static Config {
51        &CONFIG
52    }
53
54    /// Create a new configuration with default settings
55    #[must_use]
56    pub fn new() -> Self {
57        Self {
58            inner: Arc::new(ConfigInner {
59                sampling_rate: AtomicUsize::new(1),   // Track all tasks by default
60                max_events: AtomicUsize::new(10_000), // Default: keep last 10k events
61                max_tasks: AtomicUsize::new(1_000),   // Default: track up to 1k tasks
62                sample_counter: AtomicU64::new(0),
63                track_awaits: AtomicUsize::new(1), // Enabled by default
64                track_polls: AtomicUsize::new(1),  // Enabled by default
65                enable_html: AtomicUsize::new(1),  // Enabled by default
66                overhead_ns: AtomicU64::new(0),
67                instrumentation_calls: AtomicU64::new(0),
68            }),
69        }
70    }
71
72    /// Set sampling rate (1 = track all, 10 = track 1 in 10, etc.)
73    pub fn set_sampling_rate(&self, rate: usize) {
74        self.inner
75            .sampling_rate
76            .store(rate.max(1), Ordering::Relaxed);
77    }
78
79    /// Get current sampling rate
80    #[must_use]
81    pub fn sampling_rate(&self) -> usize {
82        self.inner.sampling_rate.load(Ordering::Relaxed)
83    }
84
85    /// Set maximum number of events to retain
86    pub fn set_max_events(&self, max: usize) {
87        self.inner.max_events.store(max, Ordering::Relaxed);
88    }
89
90    /// Get maximum number of events
91    #[must_use]
92    pub fn max_events(&self) -> usize {
93        self.inner.max_events.load(Ordering::Relaxed)
94    }
95
96    /// Set maximum number of tasks to track
97    pub fn set_max_tasks(&self, max: usize) {
98        self.inner.max_tasks.store(max, Ordering::Relaxed);
99    }
100
101    /// Get maximum number of tasks
102    #[must_use]
103    pub fn max_tasks(&self) -> usize {
104        self.inner.max_tasks.load(Ordering::Relaxed)
105    }
106
107    /// Enable or disable await tracking
108    pub fn set_track_awaits(&self, enabled: bool) {
109        self.inner
110            .track_awaits
111            .store(usize::from(enabled), Ordering::Relaxed);
112    }
113
114    /// Check if await tracking is enabled
115    #[must_use]
116    pub fn track_awaits(&self) -> bool {
117        self.inner.track_awaits.load(Ordering::Relaxed) != 0
118    }
119
120    /// Enable or disable poll tracking
121    pub fn set_track_polls(&self, enabled: bool) {
122        self.inner
123            .track_polls
124            .store(usize::from(enabled), Ordering::Relaxed);
125    }
126
127    /// Check if poll tracking is enabled
128    #[must_use]
129    pub fn track_polls(&self) -> bool {
130        self.inner.track_polls.load(Ordering::Relaxed) != 0
131    }
132
133    /// Enable or disable HTML report generation
134    pub fn set_enable_html(&self, enabled: bool) {
135        self.inner
136            .enable_html
137            .store(usize::from(enabled), Ordering::Relaxed);
138    }
139
140    /// Check if HTML reports are enabled
141    #[must_use]
142    pub fn enable_html(&self) -> bool {
143        self.inner.enable_html.load(Ordering::Relaxed) != 0
144    }
145
146    /// Decide whether to sample this task
147    #[must_use]
148    pub fn should_sample(&self) -> bool {
149        let rate = self.sampling_rate();
150        if rate <= 1 {
151            return true;
152        }
153
154        let count = self.inner.sample_counter.fetch_add(1, Ordering::Relaxed);
155        count % rate as u64 == 0
156    }
157
158    /// Record instrumentation overhead
159    pub fn record_overhead(&self, nanos: u64) {
160        self.inner.overhead_ns.fetch_add(nanos, Ordering::Relaxed);
161        self.inner
162            .instrumentation_calls
163            .fetch_add(1, Ordering::Relaxed);
164    }
165
166    /// Get total overhead in nanoseconds
167    #[must_use]
168    pub fn total_overhead_ns(&self) -> u64 {
169        self.inner.overhead_ns.load(Ordering::Relaxed)
170    }
171
172    /// Get total instrumentation calls
173    #[must_use]
174    pub fn instrumentation_calls(&self) -> u64 {
175        self.inner.instrumentation_calls.load(Ordering::Relaxed)
176    }
177
178    /// Get average overhead per call in nanoseconds
179    #[must_use]
180    pub fn avg_overhead_ns(&self) -> f64 {
181        let calls = self.instrumentation_calls();
182        if calls == 0 {
183            return 0.0;
184        }
185        self.total_overhead_ns() as f64 / calls as f64
186    }
187
188    /// Configure for production use (minimal overhead)
189    pub fn production_mode(&self) {
190        self.set_sampling_rate(100); // Track 1% of tasks
191        self.set_max_events(1_000); // Keep only 1k events
192        self.set_max_tasks(500); // Track up to 500 tasks
193        self.set_track_awaits(false); // Disable detailed await tracking
194        self.set_enable_html(false); // Disable HTML generation
195    }
196
197    /// Configure for development use (full tracking)
198    pub fn development_mode(&self) {
199        self.set_sampling_rate(1); // Track all tasks
200        self.set_max_events(10_000); // Keep 10k events
201        self.set_max_tasks(1_000); // Track up to 1k tasks
202        self.set_track_awaits(true); // Enable await tracking
203        self.set_enable_html(true); // Enable HTML generation
204    }
205
206    /// Configure for debugging (maximum detail)
207    pub fn debug_mode(&self) {
208        self.set_sampling_rate(1); // Track all tasks
209        self.set_max_events(0); // Unlimited events
210        self.set_max_tasks(0); // Unlimited tasks
211        self.set_track_awaits(true); // Enable await tracking
212        self.set_enable_html(true); // Enable HTML generation
213    }
214
215    /// Get overhead statistics
216    #[must_use]
217    pub fn overhead_stats(&self) -> OverheadStats {
218        OverheadStats {
219            total_ns: self.total_overhead_ns(),
220            calls: self.instrumentation_calls(),
221            avg_ns: self.avg_overhead_ns(),
222        }
223    }
224
225    /// Reset overhead counters
226    pub fn reset_overhead(&self) {
227        self.inner.overhead_ns.store(0, Ordering::Relaxed);
228        self.inner.instrumentation_calls.store(0, Ordering::Relaxed);
229    }
230}
231
232impl Default for Config {
233    fn default() -> Self {
234        Self::new()
235    }
236}
237
238/// Overhead statistics
239#[derive(Debug, Clone, Copy)]
240pub struct OverheadStats {
241    /// Total overhead in nanoseconds
242    pub total_ns: u64,
243
244    /// Number of instrumentation calls
245    pub calls: u64,
246
247    /// Average overhead per call in nanoseconds
248    pub avg_ns: f64,
249}
250
251impl OverheadStats {
252    /// Get total overhead in milliseconds
253    #[must_use]
254    pub fn total_ms(&self) -> f64 {
255        self.total_ns as f64 / 1_000_000.0
256    }
257
258    /// Get average overhead in microseconds
259    #[must_use]
260    pub fn avg_us(&self) -> f64 {
261        self.avg_ns / 1_000.0
262    }
263}
264
265/// Helper macro to measure and record overhead
266#[macro_export]
267macro_rules! measure_overhead {
268    ($expr:expr) => {{
269        let start = std::time::Instant::now();
270        let result = $expr;
271        let elapsed = start.elapsed().as_nanos() as u64;
272        $crate::config::Config::global().record_overhead(elapsed);
273        result
274    }};
275}
276
277/// Helper to conditionally execute code only when sampling
278#[macro_export]
279macro_rules! if_sampled {
280    ($body:block) => {
281        if $crate::config::Config::global().should_sample() $body
282    };
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn test_sampling_rate() {
291        let config = Config::new();
292        config.set_sampling_rate(10);
293        assert_eq!(config.sampling_rate(), 10);
294
295        // Check that sampling works
296        let mut sampled = 0;
297        for _ in 0..100 {
298            if config.should_sample() {
299                sampled += 1;
300            }
301        }
302        // Should sample approximately 10 times (1 in 10)
303        assert!((8..=12).contains(&sampled));
304    }
305
306    #[test]
307    fn test_overhead_tracking() {
308        let config = Config::new();
309        config.reset_overhead();
310
311        config.record_overhead(1000);
312        config.record_overhead(2000);
313
314        let stats = config.overhead_stats();
315        assert_eq!(stats.total_ns, 3000);
316        assert_eq!(stats.calls, 2);
317        assert_eq!(stats.avg_ns, 1500.0);
318    }
319
320    #[test]
321    fn test_production_mode() {
322        let config = Config::new();
323        config.production_mode();
324
325        assert_eq!(config.sampling_rate(), 100);
326        assert!(!config.track_awaits());
327        assert!(!config.enable_html());
328    }
329}