Skip to content

Commit fbea486

Browse files
author
Ariel Ben-Yehuda
committed
feat: add parsing of CPU and Memory fields
1 parent babaa12 commit fbea486

File tree

5 files changed

+155
-23
lines changed

5 files changed

+155
-23
lines changed

Cargo.lock

Lines changed: 26 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ chrono = "0.4"
1717
futures = { version = "0.3", default-features = false, features = ["alloc"] }
1818
libloading = "0.8"
1919
reqwest = { version = "0.12", default-features = false, optional = true, features = ["charset", "http2"] }
20+
ordered-float = { version = "5", features=["serde"]}
2021
serde_json = "1"
2122
serde = { version = "1", features = ["derive"] }
2223
tempfile = "3"
@@ -40,13 +41,16 @@ humantime = "2"
4041
name = 'simple'
4142

4243
[features]
43-
default = ["s3", "aws-metadata"]
44+
default = ["s3", "aws-metadata", "__unstable-fargate-cpu-count"]
4445
s3 = ["s3-no-defaults", "aws-config/default", "aws-sdk-s3/default"]
4546
# A version of the s3 feature that does not enable AWS default features
4647
s3-no-defaults = ["dep:aws-config", "dep:aws-sdk-s3"]
4748
aws-metadata = ["aws-metadata-no-defaults", "aws-config/default", "reqwest/rustls-tls"]
4849
# A version of the aws-metadata feature that does not enable AWS default features
4950
aws-metadata-no-defaults = ["dep:reqwest", "dep:aws-config", "dep:aws-arn"]
51+
# *Unstable* feature to allow Fargate CPU count. The feature will be stabilized
52+
# and removed soon.
53+
__unstable-fargate-cpu-count = []
5054

5155
[package.metadata.docs.rs]
5256
all-features = true

src/metadata/aws.rs

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use reqwest::Method;
99
use serde::Deserialize;
1010
use thiserror::Error;
1111

12+
use crate::metadata::OrderedF64;
13+
1214
use super::AgentMetadata;
1315

1416
/// An error converting Fargate IMDS metadata to Agent metadata. This error
@@ -80,12 +82,25 @@ async fn read_ec2_metadata() -> Result<ImdsEc2InstanceMetadata, AwsProfilerMetad
8082
Ok(serde_json::from_str(imds_document.as_ref())?)
8183
}
8284

85+
#[derive(Deserialize, Debug, PartialEq, Eq)]
86+
struct FargateLimits {
87+
#[serde(rename = "CPU")]
88+
cpu: Option<OrderedF64>,
89+
#[serde(rename = "Memory")]
90+
memory: Option<u64>,
91+
}
92+
8393
#[derive(Deserialize, Debug, PartialEq, Eq)]
8494
struct FargateMetadata {
8595
#[serde(rename = "Cluster")]
8696
cluster: String,
8797
#[serde(rename = "TaskARN")]
8898
task_arn: String,
99+
// According to <https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4-fargate-response.html>
100+
// Limits: The resource limits specified at the task levels such as CPU (expressed in vCPUs) and memory.
101+
// This parameter is omitted if no resource limits are defined.
102+
#[serde(rename = "Limits")]
103+
limits: Option<FargateLimits>,
89104
}
90105

91106
async fn read_fargate_metadata(
@@ -144,6 +159,10 @@ impl super::AgentMetadata {
144159
.to_string(),
145160
ecs_task_arn: fargate_metadata.task_arn,
146161
ecs_cluster_arn: fargate_metadata.cluster,
162+
#[cfg(feature = "__unstable-fargate-cpu-count")]
163+
cpu_limit: fargate_metadata.limits.as_ref().and_then(|limits| limits.cpu),
164+
#[cfg(feature = "__unstable-fargate-cpu-count")]
165+
memory_limit: fargate_metadata.limits.as_ref().and_then(|limits| limits.memory),
147166
})
148167
}
149168
}
@@ -173,6 +192,7 @@ pub async fn load_agent_metadata() -> Result<AgentMetadata, AwsProfilerMetadataE
173192
#[cfg(test)]
174193
mod tests {
175194
use super::*;
195+
use test_case::test_case;
176196

177197
// these constants are "anonymized" (aka randomly generated in that format)
178198

@@ -237,10 +257,29 @@ mod tests {
237257
)
238258
}
239259

