Skip to main content

micromegas_analytics/
time.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use chrono::{DateTime, TimeDelta, Utc};
5use datafusion::scalar::ScalarValue;
6use micromegas_telemetry::types::block::BlockMetadata;
7
8use crate::metadata::ProcessMetadata;
9
10const NANOS_PER_SEC: f64 = 1000.0 * 1000.0 * 1000.0;
11
12/// A time range, with a beginning and an end.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct TimeRange {
15    pub begin: DateTime<Utc>,
16    pub end: DateTime<Utc>,
17}
18
19impl TimeRange {
20    pub fn new(begin: DateTime<Utc>, end: DateTime<Utc>) -> Self {
21        Self { begin, end }
22    }
23}
24
25/// Creates a `ConvertTicks` from a block's metadata.
26pub fn make_time_converter_from_block_meta(
27    process: &ProcessMetadata,
28    block: &BlockMetadata,
29) -> Result<ConvertTicks> {
30    if process.tsc_frequency > 0 {
31        // we have a good tsc freq provided
32        return ConvertTicks::from_meta_data(
33            process.start_ticks,
34            process.start_time.timestamp_nanos_opt().unwrap_or_default(),
35            process.tsc_frequency,
36        );
37    }
38    let delta_time = block.end_time - process.start_time;
39    let nb_seconds = delta_time.num_nanoseconds().unwrap_or_default() as f64 / 1_000_000_000.0;
40    let ticks_per_second = block.end_ticks as f64 / nb_seconds;
41    ConvertTicks::from_meta_data(
42        process.start_ticks,
43        process.start_time.timestamp_nanos_opt().unwrap_or_default(),
44        ticks_per_second.round() as i64,
45    )
46}
47
48/// Creates a `ConvertTicks` using the latest timing information from the process.
49/// This should be used instead of per-block timing to ensure consistent tick conversion
50/// across all blocks from the same process.
51pub fn make_time_converter_from_latest_timing(
52    process: &ProcessMetadata,
53    last_block_end_ticks: i64,
54    last_block_end_time: chrono::DateTime<chrono::Utc>,
55) -> Result<ConvertTicks> {
56    if process.tsc_frequency > 0 {
57        // we have a good tsc freq provided
58        return ConvertTicks::from_meta_data(
59            process.start_ticks,
60            process.start_time.timestamp_nanos_opt().unwrap_or_default(),
61            process.tsc_frequency,
62        );
63    }
64    // Calculate frequency using the latest timing data from the process
65    let delta_time = last_block_end_time - process.start_time;
66    let nb_seconds = delta_time.num_nanoseconds().unwrap_or_default() as f64 / 1_000_000_000.0;
67    let ticks_per_second = last_block_end_ticks as f64 / nb_seconds;
68    ConvertTicks::from_meta_data(
69        process.start_ticks,
70        process.start_time.timestamp_nanos_opt().unwrap_or_default(),
71        ticks_per_second.round() as i64,
72    )
73}
74
75/// ConvertTicks helps converting between a process's tick count and more convenient date/time representations
76#[derive(Debug, Clone)]
77pub struct ConvertTicks {
78    tick_offset: i64,
79    process_start_ns: i64,
80    frequency: i64, // ticks per second
81    inv_tsc_frequency_ns: f64,
82    inv_tsc_frequency_ms: f64,
83}
84
85impl ConvertTicks {
86    pub fn from_meta_data(start_ticks: i64, process_start_ns: i64, frequency: i64) -> Result<Self> {
87        if frequency <= 0 {
88            anyhow::bail!("invalid frequency")
89        }
90        Ok(Self {
91            tick_offset: start_ticks,
92            process_start_ns,
93            frequency,
94            inv_tsc_frequency_ns: get_tsc_frequency_inverse_ns(frequency),
95            inv_tsc_frequency_ms: get_tsc_frequency_inverse_ms(frequency),
96        })
97    }
98
99    /// Get the frequency used for tick conversion
100    pub fn get_frequency(&self) -> i64 {
101        self.frequency
102    }
103
104    /// from relative time to relative tick count
105    pub fn to_ticks(&self, delta: TimeDelta) -> i64 {
106        let mut seconds = delta.num_seconds() as f64;
107        seconds += delta.subsec_nanos() as f64 / NANOS_PER_SEC;
108        let freq = self.frequency as f64;
109        (seconds * freq).round() as i64
110    }
111
112    /// from absolute ticks to absolute nanoseconds
113    pub fn ticks_to_nanoseconds(&self, ticks: i64) -> i64 {
114        let delta = (ticks - self.tick_offset) as f64;
115        let ns_since_process_start = (delta * self.inv_tsc_frequency_ns).round() as i64;
116        self.process_start_ns + ns_since_process_start
117    }
118
119    /// from relative ticks to absolute date/time
120    pub fn delta_ticks_to_time(&self, delta: i64) -> DateTime<Utc> {
121        let ns_since_process_start = (delta as f64 * self.inv_tsc_frequency_ns).round() as i64;
122        DateTime::from_timestamp_nanos(self.process_start_ns + ns_since_process_start)
123    }
124
125    /// from relative ticks to absolute nanoseconds
126    pub fn delta_ticks_to_ns(&self, delta: i64) -> i64 {
127        let ns_since_process_start = (delta as f64 * self.inv_tsc_frequency_ns).round() as i64;
128        self.process_start_ns + ns_since_process_start
129    }
130
131    /// from relative ticks to relative milliseconds
132    pub fn delta_ticks_to_ms(&self, delta_ticks: i64) -> f64 {
133        let delta = delta_ticks as f64;
134        delta * self.inv_tsc_frequency_ms
135    }
136
137    /// from time to relative ticks
138    pub fn time_to_delta_ticks(&self, time: DateTime<Utc>) -> i64 {
139        self.to_ticks(time - DateTime::from_timestamp_nanos(self.process_start_ns))
140    }
141}
142
143/// Returns the inverse of the TSC frequency in milliseconds.
144#[allow(clippy::cast_precision_loss)]
145pub fn get_tsc_frequency_inverse_ms(tsc_frequency: i64) -> f64 {
146    1000.0 / tsc_frequency as f64
147}
148
149/// Returns the inverse of the TSC frequency in nanoseconds.
150#[allow(clippy::cast_precision_loss)]
151pub fn get_tsc_frequency_inverse_ns(tsc_frequency: i64) -> f64 {
152    NANOS_PER_SEC / tsc_frequency as f64
153}
154
155/// Converts a `DateTime<Utc>` to a `ScalarValue`.
156pub fn datetime_to_scalar(v: DateTime<Utc>) -> ScalarValue {
157    lazy_static::lazy_static! {
158        static ref UTC_OFFSET: Arc<str> = Arc::from("+00:00");
159    }
160    ScalarValue::TimestampNanosecond(v.timestamp_nanos_opt(), Some(UTC_OFFSET.clone()))
161}