Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/actions/rust-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ runs:
- uses: Swatinem/rust-cache@v2
- name: Build
shell: bash
run: if [ "${{ inputs.toolchain }}" != stable ]; then rm -fv Cargo.lock; fi && cargo build --all-features --verbose
run: |
if [ "${{ inputs.toolchain }}" != stable ]; then
rm -fv Cargo.lock
fi
cargo build --all-features --verbose
- name: Run tests
shell: bash
run: cargo test --all-features --verbose
24 changes: 0 additions & 24 deletions .github/workflows/build-decoder.yml

This file was deleted.

90 changes: 88 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on: [pull_request]

jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -17,5 +18,90 @@ jobs:
uses: ./.github/actions/rust-build
with:
toolchain: ${{ matrix.toolchain }}


build-for-testing:
name: Build For Testing
runs-on: ubuntu-latest
env:
RUSTFLAGS: --cfg tokio_unstable
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
- name: Build
shell: bash
run: cargo build --all-features --verbose --example simple
- name: Upload artifact for testing
uses: actions/upload-artifact@v4
with:
name: example-simple
path: ./target/debug/examples/simple
build-decoder:
name: Build Decoder
runs-on: ubuntu-latest
env:
RUST_BACKTRACE: 1
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- uses: Swatinem/rust-cache@v2
with:
cache-directories: decoder
- name: Build
working-directory: decoder
shell: bash
run: cargo build --all-features --verbose
- name: Run tests
working-directory: decoder
shell: bash
run: cargo test --all-features --verbose
- name: Upload artifact for testing
uses: actions/upload-artifact@v4
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 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, build-for-testing, build-decoder, build-async-profiler]
steps:
- uses: actions/checkout@v4
- name: Download pollcatch-decoder
uses: actions/download-artifact@v4
with:
name: pollcatch-decoder
path: ./tests
- name: Download example-simple
uses: actions/download-artifact@v4
with:
name: example-simple
path: ./tests
- name: Download libasyncProfiler
uses: actions/download-artifact@v4
with:
name: libasyncProfiler
path: ./tests
- name: Run integration test
shell: bash
working-directory: tests
run: chmod +x simple pollcatch-decoder && LD_LIBRARY_PATH=$PWD ./integration.sh
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/target
/decoder/target
/tests/async-profiler
/tests/profiles
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1", features = ["test-util", "full"] }
test-case = "3"
rand = "0.9"
humantime = "2"

[[example]]
name = 'simple'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ When starting, the profiler [dlopen(3)]'s `libasyncProfiler.so` and returns an `
[^1]: the dlopen search path includes RPATH and LD_LIBRARY_PATH, but *not* the current directory to avoid current directory attacks.
[dlopen(3)]: https://linux.die.net/man/3/dlopen

You can write your own reporter (via the `Reporter` trait) to upload the profile results to your favorite profiler backend.

You can use the S3 reporter, which uploads the reports to an S3 bucket, as follows:

