Skip to content

Commit f40e3a1

Browse files
author
Ariel Ben-Yehuda
committed
ci: use async-profiler from upstream
Add an explicit allocation since the new async-profiler doesn't capture its own allocations. the original example code didn't call `handle.stop`, which led to a race between shutdown and async-profiler pushing the new JFR. In upstream async-profiler, the race was often lost. Add that, then change the integration test to ignore overly-short samples to avoid it erroring.
1 parent b993ac4 commit f40e3a1

File tree

5 files changed

+75
-47
lines changed

5 files changed

+75
-47
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,27 +69,10 @@ jobs:
6969
with:
7070
name: pollcatch-decoder
7171
path: ./decoder/target/debug/pollcatch-decoder
72-
build-async-profiler:
73-
name: Build async-profiler
74-
runs-on: ubuntu-latest
75-
steps:
76-
- name: Checkout
77-
uses: actions/checkout@v4
78-
- name: Install async-profiler Dependencies
79-
run: sudo apt-get update && sudo apt-get install -y sudo libicu-dev patchelf curl make g++ openjdk-11-jdk-headless gcovr
80-
- name: Build async-profiler
81-
working-directory: tests
82-
shell: bash
83-
run: ./build-async-profiler.sh
84-
- name: Upload artifact
85-
uses: actions/upload-artifact@v4
86-
with:
87-
name: libasyncProfiler
88-
path: ./tests/async-profiler/build/lib/libasyncProfiler.so
8972
test:
9073
name: Integration Test
9174
runs-on: ubuntu-latest
92-
needs: [build-for-testing, build-decoder, build-async-profiler]
75+
needs: [build-for-testing, build-decoder]
9376
steps:
9477
- uses: actions/checkout@v4
9578
- name: Download pollcatch-decoder
@@ -102,11 +85,10 @@ jobs:
10285
with:
10386
name: example-simple
10487
path: ./tests
105-
- name: Download libasyncProfiler
106-
uses: actions/download-artifact@v4
107-
with:
108-
name: libasyncProfiler
109-
path: ./tests
88+
- name: Download async-profiler
89+
shell: bash
90+
working-directory: tests
91+
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 .
11092
- name: Run integration test
11193
shell: bash
11294
working-directory: tests

decoder/src/main.rs

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ enum Commands {
5454
#[arg(long, default_value = "5")]
5555
stack_depth: usize,
5656
},
57+
/// Print the total duration (in seconds) of the JFR recording. Used in the integration tests.
58+
Duration {
59+
/// JFR file to read from
60+
jfr_file: OsString,
61+
/// If true, unzip first
62+
#[arg(long)]
63+
zip: bool,
64+
},
5765
}
5866

5967
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -78,6 +86,24 @@ fn extract_async_profiler_jfr_from_zip(file: File) -> anyhow::Result<Option<Vec<
7886
Ok(None)
7987
}
8088

89+
// doing this as a macro because functions are not higher-order enough
90+
// extract events from $jfr_file, $zip decides if it's a zip, then run $f($FILE, input)
91+
macro_rules! extract_events_from_path {
92+
($jfr_file:expr, $zip:expr, $f:expr, $input:expr) => {{
93+
let mut jfr_file = std::fs::File::open($jfr_file)?;
94+
match $zip {
95+
false => $f(&mut jfr_file, $input),
96+
true => {
97+
if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? {
98+
$f(&mut Cursor::new(&data), $input)
99+
} else {
100+
anyhow::bail!("no async_profiler_dump_0.jfr file found");
101+
}
102+
}
103+
}
104+
}};
105+
}
106+
81107
fn main() -> anyhow::Result<()> {
82108
let cli = Cli::parse();
83109
tracing_subscriber::fmt::init();
@@ -89,21 +115,11 @@ fn main() -> anyhow::Result<()> {
89115
zip,
90116
include_non_pollcatch,
91117
} => {
92-
let mut jfr_file = std::fs::File::open(jfr_file)?;
93118
let config = LongPollConfig {
94119
min_length,
95120
include_non_pollcatch,
96121
};
97-
let samples = match zip {
98-
false => jfr_samples(&mut jfr_file, &config)?,
99-
true => {
100-
if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? {
101-
jfr_samples(&mut Cursor::new(&data), &config)?
102-
} else {
103-
anyhow::bail!("no async_profiler_dump_0.jfr file found");
104-
}
105-
}
106-
};
122+
let samples = extract_events_from_path!(jfr_file, zip, jfr_samples, &config)?;
107123
print_samples(&mut io::stdout(), samples, stack_depth).ok();
108124
Ok(())
109125
}
@@ -113,20 +129,15 @@ fn main() -> anyhow::Result<()> {
113129
zip,
114130
stack_depth,
115131
} => {
116-
let mut jfr_file = std::fs::File::open(jfr_file)?;
117-
let events = match zip {
118-
false => jfr_native_mem_events(&mut jfr_file, &type_)?,
119-
true => {
120-
if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? {
121-
jfr_native_mem_events(&mut Cursor::new(&data), &type_)?
122-
} else {
123-
anyhow::bail!("no async_profiler_dump_0.jfr file found");
124-
}
125-
}
126-
};
132+
let events = extract_events_from_path!(jfr_file, zip, jfr_native_mem_events, &type_)?;
127133
print_native_mem_events(&mut io::stdout(), events, &type_, stack_depth).ok();
128134
Ok(())
129135
}
136+
Commands::Duration { jfr_file, zip } => {
137+
let duration = extract_events_from_path!(jfr_file, zip, jfr_duration, ())?;
138+
println!("{}", duration.as_secs_f64());
139+
Ok(())
140+
}
130141
}
131142
}
132143

