async_profiler_agent/reporter/
local.rs1use async_trait::async_trait;
5use chrono::SecondsFormat;
6use std::path::PathBuf;
7use std::time::SystemTime;
8use thiserror::Error;
9
10use crate::metadata::ReportMetadata;
11
12use super::Reporter;
13
14#[derive(Error, Debug)]
15enum LocalReporterError {
16 #[error("{0}")]
17 IoError(#[from] std::io::Error),
18}
19
20#[derive(Debug)]
24pub struct LocalReporter {
25 directory: PathBuf,
26}
27
28impl LocalReporter {
29 pub fn new(directory: impl Into<PathBuf>) -> Self {
31 LocalReporter {
32 directory: directory.into(),
33 }
34 }
35
36 async fn report_profiling_data(
38 &self,
39 jfr: Vec<u8>,
40 _metadata_obj: &ReportMetadata<'_>,
41 ) -> Result<(), std::io::Error> {
42 let time: chrono::DateTime<chrono::Utc> = SystemTime::now().into();
43 let time = time
44 .to_rfc3339_opts(SecondsFormat::Secs, true)
45 .replace(":", "-");
46 tracing::debug!("reporting {time}.jfr");
47 let file_name = format!("{time}.jfr");
48 tokio::fs::write(self.directory.join(file_name), jfr).await?;
49 Ok(())
50 }
51}
52
53#[async_trait]
54impl Reporter for LocalReporter {
55 async fn report(
56 &self,
57 jfr: Vec<u8>,
58 metadata: &ReportMetadata,
59 ) -> Result<(), Box<dyn std::error::Error + Send>> {
60 self.report_profiling_data(jfr, metadata)
61 .await
62 .map_err(|e| Box::new(e) as _)
63 }
64}
65
66#[cfg(test)]
67mod test {
68 use std::path::Path;
69
70 use crate::{
71 metadata::DUMMY_METADATA,
72 reporter::{local::LocalReporter, Reporter},
73 };
74
75 #[tokio::test]
76 async fn test_local_reporter() {
77 let dir = tempfile::tempdir().unwrap();
78 let reporter = LocalReporter::new(dir.path());
79 reporter
80 .report(b"JFR".into(), &DUMMY_METADATA)
81 .await
82 .unwrap();
83 let jfr_file = std::fs::read_dir(dir.path())
84 .unwrap()
85 .flat_map(|f| f.ok())
86 .filter(|f| {
87 Path::new(&f.file_name())
88 .extension()
89 .is_some_and(|e| e == "jfr")
90 })
91 .next()
92 .unwrap();
93 assert_eq!(tokio::fs::read(jfr_file.path()).await.unwrap(), b"JFR");
94 }
95}