240-
#[test]
241-
fn test_fargate_metadata() {
242-
let json_str = r#"
243-
{
260+
#[test_case(
261+
r#"{
262+
"Cluster": "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster",
263+
"TaskARN": "arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f"
264+
}"#,
265+
None,
266+
None,
267+
None
268+
; "no_limits"
269+
)]
270+
#[test_case(
271+
r#"{
272+
"Cluster": "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster",
273+
"TaskARN": "arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f",
274+
"Limits": {}
275+
}"#,
276+
Some(FargateLimits { cpu: None, memory: None }),
277+
None,
278+
None
279+
; "empty_limits"
280+
)]
281+
#[test_case(
282+
r#"{
244283
"Cluster": "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster",
245284
"TaskARN": "arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f",
246285
"Family": "profiler-metadata",
@@ -316,17 +355,27 @@ mod tests {
316355
"Utilized": 208,
317356
"Reserved": 20496
318357
}
319-
}
320-
"#;
321-
322-
let fargate_metadata: FargateMetadata = serde_json::from_str(&json_str).unwrap();
358+
}"#,
359+
Some(FargateLimits { cpu: Some(0.25.into()), memory: Some(2048) }),
360+
Some(0.25.into()),
361+
Some(2048)
362+
; "with_limits"
363+
)]
364+
fn test_fargate_metadata(
365+
json_str: &str,
366+
expected_limits: Option<FargateLimits>,
367+
_expected_cpu_limit: Option<OrderedF64>,
368+
_expected_memory_limit: Option<u64>,
369+
) {
370+
let fargate_metadata: FargateMetadata = serde_json::from_str(json_str).unwrap();
323371

324372
assert_eq!(
325373
fargate_metadata,
326374
FargateMetadata {
327375
cluster: "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster"
328376
.to_owned(),
329377
task_arn: "arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f".to_owned(),
378+
limits: expected_limits,
330379
}
331380
);
332381

@@ -339,6 +388,10 @@ mod tests {
339388
aws_region_id: "us-east-1".to_owned(),
340389
ecs_task_arn: "arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f".to_owned(),
341390
ecs_cluster_arn: "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster".to_owned(),
391+
#[cfg(feature = "__unstable-fargate-cpu-count")]
392+
cpu_limit: _expected_cpu_limit,
393+
#[cfg(feature = "__unstable-fargate-cpu-count")]
394+
memory_limit: _expected_memory_limit,
342395
}
343396
)
344397
}

src/metadata/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,38 @@
33

44
//! This module includes ways to get metadata attached to profiling reports.
55
6+
use ordered_float::OrderedFloat;
67
pub use std::time::Duration;
78

