Skip to main content

async_profiler_agent/metadata/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module includes ways to get metadata attached to profiling reports.
5
6pub use std::time::Duration;
7
8/// An ordered float. Individual type to avoid public API dependencies.
9#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
10#[serde(transparent)]
11pub struct OrderedF64(f64);
12
13impl OrderedF64 {
14    /// Create a new `OrderedF64`
15    ///
16    /// ```rust
17    /// # use async_profiler_agent::metadata::OrderedF64;
18    /// let _v = OrderedF64::new(0.0);
19    /// ```
20    pub const fn new(value: f64) -> Self {
21        Self(value)
22    }
23}
24
25impl PartialEq for OrderedF64 {
26    fn eq(&self, other: &Self) -> bool {
27        if self.0.is_nan() {
28            other.0.is_nan()
29        } else {
30            self.0 == other.0
31        }
32    }
33}
34
35impl Eq for OrderedF64 {}
36
37impl From<f64> for OrderedF64 {
38    fn from(value: f64) -> Self {
39        Self(value)
40    }
41}
42
43impl From<OrderedF64> for f64 {
44    fn from(value: OrderedF64) -> Self {
45        value.0
46    }
47}
48
49/// Host Metadata, which describes a host that runs a profiling agent. The current set of supported agent metadata is
50/// AWS-specific. If you are not running on AWS, you can use [AgentMetadata::NoMetadata].
51#[derive(Debug, Clone, PartialEq, Eq)]
52#[non_exhaustive]
53pub enum AgentMetadata {
54    /// Metadata for an [EC2] instance running on AWS
55    ///
56    /// [EC2]: https://aws.amazon.com/ec2
57    #[non_exhaustive]
58    Ec2AgentMetadata {
59        /// The AWS account id
60        aws_account_id: String,
61        /// The AWS region id
62        aws_region_id: String,
63        /// The EC2 instance id
64        ec2_instance_id: String,
65        /// The EC2 instance type
66        ec2_instance_type: String,
67    },
68    /// Metadata for a [Fargate] task running on AWS.
69    ///
70    /// [Fargate]: https://aws.amazon.com/fargate
71    #[non_exhaustive]
72    FargateAgentMetadata {
73        /// The AWS account id
74        aws_account_id: String,
75        /// The AWS region id
76        aws_region_id: String,
77        /// The ECS task ARN
78        ///
79        /// For example, `arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f`
80        ///
81        /// See the ECS documentation for more details
82        ecs_task_arn: String,
83        /// The ECS cluster ARN
84        ///
85        /// For example, `arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster`
86        ///
87        /// See the ECS documentation for more details
88        ecs_cluster_arn: String,
89        /// The CPU limit for the Fargate cluster
90        ///
91        /// For example, `Some(0.25)`. This will be `None` if the CPU limit is not specified.
92        ///
93        /// See the ECS documentation for more details
94        cpu_limit: Option<OrderedF64>,
95        /// The memory limit for the Fargate cluster (in megabytes)
96        ///
97        /// For example, `Some(2048)`. This will be `None` if the memory limit is not specified.
98        ///
99        /// See the ECS documentation for more details
100        memory_limit: Option<u64>,
101    },
102    /// Metadata for a host that is neither an EC2 nor a Fargate
103    #[deprecated = "Use AgentMetadata::NoMetadata"]
104    Other,
105    /// A placeholder when a host has no metadata, or when a reporter does not
106    /// use metadata.
107    NoMetadata,
108}
109
110impl AgentMetadata {
111    /// Create a builder for EC2 agent metadata
112    ///
113    /// Normally, for real-world use, you would get the metadata using
114    /// autodetection via
115    #[cfg_attr(
116        feature = "aws-metadata-no-defaults",
117        doc = "[aws::load_agent_metadata],"
118    )]
119    #[cfg_attr(
120        feature = "aws-metadata-no-defaults",
121        doc = "`aws::load_agent_metadata`,"
122    )]
123    /// this function is intended for use in tests.
124    ///
125    /// ## Example
126    ///
127    /// ```rust
128    /// # use async_profiler_agent::metadata::AgentMetadata;
129    /// let metadata = AgentMetadata::ec2_agent_metadata(
130    ///     "123456789012".to_string(),
131    ///     "us-east-1".to_string(),
132    ///     "i-1234567890abcdef0".to_string(),
133    /// ).build();
134    /// ```
135    pub fn ec2_agent_metadata(
136        aws_account_id: String,
137        aws_region_id: String,
138        ec2_instance_id: String,
139    ) -> Ec2AgentMetadataBuilder {
140        Ec2AgentMetadataBuilder {
141            aws_account_id,
142            aws_region_id,
143            ec2_instance_id,
144            ec2_instance_type: None,
145        }
146    }
147
148    /// Create a builder for Fargate agent metadata
149    ///
150    /// Normally, for real-world use, you would get the metadata using
151    /// autodetection via
152    #[cfg_attr(
153        feature = "aws-metadata-no-defaults",
154        doc = "[aws::load_agent_metadata],"
155    )]
156    #[cfg_attr(
157        feature = "aws-metadata-no-defaults",
158        doc = "`aws::load_agent_metadata`,"
159    )]
160    /// this function is intended for use in tests.
161    ///
162    /// ## Example
163    ///
164    /// ```rust
165    /// # use async_profiler_agent::metadata::AgentMetadata;
166    /// let metadata = AgentMetadata::fargate_agent_metadata(
167    ///     "123456789012".to_string(),
168    ///     "us-east-1".to_string(),
169    ///     "arn:aws:ecs:us-east-1:123456789012:task/cluster/5261e761e0e2a3d92da3f02c8e5bab1f".to_string(),
170    ///     "arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster".to_string(),
171    /// )
172    /// .with_cpu_limit(0.25)
173    /// .with_memory_limit(2048)
174    /// .build();
175    /// ```
176    pub fn fargate_agent_metadata(
177        aws_account_id: String,
178        aws_region_id: String,
179        ecs_task_arn: String,
180        ecs_cluster_arn: String,
181    ) -> FargateAgentMetadataBuilder {
182        FargateAgentMetadataBuilder {
183            aws_account_id,
184            aws_region_id,
185            ecs_task_arn,
186            ecs_cluster_arn,
187            cpu_limit: None,
188            memory_limit: None,
189        }
190    }
191}
192
193/// Builder for EC2 agent metadata
194#[derive(Debug, Clone)]
195pub struct Ec2AgentMetadataBuilder {
196    aws_account_id: String,
197    aws_region_id: String,
198    ec2_instance_id: String,
199    ec2_instance_type: Option<String>,
200}
201
202impl Ec2AgentMetadataBuilder {
203    /// Set the EC2 instance type
204    pub fn with_ec2_instance_type(mut self, ec2_instance_type: String) -> Self {
205        self.ec2_instance_type = Some(ec2_instance_type);
206        self
207    }
208
209    /// Build the AgentMetadata
210    pub fn build(self) -> AgentMetadata {
211        AgentMetadata::Ec2AgentMetadata {
212            aws_account_id: self.aws_account_id,
213            aws_region_id: self.aws_region_id,
214            ec2_instance_id: self.ec2_instance_id,
215            ec2_instance_type: self.ec2_instance_type.unwrap_or_default(),
216        }
217    }
218}
219
220/// Builder for Fargate agent metadata
221#[derive(Debug, Clone)]
222pub struct FargateAgentMetadataBuilder {
223    aws_account_id: String,
224    aws_region_id: String,
225    ecs_task_arn: String,
226    ecs_cluster_arn: String,
227    cpu_limit: Option<f64>,
228    memory_limit: Option<u64>,
229}
230
231impl FargateAgentMetadataBuilder {
232    /// Set the CPU limit (in vCPUs)
233    pub fn with_cpu_limit(mut self, cpu_limit: f64) -> Self {
234        self.cpu_limit = Some(cpu_limit);
235        self
236    }
237
238    /// Set the memory limit (in megabytes)
239    pub fn with_memory_limit(mut self, memory_limit: u64) -> Self {
240        self.memory_limit = Some(memory_limit);
241        self
242    }
243
244    /// Build the AgentMetadata
245    pub fn build(self) -> AgentMetadata {
246        AgentMetadata::FargateAgentMetadata {
247            aws_account_id: self.aws_account_id,
248            aws_region_id: self.aws_region_id,
249            ecs_task_arn: self.ecs_task_arn,
250            ecs_cluster_arn: self.ecs_cluster_arn,
251            cpu_limit: self.cpu_limit.map(Into::into),
252            memory_limit: self.memory_limit,
253        }
254    }
255}
256
257/// Metadata associated with a specific individual profiling report
258#[derive(Debug, Clone, PartialEq, Eq)]
259pub struct ReportMetadata<'a> {
260    /// The host running the agent
261    pub instance: &'a AgentMetadata,
262    /// The start time of the profiling report, as a duration from the process start
263    pub start: Duration,
264    /// The end time of the profiling report, as a duration from the process start
265    pub end: Duration,
266    /// The desired reporting interval (on average, this should be
267    /// approximately the same as `self.end - self.start`).
268    pub reporting_interval: Duration,
269}
270
271#[cfg(feature = "aws-metadata-no-defaults")]
272pub mod aws;
273
274/// [private] dummy metadata to make testing easier
275#[cfg(test)]
276pub(crate) const DUMMY_METADATA: ReportMetadata<'static> = ReportMetadata {
277    instance: &AgentMetadata::NoMetadata,
278    start: Duration::from_secs(1),
279    end: Duration::from_secs(2),
280    reporting_interval: Duration::from_secs(1),
281};