|
| 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 | +
|
| 6 | +use std::time::Instant; |
| 7 | + |
| 8 | +/// High-performance clock: monotonic + base UTC microsecond timestamp. |
| 9 | +#[derive(Debug)] |
| 10 | +pub struct HighPerformanceClock { |
| 11 | + base_instant: Instant, |
| 12 | + base_timestamp_us: i64, |
| 13 | + last_calibration: Instant, |
| 14 | + calibration_interval_secs: u64, |
| 15 | +} |
| 16 | + |
| 17 | +impl 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 | + |
| 77 | +impl Default for HighPerformanceClock { |
| 78 | + fn default() -> Self { |
| 79 | + Self::new() |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +static 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)] |
| 88 | +pub 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)] |
| 95 | +pub fn elapsed_micros_since(start_timestamp_us: i64) -> i64 { |
| 96 | + now_micros() - start_timestamp_us |
| 97 | +} |
0 commit comments