Skip to main content

fakecloud_cloudwatch/
delivery.rs

1//! Cross-service CloudWatch metrics adapter.
2//!
3//! Lets non-cloudwatch services (currently CloudWatch Logs metric
4//! filters) publish metric data points without depending on this crate
5//! directly.
6
7use std::collections::BTreeMap;
8
9use chrono::{TimeZone, Utc};
10
11use fakecloud_core::delivery::CloudwatchDelivery;
12
13use crate::state::{MetricDatum, SharedCloudWatchState};
14
15pub struct CloudwatchDeliveryImpl {
16    state: SharedCloudWatchState,
17}
18
19impl CloudwatchDeliveryImpl {
20    pub fn new(state: SharedCloudWatchState) -> Self {
21        Self { state }
22    }
23}
24
25impl CloudwatchDelivery for CloudwatchDeliveryImpl {
26    fn put_metric(
27        &self,
28        account_id: &str,
29        region: &str,
30        namespace: &str,
31        metric_name: &str,
32        value: f64,
33        unit: Option<&str>,
34        dimensions: BTreeMap<String, String>,
35        timestamp_ms: i64,
36    ) {
37        let timestamp = Utc
38            .timestamp_millis_opt(timestamp_ms)
39            .single()
40            .unwrap_or_else(Utc::now);
41        let mut state = self.state.write();
42        let acct = state.get_or_create(account_id);
43        let metrics_map = acct.metrics_in_mut(region);
44        let bucket = metrics_map.entry(namespace.to_string()).or_default();
45        bucket.push(MetricDatum {
46            metric_name: metric_name.to_string(),
47            dimensions,
48            timestamp,
49            value: Some(value),
50            statistic_values: None,
51            unit: unit.map(|s| s.to_string()),
52            storage_resolution: None,
53        });
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::state::CloudWatchAccounts;
61    use parking_lot::RwLock;
62    use std::sync::Arc;
63
64    #[test]
65    fn put_metric_appends_datum_to_namespace() {
66        let state: SharedCloudWatchState = Arc::new(RwLock::new(CloudWatchAccounts::new()));
67        let delivery = CloudwatchDeliveryImpl::new(state.clone());
68
69        delivery.put_metric(
70            "123456789012",
71            "us-east-1",
72            "MyApp",
73            "ErrorCount",
74            1.0,
75            None,
76            BTreeMap::new(),
77            1_700_000_000_000,
78        );
79
80        let guard = state.read();
81        let acct = guard.get("123456789012").unwrap();
82        let metrics = acct.metrics_in("us-east-1").unwrap();
83        let bucket = metrics.get("MyApp").unwrap();
84        assert_eq!(bucket.len(), 1);
85        assert_eq!(bucket[0].metric_name, "ErrorCount");
86        assert_eq!(bucket[0].value, Some(1.0));
87    }
88}