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