1use crate::config::AwsConfig;
2use anyhow::Result;
3
4pub use aws_sdk_cloudwatch::types::Statistic;
5
6#[derive(Clone, Debug)]
7pub struct LambdaFunction {
8 pub name: String,
9 pub arn: String,
10 pub application: Option<String>,
11 pub description: String,
12 pub package_type: String,
13 pub runtime: String,
14 pub architecture: String,
15 pub code_size: i64,
16 pub code_sha256: String,
17 pub memory_mb: i32,
18 pub timeout_seconds: i32,
19 pub last_modified: String,
20 pub layers: Vec<LambdaLayer>,
21}
22
23#[derive(Clone, Debug)]
24pub struct LambdaLayer {
25 pub arn: String,
26 pub code_size: i64,
27}
28
29#[derive(Clone, Debug)]
30pub struct LambdaVersion {
31 pub version: String,
32 pub aliases: String,
33 pub description: String,
34 pub last_modified: String,
35 pub architecture: String,
36}
37
38#[derive(Clone, Debug)]
39pub struct LambdaAlias {
40 pub name: String,
41 pub versions: String,
42 pub description: String,
43}
44
45pub struct LambdaClient {
46 config: AwsConfig,
47}
48
49impl LambdaClient {
50 pub fn new(config: AwsConfig) -> Self {
51 Self { config }
52 }
53
54 pub async fn list_functions(&self) -> Result<Vec<LambdaFunction>> {
55 let client = self.config.lambda_client().await;
56
57 let mut functions = Vec::new();
58 let mut next_marker: Option<String> = None;
59
60 loop {
61 let mut request = client.list_functions().max_items(100);
62 if let Some(marker) = next_marker {
63 request = request.marker(marker);
64 }
65
66 let response = request.send().await?;
67
68 if let Some(funcs) = response.functions {
69 for func in funcs {
70 let last_modified = func
72 .last_modified
73 .as_deref()
74 .map(|s| {
75 if let Ok(dt) =
77 chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%z")
78 {
79 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
80 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
81 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
82 } else {
83 s.to_string()
84 }
85 })
86 .unwrap_or_default();
87
88 let function_name = func.function_name.unwrap_or_default();
89
90 let application = function_name
93 .rsplit_once('-')
94 .map(|(prefix, _)| prefix.to_string());
95
96 let layers = func
97 .layers
98 .unwrap_or_default()
99 .into_iter()
100 .map(|layer| LambdaLayer {
101 arn: layer.arn.unwrap_or_default(),
102 code_size: layer.code_size,
103 })
104 .collect();
105
106 functions.push(LambdaFunction {
107 name: function_name,
108 arn: func.function_arn.unwrap_or_default(),
109 application,
110 description: func.description.unwrap_or_default(),
111 package_type: func
112 .package_type
113 .map(|p| format!("{:?}", p))
114 .unwrap_or_default(),
115 runtime: func.runtime.map(|r| format!("{:?}", r)).unwrap_or_default(),
116 architecture: func
117 .architectures
118 .and_then(|a| a.first().map(|arch| format!("{:?}", arch)))
119 .unwrap_or_default(),
120 code_size: func.code_size,
121 code_sha256: func.code_sha256.unwrap_or_default(),
122 memory_mb: func.memory_size.unwrap_or(0),
123 timeout_seconds: func.timeout.unwrap_or(0),
124 last_modified,
125 layers,
126 });
127 }
128 }
129
130 next_marker = response.next_marker;
131 if next_marker.is_none() {
132 break;
133 }
134 }
135
136 Ok(functions)
137 }
138
139 pub async fn list_applications(&self) -> Result<Vec<LambdaApplication>> {
140 let client = self.config.cloudformation_client().await;
141
142 let mut applications = Vec::new();
143 let mut next_token: Option<String> = None;
144
145 loop {
146 let mut request = client.list_stacks();
147 if let Some(token) = next_token {
148 request = request.next_token(token);
149 }
150
151 let response = request.send().await?;
152
153 if let Some(stacks) = response.stack_summaries {
154 for stack in stacks {
155 let status = stack
157 .stack_status
158 .map(|s| format!("{:?}", s))
159 .unwrap_or_default();
160 if status.contains("DELETE") {
161 continue;
162 }
163
164 applications.push(LambdaApplication {
165 name: stack.stack_name.unwrap_or_default(),
166 arn: stack.stack_id.unwrap_or_default(),
167 description: stack.template_description.unwrap_or_default(),
168 status,
169 last_modified: stack
170 .last_updated_time
171 .or(stack.creation_time)
172 .map(|dt| {
173 let timestamp = dt.secs();
174 let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
175 .unwrap_or_default();
176 datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
177 })
178 .unwrap_or_default(),
179 });
180 }
181 }
182
183 next_token = response.next_token;
184 if next_token.is_none() {
185 break;
186 }
187 }
188
189 Ok(applications)
190 }
191
192 pub async fn list_versions(&self, function_name: &str) -> Result<Vec<LambdaVersion>> {
193 let client = self.config.lambda_client().await;
194
195 let mut versions = Vec::new();
196 let mut next_marker: Option<String> = None;
197
198 loop {
199 let mut request = client
200 .list_versions_by_function()
201 .function_name(function_name)
202 .max_items(100);
203 if let Some(marker) = next_marker {
204 request = request.marker(marker);
205 }
206
207 let response = request.send().await?;
208
209 if let Some(vers) = response.versions {
210 for ver in vers {
211 let last_modified = ver
212 .last_modified
213 .as_deref()
214 .map(|s| {
215 if let Ok(dt) =
216 chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%z")
217 {
218 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
219 } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
220 dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
221 } else {
222 s.to_string()
223 }
224 })
225 .unwrap_or_default();
226
227 versions.push(LambdaVersion {
228 version: ver.version.unwrap_or_default(),
229 aliases: String::new(), description: ver.description.unwrap_or_default(),
231 last_modified,
232 architecture: ver
233 .architectures
234 .and_then(|a| a.first().map(|arch| format!("{:?}", arch)))
235 .unwrap_or_default(),
236 });
237 }
238 }
239
240 next_marker = response.next_marker;
241 if next_marker.is_none() {
242 break;
243 }
244 }
245
246 let aliases_response = client
248 .list_aliases()
249 .function_name(function_name)
250 .send()
251 .await?;
252
253 if let Some(aliases) = aliases_response.aliases {
254 for alias in aliases {
255 let alias_name = alias.name.unwrap_or_default();
256
257 if let Some(version) = alias.function_version {
259 if let Some(ver) = versions.iter_mut().find(|v| v.version == version) {
260 if !ver.aliases.is_empty() {
261 ver.aliases.push_str(", ");
262 }
263 ver.aliases.push_str(&alias_name);
264 }
265 }
266
267 if let Some(routing_config) = alias.routing_config {
269 if let Some(additional_version_weights) =
270 routing_config.additional_version_weights
271 {
272 for (version, _weight) in additional_version_weights {
273 if let Some(ver) = versions.iter_mut().find(|v| v.version == version) {
274 if !ver.aliases.is_empty() {
275 ver.aliases.push_str(", ");
276 }
277 ver.aliases.push_str(&alias_name);
278 }
279 }
280 }
281 }
282 }
283 }
284
285 Ok(versions)
286 }
287}
288
289#[derive(Clone, Debug)]
290pub struct LambdaApplication {
291 pub name: String,
292 pub arn: String,
293 pub description: String,
294 pub status: String,
295 pub last_modified: String,
296}
297
298impl LambdaClient {
299 pub async fn list_aliases(&self, function_name: &str) -> Result<Vec<LambdaAlias>> {
300 let client = self.config.lambda_client().await;
301 let response = client
302 .list_aliases()
303 .function_name(function_name)
304 .send()
305 .await?;
306
307 let mut aliases = Vec::new();
308 if let Some(alias_list) = response.aliases {
309 for alias in alias_list {
310 let primary_version = alias.function_version.unwrap_or_default();
311
312 let mut versions_str = primary_version.clone();
314 if let Some(routing_config) = alias.routing_config {
315 if let Some(additional_version_weights) =
316 routing_config.additional_version_weights
317 {
318 for (version, weight) in additional_version_weights {
319 versions_str.push_str(&format!(
320 ", {} ({}%)",
321 version,
322 (weight * 100.0) as i32
323 ));
324 }
325 }
326 }
327
328 aliases.push(LambdaAlias {
329 name: alias.name.unwrap_or_default(),
330 versions: versions_str,
331 description: alias.description.unwrap_or_default(),
332 });
333 }
334 }
335
336 Ok(aliases)
337 }
338
339 pub async fn get_invocations_metric(
340 &self,
341 function_name: &str,
342 resource: Option<&str>,
343 ) -> Result<Vec<(i64, f64)>> {
344 let cw_client = self.config.cloudwatch_client().await;
345
346 let end_time = aws_smithy_types::DateTime::from_secs(
347 std::time::SystemTime::now()
348 .duration_since(std::time::UNIX_EPOCH)?
349 .as_secs() as i64,
350 );
351 let start_time = aws_smithy_types::DateTime::from_secs(
352 std::time::SystemTime::now()
353 .duration_since(std::time::UNIX_EPOCH)?
354 .as_secs() as i64
355 - 3 * 3600,
356 );
357
358 let mut dimensions = vec![aws_sdk_cloudwatch::types::Dimension::builder()
359 .name("FunctionName")
360 .value(function_name)
361 .build()];
362
363 if let Some(res) = resource {
364 dimensions.push(
365 aws_sdk_cloudwatch::types::Dimension::builder()
366 .name("Resource")
367 .value(res)
368 .build(),
369 );
370 }
371
372 let mut request = cw_client
373 .get_metric_statistics()
374 .namespace("AWS/Lambda")
375 .metric_name("Invocations")
376 .start_time(start_time)
377 .end_time(end_time)
378 .period(60)
379 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum);
380
381 for dim in dimensions {
382 request = request.dimensions(dim);
383 }
384
385 let response = request.send().await?;
386
387 let mut data = Vec::new();
388 if let Some(datapoints) = response.datapoints {
389 for dp in datapoints {
390 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
391 data.push((timestamp.secs(), value));
392 }
393 }
394 }
395
396 data.sort_by_key(|&(timestamp, _)| timestamp);
397 Ok(data)
398 }
399
400 pub async fn get_duration_metric(
401 &self,
402 function_name: &str,
403 stat: aws_sdk_cloudwatch::types::Statistic,
404 ) -> Result<Vec<(i64, f64)>> {
405 let cw_client = self.config.cloudwatch_client().await;
406
407 let end_time = aws_smithy_types::DateTime::from_secs(
408 std::time::SystemTime::now()
409 .duration_since(std::time::UNIX_EPOCH)?
410 .as_secs() as i64,
411 );
412 let start_time = aws_smithy_types::DateTime::from_secs(
413 std::time::SystemTime::now()
414 .duration_since(std::time::UNIX_EPOCH)?
415 .as_secs() as i64
416 - 3 * 3600,
417 );
418
419 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
420 .name("FunctionName")
421 .value(function_name)
422 .build();
423
424 let response = cw_client
425 .get_metric_statistics()
426 .namespace("AWS/Lambda")
427 .metric_name("Duration")
428 .dimensions(dimension)
429 .start_time(start_time)
430 .end_time(end_time)
431 .period(60)
432 .statistics(stat.clone())
433 .send()
434 .await?;
435
436 let mut data = Vec::new();
437 if let Some(datapoints) = response.datapoints {
438 for dp in datapoints {
439 let value = match stat {
440 aws_sdk_cloudwatch::types::Statistic::Minimum => dp.minimum,
441 aws_sdk_cloudwatch::types::Statistic::Average => dp.average,
442 aws_sdk_cloudwatch::types::Statistic::Maximum => dp.maximum,
443 _ => None,
444 };
445 if let (Some(timestamp), Some(value)) = (dp.timestamp, value) {
446 data.push((timestamp.secs(), value));
447 }
448 }
449 }
450
451 data.sort_by_key(|&(timestamp, _)| timestamp);
452 Ok(data)
453 }
454
455 pub async fn get_errors_metric(&self, function_name: &str) -> Result<Vec<(i64, f64)>> {
456 let cw_client = self.config.cloudwatch_client().await;
457
458 let end_time = aws_smithy_types::DateTime::from_secs(
459 std::time::SystemTime::now()
460 .duration_since(std::time::UNIX_EPOCH)?
461 .as_secs() as i64,
462 );
463 let start_time = aws_smithy_types::DateTime::from_secs(
464 std::time::SystemTime::now()
465 .duration_since(std::time::UNIX_EPOCH)?
466 .as_secs() as i64
467 - 3 * 3600,
468 );
469
470 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
471 .name("FunctionName")
472 .value(function_name)
473 .build();
474
475 let response = cw_client
476 .get_metric_statistics()
477 .namespace("AWS/Lambda")
478 .metric_name("Errors")
479 .dimensions(dimension)
480 .start_time(start_time)
481 .end_time(end_time)
482 .period(60)
483 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
484 .send()
485 .await?;
486
487 let mut data = Vec::new();
488 if let Some(datapoints) = response.datapoints {
489 for dp in datapoints {
490 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
491 data.push((timestamp.secs(), value));
492 }
493 }
494 }
495
496 data.sort_by_key(|&(timestamp, _)| timestamp);
497 Ok(data)
498 }
499
500 pub async fn get_throttles_metric(&self, function_name: &str) -> Result<Vec<(i64, f64)>> {
501 let cw_client = self.config.cloudwatch_client().await;
502
503 let end_time = aws_smithy_types::DateTime::from_secs(
504 std::time::SystemTime::now()
505 .duration_since(std::time::UNIX_EPOCH)?
506 .as_secs() as i64,
507 );
508 let start_time = aws_smithy_types::DateTime::from_secs(
509 std::time::SystemTime::now()
510 .duration_since(std::time::UNIX_EPOCH)?
511 .as_secs() as i64
512 - 3 * 3600,
513 );
514
515 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
516 .name("FunctionName")
517 .value(function_name)
518 .build();
519
520 let response = cw_client
521 .get_metric_statistics()
522 .namespace("AWS/Lambda")
523 .metric_name("Throttles")
524 .dimensions(dimension)
525 .start_time(start_time)
526 .end_time(end_time)
527 .period(60)
528 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
529 .send()
530 .await?;
531
532 let mut data = Vec::new();
533 if let Some(datapoints) = response.datapoints {
534 for dp in datapoints {
535 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
536 data.push((timestamp.secs(), value));
537 }
538 }
539 }
540
541 data.sort_by_key(|&(timestamp, _)| timestamp);
542 Ok(data)
543 }
544
545 pub async fn get_concurrent_executions_metric(
546 &self,
547 function_name: &str,
548 ) -> Result<Vec<(i64, f64)>> {
549 let cw_client = self.config.cloudwatch_client().await;
550
551 let end_time = aws_smithy_types::DateTime::from_secs(
552 std::time::SystemTime::now()
553 .duration_since(std::time::UNIX_EPOCH)?
554 .as_secs() as i64,
555 );
556 let start_time = aws_smithy_types::DateTime::from_secs(
557 std::time::SystemTime::now()
558 .duration_since(std::time::UNIX_EPOCH)?
559 .as_secs() as i64
560 - 3 * 3600,
561 );
562
563 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
564 .name("FunctionName")
565 .value(function_name)
566 .build();
567
568 let response = cw_client
569 .get_metric_statistics()
570 .namespace("AWS/Lambda")
571 .metric_name("ConcurrentExecutions")
572 .dimensions(dimension)
573 .start_time(start_time)
574 .end_time(end_time)
575 .period(60)
576 .statistics(aws_sdk_cloudwatch::types::Statistic::Maximum)
577 .send()
578 .await?;
579
580 let mut data = Vec::new();
581 if let Some(datapoints) = response.datapoints {
582 for dp in datapoints {
583 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.maximum) {
584 data.push((timestamp.secs(), value));
585 }
586 }
587 }
588
589 data.sort_by_key(|&(timestamp, _)| timestamp);
590 Ok(data)
591 }
592
593 pub async fn get_recursive_invocations_dropped_metric(
594 &self,
595 function_name: &str,
596 ) -> Result<Vec<(i64, f64)>> {
597 let cw_client = self.config.cloudwatch_client().await;
598
599 let end_time = aws_smithy_types::DateTime::from_secs(
600 std::time::SystemTime::now()
601 .duration_since(std::time::UNIX_EPOCH)?
602 .as_secs() as i64,
603 );
604 let start_time = aws_smithy_types::DateTime::from_secs(
605 std::time::SystemTime::now()
606 .duration_since(std::time::UNIX_EPOCH)?
607 .as_secs() as i64
608 - 3 * 3600,
609 );
610
611 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
612 .name("FunctionName")
613 .value(function_name)
614 .build();
615
616 let response = cw_client
617 .get_metric_statistics()
618 .namespace("AWS/Lambda")
619 .metric_name("RecursiveInvocationsDropped")
620 .dimensions(dimension)
621 .start_time(start_time)
622 .end_time(end_time)
623 .period(60)
624 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
625 .send()
626 .await?;
627
628 let mut data = Vec::new();
629 if let Some(datapoints) = response.datapoints {
630 for dp in datapoints {
631 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
632 data.push((timestamp.secs(), value));
633 }
634 }
635 }
636
637 data.sort_by_key(|&(timestamp, _)| timestamp);
638 Ok(data)
639 }
640
641 pub async fn get_async_event_age_metric(
642 &self,
643 function_name: &str,
644 stat: aws_sdk_cloudwatch::types::Statistic,
645 ) -> Result<Vec<(i64, f64)>> {
646 let cw_client = self.config.cloudwatch_client().await;
647
648 let end_time = aws_smithy_types::DateTime::from_secs(
649 std::time::SystemTime::now()
650 .duration_since(std::time::UNIX_EPOCH)?
651 .as_secs() as i64,
652 );
653 let start_time = aws_smithy_types::DateTime::from_secs(
654 std::time::SystemTime::now()
655 .duration_since(std::time::UNIX_EPOCH)?
656 .as_secs() as i64
657 - 3 * 3600,
658 );
659
660 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
661 .name("FunctionName")
662 .value(function_name)
663 .build();
664
665 let response = cw_client
666 .get_metric_statistics()
667 .namespace("AWS/Lambda")
668 .metric_name("AsyncEventAge")
669 .dimensions(dimension)
670 .start_time(start_time)
671 .end_time(end_time)
672 .period(60)
673 .statistics(stat.clone())
674 .send()
675 .await?;
676
677 let mut data = Vec::new();
678 if let Some(datapoints) = response.datapoints {
679 for dp in datapoints {
680 let value = match stat {
681 aws_sdk_cloudwatch::types::Statistic::Minimum => dp.minimum,
682 aws_sdk_cloudwatch::types::Statistic::Average => dp.average,
683 aws_sdk_cloudwatch::types::Statistic::Maximum => dp.maximum,
684 _ => None,
685 };
686 if let (Some(timestamp), Some(value)) = (dp.timestamp, value) {
687 data.push((timestamp.secs(), value));
688 }
689 }
690 }
691
692 data.sort_by_key(|&(timestamp, _)| timestamp);
693 Ok(data)
694 }
695
696 pub async fn get_async_events_received_metric(
697 &self,
698 function_name: &str,
699 ) -> Result<Vec<(i64, f64)>> {
700 let cw_client = self.config.cloudwatch_client().await;
701
702 let end_time = aws_smithy_types::DateTime::from_secs(
703 std::time::SystemTime::now()
704 .duration_since(std::time::UNIX_EPOCH)?
705 .as_secs() as i64,
706 );
707 let start_time = aws_smithy_types::DateTime::from_secs(
708 std::time::SystemTime::now()
709 .duration_since(std::time::UNIX_EPOCH)?
710 .as_secs() as i64
711 - 3 * 3600,
712 );
713
714 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
715 .name("FunctionName")
716 .value(function_name)
717 .build();
718
719 let response = cw_client
720 .get_metric_statistics()
721 .namespace("AWS/Lambda")
722 .metric_name("AsyncEventsReceived")
723 .dimensions(dimension)
724 .start_time(start_time)
725 .end_time(end_time)
726 .period(60)
727 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
728 .send()
729 .await?;
730
731 let mut data = Vec::new();
732 if let Some(datapoints) = response.datapoints {
733 for dp in datapoints {
734 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
735 data.push((timestamp.secs(), value));
736 }
737 }
738 }
739
740 data.sort_by_key(|&(timestamp, _)| timestamp);
741 Ok(data)
742 }
743
744 pub async fn get_async_events_dropped_metric(
745 &self,
746 function_name: &str,
747 ) -> Result<Vec<(i64, f64)>> {
748 let cw_client = self.config.cloudwatch_client().await;
749
750 let end_time = aws_smithy_types::DateTime::from_secs(
751 std::time::SystemTime::now()
752 .duration_since(std::time::UNIX_EPOCH)?
753 .as_secs() as i64,
754 );
755 let start_time = aws_smithy_types::DateTime::from_secs(
756 std::time::SystemTime::now()
757 .duration_since(std::time::UNIX_EPOCH)?
758 .as_secs() as i64
759 - 3 * 3600,
760 );
761
762 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
763 .name("FunctionName")
764 .value(function_name)
765 .build();
766
767 let response = cw_client
768 .get_metric_statistics()
769 .namespace("AWS/Lambda")
770 .metric_name("AsyncEventsDropped")
771 .dimensions(dimension)
772 .start_time(start_time)
773 .end_time(end_time)
774 .period(60)
775 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
776 .send()
777 .await?;
778
779 let mut data = Vec::new();
780 if let Some(datapoints) = response.datapoints {
781 for dp in datapoints {
782 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
783 data.push((timestamp.secs(), value));
784 }
785 }
786 }
787
788 data.sort_by_key(|&(timestamp, _)| timestamp);
789 Ok(data)
790 }
791
792 pub async fn get_destination_delivery_failures_metric(
793 &self,
794 function_name: &str,
795 ) -> Result<Vec<(i64, f64)>> {
796 let cw_client = self.config.cloudwatch_client().await;
797
798 let end_time = aws_smithy_types::DateTime::from_secs(
799 std::time::SystemTime::now()
800 .duration_since(std::time::UNIX_EPOCH)?
801 .as_secs() as i64,
802 );
803 let start_time = aws_smithy_types::DateTime::from_secs(
804 std::time::SystemTime::now()
805 .duration_since(std::time::UNIX_EPOCH)?
806 .as_secs() as i64
807 - 3 * 3600,
808 );
809
810 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
811 .name("FunctionName")
812 .value(function_name)
813 .build();
814
815 let response = cw_client
816 .get_metric_statistics()
817 .namespace("AWS/Lambda")
818 .metric_name("DestinationDeliveryFailures")
819 .dimensions(dimension)
820 .start_time(start_time)
821 .end_time(end_time)
822 .period(60)
823 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
824 .send()
825 .await?;
826
827 let mut data = Vec::new();
828 if let Some(datapoints) = response.datapoints {
829 for dp in datapoints {
830 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
831 data.push((timestamp.secs(), value));
832 }
833 }
834 }
835
836 data.sort_by_key(|&(timestamp, _)| timestamp);
837 Ok(data)
838 }
839
840 pub async fn get_dead_letter_errors_metric(
841 &self,
842 function_name: &str,
843 ) -> Result<Vec<(i64, f64)>> {
844 let cw_client = self.config.cloudwatch_client().await;
845
846 let end_time = aws_smithy_types::DateTime::from_secs(
847 std::time::SystemTime::now()
848 .duration_since(std::time::UNIX_EPOCH)?
849 .as_secs() as i64,
850 );
851 let start_time = aws_smithy_types::DateTime::from_secs(
852 std::time::SystemTime::now()
853 .duration_since(std::time::UNIX_EPOCH)?
854 .as_secs() as i64
855 - 3 * 3600,
856 );
857
858 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
859 .name("FunctionName")
860 .value(function_name)
861 .build();
862
863 let response = cw_client
864 .get_metric_statistics()
865 .namespace("AWS/Lambda")
866 .metric_name("DeadLetterErrors")
867 .dimensions(dimension)
868 .start_time(start_time)
869 .end_time(end_time)
870 .period(60)
871 .statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
872 .send()
873 .await?;
874
875 let mut data = Vec::new();
876 if let Some(datapoints) = response.datapoints {
877 for dp in datapoints {
878 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
879 data.push((timestamp.secs(), value));
880 }
881 }
882 }
883
884 data.sort_by_key(|&(timestamp, _)| timestamp);
885 Ok(data)
886 }
887
888 pub async fn get_iterator_age_metric(&self, function_name: &str) -> Result<Vec<(i64, f64)>> {
889 let cw_client = self.config.cloudwatch_client().await;
890
891 let end_time = aws_smithy_types::DateTime::from_secs(
892 std::time::SystemTime::now()
893 .duration_since(std::time::UNIX_EPOCH)?
894 .as_secs() as i64,
895 );
896 let start_time = aws_smithy_types::DateTime::from_secs(
897 std::time::SystemTime::now()
898 .duration_since(std::time::UNIX_EPOCH)?
899 .as_secs() as i64
900 - 3 * 3600,
901 );
902
903 let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
904 .name("FunctionName")
905 .value(function_name)
906 .build();
907
908 let response = cw_client
909 .get_metric_statistics()
910 .namespace("AWS/Lambda")
911 .metric_name("IteratorAge")
912 .dimensions(dimension)
913 .start_time(start_time)
914 .end_time(end_time)
915 .period(60)
916 .statistics(aws_sdk_cloudwatch::types::Statistic::Maximum)
917 .send()
918 .await?;
919
920 let mut data = Vec::new();
921 if let Some(datapoints) = response.datapoints {
922 for dp in datapoints {
923 if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.maximum) {
924 data.push((timestamp.secs(), value));
925 }
926 }
927 }
928
929 data.sort_by_key(|&(timestamp, _)| timestamp);
930 Ok(data)
931 }
932}
933
934#[cfg(test)]
935mod tests {
936 #[test]
937 fn test_application_extraction_from_function_name() {
938 let name = "storefront-studio-beta-api";
940 let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
941 assert_eq!(application, "storefront-studio-beta");
942
943 let name = "myapp-api";
945 let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
946 assert_eq!(application, "myapp");
947
948 let name = "simplefunction";
950 let application = name.rsplit_once('-').map(|(prefix, _)| prefix);
951 assert_eq!(application, None);
952
953 let name = "my-complex-app-name-worker";
955 let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
956 assert_eq!(application, "my-complex-app-name");
957 }
958
959 #[test]
960 fn test_invocations_metric_data_structure() {
961 let data: Vec<(i64, f64)> =
963 vec![(1700000000, 10.0), (1700000060, 15.0), (1700000120, 20.0)];
964 assert_eq!(data.len(), 3);
965 assert_eq!(data[0].0, 1700000000);
966 assert_eq!(data[0].1, 10.0);
967 }
968
969 #[test]
970 fn test_invocations_metric_sorting() {
971 let mut data: Vec<(i64, f64)> =
973 vec![(1700000120, 20.0), (1700000000, 10.0), (1700000060, 15.0)];
974 data.sort_by_key(|&(timestamp, _)| timestamp);
975 assert_eq!(data[0].0, 1700000000);
976 assert_eq!(data[1].0, 1700000060);
977 assert_eq!(data[2].0, 1700000120);
978 }
979}