1use chrono::{DateTime, Utc};
9
10use crate::state::{MetricDatum, SharedCloudWatchState};
11
12#[derive(Debug, Clone)]
14pub struct DimensionRow {
15 pub name: String,
16 pub value: String,
17}
18
19#[derive(Debug, Clone)]
21pub struct AlarmRow {
22 pub account_id: String,
23 pub region: String,
24 pub name: String,
25 pub kind: &'static str,
27 pub state: String,
29 pub state_reason: String,
30 pub state_updated_timestamp: Option<DateTime<Utc>>,
31 pub actions_enabled: bool,
32 pub alarm_actions: Vec<String>,
33 pub ok_actions: Vec<String>,
34 pub insufficient_data_actions: Vec<String>,
35 pub namespace: Option<String>,
37 pub metric_name: Option<String>,
38 pub threshold: Option<f64>,
39 pub comparison_operator: Option<String>,
40 pub alarm_rule: Option<String>,
42}
43
44#[derive(Debug, Clone)]
46pub struct LatestDatapoint {
47 pub timestamp: DateTime<Utc>,
48 pub value: Option<f64>,
49 pub unit: Option<String>,
50}
51
52#[derive(Debug, Clone)]
54pub struct MetricRow {
55 pub account_id: String,
56 pub region: String,
57 pub namespace: String,
58 pub metric_name: String,
59 pub dimensions: Vec<DimensionRow>,
60 pub datapoint_count: usize,
61 pub latest: Option<LatestDatapoint>,
62}
63
64fn dims_to_rows(dims: &std::collections::BTreeMap<String, String>) -> Vec<DimensionRow> {
65 dims.iter()
66 .map(|(name, value)| DimensionRow {
67 name: name.clone(),
68 value: value.clone(),
69 })
70 .collect()
71}
72
73pub fn list_all_alarms(state: &SharedCloudWatchState) -> Vec<AlarmRow> {
76 let accounts = state.read();
77 let mut rows: Vec<AlarmRow> = Vec::new();
78 for (account_id, acct) in &accounts.accounts {
79 for (region, alarms) in &acct.alarms {
80 for (name, a) in alarms {
81 rows.push(AlarmRow {
82 account_id: account_id.clone(),
83 region: region.clone(),
84 name: name.clone(),
85 kind: "metric",
86 state: a.state_value.as_str().to_string(),
87 state_reason: a.state_reason.clone(),
88 state_updated_timestamp: Some(a.state_updated_timestamp),
89 actions_enabled: a.actions_enabled,
90 alarm_actions: a.alarm_actions.clone(),
91 ok_actions: a.ok_actions.clone(),
92 insufficient_data_actions: a.insufficient_data_actions.clone(),
93 namespace: a.namespace.clone(),
94 metric_name: a.metric_name.clone(),
95 threshold: a.threshold,
96 comparison_operator: Some(a.comparison_operator.clone()),
97 alarm_rule: None,
98 });
99 }
100 }
101 for (region, alarms) in &acct.composite_alarms {
102 for (name, a) in alarms {
103 rows.push(AlarmRow {
104 account_id: account_id.clone(),
105 region: region.clone(),
106 name: name.clone(),
107 kind: "composite",
108 state: a.state_value.as_str().to_string(),
109 state_reason: a.state_reason.clone(),
110 state_updated_timestamp: Some(a.state_updated_timestamp),
111 actions_enabled: a.actions_enabled,
112 alarm_actions: a.alarm_actions.clone(),
113 ok_actions: a.ok_actions.clone(),
114 insufficient_data_actions: a.insufficient_data_actions.clone(),
115 namespace: None,
116 metric_name: None,
117 threshold: None,
118 comparison_operator: None,
119 alarm_rule: Some(a.alarm_rule.clone()),
120 });
121 }
122 }
123 }
124 rows.sort_by(|a, b| {
125 a.account_id
126 .cmp(&b.account_id)
127 .then_with(|| a.region.cmp(&b.region))
128 .then_with(|| a.name.cmp(&b.name))
129 });
130 rows
131}
132
133pub fn list_all_metrics(state: &SharedCloudWatchState) -> Vec<MetricRow> {
137 let accounts = state.read();
138 let mut rows: Vec<MetricRow> = Vec::new();
139 for (account_id, acct) in &accounts.accounts {
140 for (region, by_namespace) in &acct.metrics {
142 for (namespace, data) in by_namespace {
143 type SeriesKey = (String, Vec<(String, String)>);
145 let mut groups: std::collections::BTreeMap<SeriesKey, Vec<&MetricDatum>> =
146 std::collections::BTreeMap::new();
147 for d in data {
148 let dim_key: Vec<(String, String)> = d
149 .dimensions
150 .iter()
151 .map(|(k, v)| (k.clone(), v.clone()))
152 .collect();
153 groups
154 .entry((d.metric_name.clone(), dim_key))
155 .or_default()
156 .push(d);
157 }
158 for ((metric_name, _), series) in groups {
159 let dimensions = series
160 .first()
161 .map(|d| dims_to_rows(&d.dimensions))
162 .unwrap_or_default();
163 let latest =
164 series
165 .iter()
166 .max_by_key(|d| d.timestamp)
167 .map(|d| LatestDatapoint {
168 timestamp: d.timestamp,
169 value: d.value,
170 unit: d.unit.clone(),
171 });
172 rows.push(MetricRow {
173 account_id: account_id.clone(),
174 region: region.clone(),
175 namespace: namespace.clone(),
176 metric_name,
177 dimensions,
178 datapoint_count: series.len(),
179 latest,
180 });
181 }
182 }
183 }
184 }
185 rows.sort_by(|a, b| {
186 a.account_id
187 .cmp(&b.account_id)
188 .then_with(|| a.region.cmp(&b.region))
189 .then_with(|| a.namespace.cmp(&b.namespace))
190 .then_with(|| a.metric_name.cmp(&b.metric_name))
191 .then_with(|| a.dimensions.len().cmp(&b.dimensions.len()))
192 });
193 rows
194}