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