9+
/// An ordered float. Individual type to avoid public API dependencies.
10+
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
11+
#[serde(transparent)]
12+
pub struct OrderedF64(OrderedFloat<f64>);
13+
14+
impl OrderedF64 {
15+
/// Create a new `OrderedF64`
16+
///
17+
/// ```rust
18+
/// # use async_profiler_agent::metadata::OrderedF64;
19+
/// let _v = OrderedF64::new(0.0);
20+
/// ```
21+
pub const fn new(value: f64) -> Self {
22+
Self(OrderedFloat(value))
23+
}
24+
}
25+
26+
impl From<f64> for OrderedF64 {
27+
fn from(value: f64) -> Self {
28+
Self(OrderedFloat(value))
29+
}
30+
}
31+
32+
impl From<OrderedF64> for f64 {
33+
fn from(value: OrderedF64) -> Self {
34+
value.0 .0
35+
}
36+
}
37+
838
/// Host Metadata, which describes a host that runs a profiling agent. The current set of supported agent metadata is
939
/// AWS-specific. If you are not running on AWS, you can use [AgentMetadata::NoMetadata].
1040
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -13,6 +43,7 @@ pub enum AgentMetadata {
1343
/// Metadata for an [EC2] instance running on AWS
1444
///
1545
/// [EC2]: https://aws.amazon.com/ec2
46+
#[cfg_attr(feature = "__unstable-fargate-cpu-count", non_exhaustive)]
1647
Ec2AgentMetadata {
1748
/// The AWS account id
1849
aws_account_id: String,
@@ -24,6 +55,7 @@ pub enum AgentMetadata {
2455
/// Metadata for a [Fargate] task running on AWS.
2556
///
2657
/// [Fargate]: https://aws.amazon.com/fargate
58+
#[cfg_attr(feature = "__unstable-fargate-cpu-count", non_exhaustive)]
2759
FargateAgentMetadata {
2860
/// The AWS account id
2961
aws_account_id: String,
@@ -41,6 +73,20 @@ pub enum AgentMetadata {
4173
///
4274
/// See the ECS documentation for more details
4375
ecs_cluster_arn: String,
76+
/// The CPU limit for the Fargate cluster
77+
///
78+
/// For example, `Some(0.25)`. This will be `None` if the CPU limit is not specified.
79+
///
80+
/// See the ECS documentation for more details
81+
#[cfg(feature = "__unstable-fargate-cpu-count")]
82+
cpu_limit: Option<OrderedF64>,
83+
/// The memory limit for the Fargate cluster (in megabytes)
84+
///
85+
/// For example, `Some(2048)`. This will be `None` if the memory limit is not specified.
86+
///
87+
/// See the ECS documentation for more details
88+
#[cfg(feature = "__unstable-fargate-cpu-count")]
89+
memory_limit: Option<u64>,
4490
},
4591
/// Metadata for a host that is neither an EC2 nor a Fargate
4692
#[deprecated = "Use AgentMetadata::NoMetadata"]
@@ -99,6 +145,10 @@ impl AgentMetadata {
99145
aws_region_id,
100146
ecs_task_arn,
101147
ecs_cluster_arn,
148+
#[cfg(feature = "__unstable-fargate-cpu-count")]
149+
cpu_limit: None,
150+
#[cfg(feature = "__unstable-fargate-cpu-count")]
151+
memory_limit: None,
102152
}
103153
}
104154
}

src/reporter/s3.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ fn make_s3_file_name(
132132
aws_account_id: _,
133133
aws_region_id: _,
134134
ec2_instance_id,
135+
..
135136
} => {
136137
let ec2_instance_id = ec2_instance_id.replace("/", "-").replace("_", "-");
137138
format!("ec2_{ec2_instance_id}_")
@@ -141,6 +142,7 @@ fn make_s3_file_name(
141142
aws_region_id: _,
142143
ecs_task_arn,
143144
ecs_cluster_arn: _,
145+
..
144146
} => {
145147
let task_arn = ecs_task_arn.replace("/", "-").replace("_", "-");
146148
format!("ecs_{task_arn}_")
@@ -256,17 +258,17 @@ mod test {
256258

257259
#[test_case(#[allow(deprecated)] { AgentMetadata::Other }, "profile_pg_onprem___<pid>_<time>.zip"; "other")]
258260
#[test_case(AgentMetadata::NoMetadata, "profile_pg_unknown___<pid>_<time>.zip"; "no-metadata")]
259-
#[test_case(AgentMetadata::Ec2AgentMetadata {
260-
aws_account_id: "1".into(),
261-
aws_region_id: "us-east-1".into(),
262-
ec2_instance_id: "i-0".into()
263-
}, "profile_pg_ec2_i-0__<pid>_<time>.zip"; "ec2")]
264-
#[test_case(AgentMetadata::FargateAgentMetadata {
265-
aws_account_id: "1".into(),
266-
aws_region_id: "us-east-1".into(),
267-
ecs_task_arn: "arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f".into(),
268-
ecs_cluster_arn: "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster".into()
269-
}, "profile_pg_ecs_arn:aws:ecs:us-east-1:123456789012:task-profiler-metadata-cluster-5261e761e0e2a3d92da3f02c8e5bab1f__<pid>_<time>.zip"; "ecs")]
261+
#[test_case(AgentMetadata::ec2_agent_metadata_v1(
262+
"1".into(),
263+
"us-east-1".into(),
264+
"i-0".into()
265+
), "profile_pg_ec2_i-0__<pid>_<time>.zip"; "ec2")]
266+
#[test_case(AgentMetadata::fargate_agent_metadata_v1(
267+
"1".into(),
268+
"us-east-1".into(),
269+
"arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f".into(),
270+
"arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster".into(),
271+
), "profile_pg_ecs_arn:aws:ecs:us-east-1:123456789012:task-profiler-metadata-cluster-5261e761e0e2a3d92da3f02c8e5bab1f__<pid>_<time>.zip"; "ecs")]
270272
fn test_make_s3_file_name(metadata: AgentMetadata, expected: &str) {
271273
let file_name = super::make_s3_file_name(&metadata, "pg", SystemTime::UNIX_EPOCH);
272274
assert_eq!(

0 commit comments

Comments
 (0)