```no_run
Expand Down Expand Up @@ -54,6 +52,8 @@ The S3 reporter uploads each report in a `zip` file, that currently contains 2 f
The `zip` file is uploaded to the bucket under the path `profile_{profiling_group_name}_{machine}_{pid}_{time}.zip`,
where `{machine}` is either `ec2_{ec2_instance_id}_`, `ecs_{cluster_arn}_{task_arn}`, or `onprem__`.

In addition to the S3 reporter, `async-profiler-agent` also includes `LocalReporter` that writes to a directory, and a `MultiReporter` that allows combining reporters. You can also write your own reporter (via the `Reporter` trait) to upload the profile results to your favorite profiler backend.

[JFR]: https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm

#### Sample program
Expand Down
120 changes: 110 additions & 10 deletions decoder/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::{ffi::OsString, fs::File, io::Cursor};
use std::{
ffi::OsString,
fs::File,
io::{self, Cursor, Write},
};

use clap::{Parser, Subcommand};
use jfrs::reader::{
Expand Down Expand Up @@ -78,7 +82,7 @@ fn main() -> anyhow::Result<()> {
}
}
};
print_samples(samples, stack_depth);
print_samples(&mut io::stdout(), samples, stack_depth).ok();
Ok(())
}
}
Expand All @@ -94,7 +98,7 @@ fn symbol_to_string(s: Accessor<'_>) -> Option<&str> {
None
}

fn print_samples(samples: Vec<Sample>, stack_depth: usize) {
fn print_samples<F: Write>(to: &mut F, samples: Vec<Sample>, stack_depth: usize) -> io::Result<()> {
for sample in samples {
if sample.frames.iter().any(|f| {
f.name.as_ref().is_some_and(|n| {
Expand All @@ -106,39 +110,45 @@ fn print_samples(samples: Vec<Sample>, stack_depth: usize) {
// skip samples that are of sleeps
continue;
}
println!(
writeln!(
to,
"[{:.6}] thread {} - poll of {}us",
sample.start_time.as_secs_f64(),
sample.thread_id,
sample.delta_t.as_micros()
);
)?;
for (i, frame) in sample.frames.iter().enumerate() {
if i == stack_depth {
println!(
writeln!(
to,
" - {:3} more frame(s) (pass --stack-depth={} to show)",
sample.frames.len() - stack_depth,
sample.frames.len()
);
)?;
break;
}
println!(
writeln!(
to,
" - {:3}: {}.{}",
i + 1,
frame.class_name.as_deref().unwrap_or("<unknown>"),
frame.name.as_deref().unwrap_or("<unknown>")
);
)?;
}
println!();
writeln!(to)?;
}
Ok(())
}

#[derive(Debug)]
struct Sample {
delta_t: Duration,
start_time: Duration,
thread_id: i64,
frames: Vec<StackFrame>,
}

#[derive(Debug)]
struct StackFrame {
class_name: Option<String>,
name: Option<String>,
Expand Down Expand Up @@ -536,3 +546,93 @@ where
}
Ok(samples)
}

#[cfg(test)]
mod test {
use super::{jfr_samples, print_samples, Sample, StackFrame};
use std::io;
use std::time::Duration;

#[test]
fn test_print_samples() {
let mut to = vec![];
print_samples(
&mut to,
vec![Sample {
delta_t: Duration::from_millis(1),
start_time: Duration::from_secs(1),
thread_id: 1,
frames: vec![
StackFrame {
class_name: None,
name: None,
},
StackFrame {
class_name: None,
name: Some("foo".into()),
},
StackFrame {
class_name: Some("cls".into()),
name: Some("foo".into()),
},
StackFrame {
class_name: Some("cls".into()),
name: Some("bar".into()),
},
],
}],
3,
)
.unwrap();
assert_eq!(
String::from_utf8(to).unwrap(),
r#"[1.000000] thread 1 - poll of 1000us
- 1: <unknown>.<unknown>
- 2: <unknown>.foo
- 3: cls.foo
- 1 more frame(s) (pass --stack-depth=4 to show)

"#
);
}

#[test]
fn test_jfr_samples() {
let jfr = include_bytes!("../../tests/test.jfr");
let samples = jfr_samples(&mut io::Cursor::new(jfr), Duration::from_micros(200)).unwrap();
let mut to = vec![];
print_samples(&mut to, samples, 4).unwrap();
assert_eq!(
String::from_utf8(to).unwrap(),
r#"[95.789203] thread 1880 - poll of 219us
- 1: libc.so.6.clock_nanosleep
- 2: libc.so.6.nanosleep
- 3: simple.std::thread::sleep
- 4: simple.simple::slow::short_sleep
- 55 more frame(s) (pass --stack-depth=59 to show)

[92.789145] thread 1881 - poll of 1733us
- 1: libc.so.6.clock_nanosleep
- 2: libc.so.6.nanosleep
- 3: simple.std::thread::sleep_ms
- 4: simple.simple::slow::accidentally_slow
- 55 more frame(s) (pass --stack-depth=59 to show)

[96.789218] thread 1881 - poll of 203us
- 1: libc.so.6.clock_nanosleep
- 2: libc.so.6.nanosleep
- 3: simple.std::thread::sleep
- 4: simple.simple::slow::short_sleep
- 55 more frame(s) (pass --stack-depth=59 to show)

[98.789191] thread 1881 - poll of 214us
- 1: libc.so.6.clock_nanosleep
- 2: libc.so.6.nanosleep
- 3: simple.std::thread::sleep
- 4: simple.simple::slow::short_sleep
- 55 more frame(s) (pass --stack-depth=59 to show)

"#
);
}
}
Loading