Skip to main content

px_core/
timing.rs

1//! Comprehensive timing instrumentation for HFT-level latency tracking.
2//!
3//! This module provides utilities for recording detailed timing metrics at every
4//! step of request processing. All timings are recorded as Prometheus histograms
5//! in microseconds for maximum precision.
6//!
7//! HTTP timing across exchange crates follows a common pattern (send → body → parse).
8//! Each exchange owns its timing to preserve exchange-specific metric labels and
9//! auth header patterns. The `timed!` macro provides the building block.
10
11use metrics::histogram;
12use std::time::Instant;
13
14/// Zero-cost timing macro. Expands inline at compile time.
15#[macro_export]
16macro_rules! timed {
17    ($metric:expr, $($k:expr => $v:expr),+; $block:expr) => {{
18        let __start = std::time::Instant::now();
19        let __result = $block;
20        let __us = __start.elapsed().as_micros() as f64;
21        metrics::histogram!($metric, $($k => $v),+).record(__us);
22        __result
23    }};
24    ($metric:expr; $block:expr) => {{
25        let __start = std::time::Instant::now();
26        let __result = $block;
27        let __us = __start.elapsed().as_micros() as f64;
28        metrics::histogram!($metric).record(__us);
29        __result
30    }};
31}
32
33/// A guard that automatically records timing when dropped.
34/// Useful for measuring the total duration of a scope.
35pub struct TimingGuard {
36    name: &'static str,
37    start: Instant,
38    label_key: Option<&'static str>,
39    label_value: Option<String>,
40}
41
42impl TimingGuard {
43    /// Create a new timing guard with no labels.
44    pub fn new(name: &'static str) -> Self {
45        Self {
46            name,
47            start: Instant::now(),
48            label_key: None,
49            label_value: None,
50        }
51    }
52
53    /// Create a new timing guard with a single label.
54    pub fn with_label(name: &'static str, key: &'static str, value: impl Into<String>) -> Self {
55        Self {
56            name,
57            start: Instant::now(),
58            label_key: Some(key),
59            label_value: Some(value.into()),
60        }
61    }
62}
63
64impl Drop for TimingGuard {
65    fn drop(&mut self) {
66        let elapsed_us = self.start.elapsed().as_micros() as f64;
67        if let (Some(key), Some(value)) = (self.label_key, self.label_value.take()) {
68            histogram!(self.name, key => value).record(elapsed_us);
69        } else {
70            histogram!(self.name).record(elapsed_us);
71        }
72    }
73}
74
75// ============================================================================
76// Metric name constants for consistency across crates
77// ============================================================================
78
79/// Exchange implementation metrics
80pub mod exchange {
81    /// HTTP request time to exchange API
82    pub const HTTP_REQUEST: &str = "openpx.exchange.http_request_us";
83    /// Response parsing time
84    pub const PARSE_RESPONSE: &str = "openpx.exchange.parse_response_us";
85    /// Signature generation time (for authenticated requests)
86    pub const SIGN_REQUEST: &str = "openpx.exchange.sign_request_us";
87    /// WebSocket message send time
88    pub const WS_SEND: &str = "openpx.exchange.ws_send_us";
89    /// WebSocket message receive time
90    pub const WS_RECEIVE: &str = "openpx.exchange.ws_receive_us";
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use std::thread::sleep;
97    use std::time::Duration;
98
99    #[test]
100    fn test_timing_guard() {
101        // Just ensure it compiles and doesn't panic
102        let _guard = TimingGuard::new("test.metric");
103        sleep(Duration::from_micros(100));
104    }
105
106    #[test]
107    fn test_timing_guard_with_label() {
108        let _guard = TimingGuard::with_label("test.metric", "exchange", "polymarket");
109        sleep(Duration::from_micros(100));
110    }
111}