1use crate::config::AwsConfig;
2use anyhow::Result;
3
4pub struct AlarmsClient {
5 config: AwsConfig,
6}
7
8impl AlarmsClient {
9 pub fn new(config: AwsConfig) -> Self {
10 Self { config }
11 }
12
13 pub async fn list_alarms(
14 &self,
15 ) -> Result<
16 Vec<(
17 String,
18 String,
19 String,
20 String,
21 String,
22 String,
23 String,
24 u32,
25 String,
26 f64,
27 bool,
28 String,
29 String,
30 String,
31 String,
32 String,
33 String,
34 )>,
35 > {
36 let client = self.config.cloudwatch_client();
37
38 let resp = client.describe_alarms().send().await?;
39
40 let mut alarms = Vec::new();
41
42 for alarm in resp.metric_alarms() {
43 let name = alarm.alarm_name().unwrap_or("").to_string();
44 let state = alarm
45 .state_value()
46 .map(|s| s.as_str())
47 .unwrap_or("INSUFFICIENT_DATA")
48 .to_string();
49 let state_updated = alarm
50 .state_updated_timestamp()
51 .map(|t| {
52 let dt = chrono::DateTime::parse_from_rfc3339(&t.to_string())
53 .unwrap_or_else(|_| chrono::Utc::now().into());
54 dt.format("%Y-%m-%d %H:%M:%S").to_string()
55 })
56 .unwrap_or_default();
57 let description = alarm.alarm_description().unwrap_or("").to_string();
58 let metric_name = alarm.metric_name().unwrap_or("").to_string();
59 let namespace = alarm.namespace().unwrap_or("").to_string();
60 let statistic = alarm
61 .statistic()
62 .map(|s| s.as_str())
63 .unwrap_or("")
64 .to_string();
65 let period = alarm.period().unwrap_or(0) as u32;
66 let comparison = alarm
67 .comparison_operator()
68 .map(|c| c.as_str())
69 .unwrap_or("")
70 .to_string();
71 let threshold = alarm.threshold().unwrap_or(0.0);
72 let actions_enabled = alarm.actions_enabled().unwrap_or(false);
73 let state_reason = alarm.state_reason().unwrap_or("").to_string();
74
75 let resource = alarm
76 .dimensions()
77 .iter()
78 .map(|d| format!("{}={}", d.name().unwrap_or(""), d.value().unwrap_or("")))
79 .collect::<Vec<_>>()
80 .join(", ");
81
82 let dimensions = resource.clone();
83 let expression = if !alarm.metrics().is_empty() {
84 "Expression".to_string()
85 } else {
86 String::new()
87 };
88 let alarm_type = if !alarm.metrics().is_empty() {
89 "Metric math"
90 } else {
91 "Metric"
92 }
93 .to_string();
94 let cross_account = "".to_string();
95
96 alarms.push((
97 name,
98 state,
99 state_updated,
100 description,
101 metric_name,
102 namespace,
103 statistic,
104 period,
105 comparison,
106 threshold,
107 actions_enabled,
108 state_reason,
109 resource,
110 dimensions,
111 expression,
112 alarm_type,
113 cross_account,
114 ));
115 }
116
117 Ok(alarms)
118 }
119
120 pub async fn get_metric_statistics(
121 &self,
122 namespace: &str,
123 metric_name: &str,
124 statistic: &str,
125 period: i32,
126 start_time: chrono::DateTime<chrono::Utc>,
127 end_time: chrono::DateTime<chrono::Utc>,
128 ) -> Result<Vec<(i64, f64)>> {
129 let client = self.config.cloudwatch_client();
130
131 let resp = client
132 .get_metric_statistics()
133 .namespace(namespace)
134 .metric_name(metric_name)
135 .statistics(aws_sdk_cloudwatch::types::Statistic::from(statistic))
136 .period(period)
137 .start_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
138 start_time.timestamp_millis(),
139 ))
140 .end_time(aws_sdk_cloudwatch::primitives::DateTime::from_millis(
141 end_time.timestamp_millis(),
142 ))
143 .send()
144 .await?;
145
146 let mut data: Vec<(i64, f64)> = resp
147 .datapoints()
148 .iter()
149 .filter_map(|dp| {
150 let timestamp = dp.timestamp()?.as_secs_f64() as i64;
151 let value = match statistic {
152 "Average" => dp.average(),
153 "Sum" => dp.sum(),
154 "Minimum" => dp.minimum(),
155 "Maximum" => dp.maximum(),
156 "SampleCount" => dp.sample_count(),
157 _ => dp.average(),
158 }?;
159 Some((timestamp, value))
160 })
161 .collect();
162
163 data.sort_by_key(|(ts, _)| *ts);
164 Ok(data)
165 }
166}