@@ -659,6 +670,19 @@ where
659670
Ok(samples)
660671
}
661672

673+
fn jfr_duration<T>(reader: &mut T, _config: ()) -> anyhow::Result<Duration>
674+
where
675+
T: Read + Seek,
676+
{
677+
let mut jfr_reader = JfrReader::new(reader);
678+
679+
let total_nanos = jfr_reader
680+
.chunks()
681+
.map(|i| i.map(|i| i.1.header.duration_nanos))
682+
.sum::<Result<i64, _>>()?;
683+
Ok(Duration::from_nanos(total_nanos as u64))
684+
}
685+
662686
#[derive(Debug)]
663687
struct NativeMemEvent {
664688
start_time: Duration,
@@ -934,6 +958,9 @@ mod test {
934958
}
935959
print_samples(&mut to, samples, 4).unwrap();
936960
assert_eq!(String::from_utf8(to).unwrap(), result);
961+
962+
let duration = crate::jfr_duration(&mut io::Cursor::new(jfr), ()).unwrap();
963+
assert_eq!(duration.as_nanos(), 15003104000);
937964
}
938965

939966
#[test]

examples/simple/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async fn main_internal(args: Args) -> Result<(), anyhow::Error> {
134134
.build();
135135

136136
tracing::info!("starting profiler");
137-
profiler.spawn()?;
137+
let handle = profiler.spawn_controllable()?;
138138
tracing::info!("profiler started");
139139

140140
if let Some(timeout) = args.duration {
@@ -145,5 +145,7 @@ async fn main_internal(args: Args) -> Result<(), anyhow::Error> {
145145
slow::run().await;
146146
}
147147

148+
handle.stop().await;
149+
148150
Ok(())
149151
}

examples/simple/slow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
#[allow(deprecated)]
33
fn accidentally_slow() {
44
std::thread::sleep_ms(10);
5-
std::hint::black_box(0);
5+
// do a phony allocation for the allocation profiler
6+
std::hint::black_box(Vec::<u8>::with_capacity(1 << 20));
67
}
78

89
#[inline(never)]

tests/integration.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@ rm -f profiles/*.jfr
1313
# Pass --worker-threads 16 to make the test much less flaky since there is always some worker thread running
1414
./simple --local profiles --duration 30s --reporting-interval 10s --worker-threads 16 --native-mem 4k
1515

16+
found_good=0
17+
1618
for profile in profiles/*.jfr; do
19+
duration=$(./pollcatch-decoder duration "$profile")
20+
# Ignore "partial" profiles of less than 8s
21+
if [[ $duration > 8 ]]; then
22+
found_good=1
23+
else
24+
echo "Profile $profile is too short"
25+
continue
26+
fi
27+
1728
# Basic event presence check
1829
native_malloc_count=$(./pollcatch-decoder nativemem --type malloc "$profile" | wc -l)
1930
if [ "$native_malloc_count" -lt 1 ]; then
@@ -48,3 +59,8 @@ for profile in profiles/*.jfr; do
4859
exit 1
4960
fi
5061
done
62+
63+
if [ "$found_good" -eq 0 ]; then
64+
echo Found no good profiles
65+
exit 1
66+
fi

0 commit comments

Comments
 (0)