rusty_engine/
timing.rs

1//! High-performance timing utilities (quanta 쓰는 개빠른 시간 측정)
2//! 캐싱해뒀다가 그거 갖다쓰는거임
3//! This module provides high-precision timing facilities using the quanta crate.
4//! It configures the "recent time" feature that provides ultra-low-overhead access
5//! to a slightly-delayed global time, which can be 4-10x faster than direct time queries.
6
7use fastrace::prelude::*;
8use log::{debug, info, warn};
9use quanta::{Clock, Instant, Upkeep};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::time::Duration;
12
13/// Singleton upkeep handle to prevent multiple upkeep threads
14static UPKEEP_STARTED: AtomicBool = AtomicBool::new(false);
15
16/// Upkeep handle for the high-performance timing system
17#[derive(Debug)]
18pub struct TimingUpkeep {
19    /// The quanta upkeep handle
20    _handle: Option<quanta::Handle>,
21    /// Whether this upkeep is active
22    active: bool,
23}
24
25impl TimingUpkeep {
26    /// Create and start a new timing upkeep thread
27    ///
28    /// This configures the quanta crate's "recent time" feature, which provides
29    /// ultra-low-overhead access to a slightly-delayed global time. This is useful
30    /// for high-performance applications where the cost of time queries is significant.
31    ///
32    /// The upkeep thread updates the global time value at the specified interval,
33    /// allowing other threads to read it without the overhead of a full time query.
34    ///
35    /// # Arguments
36    ///
37    /// * `update_interval` - How often to update the global time (smaller values
38    ///   provide more accurate time but increase CPU usage)
39    ///
40    /// # Returns
41    ///
42    /// A handle to the upkeep thread. The thread runs until this handle is dropped.
43    #[must_use]
44    pub fn start(update_interval: Duration) -> Self {
45        let span = Span::root("timing_upkeep_start", SpanContext::random());
46        let _guard = span.set_local_parent();
47
48        // Ensure we don't start multiple upkeep threads
49        if UPKEEP_STARTED.swap(true, Ordering::SeqCst) {
50            warn!("Timing upkeep thread already running, not starting another one");
51            return Self {
52                _handle: None,
53                active: false,
54            };
55        }
56
57        info!("Starting quanta timing upkeep thread with interval: {update_interval:?}");
58
59        // Use an existing clock to avoid recalibration
60        let clock = Clock::new();
61        let upkeep = Upkeep::new_with_clock(update_interval, clock);
62
63        match upkeep.start() {
64            Ok(handle) => {
65                debug!("Timing upkeep thread started successfully");
66                Self {
67                    _handle: Some(handle),
68                    active: true,
69                }
70            }
71            Err(e) => {
72                // Reset the flag since we couldn't start the thread
73                UPKEEP_STARTED.store(false, Ordering::SeqCst);
74                warn!("Failed to start timing upkeep thread: {e:?}");
75                Self {
76                    _handle: None,
77                    active: false,
78                }
79            }
80        }
81    }
82
83    /// Check if this upkeep is active
84    pub const fn is_active(&self) -> bool {
85        self.active
86    }
87
88    /// Manually update the global recent time
89    ///
90    /// This is useful if you don't want to use the upkeep thread but still
91    /// want to occasionally update the global time.
92    pub fn update_recent_time() {
93        let now = Clock::new().now();
94        quanta::set_recent(now);
95    }
96
97    /// Get the current time using the most efficient method available
98    ///
99    /// This function uses the ultra-low-overhead recent time if available,
100    /// falling back to a regular time query if not.
101    #[must_use]
102    pub fn now() -> Instant {
103        Instant::recent()
104    }
105
106    /// Get the current timestamp in nanoseconds
107    ///
108    /// This function uses the ultra-low-overhead recent time if available,
109    /// falling back to a regular time query if not.
110    #[must_use]
111    pub fn now_ns() -> u64 {
112        // Use `quanta::Clock` directly to obtain a raw timestamp in
113        // nanoseconds. This avoids the incorrect calculation that was based on
114        // a one hour offset and provides the same monotonic timestamp used
115        // throughout the rest of the codebase.
116        Clock::new().raw()
117    }
118}
119
120impl Drop for TimingUpkeep {
121    fn drop(&mut self) {
122        if self.active {
123            info!("Stopping timing upkeep thread");
124            UPKEEP_STARTED.store(false, Ordering::SeqCst);
125        }
126    }
127}
128
129/// Initialize the timing subsystem with recommended settings for HFT applications
130///
131/// This function sets up the quanta timing system with the recommended settings
132/// for high-frequency trading applications. It configures the "recent time" feature
133/// to provide ultra-low-overhead access to a slightly-delayed global time.
134///
135/// # Returns
136///
137/// A handle to the upkeep thread. The thread runs until this handle is dropped.
138#[must_use]
139pub fn init_timing() -> TimingUpkeep {
140    // For HFT applications, we want to update the global time frequently to
141    // minimize the staleness of the cached time, while not updating so frequently
142    // that it creates too much overhead
143    let update_interval = Duration::from_micros(500); // 500 μs update interval
144
145    // Start the upkeep thread
146    TimingUpkeep::start(update_interval)
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use std::thread;
153
154    #[test]
155    fn test_upkeep_initialization() {
156        // Reset the static flag for testing purposes
157        UPKEEP_STARTED.store(false, Ordering::SeqCst);
158
159        // First attempt should try to create an upkeep
160        let _upkeep = init_timing();
161
162        // Second upkeep should not be active since we already have one
163        let upkeep2 = init_timing();
164        assert!(!upkeep2.is_active());
165
166        // In tests we can't reliably check if upkeep.is_active() because
167        // it depends on system thread creation which might fail in test environments
168    }
169
170    #[test]
171    fn test_recent_time() {
172        // Reset the static flag for testing purposes
173        UPKEEP_STARTED.store(false, Ordering::SeqCst);
174
175        // Start upkeep thread
176        let _upkeep = init_timing();
177
178        // Allow the upkeep thread to update the recent time
179        thread::sleep(Duration::from_millis(50)); // Increased sleep time for reliability
180
181        // Get recent time
182        let recent_time = TimingUpkeep::now();
183
184        // Ensure it's within a reasonable range of the current time
185        // The recent time should be very close to now
186        let now = Clock::new().now();
187
188        // Handle both cases: recent_time might be slightly before or after 'now'
189        let diff = if now > recent_time {
190            now.duration_since(recent_time)
191        } else {
192            // In rare cases, recent_time might be slightly ahead due to timing precision
193            Duration::from_nanos(0)
194        };
195
196        // Should be less than 100ms difference (increased tolerance for test reliability)
197        assert!(
198            diff < Duration::from_millis(100),
199            "Time difference {} ms is too large",
200            diff.as_millis()
201        );
202    }
203
204    #[test]
205    fn test_now_ns() {
206        // Reset the static flag for testing purposes
207        UPKEEP_STARTED.store(false, Ordering::SeqCst);
208
209        // Start upkeep thread
210        let _upkeep = init_timing();
211
212        // Get nanosecond timestamp
213        let ns = TimingUpkeep::now_ns();
214
215        // Just verify it's a reasonable value (non-zero)
216        assert!(ns > 0);
217
218        // For a more reliable test of the timing functionality,
219        // we'll use direct Clock measurements
220        let clock = Clock::new();
221        let t1 = clock.raw();
222        thread::sleep(Duration::from_millis(1));
223        let t2 = clock.raw();
224        assert!(t2 > t1, "Clock timestamps should increase over time");
225    }
226}