diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 62e6677..7a08283 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,27 +69,10 @@ jobs: with: name: pollcatch-decoder path: ./decoder/target/debug/pollcatch-decoder - build-async-profiler: - name: Build async-profiler - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install async-profiler Dependencies - run: sudo apt-get update && sudo apt-get install -y sudo libicu-dev patchelf curl make g++ openjdk-11-jdk-headless gcovr - - name: Build async-profiler - working-directory: tests - shell: bash - run: ./build-async-profiler.sh - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: libasyncProfiler - path: ./tests/async-profiler/build/lib/libasyncProfiler.so test: name: Integration Test runs-on: ubuntu-latest - needs: [build-for-testing, build-decoder, build-async-profiler] + needs: [build-for-testing, build-decoder] steps: - uses: actions/checkout@v4 - name: Download pollcatch-decoder @@ -102,11 +85,10 @@ jobs: with: name: example-simple path: ./tests - - name: Download libasyncProfiler - uses: actions/download-artifact@v4 - with: - name: libasyncProfiler - path: ./tests + - name: Download async-profiler + shell: bash + working-directory: tests + run: wget https://github.com/async-profiler/async-profiler/releases/download/v4.1/async-profiler-4.1-linux-x64.tar.gz -O async-profiler.tar.gz && tar xvf async-profiler.tar.gz && mv -vf async-profiler-*/lib/libasyncProfiler.so . - name: Run integration test shell: bash working-directory: tests diff --git a/decoder/src/main.rs b/decoder/src/main.rs index f5d74a0..768d1ca 100644 --- a/decoder/src/main.rs +++ b/decoder/src/main.rs @@ -54,6 +54,14 @@ enum Commands { #[arg(long, default_value = "5")] stack_depth: usize, }, + /// Print the total duration (in seconds) of the JFR recording. Used in the integration tests. + Duration { + /// JFR file to read from + jfr_file: OsString, + /// If true, unzip first + #[arg(long)] + zip: bool, + }, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -78,6 +86,24 @@ fn extract_async_profiler_jfr_from_zip(file: File) -> anyhow::Result {{ + let mut jfr_file = std::fs::File::open($jfr_file)?; + match $zip { + false => $f(&mut jfr_file, $input), + true => { + if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? { + $f(&mut Cursor::new(&data), $input) + } else { + anyhow::bail!("no async_profiler_dump_0.jfr file found"); + } + } + } + }}; +} + fn main() -> anyhow::Result<()> { let cli = Cli::parse(); tracing_subscriber::fmt::init(); @@ -89,21 +115,11 @@ fn main() -> anyhow::Result<()> { zip, include_non_pollcatch, } => { - let mut jfr_file = std::fs::File::open(jfr_file)?; let config = LongPollConfig { min_length, include_non_pollcatch, }; - let samples = match zip { - false => jfr_samples(&mut jfr_file, &config)?, - true => { - if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? { - jfr_samples(&mut Cursor::new(&data), &config)? - } else { - anyhow::bail!("no async_profiler_dump_0.jfr file found"); - } - } - }; + let samples = extract_events_from_path!(jfr_file, zip, jfr_samples, &config)?; print_samples(&mut io::stdout(), samples, stack_depth).ok(); Ok(()) } @@ -113,20 +129,15 @@ fn main() -> anyhow::Result<()> { zip, stack_depth, } => { - let mut jfr_file = std::fs::File::open(jfr_file)?; - let events = match zip { - false => jfr_native_mem_events(&mut jfr_file, &type_)?, - true => { - if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? { - jfr_native_mem_events(&mut Cursor::new(&data), &type_)? - } else { - anyhow::bail!("no async_profiler_dump_0.jfr file found"); - } - } - }; + let events = extract_events_from_path!(jfr_file, zip, jfr_native_mem_events, &type_)?; print_native_mem_events(&mut io::stdout(), events, &type_, stack_depth).ok(); Ok(()) } + Commands::Duration { jfr_file, zip } => { + let duration = extract_events_from_path!(jfr_file, zip, jfr_duration, ())?; + println!("{}", duration.as_secs_f64()); + Ok(()) + } } } @@ -659,6 +670,19 @@ where Ok(samples) } +fn jfr_duration(reader: &mut T, _config: ()) -> anyhow::Result +where + T: Read + Seek, +{ + let mut jfr_reader = JfrReader::new(reader); + + let total_nanos = jfr_reader + .chunks() + .map(|i| i.map(|i| i.1.header.duration_nanos)) + .sum::>()?; + Ok(Duration::from_nanos(total_nanos as u64)) +} + #[derive(Debug)] struct NativeMemEvent { start_time: Duration, @@ -934,6 +958,9 @@ mod test { } print_samples(&mut to, samples, 4).unwrap(); assert_eq!(String::from_utf8(to).unwrap(), result); + + let duration = crate::jfr_duration(&mut io::Cursor::new(jfr), ()).unwrap(); + assert_eq!(duration.as_nanos(), 15003104000); } #[test] diff --git a/examples/simple/main.rs b/examples/simple/main.rs index 6511529..4d6f063 100644 --- a/examples/simple/main.rs +++ b/examples/simple/main.rs @@ -134,7 +134,7 @@ async fn main_internal(args: Args) -> Result<(), anyhow::Error> { .build(); tracing::info!("starting profiler"); - profiler.spawn()?; + let handle = profiler.spawn_controllable()?; tracing::info!("profiler started"); if let Some(timeout) = args.duration { @@ -145,5 +145,7 @@ async fn main_internal(args: Args) -> Result<(), anyhow::Error> { slow::run().await; } + handle.stop().await; + Ok(()) } diff --git a/examples/simple/slow.rs b/examples/simple/slow.rs index 4ce4995..f80cbcb 100644 --- a/examples/simple/slow.rs +++ b/examples/simple/slow.rs @@ -2,7 +2,8 @@ #[allow(deprecated)] fn accidentally_slow() { std::thread::sleep_ms(10); - std::hint::black_box(0); + // do a phony allocation for the allocation profiler + std::hint::black_box(Vec::::with_capacity(1 << 20)); } #[inline(never)] diff --git a/tests/integration.sh b/tests/integration.sh index 7186cb3..fa8650c 100755 --- a/tests/integration.sh +++ b/tests/integration.sh @@ -13,7 +13,18 @@ rm -f profiles/*.jfr # Pass --worker-threads 16 to make the test much less flaky since there is always some worker thread running ./simple --local profiles --duration 30s --reporting-interval 10s --worker-threads 16 --native-mem 4k +found_good=0 + for profile in profiles/*.jfr; do + duration=$(./pollcatch-decoder duration "$profile") + # Ignore "partial" profiles of less than 8s + if [[ $duration > 8 ]]; then + found_good=1 + else + echo "Profile $profile is too short" + continue + fi + # Basic event presence check native_malloc_count=$(./pollcatch-decoder nativemem --type malloc "$profile" | wc -l) if [ "$native_malloc_count" -lt 1 ]; then @@ -48,3 +59,8 @@ for profile in profiles/*.jfr; do exit 1 fi done + +if [ "$found_good" -eq 0 ]; then + echo Found no good profiles + exit 1 +fi