Skip to content

Commit e2da5e6

Browse files
authored
Merge pull request #32 from arielb1/deny-missing-docs
deny missing docs
2 parents 07544ea + 1257c03 commit e2da5e6

File tree

11 files changed

+214
-6
lines changed

11 files changed

+214
-6
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
with:
2020
toolchain: ${{ matrix.toolchain }}
2121
build-for-testing:
22-
name: Build For Testing
22+
name: Build for testing
2323
runs-on: ubuntu-latest
2424
env:
2525
RUSTFLAGS: --cfg tokio_unstable

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ let profiler = ProfilerBuilder::default()
3838
sdk_config: &sdk_config,
3939
bucket_owner: bucket_owner.into(),
4040
bucket_name: bucket_name.into(),
41-
profiling_group: profiling_group.into(),
41+
profiling_group_name: profiling_group.into(),
4242
}))
4343
.build();
4444

src/lib.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,94 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
#![deny(missing_docs)]
5+
6+
//! ## async-profiler Rust agent
7+
//! An in-process Rust agent for profiling an application using [async-profiler] and uploading the resulting profiles.
8+
//!
9+
//! [async-profiler]: https://github.com/async-profiler/async-profiler
10+
//!
11+
//! ### OS/CPU Support
12+
//!
13+
//! This Rust agent currently only supports Linux, on either x86-64 or aarch64.
14+
//!
15+
//! ### Usage
16+
//!
17+
//! The agent runs the profiler and uploads the output periodically via a reporter.
18+
//!
19+
//! When starting, the profiler [dlopen(3)]'s `libasyncProfiler.so` and returns an [`Err`] if it is not found,
20+
//! so make sure there is a `libasyncProfiler.so` in the search path[^1].
21+
//!
22+
//! [^1]: the dlopen search path includes RPATH and LD_LIBRARY_PATH, but *not* the current directory to avoid current directory attacks.
23+
//! [dlopen(3)]: <https://linux.die.net/man/3/dlopen>
24+
//!
25+
//! You can use the [S3Reporter], which uploads the reports to an S3 bucket, as follows:
26+
//!
27+
//! ```no_run
28+
//! # use async_profiler_agent::profiler::{ProfilerBuilder, SpawnError};
29+
//! # use async_profiler_agent::reporter::s3::{S3Reporter, S3ReporterConfig};
30+
//! # use aws_config::BehaviorVersion;
31+
//! # #[tokio::main]
32+
//! # async fn main() -> Result<(), SpawnError> {
33+
//!
34+
//! let bucket_owner = "<your account id>";
35+
//! let bucket_name = "<your bucket name>";
36+
//! let profiling_group = "a-name-to-give-the-uploaded-data";
37+
//!
38+
//! let sdk_config = aws_config::defaults(BehaviorVersion::latest()).load().await;
39+
//!
40+
//! let profiler = ProfilerBuilder::default()
41+
//! .with_reporter(S3Reporter::new(S3ReporterConfig {
42+
//! sdk_config: &sdk_config,
43+
//! bucket_owner: bucket_owner.into(),
44+
//! bucket_name: bucket_name.into(),
45+
//! profiling_group_name: profiling_group.into(),
46+
//! }))
47+
//! .build();
48+
//!
49+
//! profiler.spawn()?;
50+
//! # Ok(())
51+
//! # }
52+
//! ```
53+
//!
54+
//! The [S3Reporter] uploads each report in a `zip` file, that currently contains 2 files:
55+
//! 1. a [JFR] as `async_profiler_dump_0.jfr`
56+
//! 2. metadata as `metadata.json`, in format [`reporter::s3::MetadataJson`].
57+
//!
58+
//! The `zip` file is uploaded to the bucket under the path `profile_{profiling_group_name}_{machine}_{pid}_{time}.zip`,
59+
//! where `{machine}` is either `ec2_{ec2_instance_id}_`, `ecs_{cluster_arn}_{task_arn}`, or `onprem__`.
60+
//!
61+
//! In addition to the S3 reporter, this crate also includes [`LocalReporter`] that writes to a directory, and a `MultiReporter` that allows combining reporters. You can also write your own reporter (via the `Reporter` trait) to upload the profile results to your favorite profiler backend.
62+
//!
63+
//! [`LocalReporter`]: reporter::local::LocalReporter
64+
//! [JFR]: https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm
65+
//!
66+
//! #### Sample program
67+
//!
68+
//! You can test the agent by using the sample program, for example:
69+
//!
70+
//! ```notrust
71+
//! LD_LIBRARY_PATH=/path/to/libasyncProfiler.so cargo run --release --example simple -- --profiling-group PG --bucket-owner YOUR-AWS-ACCOUNT-ID --bucket YOUR_BUCKET_ID
72+
//! ```
73+
//!
74+
//! ### Host Metadata Auto-Detection
75+
//!
76+
//! The Rust agent currently auto-detects the machine's [EC2] or [Fargate] id using [IMDS].
77+
//!
78+
//! If you want to run the agent on a machine that is not EC2 or Fargate, you can use [`profiler::ProfilerBuilder::with_custom_agent_metadata`] to provide your own metadata.
79+
//!
80+
//! The metadata is not used by the agent directly, and only provided to the reporters, to allow them to associate the profiling data with the correct host. In the S3 reporter, it's used to generate the `metadata.json` and zip file name.
81+
//!
82+
//! [EC2]: https://aws.amazon.com/ec2
83+
//! [Fargate]: https://aws.amazon.com/fargate
84+
//! [IMDS]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
85+
//!
86+
//! ### PollCatch
87+
//!
88+
//! If you want to find long poll times, and you have `RUSTFLAGS="--cfg tokio_unstable"`, see the
89+
//! [pollcatch] module for emitting `tokio.PollCatchV1` events.
490
mod asprof;
91+
592
pub mod metadata;
693
pub mod pollcatch;
794
pub mod profiler;

