Skip to content

Commit c2ef426

Browse files
author
Ariel Ben-Yehuda
committed
address review comments
1 parent 37ff678 commit c2ef426

File tree

8 files changed

+112
-177
lines changed

8 files changed

+112
-177
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,35 @@ The metadata is not used by the agent directly, and only provided to the reporte
7676
[Fargate]: https://aws.amazon.com/fargate
7777
[IMDS]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
7878

79+
### What information the profiler gathers
80+
81+
Memory samples (JFR `profiler.Malloc`) sample allocated memory every
82+
so many bytes of allocated memory, and are matched by `profiler.Free`
83+
to allow detecting if that memory is not free'd.
84+
85+
CPU-time samples (JFR `jdk.ExecutionSample`) sample only threads that
86+
are currently running on a CPU, not threads that are sleeping.
87+
88+
Wall-clock samples (JFR `profiler.WallClockSample`) sample threads
89+
whether they are sleeping or running, and can therefore be
90+
very useful for finding threads that are blocked, for example
91+
on a synchronous lock or a slow system call.
92+
93+
When using Tokio, since tasks are not threads, tasks that are not
94+
currently running will not be sampled by a wall clock sample. However,
95+
a wall clock sample is still very useful in Tokio, since it is what
96+
you want to catch tasks that are blocking a thread by waiting on
97+
synchronous operations.
98+
99+
The default is to do a wall-clock sample every second, and a CPU-time
100+
sample every 100 CPU milliseconds. This can be configured via
101+
[`ProfilerOptionsBuilder`].
102+
103+
Memory samples are not enabled by default, but can be enabled by [`with_native_mem_bytes`].
104+
105+
[`ProfilerOptionsBuilder`]: https://docs.rs/async-profiler-agent/0.1/async_profiler_agent/profiler/struct.ProfilerOptionsBuilder.html
106+
[`with_native_mem_bytes`]: https://docs.rs/async-profiler-agent/0.1/async_profiler_agent/profiler/struct.ProfilerOptionsBuilder.html#method.with_native_mem_bytes
107+
79108
### PollCatch
80109

81110
If you want to find long poll times, and you have `RUSTFLAGS="--cfg tokio_unstable"`, you can

