async_profiler_agent/reporter/
local.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! A reporter that reports into a directory.
5
6use async_trait::async_trait;
7use chrono::SecondsFormat;
8use std::path::PathBuf;
9use std::time::SystemTime;
10use thiserror::Error;
11
12use crate::metadata::ReportMetadata;
13
14use super::Reporter;
15
16#[derive(Error, Debug)]
17enum LocalReporterError {
18    #[error("{0}")]
19    IoError(#[from] std::io::Error),
20}
21
22/// A reporter that reports into a directory.
23///
24/// The files are reported with the filename `yyyy-mm-ddTHH-MM-SSZ.jfr`
25///
26/// It does not currently use the metadata, so if you are using
27/// [LocalReporter] alone, rather than inside a [MultiReporter], you
28/// can just use [AgentMetadata::NoMetadata] as metadata.
29///
30/// [AgentMetadata::NoMetadata]: crate::metadata::AgentMetadata::NoMetadata
31/// [MultiReporter]: crate::reporter::multi::MultiReporter
32///
33/// ### Example
34///
35/// ```
36/// # use async_profiler_agent::metadata::AgentMetadata;
37/// # use async_profiler_agent::profiler::{ProfilerBuilder, SpawnError};
38/// # #[tokio::main]
39/// # async fn main() -> Result<(), SpawnError> {
40/// let profiler = ProfilerBuilder::default()
41///    .with_local_reporter("/tmp/profiles")
42///    .build();
43/// # if false { // don't spawn the profiler in doctests
44/// let profiler = profiler.spawn_controllable()?;
45/// // ... your program goes here
46/// profiler.stop().await; // make sure the last profile is flushed
47/// # }
48/// # Ok(())
49/// # }
50/// ```
51#[derive(Debug)]
52pub struct LocalReporter {
53    directory: PathBuf,
54}
55
56impl LocalReporter {
57    /// Instantiate a new LocalReporter writing into the provided directory.
58    pub fn new(directory: impl Into<PathBuf>) -> Self {
59        LocalReporter {
60            directory: directory.into(),
61        }
62    }
63
64    /// Writes the jfr file to disk.
65    async fn report_profiling_data(
66        &self,
67        jfr: Vec<u8>,
68        _metadata_obj: &ReportMetadata<'_>,
69    ) -> Result<(), std::io::Error> {
70        let time: chrono::DateTime<chrono::Utc> = SystemTime::now().into();
71        let time = time
72            .to_rfc3339_opts(SecondsFormat::Secs, true)
73            .replace(":", "-");
74        tracing::debug!("reporting {time}.jfr");
75        let file_name = format!("{time}.jfr");
76        tokio::fs::write(self.directory.join(file_name), jfr).await?;
77        Ok(())
78    }
79}
80
81#[async_trait]
82impl Reporter for LocalReporter {
83    async fn report(
84        &self,
85        jfr: Vec<u8>,
86        metadata: &ReportMetadata,
87    ) -> Result<(), Box<dyn std::error::Error + Send>> {
88        self.report_profiling_data(jfr, metadata)
89            .await
90            .map_err(|e| Box::new(LocalReporterError::IoError(e)) as _)
91    }
92}
93
94#[cfg(test)]
95mod test {
96    use std::path::Path;
97
98    use crate::{
99        metadata::DUMMY_METADATA,
100        reporter::{Reporter, local::LocalReporter},
101    };
102
103    #[tokio::test]
104    async fn test_local_reporter() {
105        let dir = tempfile::tempdir().unwrap();
106        let reporter = LocalReporter::new(dir.path());
107        reporter
108            .report(b"JFR".into(), &DUMMY_METADATA)
109            .await
110            .unwrap();
111        let jfr_file = std::fs::read_dir(dir.path())
112            .unwrap()
113            .flat_map(|f| f.ok())
114            .filter(|f| {
115                Path::new(&f.file_name())
116                    .extension()
117                    .is_some_and(|e| e == "jfr")
118            })
119            .next()
120            .unwrap();
121        assert_eq!(tokio::fs::read(jfr_file.path()).await.unwrap(), b"JFR");
122    }
123}