Skip to main content

millipede_core/autoscale/
snapshotter.rs

1use super::LoadSignal;
2use crate::errors::CrawlError;
3use std::{fmt, sync::Arc, time::Duration};
4
5/// Configuration for a collection of load signals and their sampling window.
6#[derive(Clone)]
7#[non_exhaustive]
8#[must_use = "snapshotter options do nothing unless passed to Snapshotter::new"]
9pub struct SnapshotterOptions {
10    /// Signals evaluated by the autoscaler.
11    pub signals: Vec<Arc<dyn LoadSignal>>,
12    /// Sliding window requested from each signal.
13    pub window: Duration,
14}
15
16impl Default for SnapshotterOptions {
17    fn default() -> Self {
18        Self {
19            signals: Vec::new(),
20            window: Duration::from_secs(30),
21        }
22    }
23}
24
25impl fmt::Debug for SnapshotterOptions {
26    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27        formatter
28            .debug_struct("SnapshotterOptions")
29            .field("signal_count", &self.signals.len())
30            .field("window", &self.window)
31            .finish()
32    }
33}
34
35/// Coordinates lifecycle and access to configured load signals.
36pub struct Snapshotter {
37    options: SnapshotterOptions,
38}
39
40impl Snapshotter {
41    /// Creates a snapshotter from the supplied options.
42    pub fn new(options: SnapshotterOptions) -> Self {
43        Self { options }
44    }
45
46    /// Returns the configured load signals.
47    pub fn signals(&self) -> &[Arc<dyn LoadSignal>] {
48        &self.options.signals
49    }
50
51    /// Returns the sampling window requested from each signal.
52    pub fn window(&self) -> Duration {
53        self.options.window
54    }
55
56    /// Starts each signal, stopping at the first error.
57    pub async fn start(&self) -> Result<(), CrawlError> {
58        for signal in &self.options.signals {
59            signal.start().await?;
60        }
61        Ok(())
62    }
63
64    /// Stops every signal and returns the first error encountered.
65    pub async fn stop(&self) -> Result<(), CrawlError> {
66        let mut first_error = None;
67        for signal in &self.options.signals {
68            if let Err(error) = signal.stop().await {
69                if first_error.is_none() {
70                    first_error = Some(error);
71                }
72            }
73        }
74        match first_error {
75            Some(error) => Err(error),
76            None => Ok(()),
77        }
78    }
79}