src/metadata/aws.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,62 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
//! Contains functions for getting host metadata from [IMDS]
5+
//!
6+
//! [IMDS]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
7+
48
use reqwest::Method;
59
use serde::Deserialize;
610
use thiserror::Error;
711

812
use super::AgentMetadata;
913

14+
/// An error converting Fargate IMDS metadata to Agent metadata. This error
15+
/// should probably not happen except in case of a bug in either this crate or IMDS.
1016
#[derive(Error, Debug)]
1117
pub enum FargateMetadataToAgentMetadataError {
18+
/// unable to parse task ARN as a valid ARN
1219
#[error("unable to parse task ARN as a valid ARN")]
1320
TaskArnInvalid(#[from] aws_arn::Error),
21+
/// AWS account id not found in Fargate metadata
1422
#[error("AWS account id not found in Fargate metadata")]
1523
AccountIdNotFound,
24+
/// AWS region not found in Fargate metadata
1625
#[error("AWS region not found in Fargate metadata")]
1726
AwsRegionNotFound,
1827
}
1928

29+
/// An error getting IMDS metadata
2030
#[derive(Error, Debug)]
2131
#[error("profiler metadata error: {0}")]
2232
pub enum AwsProfilerMetadataError {
33+
/// Internal IO error
2334
#[error("failed to create profiler metadata file: {0}")]
2435
FailedToCreateFile(#[from] std::io::Error),
2536

37+
/// Error parsing IMDS metadata. Should normally not happen except in case of a bug
2638
#[error("failed fetching valid Fargate metadata: {0}")]
2739
FargateMetadataToAgentMetadataError(#[from] FargateMetadataToAgentMetadataError),
2840

29-
#[error("retrieved invalid endpoint URI: {0}")]
41+
/// Invalid endpoint URI in `ECS_CONTAINER_METADATA_URI_V4`
42+
#[error("retrieved invalid endpoint URI from ECS_CONTAINER_METADATA_URI_V4: {0}")]
3043
InvalidUri(String),
3144

45+
/// Failed to fetch metadata from FarGate endpoint
3246
#[error("failed to fetch metadata from endpoint over HTTP: {0}")]
3347
FailedToFetchMetadataFromEndpoint(reqwest::Error),
3448

49+
/// Failed to fetch metadata from IMDS
3550
#[error("failed to fetch metadata from IMDS endpoint over HTTP: {0}")]
3651
FailedToFetchMetadataFromImds(#[from] aws_config::imds::client::error::ImdsError),
3752

53+
/// Failed to parse metadata from IMDS - this indicates a bug in this crate
54+
/// or in IMDS
3855
#[error("failed to parse metadata as valid UTF-8 from endpoint over HTTP: {0}")]
3956
FailedToParseMetadataFromEndpoint(reqwest::Error),
4057

58+
/// Failed to serialize metadata file - this indicates a bug in this crate
59+
/// or in IMDS
4160
#[error("failed to serialize metadata file: {0}")]
4261
FailedToSerializeMetadataFile(#[from] serde_json::Error),
4362
}
@@ -132,6 +151,13 @@ impl super::AgentMetadata {
132151
}
133152
}
134153

154+
/// Load agent metadata from [Fargate] or [IMDS].
155+
///
156+
/// This will return an error if this machine does not appear to be a [Fargate] or [EC2].
157+
///
158+
/// [Fargate]: https://aws.amazon.com/fargate
159+
/// [IMDS]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
160+
/// [EC2]: https://aws.amazon.com/ec2
135161
pub async fn load_agent_metadata() -> Result<AgentMetadata, AwsProfilerMetadataError> {
136162
let agent_metadata: AgentMetadata = match read_ec2_metadata().await {
137163
Ok(imds_ec2_instance_metadata) => {

src/metadata/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,62 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
//! This module includes ways to get metadata attached to profiling reports.
5+
46
pub use std::time::Duration;
57

8+
/// Host Metadata, which describes a host that runs a profiling agent. The current set of supported agent metadata is
9+
/// AWS-specific. If you are not running on AWS, you can use [AgentMetadata::Other].
610
#[derive(Debug, Clone, PartialEq, Eq)]
711
#[non_exhaustive]
812
pub enum AgentMetadata {
13+
/// Metadata for an [EC2] instance running on AWS
14+
///
15+
/// [EC2]: https://aws.amazon.com/ec2
916
Ec2AgentMetadata {
17+
/// The AWS account id
1018
aws_account_id: String,
19+
/// The AWS region id
1120
aws_region_id: String,
21+
/// The EC2 instance id
1222
ec2_instance_id: String,
1323
},
24+
/// Metadata for a [Fargate] task running on AWS.
25+
///
26+
/// [Fargate]: https://aws.amazon.com/fargate
1427
FargateAgentMetadata {
28+
/// The AWS account id
1529
aws_account_id: String,
30+
/// The AWS region id
1631
aws_region_id: String,
32+
/// The ECS task ARN
33+
///
34+
/// For example, `arn:aws:ecs:us-east-1:123456789012:task/profiler-metadata-cluster/5261e761e0e2a3d92da3f02c8e5bab1f`
35+
///
36+
/// See the ECS documentation for more details
1737
ecs_task_arn: String,
38+
/// The ECS cluster ARN
39+
///
40+
/// For example, `arn:aws:ecs:us-east-1:123456789012:cluster/profiler-metadata-cluster`
41+
///
42+
/// See the ECS documentation for more details
1843
ecs_cluster_arn: String,
1944
},
45+
/// Metadata for a host that is neither an EC2 nor a Fargate
2046
Other,
2147
}
2248

49+
/// Metadata associated with a specific individual profiling report
2350
#[derive(Debug, Clone, PartialEq, Eq)]
2451
pub struct ReportMetadata<'a> {
52+
/// The host running the agent
2553
pub instance: &'a AgentMetadata,
54+
/// The start time of the profiling report, as a duration from the process start
2655
pub start: Duration,
56+
/// The end time of the profiling report, as a duration from the process start
2757
pub end: Duration,
58+
/// The desired reporting interval (on average, this should be
59+
/// approximately the same as `self.end - self.start`).
2860
pub reporting_interval: Duration,
2961
}
3062

src/pollcatch/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
//! To emit `tokio.PollCatchV1` events, you can set up the task hooks when setting up your Tokio runtime:
2+
//!
3+
//! Then, you can use the `decoder` (look at the crate README) to find long polls in your program.
4+
//!
5+
//! Use it around your `main` like this:
6+
//! ```
7+
//! # async fn your_main() {}
8+
//!
9+
//! let mut rt: tokio::runtime::Builder = tokio::runtime::Builder::new_multi_thread();
10+
//! rt.enable_all();
11+
//!
12+
//! #[cfg(tokio_unstable)]
13+
//! {
14+
//! rt.on_before_task_poll(|_| async_profiler_agent::pollcatch::before_poll_hook())
15+
//! .on_after_task_poll(|_| async_profiler_agent::pollcatch::after_poll_hook());
16+
//! }
17+
//! let rt = rt.build().unwrap();
18+
//! rt.block_on(your_main())
19+
//! ```
20+
//!
21+
//! Except on a poll that is involved in a profiling sample, the poll hook overhead
22+
//! is limited to a few thread-local accesses and should be very very fast.
23+
//!
24+
//! When a profiling sample is taken (normally around 1 / second), it adds slightly
25+
//! more overhead to report the sample, but that is a matter of microseconds
26+
//! and therefore should not worsen tail latency problems.
27+
128
use std::{
229
cell::Cell,
330
sync::{

src/profiler.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
//! A profiler that periodically uploads profiling samples of your program to a [Reporter]
5+
46
use crate::{
57
asprof::{self, AsProfError},
68
metadata::{aws::AwsProfilerMetadataError, AgentMetadata, ReportMetadata},
@@ -206,9 +208,12 @@ enum TickError {
206208

207209
#[derive(Debug, Error)]
208210
#[non_exhaustive]
211+
/// An error that happened spawning a profiler
209212
pub enum SpawnError {
213+
/// Error interactive with async-profiler
210214
#[error(transparent)]
211215
AsProf(#[from] asprof::AsProfError),
216+
/// Error writing to a tempfile
212217
#[error("tempfile error: {0}")]
213218
TempFile(io::Error),
214219
}

src/reporter/local.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
//! A reporter that reports into a directory.
5+
46
use async_trait::async_trait;
57
use chrono::SecondsFormat;
68
use std::path::PathBuf;

src/reporter/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
//! This module contains [Reporter]s that upload profiling data to a destination.
5+
//!
6+
//! The following [Reporter]s are included:
7+
//! 1. [local::LocalReporter], which uploads profiling data to a local directory
8+
//! 2. [s3::S3Reporter], which uploads profiling data to an S3 bucket
9+
//! 3. [multi::MultiReporter], which allows combining multiple reporters.
10+
411
use std::fmt;
512

613
use async_trait::async_trait;
@@ -15,6 +22,17 @@ pub mod s3;
1522
/// Abstraction around reporting profiler data.
1623
#[async_trait]
1724
pub trait Reporter: fmt::Debug {
25+
/// Takes a profiling sample, including JFR data and sample metadata,
26+
/// and uploads it towards a destination.
27+
///
28+
/// If this function returns an error, the sample will be dropped
29+
/// but profiling will continue, and this function will be called
30+
/// again for the next sample (or theoretically, a future version
31+
/// might have configuration that will an attempt to re-upload the
32+
/// current sample will be made - but today's [`Profiler`] does
33+
/// not make any such attempts).
34+
///
35+
/// [`Profiler`]: crate::profiler::Profiler
1836
async fn report(
1937
&self,
2038
jfr: Vec<u8>,

src/reporter/multi.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! A reporter that reports profiling results to several destinations.
2+
13
use async_trait::async_trait;
24

35
use crate::metadata::ReportMetadata;
@@ -69,7 +71,7 @@ impl Reporter for MultiReporter {
6971
}
7072

7173
#[cfg(test)]
72-
pub mod test {
74+
mod test {
7375
use std::{
7476
sync::{
7577
atomic::{self, AtomicBool},

0 commit comments

Comments
 (0)