examples/simple/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ async fn main_internal(args: Args) -> Result<(), anyhow::Error> {
119119
let profiler = match (&args.local, args.s3_bucket_args()) {
120120
(Some(local), S3BucketArgs { .. }) => profiler
121121
.with_reporter(LocalReporter::new(local))
122-
.with_custom_agent_metadata(AgentMetadata::Other),
122+
.with_custom_agent_metadata(AgentMetadata::NoMetadata),
123123
#[cfg(feature = "s3-no-defaults")]
124124
(
125125
_,

src/metadata/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
pub use std::time::Duration;
77

88
/// 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].
9+
/// AWS-specific. If you are not running on AWS, you can use [AgentMetadata::NoMetadata].
1010
#[derive(Debug, Clone, PartialEq, Eq)]
1111
#[non_exhaustive]
1212
pub enum AgentMetadata {
@@ -43,7 +43,11 @@ pub enum AgentMetadata {
4343
ecs_cluster_arn: String,
4444
},
4545
/// Metadata for a host that is neither an EC2 nor a Fargate
46+
#[deprecated = "Use AgentMetadata::NoMetadata"]
4647
Other,
48+
/// A placeholder when a host has no metadata, or when a reporter does not
49+
/// use metadata.
50+
NoMetadata,
4751
}
4852

4953
/// Metadata associated with a specific individual profiling report
@@ -66,7 +70,7 @@ pub mod aws;
6670
/// [private] dummy metadata to make testing easier
6771
#[cfg(test)]
6872
pub(crate) const DUMMY_METADATA: ReportMetadata<'static> = ReportMetadata {
69-
instance: &AgentMetadata::Other,
73+
instance: &AgentMetadata::NoMetadata,
7074
start: Duration::from_secs(1),
7175
end: Duration::from_secs(2),
7276
reporting_interval: Duration::from_secs(1),

src/profiler.rs

Lines changed: 43 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ impl ProfilerOptionsBuilder {
207207
self
208208
}
209209

210-
/// Sets the interva in which the profiler will collect
210+
/// Sets the interval in which the profiler will collect
211211
/// CPU-time samples, via the [async-profiler `interval` option].
212212
///
213213
/// CPU-time samples (JFR `jdk.ExecutionSample`) sample only threads that
@@ -261,7 +261,13 @@ impl ProfilerOptionsBuilder {
261261
/// Wall-clock samples (JFR `profiler.WallClockSample`) sample threads
262262
/// whether they are sleeping or running, and can therefore be
263263
/// very useful for finding threads that are blocked, for example
264-
/// on a synchronous lock.
264+
/// on a synchronous lock or a slow system call.
265+
///
266+
/// When using Tokio, since tasks are not threads, tasks that are not
267+
/// currently running will not be sampled by a wall clock sample. However,
268+
/// a wall clock sample is still very useful in Tokio, since it is what
269+
/// you want to catch tasks that are blocking a thread by waiting on
270+
/// synchronous operations.
265271
///
266272
/// The default is to do a wall-clock sample every second.
267273
///
@@ -370,81 +376,7 @@ impl ProfilerBuilder {
370376
doc = "[`S3Reporter`]: crate::reporter::s3::S3Reporter"
371377
)]
372378
///
373-
#[cfg_attr(feature = "s3-no-defaults", doc = "## Example")]
374-
#[cfg_attr(feature = "s3-no-defaults", doc = "")]
375-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"```no_run"#)]
376-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# use std::path::PathBuf;"#)]
377-
#[cfg_attr(
378-
feature = "s3-no-defaults",
379-
doc = r#"# use async_profiler_agent::profiler::{ProfilerBuilder, SpawnError};"#
380-
)]
381-
#[cfg_attr(
382-
feature = "s3-no-defaults",
383-
doc = r#"# use async_profiler_agent::reporter::s3::{S3Reporter, S3ReporterConfig};"#
384-
)]
385-
#[cfg_attr(
386-
feature = "s3-no-defaults",
387-
doc = r#"# use async_profiler_agent::metadata::AgentMetadata;"#
388-
)]
389-
#[cfg_attr(
390-
feature = "s3-no-defaults",
391-
doc = r#"# use aws_config::BehaviorVersion;"#
392-
)]
393-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# #[tokio::main]"#)]
394-
#[cfg_attr(
395-
feature = "s3-no-defaults",
396-
doc = r#"# async fn main() -> Result<(), SpawnError> {"#
397-
)]
398-
#[cfg_attr(
399-
feature = "s3-no-defaults",
400-
doc = r#"# let path = PathBuf::from(".");"#
401-
)]
402-
#[cfg_attr(
403-
feature = "s3-no-defaults",
404-
doc = r#"let bucket_owner = "<your account id>";"#
405-
)]
406-
#[cfg_attr(
407-
feature = "s3-no-defaults",
408-
doc = r#"let bucket_name = "<your bucket name>";"#
409-
)]
410-
#[cfg_attr(
411-
feature = "s3-no-defaults",
412-
doc = r#"let profiling_group = "a-name-to-give-the-uploaded-data";"#
413-
)]
414-
#[cfg_attr(
415-
feature = "s3-no-defaults",
416-
doc = r#"let sdk_config = aws_config::defaults(BehaviorVersion::latest()).load().await;"#
417-
)]
418-
#[cfg_attr(
419-
feature = "s3-no-defaults",
420-
doc = r#"let agent = ProfilerBuilder::default()"#
421-
)]
422-
#[cfg_attr(
423-
feature = "s3-no-defaults",
424-
doc = r#" .with_reporter(S3Reporter::new(S3ReporterConfig {"#
425-
)]
426-
#[cfg_attr(
427-
feature = "s3-no-defaults",
428-
doc = r#" sdk_config: &sdk_config,"#
429-
)]
430-
#[cfg_attr(
431-
feature = "s3-no-defaults",
432-
doc = r#" bucket_owner: bucket_owner.into(),"#
433-
)]
434-
#[cfg_attr(
435-
feature = "s3-no-defaults",
436-
doc = r#" bucket_name: bucket_name.into(),"#
437-
)]
438-
#[cfg_attr(
439-
feature = "s3-no-defaults",
440-
doc = r#" profiling_group_name: profiling_group.into(),"#
441-
)]
442-
#[cfg_attr(feature = "s3-no-defaults", doc = r#" }))"#)]
443-
#[cfg_attr(feature = "s3-no-defaults", doc = r#" .build()"#)]
444-
#[cfg_attr(feature = "s3-no-defaults", doc = r#" .spawn()?;"#)]
445-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# Ok::<_, SpawnError>(())"#)]
446-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# }"#)]
447-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"```"#)]
379+
#[cfg_attr(feature = "s3-no-defaults", doc = include_str!("s3-example.md"))]
448380
pub fn with_reporter(mut self, r: impl Reporter + Send + Sync + 'static) -> ProfilerBuilder {
449381
self.reporter = Some(Box::new(r));
450382
self
@@ -482,7 +414,7 @@ impl ProfilerBuilder {
482414
/// ```
483415
pub fn with_local_reporter(mut self, path: impl Into<PathBuf>) -> ProfilerBuilder {
484416
self.reporter = Some(Box::new(LocalReporter::new(path.into())));
485-
self.with_custom_agent_metadata(AgentMetadata::Other)
417+
self.with_custom_agent_metadata(AgentMetadata::NoMetadata)
486418
}
487419

488420
/// Provide custom agent metadata.
@@ -502,94 +434,7 @@ impl ProfilerBuilder {
502434
/// you will need to create and attach your own metadata
503435
/// by calling this function.
504436
///
505-
#[cfg_attr(feature = "s3-no-defaults", doc = "## Example")]
506-
#[cfg_attr(feature = "s3-no-defaults", doc = "")]
507-
#[cfg_attr(
508-
feature = "s3-no-defaults",
509-
doc = "This will create a profiler that will upload to S3 with"
510-
)]
511-
#[cfg_attr(
512-
feature = "s3-no-defaults",
513-
doc = "empty ([AgentMetadata::Other]) metadata."
514-
)]
515-
#[cfg_attr(feature = "s3-no-defaults", doc = r#""#)]
516-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"```no_run"#)]
517-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# use std::path::PathBuf;"#)]
518-
#[cfg_attr(
519-
feature = "s3-no-defaults",
520-
doc = r#"# use async_profiler_agent::profiler::{ProfilerBuilder, SpawnError};"#
521-
)]
522-
#[cfg_attr(
523-
feature = "s3-no-defaults",
524-
doc = r#"# use async_profiler_agent::reporter::s3::{S3Reporter, S3ReporterConfig};"#
525-
)]
526-
#[cfg_attr(
527-
feature = "s3-no-defaults",
528-
doc = r#"# use async_profiler_agent::metadata::AgentMetadata;"#
529-
)]
530-
#[cfg_attr(
531-
feature = "s3-no-defaults",
532-
doc = r#"# use aws_config::BehaviorVersion;"#
533-
)]
534-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# #[tokio::main]"#)]
535-
#[cfg_attr(
536-
feature = "s3-no-defaults",
537-
doc = r#"# async fn main() -> Result<(), SpawnError> {"#
538-
)]
539-
#[cfg_attr(
540-
feature = "s3-no-defaults",
541-
doc = r#"# let path = PathBuf::from(".");"#
542-
)]
543-
#[cfg_attr(
544-
feature = "s3-no-defaults",
545-
doc = r#"let bucket_owner = "<your account id>";"#
546-
)]
547-
#[cfg_attr(
548-
feature = "s3-no-defaults",
549-
doc = r#"let bucket_name = "<your bucket name>";"#
550-
)]
551-
#[cfg_attr(
552-
feature = "s3-no-defaults",
553-
doc = r#"let profiling_group = "a-name-to-give-the-uploaded-data";"#
554-
)]
555-
#[cfg_attr(
556-
feature = "s3-no-defaults",
557-
doc = r#"let sdk_config = aws_config::defaults(BehaviorVersion::latest()).load().await;"#
558-
)]
559-
#[cfg_attr(
560-
feature = "s3-no-defaults",
561-
doc = r#"let agent = ProfilerBuilder::default()"#
562-
)]
563-
#[cfg_attr(
564-
feature = "s3-no-defaults",
565-
doc = r#" .with_reporter(S3Reporter::new(S3ReporterConfig {"#
566-
)]
567-
#[cfg_attr(
568-
feature = "s3-no-defaults",
569-
doc = r#" sdk_config: &sdk_config,"#
570-
)]
571-
#[cfg_attr(
572-
feature = "s3-no-defaults",
573-
doc = r#" bucket_owner: bucket_owner.into(),"#
574-
)]
575-
#[cfg_attr(
576-
feature = "s3-no-defaults",
577-
doc = r#" bucket_name: bucket_name.into(),"#
578-
)]
579-
#[cfg_attr(
580-
feature = "s3-no-defaults",
581-
doc = r#" profiling_group_name: profiling_group.into(),"#
582-
)]
583-
#[cfg_attr(feature = "s3-no-defaults", doc = r#" }))"#)]
584-
#[cfg_attr(
585-
feature = "s3-no-defaults",
586-
doc = r#" .with_custom_agent_metadata(AgentMetadata::Other)"#
587-
)]
588-
#[cfg_attr(feature = "s3-no-defaults", doc = r#" .build()"#)]
589-
#[cfg_attr(feature = "s3-no-defaults", doc = r#" .spawn()?;"#)]
590-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# Ok::<_, SpawnError>(())"#)]
591-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"# }"#)]
592-
#[cfg_attr(feature = "s3-no-defaults", doc = r#"```"#)]
437+
#[cfg_attr(feature = "s3-no-defaults", doc = include_str!("s3-example-custom-metadata.md"))]
593438
/// [`reporter::s3::MetadataJson`]: crate::reporter::s3::MetadataJson
594439
/// [Amazon EC2]: https://aws.amazon.com/ec2
595440
/// [Amazon Fargate]: https://aws.amazon.com/fargate
@@ -871,6 +716,33 @@ impl RunningProfilerThread {
871716

872717
/// Rust profiler based on [async-profiler].
873718
///
719+
/// Spawning a profiler can be done either in an attached (controllable)
720+
/// mode, which allows for stopping the profiler (and, in fact, stops
721+
/// it when the relevant handle is dropped), or in detached mode,
722+
/// in which the profiler keeps running forever. Applications that can
723+
/// shut down the profiler at run-time, for example applications that
724+
/// support reconfiguration of a running profiler, generally want to use
725+
/// controllable mode. Other applications (most of them) should use
726+
/// detached mode.
727+
///
728+
/// In addition, the profiler can either be spawned into the current Tokio
729+
/// runtime, or into a new one. Normally, applications should spawn
730+
/// the profiler into their own Tokio runtime, but applications that
731+
/// don't have a default Tokio runtime should spawn it into a
732+
/// different one
733+
///
734+
/// This leaves 4 functions:
735+
/// 1. [Self::spawn] - detached, same runtime
736+
/// 2. [Self::spawn_thread_to_runtime] - detached, different runtime
737+
/// 3. [Self::spawn_controllable] - controllable, same runtime
738+
/// 4. [Self::spawn_controllable_thread_to_runtime] - controllable, different runtime
739+
///
740+
/// In addition, there's a helper function that just spawns the profiler
741+
/// to a new runtime in a new thread, for applications that don't have
742+
/// a Tokio runtime and don't need complex control:
743+
///
744+
/// 5. [Self::spawn_thread] - detached, new runtime in a new thread
745+
///
874746
/// [async-profiler]: https://github.com/async-profiler/async-profiler
875747
pub struct Profiler {
876748
reporting_interval: Duration,
@@ -923,7 +795,7 @@ impl Profiler {
923795

924796
/// Like [Self::spawn], but instead of spawning within the current Tokio
925797
/// runtime, spawns within a set Tokio runtime and then runs a thread that calls
926-
/// [tokio::runtime::Runtime::block_on] on that runtime.
798+
/// [block_on](tokio::runtime::Runtime::block_on) on that runtime.
927799
///
928800
/// If your configuration is standard, use [Profiler::spawn_thread].
929801
///
@@ -970,7 +842,7 @@ impl Profiler {
970842

971843
/// Like [Self::spawn], but instead of spawning within the current Tokio
972844
/// runtime, spawns within a new Tokio runtime and then runs a thread that calls
973-
/// [tokio::runtime::Runtime::block_on] on that runtime, setting up the runtime
845+
/// [block_on](tokio::runtime::Runtime::block_on) on that runtime, setting up the runtime
974846
/// by itself.
975847
///
976848
/// If your configuration is less standard, use [Profiler::spawn_thread_to_runtime]. Calling
@@ -1080,7 +952,7 @@ impl Profiler {
1080952

1081953
/// Like [Self::spawn_controllable], but instead of spawning within the current Tokio
1082954
/// runtime, spawns within a set Tokio runtime and then runs a thread that calls
1083-
/// [tokio::runtime::Runtime::block_on] on that runtime.
955+
/// [block_on](tokio::runtime::Runtime::block_on) on that runtime.
1084956
///
1085957
/// `spawn_fn` should be [`std::thread::spawn`], or some function that behaves like it (to
1086958
/// allow for configuring thread properties, for example thread names).
@@ -1243,7 +1115,7 @@ async fn profiler_tick<E: ProfilerEngine>(
12431115
#[cfg(feature = "aws-metadata-no-defaults")]
12441116
let md = crate::metadata::aws::load_agent_metadata().await?;
12451117
#[cfg(not(feature = "aws-metadata-no-defaults"))]
1246-
let md = crate::metadata::AgentMetadata::Other;
1118+
let md = crate::metadata::AgentMetadata::NoMetadata;
12471119
tracing::debug!("loaded metadata");
12481120
agent_metadata.replace(md);
12491121
}
@@ -1597,7 +1469,7 @@ mod tests {
15971469
r#"Some(LocalReporter { directory: "." })"#
15981470
);
15991471
match reporter.agent_metadata {
1600-
Some(AgentMetadata::Other) => {}
1472+
Some(AgentMetadata::NoMetadata) => {}
16011473
bad => panic!("{bad:?}"),
16021474
};
16031475
}

src/reporter/local.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ enum LocalReporterError {
2525
///
2626
/// It does not currently use the metadata, so if you are using
2727
/// [LocalReporter] alone, rather than inside a [MultiReporter], you
28-
/// can just use [AgentMetadata::Other] as metadata.
28+
/// can just use [AgentMetadata::NoMetadata] as metadata.
2929
///
30-
/// [AgentMetadata::Other]: crate::metadata::AgentMetadata::Other
30+
/// [AgentMetadata::NoMetadata]: crate::metadata::AgentMetadata::NoMetadata
3131
/// [MultiReporter]: crate::reporter::multi::MultiReporter
3232
///
3333
/// ### Example

src/reporter/s3.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ fn make_s3_file_name(
145145
let task_arn = ecs_task_arn.replace("/", "-").replace("_", "-");
146146
format!("ecs_{task_arn}_")
147147
}
148+
#[allow(deprecated)]
148149
AgentMetadata::Other => "onprem__".to_string(),
150+
AgentMetadata::NoMetadata => "unknown__".to_string(),
149151
};
150152
let time: chrono::DateTime<chrono::Utc> = time.into();
151153
let time = time
@@ -252,7 +254,8 @@ mod test {
252254
);
253255
}
254256

255-
#[test_case(AgentMetadata::Other, "profile_pg_onprem___<pid>_<time>.zip"; "other")]
257+
#[test_case(#[allow(deprecated)] { AgentMetadata::Other }, "profile_pg_onprem___<pid>_<time>.zip"; "other")]
258+
#[test_case(AgentMetadata::NoMetadata, "profile_pg_unknown___<pid>_<time>.zip"; "no-metadata")]
256259
#[test_case(AgentMetadata::Ec2AgentMetadata {
257260
aws_account_id: "1".into(),
258261
aws_region_id: "us-east-1".into(),

src/s3-example-custom-metadata.md

Whitespace-only changes.

0 commit comments

Comments
 (0)