diff --git a/.github/workflows/build-decoder.yml b/.github/workflows/build-decoder.yml new file mode 100644 index 0000000..d40a58c --- /dev/null +++ b/.github/workflows/build-decoder.yml @@ -0,0 +1,24 @@ +name: Build (decoder) +on: [pull_request] + +jobs: + build: + 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 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2b404ec..87a1d5c 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -15,6 +15,11 @@ jobs: id: cargoFmt shell: bash run: cargo fmt --all -- --check + - name: Run fmt check - decoder + id: cargoFmtDecoder + shell: bash + working-directory: decoder + run: cargo fmt --all -- --check clippy: name: Cargo clippy runs-on: ubuntu-latest @@ -26,3 +31,17 @@ jobs: id: cargoClippy shell: bash run: cargo clippy --workspace --all-features -- -D warnings + clippyDecoder: + name: Cargo clippy (decoder) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-directories: decoder + - name: Run clippy check - decoder + id: cargoClippyDecoder + shell: bash + working-directory: decoder + run: cargo clippy --workspace --all-features -- -D warnings diff --git a/.gitignore b/.gitignore index ea8c4bf..133154d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/decoder/target diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cec6e5..8d9198c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,8 +57,6 @@ Then make a new PR for the release and get it approved. The automated release PR generation functionality is not used here. -This requires a crates.io token in GitHub secrets for the repo. Currently the "token" is literally the string `secret` but I will put a more realistic token once the repo is public. - [conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/ ## Code of Conduct diff --git a/Cargo.lock b/Cargo.lock index 144b633..f02506b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,7 +471,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "h2 0.4.8", + "h2 0.4.9", "http 0.2.12", "http 1.3.1", "http-body 0.4.6", @@ -721,9 +721,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.18" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -1246,9 +1246,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", @@ -1405,7 +1405,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.8", + "h2 0.4.9", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -1705,9 +1705,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -1991,9 +1991,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -2069,13 +2069,12 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core 0.9.3", - "zerocopy", ] [[package]] @@ -2176,7 +2175,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.8", + "h2 0.4.9", "http 1.3.1", "http-body 1.0.1", "http-body-util", diff --git a/README.md b/README.md index 45de189..92cf609 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,52 @@ emit `tokio.PollCatchV1` events this way: } ``` +## Decoder + +The `decoder` directory in the Git repository contains a decoder that can be used to view JFR files, especially with PollCatch information. + +The decoder is NOT intended right now to be used in production. In particular, it uses the [`jfrs`] crate for parsing `.jfr` files, and while that crate seems to be purely safe Rust, to my knowledge it has not been audited for security and probably contains at least denial-of-service issues if not worse. + +If you want to use the decoder for anything but debugging on trusted `.jfr` files, you bear full responsibility for the consequences. + +To use the decoder, you can download the `.zip` file from s3, and then run it: +``` +aws s3 cp s3://your-bucket/YOUR_PROFILE.zip . +# the last parameter is the long poll threshold +./decoder/target/release/pollcatch-decoder longpolls --zip profile_WHATEVER_*.zip 500us +``` + +The output should look like this +``` +[930689.953296] thread 60898 - poll of 8885us + - 1: libpthread-2.26.so.__nanosleep + - 2: simple.std::thread::sleep_ms + - 3: simple.simple::slow::accidentally_slow + - 4: simple.simple::slow::accidentally_slow_2 + - 5: simple.simple::slow::run::{{closure}}::{{closure}} + - 16 more frame(s) (pass --stack-depth=21 to show) + +[930691.953294] thread 60898 - poll of 736us + - 1: libpthread-2.26.so.__nanosleep + - 2: simple.std::thread::sleep_ms + - 3: simple.simple::slow::accidentally_slow + - 4: simple.simple::slow::accidentally_slow_2 + - 5: simple.simple::slow::run::{{closure}}::{{closure}} + - 16 more frame(s) (pass --stack-depth=21 to show) + +[930709.953293] thread 60898 - poll of 2736us + - 1: libpthread-2.26.so.__nanosleep + - 2: simple.std::thread::sleep_ms + - 3: simple.simple::slow::accidentally_slow + - 4: simple.simple::slow::accidentally_slow_2 + - 5: simple.simple::slow::run::{{closure}}::{{closure}} + - 16 more frame(s) (pass --stack-depth=21 to show) +``` + +If it does not work, make sure you are using the most recent version of `async-profiler` and that you enabled the pollcatch hooks. + +[`jfrs`]: https://docs.rs/jfrs + ## Security See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. diff --git a/decoder/Cargo.lock b/decoder/Cargo.lock new file mode 100644 index 0000000..1d731e9 --- /dev/null +++ b/decoder/Cargo.lock @@ -0,0 +1,1044 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.5.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "jfrs" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59de0339f8ebac388693c55e42e0f40ff229ba618f6b8459acf832b15192457" +dependencies = [ + "rustc-hash", + "serde", +] + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "pollcatch-decoder" +version = "0.0.1" +dependencies = [ + "anyhow", + "clap", + "humantime", + "jfrs", + "serde", + "tracing", + "tracing-subscriber", + "zip", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "flate2", + "getrandom", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/decoder/Cargo.toml b/decoder/Cargo.toml new file mode 100644 index 0000000..b23f4f3 --- /dev/null +++ b/decoder/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pollcatch-decoder" +version = "0.0.1" +edition = "2021" +license = "MIT" +description = "finds long Tokio polls, CLI" +homepage = "https://github.com/async-profiler/rust-agent" +documentation = "https://docs.rs/async-profiler-agent" +readme = "README.md" +keywords = ["timing"] + +[dependencies] +jfrs = "0.2" +clap = { version="4", features= [ "derive" ] } +anyhow = "1" +humantime = "2" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["fmt"] } +zip = "2.6" +serde = "1" diff --git a/decoder/src/main.rs b/decoder/src/main.rs new file mode 100644 index 0000000..a68f939 --- /dev/null +++ b/decoder/src/main.rs @@ -0,0 +1,538 @@ +use std::{ffi::OsString, fs::File, io::Cursor}; + +use clap::{Parser, Subcommand}; +use jfrs::reader::{ + event::Accessor, + type_descriptor::TypeDescriptor, + value_descriptor::{Primitive, ValueDescriptor}, + Chunk, JfrReader, +}; +use std::io::{Read, Seek}; +use std::time::Duration; + +#[derive(Debug, Parser)] +#[command(name = "pollcatch-decoder")] +#[command(about = "Find slow polls from a JFR")] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Print long polls from a JFR file + Longpolls { + /// JFR file to read from + jfr_file: OsString, + /// If true, unzip first + #[arg(long)] + zip: bool, + /// Poll duration to mark from + #[clap(value_parser = humantime::parse_duration)] + min_length: Duration, + #[arg(long, default_value = "5")] + stack_depth: usize, + }, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct PollEventKey { + tid: u32, + clock_start: u64, + duration: u64, +} + +fn extract_async_profiler_jfr_from_zip(file: File) -> anyhow::Result>> { + let mut zip = zip::ZipArchive::new(file)?; + + for i in 0..zip.len() { + let mut file = zip.by_index(i)?; + if file.name() == "async_profiler_dump_0.jfr" { + let mut buf = vec![]; + std::io::copy(&mut file, &mut buf)?; + return Ok(Some(buf)); + } + } + + Ok(None) +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + tracing_subscriber::fmt::init(); + match cli.command { + Commands::Longpolls { + jfr_file, + min_length, + stack_depth, + zip, + } => { + let mut jfr_file = std::fs::File::open(jfr_file)?; + let samples = match zip { + false => jfr_samples(&mut jfr_file, min_length)?, + true => { + if let Some(data) = extract_async_profiler_jfr_from_zip(jfr_file)? { + jfr_samples(&mut Cursor::new(&data), min_length)? + } else { + anyhow::bail!("no async_profiler_dump_0.jfr file found"); + } + } + }; + print_samples(samples, stack_depth); + Ok(()) + } + } +} + +fn symbol_to_string(s: Accessor<'_>) -> Option<&str> { + if let Some(sym) = s.get_field("string") { + if let Ok(val) = sym.value.try_into() { + return Some(val); + } + } + + None +} + +fn print_samples(samples: Vec, stack_depth: usize) { + for sample in samples { + if sample.frames.iter().any(|f| { + f.name.as_ref().is_some_and(|n| { + n.contains( + "::park_timeout", + ) + }) + }) { + // skip samples that are of sleeps + continue; + } + println!( + "[{:.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!( + " - {:3} more frame(s) (pass --stack-depth={} to show)", + sample.frames.len() - stack_depth, + sample.frames.len() + ); + break; + } + println!( + " - {:3}: {}.{}", + i + 1, + frame.class_name.as_deref().unwrap_or(""), + frame.name.as_deref().unwrap_or("") + ); + } + println!(); + } +} + +struct Sample { + delta_t: Duration, + start_time: Duration, + thread_id: i64, + frames: Vec, +} + +struct StackFrame { + class_name: Option, + name: Option, +} + +fn resolve_stack_trace(trace: Accessor<'_>) -> Vec { + let mut res = vec![]; + if let Some(frames) = trace.get_field("frames") { + if let Some(frames) = frames.as_iter() { + for frame in frames { + let mut class_name_s = None; + let mut name_s = None; + if let Some(method) = frame.get_field("method") { + if let Some(class) = method.get_field("type") { + if let Some(class_name) = class.get_field("name") { + class_name_s = symbol_to_string(class_name).map(|x| x.to_owned()); + } + } + if let Some(name) = method.get_field("name") { + name_s = symbol_to_string(name).map(|x| x.to_owned()); + } + } + res.push(StackFrame { + class_name: class_name_s, + name: name_s, + }); + } + } + } + res +} + +fn find_delta_t_from_clock(pr_map: &[PollEventKey], tid: i64, clock_start: i64) -> Option { + if let (Ok(tid), Ok(clock_start)) = (tid.try_into(), clock_start.try_into()) { + let partition_point = pr_map + .partition_point(|x| x.tid < tid || (tid == x.tid && x.clock_start <= clock_start)); + if let Some(index) = partition_point.checked_sub(1) { + let bound = pr_map[index]; + let inside = tid == bound.tid + && bound.clock_start < clock_start + && clock_start - bound.clock_start < bound.duration; + if inside { + return Some(clock_start - bound.clock_start); + } + } + None + } else { + None + } +} + +fn process_sample( + chunk: &Chunk, + tys: &JfrTypeInfo, + pr_map: &[PollEventKey], + sampled_thread: Option<&ValueDescriptor>, + stacktrace: Option<&ValueDescriptor>, + start_time_ticks: i64, + long_poll_duration: u128, +) -> Option { + let mut delta_t = 0; + let mut thread_id = !0; + if let Some(ValueDescriptor::Object(st)) = sampled_thread { + if let Some(tid) = st.fields.get(tys.os_thread_index).and_then(as_long) { + thread_id = tid; + } + } + if delta_t == 0 { + if let Some(delta_t_) = find_delta_t_from_clock(pr_map, thread_id, start_time_ticks) { + delta_t = delta_t_; + } + } + + let delta_t_micros = (delta_t as u128) * 1000000 / (chunk.header.ticks_per_second as u128); + if delta_t_micros < long_poll_duration { + return None; + } + stacktrace.map(|trace| Sample { + thread_id, + start_time: Duration::from_nanos( + ((start_time_ticks as u128) * 1_000_000_000 / (chunk.header.ticks_per_second as u128)) + as u64, + ), + delta_t: Duration::from_micros(delta_t_micros as u64), + frames: resolve_stack_trace(Accessor::new(chunk, trace)), + }) +} + +struct JfrTypeInfo { + // profiler.WallClockSample + wall_clock_sample: Option, + wcs_start_time_index: usize, + wcs_stacktrace_index: usize, + wcs_sampled_thread_index: usize, + + // jdk.ExecutionSample + execution_sample: Option, + exs_start_time_index: usize, + exs_stacktrace_index: usize, + exs_sampled_thread_index: usize, + + // jdk.ActiveSetting + active_setting: Option, + active_setting_name_index: usize, + active_setting_value_index: usize, + + // profiler.UserEvent + user_event: Option, + user_event_type_index: usize, + user_event_start_time_index: usize, + user_event_event_thread_index: usize, + user_event_data_index: usize, + + // profiler.UserEventType + user_event_type_name: usize, + + // java.lang.Thread + os_thread_index: usize, +} + +impl JfrTypeInfo { + fn new() -> Self { + JfrTypeInfo { + wall_clock_sample: None, + wcs_start_time_index: !0, + wcs_stacktrace_index: !0, + wcs_sampled_thread_index: !0, + execution_sample: None, + exs_start_time_index: !0, + exs_stacktrace_index: !0, + exs_sampled_thread_index: !0, + active_setting: None, + active_setting_name_index: !0, + active_setting_value_index: !0, + user_event: None, + user_event_type_index: !0, + user_event_start_time_index: !0, + user_event_event_thread_index: !0, + user_event_data_index: !0, + user_event_type_name: !0, + os_thread_index: !0, + } + } + + fn load_type_descriptor(&mut self, ty: &TypeDescriptor) { + match ty.name() { + "profiler.WallClockSample" => { + self.wall_clock_sample = Some(ty.class_id); + for (i, field) in ty.fields.iter().enumerate() { + match field.name() { + "startTime" => self.wcs_start_time_index = i, + "stackTrace" => self.wcs_stacktrace_index = i, + "sampledThread" => self.wcs_sampled_thread_index = i, + _ => {} + } + } + } + "profiler.UserEvent" => { + self.user_event = Some(ty.class_id); + for (i, field) in ty.fields.iter().enumerate() { + match field.name() { + "type" => self.user_event_type_index = i, + "startTime" => self.user_event_start_time_index = i, + "eventThread" => self.user_event_event_thread_index = i, + "data" => self.user_event_data_index = i, + _ => {} + } + } + } + "profiler.types.UserEventType" => { + for (i, field) in ty.fields.iter().enumerate() { + #[allow(clippy::single_match)] + match field.name() { + "name" => self.user_event_type_name = i, + _ => {} + } + } + } + "jdk.ExecutionSample" => { + self.execution_sample = Some(ty.class_id); + for (i, field) in ty.fields.iter().enumerate() { + match field.name() { + "startTime" => self.exs_start_time_index = i, + "stackTrace" => self.exs_stacktrace_index = i, + "sampledThread" => self.exs_sampled_thread_index = i, + _ => {} + } + } + } + "java.lang.Thread" => { + for (i, field) in ty.fields.iter().enumerate() { + #[allow(clippy::single_match)] + match field.name() { + "osThreadId" => self.os_thread_index = i, + _ => {} + } + } + } + "jdk.ActiveSetting" => { + self.active_setting = Some(ty.class_id); + for (i, field) in ty.fields.iter().enumerate() { + match field.name() { + "name" => self.active_setting_name_index = i, + "value" => self.active_setting_value_index = i, + _ => {} + } + } + } + _ => {} + } + } +} + +fn as_object(x: &ValueDescriptor) -> Option<&jfrs::reader::value_descriptor::Object> { + match x { + ValueDescriptor::Object(o) => Some(o), + _ => None, + } +} + +fn as_string(x: &ValueDescriptor) -> Option<&str> { + match x { + ValueDescriptor::Primitive(Primitive::String(s)) => Some(s), + _ => None, + } +} + +fn as_long(x: &ValueDescriptor) -> Option { + match x { + ValueDescriptor::Primitive(Primitive::Long(i)) => Some(*i), + _ => None, + } +} + +fn resolve_field<'a>( + chunk: &'a Chunk, + o: &'a jfrs::reader::value_descriptor::Object, + index: usize, +) -> Option<&'a ValueDescriptor> { + o.fields + .get(index) + .and_then(|st| Accessor::new(chunk, st).resolve()) + .map(|a| a.value) +} + +fn poll_event_from_user_event( + chunk: &Chunk, + tys: &JfrTypeInfo, + event: jfrs::reader::event::Event<'_>, +) -> Option { + let event = as_object(event.value().value)?; + let ty = as_object(resolve_field(chunk, event, tys.user_event_type_index)?)?; + + if as_string(ty.fields.get(tys.user_event_type_name)?) == Some("tokio.PollcatchV1") { + let start_time_ticks = event + .fields + .get(tys.user_event_start_time_index) + .and_then(as_long) + .unwrap_or(0); + let mut thread_id = 0; + let event_thread = + resolve_field(chunk, event, tys.user_event_event_thread_index).and_then(as_object); + if let Some(et) = event_thread { + if let Some(tid) = et.fields.get(tys.os_thread_index).and_then(as_long) { + thread_id = tid; + } + } + let data = if let Some(s) = event + .fields + .get(tys.user_event_data_index) + .and_then(as_string) + { + // convert from "pseudo latin 1" to Vec + s.chars().map(|c| c as u32 as u8).collect::>() + } else { + vec![] + }; + + let before = data + .get(0..8) + .map_or(0, |x| u64::from_le_bytes(x.try_into().unwrap())); + let end = data + .get(8..16) + .map_or(0, |x| u64::from_le_bytes(x.try_into().unwrap())); + let _clock_end = data + .get(16..24) + .map_or(0, |x| u64::from_le_bytes(x.try_into().unwrap())); + let duration = end.saturating_sub(before); + + Some(PollEventKey { + tid: thread_id as u32, + clock_start: (start_time_ticks as u64).saturating_sub(duration), + duration: duration as u64, + }) + } else { + None + } +} + +fn jfr_samples(reader: &mut T, long_poll_duration: Duration) -> anyhow::Result> +where + T: Read + Seek, +{ + let mut jfr_reader = JfrReader::new(reader); + let long_poll_duration = long_poll_duration.as_micros(); + + let mut samples = vec![]; + let mut tys = JfrTypeInfo::new(); + for chunk in jfr_reader.chunks() { + let (mut c_rdr, c) = chunk?; + for ty in c.metadata.type_pool.get_types() { + tys.load_type_descriptor(ty); + } + let mut pr_map = &const { Vec::new() }; + let mut jfr_pr_map = vec![]; + for event in c_rdr.events_from_offset(&c, 0) { + let event: jfrs::reader::event::Event<'_> = event?; + if Some(event.class.class_id) == tys.user_event { + if let Some(event) = poll_event_from_user_event(&c, &tys, event) { + jfr_pr_map.push(event); + } + } + } + jfr_pr_map.sort(); + for event in c_rdr.events_from_offset(&c, 0) { + let event: jfrs::reader::event::Event<'_> = event?; + if Some(event.class.class_id) == tys.active_setting { + if let ValueDescriptor::Object(o) = event.value().value { + let name = + resolve_field(&c, o, tys.active_setting_name_index).and_then(as_string); + let value = + resolve_field(&c, o, tys.active_setting_value_index).and_then(as_string); + if let (Some("clock"), Some(value)) = (name, value) { + if value == "tsc" { + pr_map = &jfr_pr_map; + } else { + anyhow::bail!("decoder only supports tsc profiles, not {value:?}"); + } + } + } + } + if Some(event.class.class_id) == tys.wall_clock_sample { + if let Some(o) = as_object(event.value().value) { + let start_time_ticks = o + .fields + .get(tys.wcs_start_time_index) + .and_then(as_long) + .unwrap_or(0); + let sampled_thread = o + .fields + .get(tys.wcs_sampled_thread_index) + .and_then(|st| Accessor::new(&c, st).resolve()) + .map(|a| a.value); + let stacktrace = o.fields.get(tys.wcs_stacktrace_index); + if let Some(sample) = process_sample( + &c, + &tys, + pr_map, + sampled_thread, + stacktrace, + start_time_ticks, + long_poll_duration, + ) { + samples.push(sample); + } + } + } + if Some(event.class.class_id) == tys.execution_sample { + if let Some(o) = as_object(event.value().value) { + let start_time_ticks = o + .fields + .get(tys.exs_start_time_index) + .and_then(as_long) + .unwrap_or(0); + let sampled_thread = o + .fields + .get(tys.exs_sampled_thread_index) + .and_then(|st| Accessor::new(&c, st).resolve()) + .map(|a| a.value); + let stacktrace = o.fields.get(tys.exs_stacktrace_index); + if let Some(sample) = process_sample( + &c, + &tys, + pr_map, + sampled_thread, + stacktrace, + start_time_ticks, + long_poll_duration, + ) { + samples.push(sample); + } + } + } + } + } + Ok(samples) +}