subgraph_mock/
latency.rs

1//! Simple latency generation
2use serde::{Deserialize, Serialize};
3use std::f64::consts::PI;
4use tokio::time::{Duration, Instant};
5use tracing::trace;
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
8pub struct LatencyConfig {
9    #[serde(deserialize_with = "humantime_serde::deserialize")]
10    pub base: Duration,
11    pub saw: Option<Shape>,
12    pub sine: Option<Shape>,
13    pub square: Option<Shape>,
14    pub triangle: Option<Shape>,
15}
16
17impl Default for LatencyConfig {
18    fn default() -> Self {
19        Self {
20            base: Duration::from_millis(5),
21            saw: None,
22            sine: Some(Shape {
23                amplitude: Duration::from_millis(2),
24                period: Duration::from_secs(10),
25            }),
26            square: None,
27            triangle: None,
28        }
29    }
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
33pub struct Shape {
34    #[serde(deserialize_with = "humantime_serde::deserialize")]
35    pub amplitude: Duration,
36    #[serde(deserialize_with = "humantime_serde::deserialize")]
37    pub period: Duration,
38}
39
40#[derive(Debug, Clone, Copy)]
41pub struct LatencyGenerator {
42    start: Instant,
43    cfg: LatencyConfig,
44}
45
46impl LatencyGenerator {
47    pub fn new(cfg: LatencyConfig) -> Self {
48        Self {
49            start: Instant::now(),
50            cfg,
51        }
52    }
53
54    pub fn generate(&self, when: Instant) -> Duration {
55        let mut latency_ms = self.cfg.base.as_millis() as u64;
56        let elapsed_ms = when.duration_since(self.start).as_millis() as u64;
57
58        trace!("Base latency: {latency_ms}");
59        trace!("Elapsed: {elapsed_ms}");
60
61        if let Some(saw) = self.cfg.saw {
62            latency_ms += saw_ms(saw, elapsed_ms);
63        }
64        if let Some(sine) = self.cfg.sine {
65            latency_ms += sine_ms(sine, elapsed_ms);
66        }
67        if let Some(square) = self.cfg.square {
68            latency_ms += square_ms(square, elapsed_ms);
69        }
70        if let Some(triangle) = self.cfg.triangle {
71            latency_ms += triangle_ms(triangle, elapsed_ms);
72        }
73
74        trace!("Final latency: {latency_ms}");
75        Duration::from_millis(latency_ms)
76    }
77}
78
79#[inline(always)]
80fn saw_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
81    let amplitude = amplitude.as_millis() as u64;
82    let period = period.as_millis() as u64;
83
84    trace!(
85        amplitude = amplitude,
86        period = period,
87        elapsed = elapsed,
88        "Computing saw value",
89    );
90
91    let result = ((elapsed % period) * amplitude) / period;
92
93    trace!(result = result, "Saw value computed");
94
95    result
96}
97
98#[inline(always)]
99fn sine_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
100    let amplitude = amplitude.as_millis() as u64;
101    let period = period.as_millis() as u64;
102
103    trace!(
104        amplitude = amplitude,
105        period = period,
106        elapsed = elapsed,
107        "Computing sine value",
108    );
109
110    let sine_value = ((elapsed as f64) / (period as f64) * PI * 2.0).sin(); // -1.0 to 1.0
111    let normalized = (sine_value + 1.0) / 2.0; // 0.0 to 1.0
112    let result = (normalized * amplitude as f64).round() as u64; // 0 to amplitude (in integer steps)
113
114    trace!(
115        sine_value = sine_value,
116        normalized = normalized,
117        result = result,
118        "Sine value computed"
119    );
120
121    result
122}
123
124#[inline(always)]
125fn square_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
126    let amplitude = amplitude.as_millis() as u64;
127    let period = period.as_millis() as u64;
128
129    trace!(
130        amplitude = amplitude,
131        period = period,
132        elapsed = elapsed,
133        "Computing square value",
134    );
135
136    let result = if elapsed % period < period / 2 {
137        amplitude
138    } else {
139        0
140    };
141
142    trace!(result = result, "Square value computed");
143
144    result
145}
146
147#[inline(always)]
148fn triangle_ms(Shape { amplitude, period }: Shape, elapsed: u64) -> u64 {
149    let amplitude = amplitude.as_millis() as u64;
150    let period = period.as_millis() as u64;
151
152    trace!(
153        amplitude = amplitude,
154        period = period,
155        elapsed = elapsed,
156        "Computing triangle value",
157    );
158
159    let position_in_period = elapsed % period;
160    let half_period = period / 2;
161
162    let result = if position_in_period < half_period {
163        (position_in_period * amplitude) / half_period
164    } else {
165        ((period - position_in_period) * amplitude) / half_period
166    };
167
168    trace!(result = result, "Triangle value computed");
169
170    result
171}