use crate::config::AwsConfig;
use anyhow::Result;
pub use aws_sdk_cloudwatch::types::Statistic;
#[derive(Clone, Debug)]
pub struct LambdaFunction {
pub name: String,
pub arn: String,
pub application: Option<String>,
pub description: String,
pub package_type: String,
pub runtime: String,
pub architecture: String,
pub code_size: i64,
pub code_sha256: String,
pub memory_mb: i32,
pub timeout_seconds: i32,
pub last_modified: String,
pub layers: Vec<LambdaLayer>,
}
#[derive(Clone, Debug)]
pub struct LambdaLayer {
pub arn: String,
pub code_size: i64,
}
#[derive(Clone, Debug)]
pub struct LambdaVersion {
pub version: String,
pub aliases: String,
pub description: String,
pub last_modified: String,
pub architecture: String,
}
#[derive(Clone, Debug)]
pub struct LambdaAlias {
pub name: String,
pub versions: String,
pub description: String,
}
pub struct LambdaClient {
config: AwsConfig,
}
impl LambdaClient {
pub fn new(config: AwsConfig) -> Self {
Self { config }
}
pub async fn list_functions(&self) -> Result<Vec<LambdaFunction>> {
let client = self.config.lambda_client();
let mut functions = Vec::new();
let mut next_marker: Option<String> = None;
loop {
let mut request = client.list_functions().max_items(100);
if let Some(marker) = next_marker {
request = request.marker(marker);
}
let response = request.send().await?;
if let Some(funcs) = response.functions {
for func in funcs {
let last_modified = func
.last_modified
.as_deref()
.map(|s| {
if let Ok(dt) =
chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%z")
{
dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
} else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
} else {
s.to_string()
}
})
.unwrap_or_default();
let function_name = func.function_name.unwrap_or_default();
let application = function_name
.rsplit_once('-')
.map(|(prefix, _)| prefix.to_string());
let layers = func
.layers
.unwrap_or_default()
.into_iter()
.map(|layer| LambdaLayer {
arn: layer.arn.unwrap_or_default(),
code_size: layer.code_size,
})
.collect();
functions.push(LambdaFunction {
name: function_name,
arn: func.function_arn.unwrap_or_default(),
application,
description: func.description.unwrap_or_default(),
package_type: func
.package_type
.map(|p| format!("{:?}", p))
.unwrap_or_default(),
runtime: func.runtime.map(|r| format!("{:?}", r)).unwrap_or_default(),
architecture: func
.architectures
.and_then(|a| a.first().map(|arch| format!("{:?}", arch)))
.unwrap_or_default(),
code_size: func.code_size,
code_sha256: func.code_sha256.unwrap_or_default(),
memory_mb: func.memory_size.unwrap_or(0),
timeout_seconds: func.timeout.unwrap_or(0),
last_modified,
layers,
});
}
}
next_marker = response.next_marker;
if next_marker.is_none() {
break;
}
}
Ok(functions)
}
pub async fn list_applications(&self) -> Result<Vec<LambdaApplication>> {
let client = self.config.cloudformation_client();
let mut applications = Vec::new();
let mut next_token: Option<String> = None;
loop {
let mut request = client.list_stacks();
if let Some(token) = next_token {
request = request.next_token(token);
}
let response = request.send().await?;
if let Some(stacks) = response.stack_summaries {
for stack in stacks {
let status = stack
.stack_status
.map(|s| format!("{:?}", s))
.unwrap_or_default();
if status.contains("DELETE") {
continue;
}
applications.push(LambdaApplication {
name: stack.stack_name.unwrap_or_default(),
arn: stack.stack_id.unwrap_or_default(),
description: stack.template_description.unwrap_or_default(),
status,
last_modified: stack
.last_updated_time
.or(stack.creation_time)
.map(|dt| {
let timestamp = dt.secs();
let datetime = chrono::DateTime::from_timestamp(timestamp, 0)
.unwrap_or_default();
datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
})
.unwrap_or_default(),
});
}
}
next_token = response.next_token;
if next_token.is_none() {
break;
}
}
Ok(applications)
}
pub async fn list_versions(&self, function_name: &str) -> Result<Vec<LambdaVersion>> {
let client = self.config.lambda_client();
let mut versions = Vec::new();
let mut next_marker: Option<String> = None;
loop {
let mut request = client
.list_versions_by_function()
.function_name(function_name)
.max_items(100);
if let Some(marker) = next_marker {
request = request.marker(marker);
}
let response = request.send().await?;
if let Some(vers) = response.versions {
for ver in vers {
let last_modified = ver
.last_modified
.as_deref()
.map(|s| {
if let Ok(dt) =
chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.3f%z")
{
dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
} else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
dt.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
} else {
s.to_string()
}
})
.unwrap_or_default();
versions.push(LambdaVersion {
version: ver.version.unwrap_or_default(),
aliases: String::new(), description: ver.description.unwrap_or_default(),
last_modified,
architecture: ver
.architectures
.and_then(|a| a.first().map(|arch| format!("{:?}", arch)))
.unwrap_or_default(),
});
}
}
next_marker = response.next_marker;
if next_marker.is_none() {
break;
}
}
let aliases_response = client
.list_aliases()
.function_name(function_name)
.send()
.await?;
if let Some(aliases) = aliases_response.aliases {
for alias in aliases {
let alias_name = alias.name.unwrap_or_default();
if let Some(version) = alias.function_version {
if let Some(ver) = versions.iter_mut().find(|v| v.version == version) {
if !ver.aliases.is_empty() {
ver.aliases.push_str(", ");
}
ver.aliases.push_str(&alias_name);
}
}
if let Some(routing_config) = alias.routing_config {
if let Some(additional_version_weights) =
routing_config.additional_version_weights
{
for (version, _weight) in additional_version_weights {
if let Some(ver) = versions.iter_mut().find(|v| v.version == version) {
if !ver.aliases.is_empty() {
ver.aliases.push_str(", ");
}
ver.aliases.push_str(&alias_name);
}
}
}
}
}
}
Ok(versions)
}
}
#[derive(Clone, Debug)]
pub struct LambdaApplication {
pub name: String,
pub arn: String,
pub description: String,
pub status: String,
pub last_modified: String,
}
impl LambdaClient {
pub async fn list_aliases(&self, function_name: &str) -> Result<Vec<LambdaAlias>> {
let client = self.config.lambda_client();
let response = client
.list_aliases()
.function_name(function_name)
.send()
.await?;
let mut aliases = Vec::new();
if let Some(alias_list) = response.aliases {
for alias in alias_list {
let primary_version = alias.function_version.unwrap_or_default();
let mut versions_str = primary_version.clone();
if let Some(routing_config) = alias.routing_config {
if let Some(additional_version_weights) =
routing_config.additional_version_weights
{
for (version, weight) in additional_version_weights {
versions_str.push_str(&format!(
", {} ({}%)",
version,
(weight * 100.0) as i32
));
}
}
}
aliases.push(LambdaAlias {
name: alias.name.unwrap_or_default(),
versions: versions_str,
description: alias.description.unwrap_or_default(),
});
}
}
Ok(aliases)
}
pub async fn get_invocations_metric(
&self,
function_name: &str,
resource: Option<&str>,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let mut dimensions = vec![aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build()];
if let Some(res) = resource {
dimensions.push(
aws_sdk_cloudwatch::types::Dimension::builder()
.name("Resource")
.value(res)
.build(),
);
}
let mut request = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("Invocations")
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum);
for dim in dimensions {
request = request.dimensions(dim);
}
let response = request.send().await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_duration_metric(
&self,
function_name: &str,
stat: aws_sdk_cloudwatch::types::Statistic,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("Duration")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(stat.clone())
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
let value = match stat {
aws_sdk_cloudwatch::types::Statistic::Minimum => dp.minimum,
aws_sdk_cloudwatch::types::Statistic::Average => dp.average,
aws_sdk_cloudwatch::types::Statistic::Maximum => dp.maximum,
_ => None,
};
if let (Some(timestamp), Some(value)) = (dp.timestamp, value) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_errors_metric(&self, function_name: &str) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("Errors")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_throttles_metric(&self, function_name: &str) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("Throttles")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_concurrent_executions_metric(
&self,
function_name: &str,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("ConcurrentExecutions")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Maximum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.maximum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_recursive_invocations_dropped_metric(
&self,
function_name: &str,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("RecursiveInvocationsDropped")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_async_event_age_metric(
&self,
function_name: &str,
stat: aws_sdk_cloudwatch::types::Statistic,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("AsyncEventAge")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(stat.clone())
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
let value = match stat {
aws_sdk_cloudwatch::types::Statistic::Minimum => dp.minimum,
aws_sdk_cloudwatch::types::Statistic::Average => dp.average,
aws_sdk_cloudwatch::types::Statistic::Maximum => dp.maximum,
_ => None,
};
if let (Some(timestamp), Some(value)) = (dp.timestamp, value) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_async_events_received_metric(
&self,
function_name: &str,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("AsyncEventsReceived")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_async_events_dropped_metric(
&self,
function_name: &str,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("AsyncEventsDropped")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_destination_delivery_failures_metric(
&self,
function_name: &str,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("DestinationDeliveryFailures")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_dead_letter_errors_metric(
&self,
function_name: &str,
) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("DeadLetterErrors")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Sum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.sum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
pub async fn get_iterator_age_metric(&self, function_name: &str) -> Result<Vec<(i64, f64)>> {
let cw_client = self.config.cloudwatch_client();
let end_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64,
);
let start_time = aws_smithy_types::DateTime::from_secs(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64
- 3 * 3600,
);
let dimension = aws_sdk_cloudwatch::types::Dimension::builder()
.name("FunctionName")
.value(function_name)
.build();
let response = cw_client
.get_metric_statistics()
.namespace("AWS/Lambda")
.metric_name("IteratorAge")
.dimensions(dimension)
.start_time(start_time)
.end_time(end_time)
.period(60)
.statistics(aws_sdk_cloudwatch::types::Statistic::Maximum)
.send()
.await?;
let mut data = Vec::new();
if let Some(datapoints) = response.datapoints {
for dp in datapoints {
if let (Some(timestamp), Some(value)) = (dp.timestamp, dp.maximum) {
data.push((timestamp.secs(), value));
}
}
}
data.sort_by_key(|&(timestamp, _)| timestamp);
Ok(data)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_application_extraction_from_function_name() {
let name = "storefront-studio-beta-api";
let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
assert_eq!(application, "storefront-studio-beta");
let name = "myapp-api";
let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
assert_eq!(application, "myapp");
let name = "simplefunction";
let application = name.rsplit_once('-').map(|(prefix, _)| prefix);
assert_eq!(application, None);
let name = "my-complex-app-name-worker";
let application = name.rsplit_once('-').map(|(prefix, _)| prefix).unwrap();
assert_eq!(application, "my-complex-app-name");
}
#[test]
fn test_invocations_metric_data_structure() {
let data: Vec<(i64, f64)> =
vec![(1700000000, 10.0), (1700000060, 15.0), (1700000120, 20.0)];
assert_eq!(data.len(), 3);
assert_eq!(data[0].0, 1700000000);
assert_eq!(data[0].1, 10.0);
}
#[test]
fn test_invocations_metric_sorting() {
let mut data: Vec<(i64, f64)> =
vec![(1700000120, 20.0), (1700000000, 10.0), (1700000060, 15.0)];
data.sort_by_key(|&(timestamp, _)| timestamp);
assert_eq!(data[0].0, 1700000000);
assert_eq!(data[1].0, 1700000060);
assert_eq!(data[2].0, 1700000120);
}
}