Skip to main content

sol_trade_sdk/common/
clock.rs

1//! High-performance clock (same design as sol-parser-sdk for consistent grpc_recv_us vs "now").
2//!
3//! Uses monotonic clock + base UTC timestamp to avoid frequent syscalls; aligned with sol-parser-sdk
4//! so event-side grpc_recv_us and SDK-side now_micros() share the same time scale.
5
6use std::time::Instant;
7
8/// High-performance clock: monotonic + base UTC microsecond timestamp.
9#[derive(Debug)]
10pub struct HighPerformanceClock {
11    base_instant: Instant,
12    base_timestamp_us: i64,
13    last_calibration: Instant,
14    calibration_interval_secs: u64,
15}
16
17impl HighPerformanceClock {
18    /// Calibrate every 5 minutes by default.
19    pub fn new() -> Self {
20        Self::new_with_calibration_interval(300)
21    }
22
23    /// Sample multiple times and use the lowest-latency baseline to reduce init error.
24    pub fn new_with_calibration_interval(calibration_interval_secs: u64) -> Self {
25        let mut best_offset = i64::MAX;
26        let mut best_instant = Instant::now();
27        let mut best_timestamp = chrono::Utc::now().timestamp_micros();
28
29        for _ in 0..3 {
30            let instant_before = Instant::now();
31            let timestamp = chrono::Utc::now().timestamp_micros();
32            let instant_after = Instant::now();
33            let sample_latency = instant_after.duration_since(instant_before).as_nanos() as i64;
34            if sample_latency < best_offset {
35                best_offset = sample_latency;
36                best_instant = instant_before;
37                best_timestamp = timestamp;
38            }
39        }
40
41        Self {
42            base_instant: best_instant,
43            base_timestamp_us: best_timestamp,
44            last_calibration: best_instant,
45            calibration_interval_secs,
46        }
47    }
48
49    #[inline(always)]
50    pub fn now_micros(&self) -> i64 {
51        let elapsed = self.base_instant.elapsed();
52        self.base_timestamp_us + elapsed.as_micros() as i64
53    }
54
55    /// Recalibrate when needed to prevent drift.
56    pub fn now_micros_with_calibration(&mut self) -> i64 {
57        if self.last_calibration.elapsed().as_secs() >= self.calibration_interval_secs {
58            self.recalibrate();
59        }
60        self.now_micros()
61    }
62
63    fn recalibrate(&mut self) {
64        let current_monotonic = Instant::now();
65        let current_utc = chrono::Utc::now().timestamp_micros();
66        let expected_utc = self.base_timestamp_us
67            + current_monotonic.duration_since(self.base_instant).as_micros() as i64;
68        let drift_us = current_utc - expected_utc;
69        if drift_us.abs() > 1000 {
70            self.base_instant = current_monotonic;
71            self.base_timestamp_us = current_utc;
72        }
73        self.last_calibration = current_monotonic;
74    }
75}
76
77impl Default for HighPerformanceClock {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83static HIGH_PERF_CLOCK: once_cell::sync::OnceCell<HighPerformanceClock> =
84    once_cell::sync::OnceCell::new();
85
86/// Current time in microseconds (UTC scale); same as sol-parser-sdk clock::now_micros for comparable grpc_recv_us.
87#[inline(always)]
88pub fn now_micros() -> i64 {
89    let clock = HIGH_PERF_CLOCK.get_or_init(HighPerformanceClock::new);
90    clock.now_micros()
91}
92
93/// Elapsed microseconds from start_timestamp_us to now.
94#[inline(always)]
95pub fn elapsed_micros_since(start_timestamp_us: i64) -> i64 {
96    now_micros() - start_timestamp_us
97}