mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 10:53:49 +00:00
Compare commits
372 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e441b4024 | ||
|
|
76012dc568 | ||
|
|
a76d06f1f2 | ||
|
|
81ad77eadd | ||
|
|
9fad48890c | ||
|
|
d23121855a | ||
|
|
fdb4bb4819 | ||
|
|
8fd1db0d8d | ||
|
|
77140be5fa | ||
|
|
7b50b1d834 | ||
|
|
b653d28eae | ||
|
|
9e4f2a6af6 | ||
|
|
8910a7a462 | ||
|
|
915b09067c | ||
|
|
1a5d74e23c | ||
|
|
809a19ce8f | ||
|
|
fcdb4aeec7 | ||
|
|
9e874cb94b | ||
|
|
f099abe619 | ||
|
|
d3dde7e5e7 | ||
|
|
9b5dc907a5 | ||
|
|
5da01c0a44 | ||
|
|
b8a60e66de | ||
|
|
02c5934300 | ||
|
|
29b78f4670 | ||
|
|
ff0d86b8e7 | ||
|
|
aa0305965f | ||
|
|
4397b70894 | ||
|
|
8e0b0953f6 | ||
|
|
db9c4c4c56 | ||
|
|
3d28531e26 | ||
|
|
bfdaa44260 | ||
|
|
ab6a7c9bd5 | ||
|
|
104b7bda5a | ||
|
|
8d77e0909c | ||
|
|
a1354b6a75 | ||
|
|
0d0f0f0c67 | ||
|
|
752b79ec4e | ||
|
|
48a97b64e7 | ||
|
|
4e13b8138b | ||
|
|
b1b5b9898f | ||
|
|
6ab9f83aec | ||
|
|
49d08fd068 | ||
|
|
a237cad115 | ||
|
|
50e63b7ad6 | ||
|
|
c2fed3b63a | ||
|
|
d29cb86bd0 | ||
|
|
326d797d5a | ||
|
|
cb95d37b84 | ||
|
|
e799c2a665 | ||
|
|
56a50a8002 | ||
|
|
f483c166ba | ||
|
|
3c26f5138a | ||
|
|
02e92eb45c | ||
|
|
902d17d0b1 | ||
|
|
a97cf59f5b | ||
|
|
5fc3c975fe | ||
|
|
e79c44eb38 | ||
|
|
77a308a7b9 | ||
|
|
d1de831e6d | ||
|
|
847504dfd4 | ||
|
|
dcc3ffd083 | ||
|
|
0a5ac270a0 | ||
|
|
62b2387a80 | ||
|
|
409a3e3b19 | ||
|
|
26954081d3 | ||
|
|
98ad173d24 | ||
|
|
6e014258e3 | ||
|
|
625dcd2615 | ||
|
|
a852bdd22e | ||
|
|
eb2879939b | ||
|
|
db457f3f8b | ||
|
|
25587664ab | ||
|
|
14c495dc53 | ||
|
|
27e2d4cb4b | ||
|
|
3c33c755be | ||
|
|
7a52d79b16 | ||
|
|
117594bb4d | ||
|
|
89bf3eabc0 | ||
|
|
f9138bd7f6 | ||
|
|
557a15f47f | ||
|
|
d46d6c67c0 | ||
|
|
03dc1b9c98 | ||
|
|
5d24f85d1b | ||
|
|
cb1c00013f | ||
|
|
e53eab62e4 | ||
|
|
d8f1b9c15a | ||
|
|
567b97d223 | ||
|
|
c7dd73ca6e | ||
|
|
bbc1b3270f | ||
|
|
3bdf9db876 | ||
|
|
c6a43ae2ee | ||
|
|
6c9b71594c | ||
|
|
1cb2c05161 | ||
|
|
6dfd2159fe | ||
|
|
bdceedda58 | ||
|
|
f57d3dcfda | ||
|
|
39b0bdb5e4 | ||
|
|
ac2eabfe96 | ||
|
|
f64eed870a | ||
|
|
d51f445243 | ||
|
|
578e3a1162 | ||
|
|
6b35b46070 | ||
|
|
ac561f3ba7 | ||
|
|
7f4b6536c7 | ||
|
|
e3b7bfca22 | ||
|
|
c8de91df6b | ||
|
|
91691ce039 | ||
|
|
4a7ce8ce79 | ||
|
|
a8f20ebc79 | ||
|
|
62c1a799ef | ||
|
|
d350738229 | ||
|
|
ada44ee12e | ||
|
|
9fc0495f86 | ||
|
|
db1ca37a2e | ||
|
|
4ffdb05d3a | ||
|
|
cdb8704156 | ||
|
|
c722b3972a | ||
|
|
48703edee7 | ||
|
|
63799a6055 | ||
|
|
ebda293a42 | ||
|
|
69c0340a08 | ||
|
|
45074592cf | ||
|
|
32601bccd9 | ||
|
|
8c4824be7f | ||
|
|
b08bf2d574 | ||
|
|
b4b2218782 | ||
|
|
e1f149f3ae | ||
|
|
80808a3f1b | ||
|
|
7eaefdb18f | ||
|
|
5e4a402c7e | ||
|
|
b0a44524ba | ||
|
|
ed092da71b | ||
|
|
58c62fe4e8 | ||
|
|
bdaefa9a3b | ||
|
|
8168c7dc91 | ||
|
|
98a2006386 | ||
|
|
389d6c5daa | ||
|
|
03e6fc5a17 | ||
|
|
f1e2b96a2f | ||
|
|
b5634b9d88 | ||
|
|
32b5fd8e3c | ||
|
|
b7dfd74a63 | ||
|
|
ed30401cc2 | ||
|
|
d4bee9647f | ||
|
|
d93477f680 | ||
|
|
696087c2ab | ||
|
|
1e8301e831 | ||
|
|
64d9f98a0f | ||
|
|
2613894b85 | ||
|
|
b8c8db45d7 | ||
|
|
14f58ed2c7 | ||
|
|
7fa11e768b | ||
|
|
74ecedc671 | ||
|
|
63f2539e5e | ||
|
|
31261ea7be | ||
|
|
9cec0765cd | ||
|
|
55da899511 | ||
|
|
56ae519224 | ||
|
|
733cf7c668 | ||
|
|
ee3ef243d3 | ||
|
|
28357c2fb4 | ||
|
|
2459d7eac4 | ||
|
|
cc1682d20a | ||
|
|
9ec48d9666 | ||
|
|
e2abcd2238 | ||
|
|
9ae31b0e91 | ||
|
|
a836ad6f89 | ||
|
|
c6f11d2673 | ||
|
|
989c3747e2 | ||
|
|
ce9ebc2b63 | ||
|
|
52cdc138d0 | ||
|
|
d09be06029 | ||
|
|
859c36ef9c | ||
|
|
cd084b5a97 | ||
|
|
1505345ee9 | ||
|
|
8547d642ab | ||
|
|
fa989850b6 | ||
|
|
1e4738ba7b | ||
|
|
80a3c66969 | ||
|
|
e1d5df7ffd | ||
|
|
6ca5ad2c93 | ||
|
|
7af24609eb | ||
|
|
80302eb43d | ||
|
|
0396bc7d1b | ||
|
|
b29bfd3f3a | ||
|
|
93b8171601 | ||
|
|
721225393e | ||
|
|
58a3cb0d25 | ||
|
|
6754312e83 | ||
|
|
daf844397e | ||
|
|
5e0021a99f | ||
|
|
37c56c44bb | ||
|
|
88dd2345ea | ||
|
|
ded970fd50 | ||
|
|
796aff5555 | ||
|
|
d36e8e91f9 | ||
|
|
323c02e0d7 | ||
|
|
53cfd2b8b1 | ||
|
|
4431121760 | ||
|
|
4a88ee445f | ||
|
|
ce0fc0a2d9 | ||
|
|
768b437593 | ||
|
|
20fa8564c4 | ||
|
|
aaf24effe8 | ||
|
|
3bbeed6267 | ||
|
|
cb51be8eb3 | ||
|
|
7749b72e73 | ||
|
|
7112bc14e5 | ||
|
|
56ca88677b | ||
|
|
8a258d31a5 | ||
|
|
e33a01e0c6 | ||
|
|
5d5138ea61 | ||
|
|
10b5ad0ee5 | ||
|
|
b5258cca2c | ||
|
|
0718a09e0e | ||
|
|
fe173c4101 | ||
|
|
8032daa49d | ||
|
|
fa8b8f8072 | ||
|
|
85f3a68c56 | ||
|
|
60ce15569a | ||
|
|
9838ddb693 | ||
|
|
2f341043ef | ||
|
|
ee55fbe17b | ||
|
|
d7a2a4fc8b | ||
|
|
e9b7747015 | ||
|
|
b96e07b001 | ||
|
|
ba00ca26c1 | ||
|
|
9ed175d73e | ||
|
|
b287816559 | ||
|
|
9a979a712d | ||
|
|
42442ed593 | ||
|
|
432d622aa4 | ||
|
|
f477f8d4c0 | ||
|
|
456ff57115 | ||
|
|
5ec28a86b2 | ||
|
|
e7cd6ee6fb | ||
|
|
8ba8fc748c | ||
|
|
5f37bf3ad6 | ||
|
|
26e9c7aef2 | ||
|
|
8efba10acc | ||
|
|
e894420119 | ||
|
|
2ddd4d230c | ||
|
|
1398e7ef75 | ||
|
|
3456dd3d90 | ||
|
|
c0d45fecec | ||
|
|
7e5f8a03f3 | ||
|
|
ce91abe6d9 | ||
|
|
4ba7524d7c | ||
|
|
734ef03ebf | ||
|
|
cf17e5efc3 | ||
|
|
35b420a941 | ||
|
|
ee4cd8e2b6 | ||
|
|
6f3134e99f | ||
|
|
c537b8298d | ||
|
|
605550cf96 | ||
|
|
a57bbf3587 | ||
|
|
9131344d61 | ||
|
|
2d0b9c9921 | ||
|
|
30905fda4c | ||
|
|
5a11a71db9 | ||
|
|
d79a82935f | ||
|
|
995048c2fd | ||
|
|
7331e30ed5 | ||
|
|
1f5e4ca8aa | ||
|
|
7ebed4e8e1 | ||
|
|
170451990b | ||
|
|
11a1d6d308 | ||
|
|
955413db8d | ||
|
|
8a701b41e3 | ||
|
|
dccd4c326a | ||
|
|
8771888d28 | ||
|
|
7d25210d2c | ||
|
|
9087bc57d8 | ||
|
|
3e1e1c614a | ||
|
|
b80d163699 | ||
|
|
9705b66864 | ||
|
|
3c33c6aa47 | ||
|
|
4af6b65268 | ||
|
|
03c7b36bca | ||
|
|
f8b15526b1 | ||
|
|
e519fd84c4 | ||
|
|
79e1017088 | ||
|
|
edbb9e7c03 | ||
|
|
7eb15cfcf0 | ||
|
|
eafbbaea8b | ||
|
|
d8228a1fec | ||
|
|
8a447481f8 | ||
|
|
791077354e | ||
|
|
e071b9fa14 | ||
|
|
cc340923b9 | ||
|
|
9c333219b5 | ||
|
|
9acf8c1648 | ||
|
|
5570afed9d | ||
|
|
975a506d83 | ||
|
|
09fb14bd87 | ||
|
|
3256d824de | ||
|
|
cc98710a6f | ||
|
|
452f14c3d2 | ||
|
|
be8bba1900 | ||
|
|
65b5356ace | ||
|
|
e91363c05a | ||
|
|
3e47bf7551 | ||
|
|
9447068af3 | ||
|
|
7e750825da | ||
|
|
eda5779552 | ||
|
|
fc3b1ca84f | ||
|
|
d13de48c0a | ||
|
|
552c699687 | ||
|
|
0fcc4d9bac | ||
|
|
868bfec2a5 | ||
|
|
4a77d68bcb | ||
|
|
a38a375dc6 | ||
|
|
6bcd23fcf0 | ||
|
|
8d2847a032 | ||
|
|
e0998af713 | ||
|
|
01b3e6c517 | ||
|
|
def6eb4b1c | ||
|
|
4032c56caf | ||
|
|
9b789f6516 | ||
|
|
6ddaf9ab71 | ||
|
|
11131499ab | ||
|
|
d2abac1c30 | ||
|
|
b7e9079b52 | ||
|
|
ff49ccccb7 | ||
|
|
7dd075cca6 | ||
|
|
bd8078bc11 | ||
|
|
1622fe5d72 | ||
|
|
44d7941728 | ||
|
|
d23b40048b | ||
|
|
3b2db709ff | ||
|
|
096fc88c82 | ||
|
|
4b0303916d | ||
|
|
9fb2ca800a | ||
|
|
d917cfdb63 | ||
|
|
f2006f3da1 | ||
|
|
c30b22f204 | ||
|
|
f48ebcc72b | ||
|
|
339aee5cfc | ||
|
|
cde3fae978 | ||
|
|
91eab91634 | ||
|
|
3df00e3439 | ||
|
|
c66ac2cfd0 | ||
|
|
f236482228 | ||
|
|
3ff315ea8f | ||
|
|
308074a9eb | ||
|
|
685da8d84f | ||
|
|
bd7bf9726e | ||
|
|
034677435d | ||
|
|
89f7d34456 | ||
|
|
ec8a40431a | ||
|
|
81bc1f2df2 | ||
|
|
40ff09a14f | ||
|
|
d6de541799 | ||
|
|
b807987f1d | ||
|
|
02875138f1 | ||
|
|
d1c19d1904 | ||
|
|
cc2307b92c | ||
|
|
b5d89fef29 | ||
|
|
646a92e2a0 | ||
|
|
bcd2375f39 | ||
|
|
3cbe6aec2f | ||
|
|
2d51c07b23 | ||
|
|
32ead969c1 | ||
|
|
de55fadbba | ||
|
|
da0ac08c64 | ||
|
|
5dd9e86a1d | ||
|
|
81583b9af3 | ||
|
|
7ec5c195e7 | ||
|
|
cb0f1eb72d | ||
|
|
051424890a | ||
|
|
34daf4f540 |
23
.github/workflows/ci.yml
vendored
Normal file
23
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'corretto'
|
||||
java-version: '11'
|
||||
- run: sudo sysctl kernel.perf_event_paranoid=1
|
||||
- run: make -j`nproc`
|
||||
- run: make test
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: |
|
||||
build/bin/
|
||||
build/lib/
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/build/
|
||||
/nbproject/
|
||||
/out/
|
||||
/target/
|
||||
/.idea/
|
||||
/test/*.class
|
||||
.vscode
|
||||
|
||||
11
.travis.yml
11
.travis.yml
@@ -1,11 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
dist: precise
|
||||
|
||||
sudo: required
|
||||
|
||||
before_install:
|
||||
- sudo apt-get install default-jdk
|
||||
- sudo bash -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'
|
||||
|
||||
script: make && make test
|
||||
207
CHANGELOG.md
207
CHANGELOG.md
@@ -1,21 +1,222 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0-b1] - Early access
|
||||
## [3.0] - 2024-01-20
|
||||
|
||||
### Features
|
||||
- #724: Binary launcher `asprof`
|
||||
- #751: Profile non-Java processes
|
||||
- #795: AsyncGetCallTrace replacement
|
||||
- #719: Classify execution samples into categories in JFR converter
|
||||
- #855: `ctimer` mode for accurate profiling without perf_events
|
||||
- #740: Profile CPU + Wall clock together
|
||||
- #736: Show targets of vtable/itable calls
|
||||
- #777: Show JIT compilation task
|
||||
- #644: RISC-V port
|
||||
- #770: LoongArch64 port
|
||||
|
||||
### Improvements
|
||||
- #733: Make the same `libasyncProfiler` work with both glibc and musl
|
||||
- #734: Support raw PMU event descriptors
|
||||
- #759: Configure alternative profiling signal
|
||||
- #761: Parse dynamic linking structures
|
||||
- #723: `--clock` option to select JFR timestamp source
|
||||
- #750: `--jfrsync` may specify a list of JFR events
|
||||
- #849: Parse concatenated multi-chunk JFRs
|
||||
- #833: Time-to-safepoint JFR event
|
||||
- #832: Normalize names of hidden classes / lambdas
|
||||
- #864: Reduce size of HTML Flame Graph
|
||||
- #783: Shutdown asprof gracefully on SIGTERM
|
||||
- Better demangling of C++ and Rust symbols
|
||||
- DWARF unwinding for ARM64
|
||||
- `JfrReader` can parse in-memory buffer
|
||||
- Support custom events in `JfrReader`
|
||||
- An option to read JFR file by chunks
|
||||
- Record `GCHeapSummary` events in JFR
|
||||
|
||||
### Bug fixes
|
||||
- Workaround macOS crashes in SafeFetch
|
||||
- Fixed attach to OpenJ9 on macOS
|
||||
- Support `UseCompressedObjectHeaders` aka Lilliput
|
||||
- Fixed allocation profiling on JDK 20.0.x
|
||||
- Fixed context-switches profiling
|
||||
- Prefer ObjectSampler to TLAB hooks for allocation profiling
|
||||
- Improved accuracy of ObjectSampler in `--total` mode
|
||||
- Make Flame Graph status line and search results always visible
|
||||
- `loop` and `timeout` options did not work in some modes
|
||||
- Restart interrupted poll/epoll_wait syscalls
|
||||
- Fixed stack unwinding issues on ARM64
|
||||
- Workaround for stale jmethodIDs
|
||||
- Calculate ELF base address correctly
|
||||
- Do not dump redundant threads in a JFR chunk
|
||||
- `check` action prints result to a file
|
||||
- Annotate JFR unit types with `@ContentType`
|
||||
|
||||
## [2.9] - 2022-11-27
|
||||
|
||||
### Features
|
||||
- Java Heap leak profiler
|
||||
- `meminfo` command to print profiler's memory usage
|
||||
- Profiler API with embedded agent as a Maven artifact
|
||||
|
||||
### Improvements
|
||||
- `--include`/`--exclude` options in the FlameGraph converter
|
||||
- `--simple` and `--dot` options in jfr2flame converter
|
||||
- An option for agressive recovery of `[unknown_Java]` stack traces
|
||||
- Do not truncate signatures in collapsed format
|
||||
- Display inlined frames under a runtime stub
|
||||
|
||||
### Bug fixes
|
||||
- Profiler did not work with Homebrew JDK
|
||||
- Fixed allocation profiling on Zing
|
||||
- Various `jfrsync` fixes
|
||||
- Symbol parsing fixes
|
||||
- Attaching to a container on Linux 3.x could fail
|
||||
|
||||
## [2.8.3] - 2022-07-16
|
||||
|
||||
### Improvements
|
||||
- Support virtualized ARM64 macOS
|
||||
- A switch to generate auxiliary events by async-profiler or FlightRecorder in jfrsync mode
|
||||
|
||||
### Bug fixes
|
||||
- Could not recreate perf_events after the first failure
|
||||
- Handle different versions of Zing properly
|
||||
- Do not call System.loadLibrary, when libasyncProfiler is preloaded
|
||||
|
||||
## [2.8.2] - 2022-07-13
|
||||
|
||||
### Bug fixes
|
||||
- The same .so works with glibc and musl
|
||||
- dlopen hook did not work on Arch Linux
|
||||
- Fixed JDK 7 crash
|
||||
- Fixed CPU profiling on Zing
|
||||
|
||||
### Changes
|
||||
- Mark interpreted frames with `_[0]` in collapsed output
|
||||
- Double click selects a method name on a flame graph
|
||||
|
||||
## [2.8.1] - 2022-06-10
|
||||
|
||||
### Improvements
|
||||
- JFR to pprof converter (contributed by @NeQuissimus)
|
||||
- JFR converter improvements: time range, collapsed output, pattern highlighting
|
||||
- `%n` pattern in file names; limit number of output files
|
||||
- `--lib` to customize profiler library path in a container
|
||||
- `profiler.sh list` command now works without PID
|
||||
|
||||
### Bug fixes
|
||||
- Fixed crashes related to continuous profiling
|
||||
- Fixed Alpine/musl compatibility issues
|
||||
- Fixed incomplete collapsed output due to weird locale settings
|
||||
- Workaround for JDK-8185348
|
||||
|
||||
## [2.8] - 2022-05-09
|
||||
|
||||
### Features
|
||||
- Mark top methods as interpreted, compiled (C1/C2), or inlined
|
||||
- JVM TI based allocation profiling for JDK 11+
|
||||
- Embedded HTTP management server
|
||||
|
||||
### Improvements
|
||||
- Re-implemented stack recovery for better reliability
|
||||
- Add `loglevel` argument
|
||||
- Do not mmap perf page in `--all-user` mode
|
||||
- Distinguish runnable/sleeping threads in OpenJ9 wall-clock profiler
|
||||
- `--cpu` converter option to extract CPU profile from the wall-clock output
|
||||
|
||||
## [2.7] - 2022-02-14
|
||||
|
||||
### Features
|
||||
- Experimental support for OpenJ9 VM
|
||||
- DWARF stack unwinding
|
||||
|
||||
### Improvements
|
||||
- Better handling of VM threads (fixed missing JIT threads)
|
||||
- More reliable recovery from `not_walkable` AGCT failures
|
||||
- Do not accept unknown agent arguments
|
||||
|
||||
## [2.6] - 2022-01-09
|
||||
|
||||
### Features
|
||||
- Continuous profiling; `loop` and `timeout` options
|
||||
|
||||
### Improvements
|
||||
- Reliability improvements: avoid certain crashes and deadlocks
|
||||
- Smaller and faster agent library
|
||||
- Minor `jfr` and `jfrsync` enhancements (see the commit log)
|
||||
|
||||
## [2.5.1] - 2021-12-05
|
||||
|
||||
### Bug fixes
|
||||
- Prevent early unloading of libasyncProfiler.so
|
||||
- Read kernel symbols only for perf_events
|
||||
- Escape backslashes in flame graphs
|
||||
- Avoid duplicate categories in `jfrsync` mode
|
||||
- Fixed stack overflow in RedefineClasses
|
||||
- Fixed deadlock when flushing JFR
|
||||
|
||||
### Improvements
|
||||
- Support OpenJDK C++ Interpreter (aka Zero)
|
||||
- Allow reading incomplete JFR recordings
|
||||
|
||||
## [2.5] - 2021-10-01
|
||||
|
||||
### Features
|
||||
- macOS/ARM64 (aka Apple M1) port
|
||||
- PPC64LE port (contributed by @ghaug)
|
||||
- Profile low-privileged processes with perf_events (contributed by @Jongy)
|
||||
- Raw PMU events; kprobes & uprobes
|
||||
- Dump results in the middle of profiling session
|
||||
- Chunked JFR; support JFR files larger than 2 GB
|
||||
- Integrate async-profiler events with JDK Flight Recordings
|
||||
|
||||
### Improvements
|
||||
- Use RDTSC for JFR timestamps when possible
|
||||
- Show line numbers and bci in Flame Graphs
|
||||
- jfr2flame can produce Allocation and Lock flame graphs
|
||||
- Flame Graph title depends on the event and `--total`
|
||||
- Include profiler logs and native library list in JFR output
|
||||
- Lock profiling no longer requires JVM symbols
|
||||
- Better container support
|
||||
- Native function profiler can count the specified argument
|
||||
- An option to group threads by scheduling policy
|
||||
- An option to prepend library name to native symbols
|
||||
|
||||
### Notes
|
||||
- macOS build is provided as a fat binary that works both on x86-64 and ARM64
|
||||
- 32-bit binaries are no longer shipped. It is still possible to build them from sources
|
||||
- Dropped JDK 6 support (may still work though)
|
||||
|
||||
## [2.0] - 2021-03-14
|
||||
|
||||
### Features
|
||||
- Profile multiple events together (cpu + alloc + lock)
|
||||
- HTML 5 Flame Graphs: faster rendering, smaller size
|
||||
- JFR v2 output format, compatible with FlightRecorder API
|
||||
- JFR to Flame Graph converter
|
||||
- Automatically turn profiling on/off at `--begin`/`--end` functions
|
||||
- Time-to-safepoint profiling
|
||||
- Time-to-safepoint profiling: `--ttsp`
|
||||
|
||||
### Improvements
|
||||
- Unlimited frame buffer. Removed `-b` option and 64K stack traces limit
|
||||
- Record CPU load in JFR format
|
||||
- Additional JFR events: OS, CPU, and JVM information; CPU load
|
||||
- Record bytecode indices / line numbers
|
||||
- Native stack traces for Java events
|
||||
- Improved CLI experience
|
||||
- Better error handling; an option to log warnings/errors to a dedicated stream
|
||||
- Reduced the amount of unknown stack traces
|
||||
|
||||
### Changes
|
||||
- Removed non-ASL code. No more CDDL license
|
||||
|
||||
## [1.8.4] - 2021-02-24
|
||||
|
||||
### Improvements
|
||||
- Smaller and faster agent library
|
||||
|
||||
### Bug fixes
|
||||
- Fixed JDK 7 crash during wall-clock profiling
|
||||
|
||||
## [1.8.3] - 2021-01-06
|
||||
|
||||
### Improvements
|
||||
|
||||
134
Makefile
134
Makefile
@@ -1,26 +1,28 @@
|
||||
PROFILER_VERSION=2.0-b1
|
||||
JATTACH_VERSION=1.5
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
PROFILER_VERSION=3.0
|
||||
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
|
||||
LIB_PROFILER=libasyncProfiler.$(SOEXT)
|
||||
LIB_PROFILER_SO=libasyncProfiler.so
|
||||
JATTACH=jattach
|
||||
API_JAR=async-profiler.jar
|
||||
CONVERTER_JAR=converter.jar
|
||||
LAUNCHER=bin/asprof
|
||||
LIB_PROFILER=lib/libasyncProfiler.$(SOEXT)
|
||||
API_JAR=lib/async-profiler.jar
|
||||
CONVERTER_JAR=lib/converter.jar
|
||||
|
||||
CFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
CXXFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
INCLUDES=-I$(JAVA_HOME)/include
|
||||
CFLAGS=-O3 -fno-exceptions
|
||||
CXXFLAGS=-O3 -fno-exceptions -fno-omit-frame-pointer -fvisibility=hidden
|
||||
INCLUDES=-I$(JAVA_HOME)/include -Isrc/helper
|
||||
LIBS=-ldl -lpthread
|
||||
MERGE=true
|
||||
|
||||
JAVAC=$(JAVA_HOME)/bin/javac
|
||||
JAR=$(JAVA_HOME)/bin/jar
|
||||
JAVA_TARGET=8
|
||||
JAVAC_OPTIONS=--release $(JAVA_TARGET) -Xlint:-options
|
||||
|
||||
SOURCES := $(wildcard src/*.cpp)
|
||||
HEADERS := $(wildcard src/*.h)
|
||||
RESOURCES := $(wildcard src/res/*)
|
||||
JAVA_HELPER_CLASSES := $(wildcard src/helper/one/profiler/*.class)
|
||||
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
|
||||
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
|
||||
|
||||
@@ -29,15 +31,28 @@ ifeq ($(JAVA_HOME),)
|
||||
endif
|
||||
|
||||
OS:=$(shell uname -s)
|
||||
ifeq ($(OS), Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
ifeq ($(OS),Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/server
|
||||
INCLUDES += -I$(JAVA_HOME)/include/darwin
|
||||
SOEXT=dylib
|
||||
PACKAGE_EXT=zip
|
||||
OS_TAG=macos
|
||||
ifeq ($(FAT_BINARY),true)
|
||||
FAT_BINARY_FLAGS=-arch x86_64 -arch arm64 -mmacos-version-min=10.12
|
||||
CFLAGS += $(FAT_BINARY_FLAGS)
|
||||
CXXFLAGS += $(FAT_BINARY_FLAGS)
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)
|
||||
MERGE=false
|
||||
endif
|
||||
else
|
||||
CXXFLAGS += -Wl,-z,defs
|
||||
ifeq ($(MERGE),true)
|
||||
CXXFLAGS += -fwhole-program
|
||||
endif
|
||||
LIBS += -lrt
|
||||
INCLUDES += -I$(JAVA_HOME)/include/linux
|
||||
SOEXT=so
|
||||
PACKAGE_EXT=tar.gz
|
||||
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
|
||||
OS_TAG=linux-musl
|
||||
else
|
||||
@@ -50,63 +65,98 @@ ifeq ($(ARCH),x86_64)
|
||||
ARCH_TAG=x64
|
||||
else
|
||||
ifeq ($(findstring arm,$(ARCH)),arm)
|
||||
ARCH_TAG=arm
|
||||
else
|
||||
ifeq ($(findstring aarch64,$(ARCH)),aarch64)
|
||||
ARCH_TAG=aarch64
|
||||
ifeq ($(findstring 64,$(ARCH)),64)
|
||||
ARCH_TAG=arm64
|
||||
else
|
||||
ARCH_TAG=x86
|
||||
ARCH_TAG=arm32
|
||||
endif
|
||||
else ifeq ($(findstring aarch64,$(ARCH)),aarch64)
|
||||
ARCH_TAG=arm64
|
||||
else ifeq ($(ARCH),ppc64le)
|
||||
ARCH_TAG=ppc64le
|
||||
else ifeq ($(ARCH),riscv64)
|
||||
ARCH_TAG=riscv64
|
||||
else ifeq ($(ARCH),loongarch64)
|
||||
ARCH_TAG=loongarch64
|
||||
else
|
||||
ARCH_TAG=x86
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring $(ARCH_TAG),x86 x64 arm64))
|
||||
CXXFLAGS += -momit-leaf-frame-pointer
|
||||
endif
|
||||
|
||||
.PHONY: all release test clean
|
||||
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
.PHONY: all release test native clean
|
||||
|
||||
release: build $(PACKAGE_NAME).tar.gz
|
||||
all: build/bin build/lib build/$(LIB_PROFILER) build/$(LAUNCHER) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
|
||||
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
profiler.sh LICENSE *.md
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
|
||||
chmod -R 755 $(PACKAGE_DIR)
|
||||
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
|
||||
tar cvzf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
release: JAVA_TARGET=7
|
||||
|
||||
release: $(PACKAGE_NAME).$(PACKAGE_EXT)
|
||||
|
||||
$(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR)
|
||||
tar czf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
%.$(SOEXT): %.so
|
||||
-ln -s $(<F) $@
|
||||
$(PACKAGE_NAME).zip: $(PACKAGE_DIR)
|
||||
codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/$(LAUNCHER) $(PACKAGE_DIR)/$(LIB_PROFILER)
|
||||
ditto -c -k --keepParent $(PACKAGE_DIR) $@
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
$(PACKAGE_DIR): build/bin build/lib \
|
||||
build/$(LIB_PROFILER) build/$(LAUNCHER) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
LICENSE *.md
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp -RP build/* LICENSE *.md $(PACKAGE_DIR)/
|
||||
chmod -R 755 $(PACKAGE_DIR)
|
||||
chmod 644 $(PACKAGE_DIR)/lib/* $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md
|
||||
|
||||
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
|
||||
build/%:
|
||||
mkdir -p $@
|
||||
|
||||
build/$(JATTACH): src/jattach/jattach.c
|
||||
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
|
||||
build/$(LAUNCHER): src/launcher/* src/jattach/* src/fdtransfer.h
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" -o $@ src/launcher/*.cpp src/jattach/*.c
|
||||
strip $@
|
||||
|
||||
build/$(LIB_PROFILER): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES)
|
||||
ifeq ($(MERGE),true)
|
||||
for f in src/*.cpp; do echo '#include "'$$f'"'; done |\
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ -xc++ - $(LIBS)
|
||||
else
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
|
||||
endif
|
||||
|
||||
build/$(API_JAR): $(API_SOURCES)
|
||||
mkdir -p build/api
|
||||
$(JAVAC) -source $(JAVAC_RELEASE_VERSION) -target $(JAVAC_RELEASE_VERSION) -d build/api $^
|
||||
$(JAR) cvf $@ -C build/api .
|
||||
$(JAVAC) $(JAVAC_OPTIONS) -d build/api $(API_SOURCES)
|
||||
$(JAR) cf $@ -C build/api .
|
||||
$(RM) -r build/api
|
||||
|
||||
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) src/converter/MANIFEST.MF
|
||||
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) $(RESOURCES)
|
||||
mkdir -p build/converter
|
||||
$(JAVAC) -source 7 -target 7 -d build/converter $(CONVERTER_SOURCES)
|
||||
$(JAR) cvfm $@ src/converter/MANIFEST.MF -C build/converter .
|
||||
$(JAVAC) $(JAVAC_OPTIONS) -d build/converter $(CONVERTER_SOURCES)
|
||||
$(JAR) cfe $@ Main -C build/converter . -C src/res .
|
||||
$(RM) -r build/converter
|
||||
|
||||
%.class: %.java
|
||||
$(JAVAC) -source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options -g:none $^
|
||||
|
||||
test: all
|
||||
test/smoke-test.sh
|
||||
test/thread-smoke-test.sh
|
||||
test/alloc-smoke-test.sh
|
||||
test/load-library-test.sh
|
||||
test/fdtransfer-smoke-test.sh
|
||||
echo "All tests passed"
|
||||
|
||||
native:
|
||||
mkdir -p native/linux-x64 native/linux-arm64 native/macos
|
||||
tar xfO async-profiler-$(PROFILER_VERSION)-linux-x64.tar.gz */build/libasyncProfiler.so > native/linux-x64/libasyncProfiler.so
|
||||
tar xfO async-profiler-$(PROFILER_VERSION)-linux-arm64.tar.gz */build/libasyncProfiler.so > native/linux-arm64/libasyncProfiler.so
|
||||
unzip -p async-profiler-$(PROFILER_VERSION)-macos.zip */build/libasyncProfiler.so > native/macos/libasyncProfiler.so
|
||||
|
||||
clean:
|
||||
$(RM) -r build
|
||||
|
||||
633
README.md
633
README.md
@@ -1,53 +1,634 @@
|
||||
# async-profiler
|
||||
|
||||
[](https://travis-ci.org/jvm-profiling-tools/async-profiler)
|
||||
|
||||
This project is a low overhead sampling profiler for Java
|
||||
that does not suffer from [Safepoint bias problem](http://psy-lob-saw.blogspot.ru/2016/02/why-most-sampling-java-profilers-are.html).
|
||||
It features HotSpot-specific APIs to collect stack traces
|
||||
and to track memory allocations. The profiler works with
|
||||
OpenJDK, Oracle JDK and other Java runtimes based on the HotSpot JVM.
|
||||
OpenJDK and other Java runtimes based on the HotSpot JVM.
|
||||
|
||||
async-profiler can trace the following kinds of events:
|
||||
- CPU cycles
|
||||
- Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
|
||||
- Allocations in Java Heap
|
||||
- Contented lock attempts, including both Java object monitors and ReentrantLocks
|
||||
|
||||
## Usage
|
||||
|
||||
See our [Wiki](https://github.com/jvm-profiling-tools/async-profiler/wiki) or [3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr) to learn about all set of features.
|
||||
See our [Wiki](https://github.com/async-profiler/async-profiler/wiki) or
|
||||
[3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr)
|
||||
to learn about all features.
|
||||
|
||||
## Download
|
||||
|
||||
Latest release (1.8.3):
|
||||
Current release (3.0):
|
||||
|
||||
- Linux x64 (glibc): [async-profiler-1.8.3-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-x64.tar.gz)
|
||||
- Linux x86 (glibc): [async-profiler-1.8.3-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-x86.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-1.8.3-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-musl-x64.tar.gz)
|
||||
- Linux ARM: [async-profiler-1.8.3-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-arm.tar.gz)
|
||||
- Linux AArch64: [async-profiler-1.8.3-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-aarch64.tar.gz)
|
||||
- macOS x64: [async-profiler-1.8.3-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-macos-x64.tar.gz)
|
||||
- Linux x64: [async-profiler-3.0-linux-x64.tar.gz](https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz)
|
||||
- Linux arm64: [async-profiler-3.0-linux-arm64.tar.gz](https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-arm64.tar.gz)
|
||||
- macOS x64/arm64: [async-profiler-3.0-macos.zip](https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-macos.zip)
|
||||
- Converters between profile formats: [converter.jar](https://github.com/async-profiler/async-profiler/releases/download/v3.0/converter.jar)
|
||||
(JFR to Flame Graph, JFR to pprof, collapsed stacks to Flame Graph)
|
||||
|
||||
[Early access](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.0-b1) (2.0-b1):
|
||||
[Previous releases](https://github.com/async-profiler/async-profiler/releases)
|
||||
|
||||
- Linux x64 (glibc): [async-profiler-2.0-b1-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.0-b1/async-profiler-2.0-b1-linux-x64.tar.gz)
|
||||
- macOS x64: [async-profiler-2.0-b1-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.0-b1/async-profiler-2.0-b1-macos-x64.tar.gz)
|
||||
|
||||
[Previous releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
|
||||
async-profiler also comes bundled with IntelliJ IDEA Ultimate 2018.3 and later.
|
||||
For more information refer to [IntelliJ IDEA documentation](https://www.jetbrains.com/help/idea/cpu-and-allocation-profiling-basic-concepts.html).
|
||||
|
||||
## Supported platforms
|
||||
|
||||
- **Linux** / x64 / x86 / ARM / AArch64
|
||||
- **macOS** / x64
|
||||
| | Officially maintained builds | Other available ports |
|
||||
|-----------|------------------------------|-------------------------------------------|
|
||||
| **Linux** | x64, arm64 | x86, arm32, ppc64le, riscv64, loongarch64 |
|
||||
| **macOS** | x64, arm64 | |
|
||||
|
||||
Note: macOS profiling is limited to user space code only.
|
||||
## CPU profiling
|
||||
|
||||
In this mode profiler collects stack trace samples that include **Java** methods,
|
||||
**native** calls, **JVM** code and **kernel** functions.
|
||||
|
||||
The general approach is receiving call stacks generated by `perf_events`
|
||||
and matching them up with call stacks generated by `AsyncGetCallTrace`,
|
||||
in order to produce an accurate profile of both Java and native code.
|
||||
Additionally, async-profiler provides a workaround to recover stack traces
|
||||
in some [corner cases](https://bugs.openjdk.java.net/browse/JDK-8178287)
|
||||
where `AsyncGetCallTrace` fails.
|
||||
|
||||
This approach has the following advantages compared to using `perf_events`
|
||||
directly with a Java agent that translates addresses to Java method names:
|
||||
|
||||
* Does not require `-XX:+PreserveFramePointer`, which introduces
|
||||
performance overhead that can be sometimes as high as 10%.
|
||||
|
||||
* Does not require generating a map file for translating Java code addresses
|
||||
to method names.
|
||||
|
||||
* Displays interpreter frames.
|
||||
|
||||
* Does not produce large intermediate files (perf.data) for further processing in
|
||||
user space scripts.
|
||||
|
||||
If you wish to resolve frames within `libjvm`, the [debug symbols](#installing-debug-symbols) are required.
|
||||
|
||||
## ALLOCATION profiling
|
||||
|
||||
The profiler can be configured to collect call sites where the largest amount
|
||||
of heap memory is allocated.
|
||||
|
||||
async-profiler does not use intrusive techniques like bytecode instrumentation
|
||||
or expensive DTrace probes which have significant performance impact.
|
||||
It also does not affect Escape Analysis or prevent from JIT optimizations
|
||||
like allocation elimination. Only actual heap allocations are measured.
|
||||
|
||||
The profiler features TLAB-driven sampling. It relies on HotSpot-specific
|
||||
callbacks to receive two kinds of notifications:
|
||||
- when an object is allocated in a newly created TLAB;
|
||||
- when an object is allocated on a slow path outside TLAB.
|
||||
|
||||
Sampling interval can be adjusted with `--alloc` option.
|
||||
For example, `--alloc 500k` will take one sample after 500 KB of allocated
|
||||
space on average. Prior to JDK 11, intervals less than TLAB size will not take effect.
|
||||
|
||||
### Installing Debug Symbols
|
||||
|
||||
Prior to JDK 11, the allocation profiler required HotSpot debug symbols.
|
||||
Some OpenJDK distributions (Amazon Corretto, Liberica JDK, Azul Zulu)
|
||||
already have them embedded in `libjvm.so`, other OpenJDK builds typically
|
||||
provide debug symbols in a separate package. For example, to install
|
||||
OpenJDK debug symbols on Debian / Ubuntu, run:
|
||||
```
|
||||
# apt install openjdk-17-dbg
|
||||
```
|
||||
(replace `17` with the desired version of JDK).
|
||||
|
||||
On CentOS, RHEL and some other RPM-based distributions, this could be done with
|
||||
[debuginfo-install](http://man7.org/linux/man-pages/man1/debuginfo-install.1.html) utility:
|
||||
```
|
||||
# debuginfo-install java-1.8.0-openjdk
|
||||
```
|
||||
|
||||
On Gentoo the `icedtea` OpenJDK package can be built with the per-package setting
|
||||
`FEATURES="nostrip"` to retain symbols.
|
||||
|
||||
The `gdb` tool can be used to verify if debug symbols are properly installed for the `libjvm` library.
|
||||
For example, on Linux:
|
||||
```
|
||||
$ gdb $JAVA_HOME/lib/server/libjvm.so -ex 'info address UseG1GC'
|
||||
```
|
||||
This command's output will either contain `Symbol "UseG1GC" is at 0xxxxx`
|
||||
or `No symbol "UseG1GC" in current context`.
|
||||
|
||||
## Wall-clock profiling
|
||||
|
||||
`-e wall` option tells async-profiler to sample all threads equally every given
|
||||
period of time regardless of thread status: Running, Sleeping or Blocked.
|
||||
For instance, this can be helpful when profiling application start-up time.
|
||||
|
||||
Wall-clock profiler is most useful in per-thread mode: `-t`.
|
||||
|
||||
Example: `asprof -e wall -t -i 5ms -f result.html 8983`
|
||||
|
||||
## Java method profiling
|
||||
|
||||
`-e ClassName.methodName` option instruments the given Java method
|
||||
in order to record all invocations of this method with the stack traces.
|
||||
|
||||
Example: `-e java.util.Properties.getProperty` will profile all places
|
||||
where `getProperty` method is called from.
|
||||
|
||||
Only non-native Java methods are supported. To profile a native method,
|
||||
use hardware breakpoint event instead, e.g. `-e Java_java_lang_Throwable_fillInStackTrace`
|
||||
|
||||
**Be aware** that if you attach async-profiler at runtime, the first instrumentation
|
||||
of a non-native Java method may cause the [deoptimization](https://github.com/openjdk/jdk/blob/bf2e9ee9d321ed289466b2410f12ad10504d01a2/src/hotspot/share/prims/jvmtiRedefineClasses.cpp#L4092-L4096)
|
||||
of all compiled methods. The subsequent instrumentation flushes only the _dependent code_.
|
||||
|
||||
The massive CodeCache flush doesn't occur if attaching async-profiler as an agent.
|
||||
|
||||
Here are some useful native methods that you may want to profile:
|
||||
* ```G1CollectedHeap::humongous_obj_allocate``` - trace _humongous allocations_ of the G1 GC,
|
||||
* ```JVM_StartThread``` - trace creation of new Java threads,
|
||||
* ```Java_java_lang_ClassLoader_defineClass1``` - trace class loading.
|
||||
|
||||
## Building
|
||||
|
||||
Make sure the `JAVA_HOME` environment variable points to your JDK installation,
|
||||
and then run `make`. GCC is required. After building, the profiler agent binary
|
||||
will be in the `build` subdirectory. Additionally, a small application `jattach`
|
||||
that can load the agent into the target process will also be compiled to the
|
||||
`build` subdirectory.
|
||||
Build status: [](https://github.com/async-profiler/async-profiler/actions/workflows/ci.yml)
|
||||
|
||||
Make sure the `JAVA_HOME` environment variable points to your JDK installation,
|
||||
and then run `make`. GCC or Clang is required. After building, the profiler binaries
|
||||
will be in the `build` subdirectory.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
As of Linux 4.6, capturing kernel call stacks using `perf_events` from a non-root
|
||||
process requires setting two runtime variables. You can set them using
|
||||
sysctl or as follows:
|
||||
|
||||
```
|
||||
# sysctl kernel.perf_event_paranoid=1
|
||||
# sysctl kernel.kptr_restrict=0
|
||||
```
|
||||
|
||||
async-profiler works in the context of the target Java application,
|
||||
i.e. it runs as an agent in the process being profiled.
|
||||
`asprof` is a tool to attach and control the agent.
|
||||
|
||||
A typical workflow would be to launch your Java application, attach
|
||||
the agent and start profiling, exercise your performance scenario, and
|
||||
then stop profiling. The agent's output, including the profiling results, will
|
||||
be displayed on the console where you've started `asprof`.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ asprof start 8983
|
||||
$ asprof stop 8983
|
||||
```
|
||||
|
||||
The following may be used in lieu of the `pid` (8983):
|
||||
|
||||
- The keyword `jps`, which will use the most recently launched Java process.
|
||||
- The application name as it appears in the `jps` output: e.g. `Computey`
|
||||
|
||||
Alternatively, you may specify `-d` (duration) argument to profile
|
||||
the application for a fixed period of time with a single command.
|
||||
|
||||
```
|
||||
$ asprof -d 30 8983
|
||||
```
|
||||
|
||||
By default, the profiling frequency is 100Hz (every 10ms of CPU time).
|
||||
Here is a sample of the output printed to the Java application's terminal:
|
||||
|
||||
```
|
||||
--- Execution profile ---
|
||||
Total samples: 687
|
||||
Unknown (native): 1 (0.15%)
|
||||
|
||||
--- 6790000000 (98.84%) ns, 679 samples
|
||||
[ 0] Primes.isPrime
|
||||
[ 1] Primes.primesThread
|
||||
[ 2] Primes.access$000
|
||||
[ 3] Primes$1.run
|
||||
[ 4] java.lang.Thread.run
|
||||
|
||||
... a lot of output omitted for brevity ...
|
||||
|
||||
ns percent samples top
|
||||
---------- ------- ------- ---
|
||||
6790000000 98.84% 679 Primes.isPrime
|
||||
40000000 0.58% 4 __do_softirq
|
||||
|
||||
... more output omitted ...
|
||||
```
|
||||
|
||||
This indicates that the hottest method was `Primes.isPrime`, and the hottest
|
||||
call stack leading to it comes from `Primes.primesThread`.
|
||||
|
||||
## Launching as an Agent
|
||||
|
||||
If you need to profile some code as soon as the JVM starts up, instead of using the `asprof`,
|
||||
it is possible to attach async-profiler as an agent on the command line. For example:
|
||||
|
||||
```
|
||||
$ java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ...
|
||||
```
|
||||
|
||||
Agent library is configured through the JVMTI argument interface.
|
||||
The format of the arguments string is described
|
||||
[in the source code](https://github.com/async-profiler/async-profiler/blob/v3.0/src/arguments.cpp#L44).
|
||||
`asprof` actually converts command line arguments to that format.
|
||||
|
||||
For instance, `-e wall` is converted to `event=wall`, `-f profile.html`
|
||||
is converted to `file=profile.html`, and so on. However, some arguments are processed
|
||||
directly by `asprof`. E.g. `-d 5` results in 3 actions:
|
||||
attaching profiler agent with start command, sleeping for 5 seconds,
|
||||
and then attaching the agent again with stop command.
|
||||
|
||||
## Multiple events
|
||||
|
||||
It is possible to profile CPU, allocations, and locks at the same time.
|
||||
Instead of CPU, you may choose any other execution event: wall-clock,
|
||||
perf event, tracepoint, Java method, etc.
|
||||
|
||||
The only output format that supports multiple events together is JFR.
|
||||
The recording will contain the following event types:
|
||||
- `jdk.ExecutionSample`
|
||||
- `jdk.ObjectAllocationInNewTLAB` (alloc)
|
||||
- `jdk.ObjectAllocationOutsideTLAB` (alloc)
|
||||
- `jdk.JavaMonitorEnter` (lock)
|
||||
- `jdk.ThreadPark` (lock)
|
||||
|
||||
To start profiling cpu + allocations + locks together, specify
|
||||
```
|
||||
asprof -e cpu,alloc,lock -f profile.jfr ...
|
||||
```
|
||||
or use `--alloc` and `--lock` parameters with the desired threshold:
|
||||
```
|
||||
asprof -e cpu --alloc 2m --lock 10ms -f profile.jfr ...
|
||||
```
|
||||
The same, when starting profiler as an agent:
|
||||
```
|
||||
-agentpath:/path/to/libasyncProfiler.so=start,event=cpu,alloc=2m,lock=10ms,file=profile.jfr
|
||||
```
|
||||
|
||||
## Flame Graph visualization
|
||||
|
||||
async-profiler provides out-of-the-box [Flame Graph](https://github.com/BrendanGregg/FlameGraph) support.
|
||||
Specify `-o flamegraph` argument to dump profiling results as an interactive HTML Flame Graph.
|
||||
Also, Flame Graph output format will be chosen automatically if the target filename ends with `.html`.
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ asprof -d 30 -f /tmp/flamegraph.html 8983
|
||||
```
|
||||
|
||||
[](https://htmlpreview.github.io/?https://github.com/async-profiler/async-profiler/blob/master/demo/flamegraph.html)
|
||||
|
||||
## Profiler Options
|
||||
|
||||
`asprof` command-line options.
|
||||
|
||||
* `start` - starts profiling in semi-automatic mode, i.e. profiler will run
|
||||
until `stop` command is explicitly called.
|
||||
|
||||
* `resume` - starts or resumes earlier profiling session that has been stopped.
|
||||
All the collected data remains valid. The profiling options are not preserved
|
||||
between sessions, and should be specified again.
|
||||
|
||||
* `stop` - stops profiling and prints the report.
|
||||
|
||||
* `dump` - dump collected data without stopping profiling session.
|
||||
|
||||
* `check` - check if the specified profiling event is available.
|
||||
|
||||
* `status` - prints profiling status: whether profiler is active and
|
||||
for how long.
|
||||
|
||||
* `meminfo` - prints used memory statistics.
|
||||
|
||||
* `list` - show the list of profiling events available for the target process
|
||||
(if PID is specified) or for the default JVM.
|
||||
|
||||
* `-d N` - the profiling duration, in seconds. If no `start`, `resume`, `stop`
|
||||
or `status` option is given, the profiler will run for the specified period
|
||||
of time and then automatically stop.
|
||||
Example: `asprof -d 30 8983`
|
||||
|
||||
* `-e event` - the profiling event: `cpu`, `alloc`, `lock`, `cache-misses` etc.
|
||||
Use `list` to see the complete list of available events.
|
||||
|
||||
In allocation profiling mode the top frame of every call trace is the class
|
||||
of the allocated object, and the counter is the heap pressure (the total size
|
||||
of allocated TLABs or objects outside TLAB).
|
||||
|
||||
In lock profiling mode the top frame is the class of lock/monitor, and
|
||||
the counter is number of nanoseconds it took to enter this lock/monitor.
|
||||
|
||||
Two special event types are supported on Linux: hardware breakpoints
|
||||
and kernel tracepoints:
|
||||
- `-e mem:<func>[:rwx]` sets read/write/exec breakpoint at function
|
||||
`<func>`. The format of `mem` event is the same as in `perf-record`.
|
||||
Execution breakpoints can be also specified by the function name,
|
||||
e.g. `-e malloc` will trace all calls of native `malloc` function.
|
||||
- `-e trace:<id>` sets a kernel tracepoint. It is possible to specify
|
||||
tracepoint symbolic name, e.g. `-e syscalls:sys_enter_open` will trace
|
||||
all `open` syscalls.
|
||||
|
||||
* `-i N` - sets the profiling interval in nanoseconds or in other units,
|
||||
if N is followed by `ms` (for milliseconds), `us` (for microseconds),
|
||||
or `s` (for seconds). Only CPU active time is counted. No samples
|
||||
are collected while CPU is idle. The default is 10000000 (10ms).
|
||||
Example: `asprof -i 500us 8983`
|
||||
|
||||
* `--alloc N` - allocation profiling interval in bytes or in other units,
|
||||
if N is followed by `k` (kilobytes), `m` (megabytes), or `g` (gigabytes).
|
||||
|
||||
* `--live` - retain allocation samples with live objects only
|
||||
(object that have not been collected by the end of profiling session).
|
||||
Useful for finding Java heap memory leaks.
|
||||
|
||||
* `--lock N` - lock profiling threshold in nanoseconds (or other units).
|
||||
In lock profiling mode, record contended locks that the JVM has waited for
|
||||
longer than the specified duration.
|
||||
|
||||
* `-j N` - sets the Java stack profiling depth. This option will be ignored if N is greater
|
||||
than default 2048.
|
||||
Example: `asprof -j 30 8983`
|
||||
|
||||
* `-t` - profile threads separately. Each stack trace will end with a frame
|
||||
that denotes a single thread.
|
||||
Example: `asprof -t 8983`
|
||||
|
||||
* `-s` - print simple class names instead of FQN.
|
||||
|
||||
* `-n` - normalize names of hidden classes / lambdas.
|
||||
|
||||
* `-g` - print method signatures.
|
||||
|
||||
* `-a` - annotate JIT compiled methods with `_[j]`, inlined methods with `_[i]`, interpreted methods with `_[0]` and C1 compiled methods with `_[1]`.
|
||||
|
||||
* `-l` - prepend library names to symbols, e.g. ``libjvm.so`JVM_DefineClassWithSource``.
|
||||
|
||||
* `-o fmt` - specifies what information to dump when profiling ends.
|
||||
`fmt` can be one of the following options:
|
||||
- `traces[=N]` - dump call traces (at most N samples);
|
||||
- `flat[=N]` - dump flat profile (top N hot methods);
|
||||
can be combined with `traces`, e.g. `traces=200,flat=200`
|
||||
- `jfr` - dump events in Java Flight Recorder format readable by Java Mission Control.
|
||||
This *does not* require JDK commercial features to be enabled.
|
||||
- `collapsed` - dump collapsed call traces in the format used by
|
||||
[FlameGraph](https://github.com/brendangregg/FlameGraph) script. This is
|
||||
a collection of call stacks, where each line is a semicolon separated list
|
||||
of frames followed by a counter.
|
||||
- `flamegraph` - produce Flame Graph in HTML format.
|
||||
- `tree` - produce Call Tree in HTML format.
|
||||
`--reverse` option will generate backtrace view.
|
||||
|
||||
* `--total` - count the total value of the collected metric instead of the number of samples,
|
||||
e.g. total allocation size.
|
||||
|
||||
* `--chunksize N`, `--chunktime N` - approximate size and time limits for a single JFR chunk.
|
||||
A new chunk will be started whenever either limit is reached.
|
||||
The default `chunksize` is 100MB, and the default `chunktime` is 1 hour.
|
||||
Example: `asprof -f profile.jfr --chunksize 100m --chunktime 1h 8983`
|
||||
|
||||
* `-I include`, `-X exclude` - filter stack traces by the given pattern(s).
|
||||
`-I` defines the name pattern that *must* be present in the stack traces,
|
||||
while `-X` is the pattern that *must not* occur in any of stack traces in the output.
|
||||
`-I` and `-X` options can be specified multiple times. A pattern may begin or end with
|
||||
a star `*` that denotes any (possibly empty) sequence of characters.
|
||||
Example: `asprof -I 'Primes.*' -I 'java/*' -X '*Unsafe.park*' 8983`
|
||||
|
||||
* `-L level` - log level: `debug`, `info`, `warn`, `error` or `none`.
|
||||
|
||||
* `-F features` - comma separated list of HotSpot-specific features
|
||||
to include in stack traces. Supported features are:
|
||||
- `vtable` - display targets of megamorphic virtual calls as an extra frame
|
||||
on top of `vtable stub` or `itable stub`.
|
||||
- `comptask` - display current compilation task (a Java method being compiled)
|
||||
in a JIT compiler stack trace.
|
||||
|
||||
* `--title TITLE`, `--minwidth PERCENT`, `--reverse` - FlameGraph parameters.
|
||||
Example: `asprof -f profile.html --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
|
||||
* `-f FILENAME` - the file name to dump the profile information to.
|
||||
`%p` in the file name is expanded to the PID of the target JVM;
|
||||
`%t` - to the timestamp;
|
||||
`%n{MAX}` - to the sequence number;
|
||||
`%{ENV}` - to the value of the given environment variable.
|
||||
Example: `asprof -o collapsed -f /tmp/traces-%t.txt 8983`
|
||||
|
||||
* `--loop TIME` - run profiler in a loop (continuous profiling).
|
||||
The argument is either a clock time (`hh:mm:ss`) or
|
||||
a loop duration in `s`econds, `m`inutes, `h`ours, or `d`ays.
|
||||
Make sure the filename includes a timestamp pattern, or the output
|
||||
will be overwritten on each iteration.
|
||||
Example: `asprof --loop 1h -f /var/log/profile-%t.jfr 8983`
|
||||
|
||||
* `--all-user` - include only user-mode events. This option is helpful when kernel profiling
|
||||
is restricted by `perf_event_paranoid` settings.
|
||||
|
||||
* `--sched` - group threads by Linux-specific scheduling policy: BATCH/IDLE/OTHER.
|
||||
|
||||
* `--cstack MODE` - how to walk native frames (C stack). Possible modes are
|
||||
`fp` (Frame Pointer), `dwarf` (DWARF unwind info),
|
||||
`lbr` (Last Branch Record, available on Haswell since Linux 4.1),
|
||||
`vm` (HotSpot VM Structs) and `no` (do not collect C stack).
|
||||
|
||||
By default, C stack is shown in cpu, ctimer, wall-clock and perf-events profiles.
|
||||
Java-level events like `alloc` and `lock` collect only Java stack.
|
||||
|
||||
* `--signal NUM` - use alternative signal for cpu or wall clock profiling.
|
||||
To change both signals, specify two numbers separated by a slash: `--signal SIGCPU/SIGWALL`.
|
||||
|
||||
* `--clock SOURCE` - clock source for JFR timestamps: `tsc` (default)
|
||||
or `monotonic` (equivalent for `CLOCK_MONOTONIC`).
|
||||
|
||||
* `--begin function`, `--end function` - automatically start/stop profiling
|
||||
when the specified native function is executed.
|
||||
|
||||
* `--ttsp` - time-to-safepoint profiling. An alias for
|
||||
`--begin SafepointSynchronize::begin --end RuntimeService::record_safepoint_synchronized`
|
||||
It is not a separate event type, but rather a constraint. Whatever event type
|
||||
you choose (e.g. `cpu` or `wall`), the profiler will work as usual, except that
|
||||
only events between the safepoint request and the start of the VM operation
|
||||
will be recorded.
|
||||
|
||||
* `--jfrsync CONFIG` - start Java Flight Recording with the given configuration
|
||||
synchronously with the profiler. The output .jfr file will include all regular
|
||||
JFR events, except that execution samples will be obtained from async-profiler.
|
||||
This option implies `-o jfr`.
|
||||
- `CONFIG` is a predefined JFR profile or a JFR configuration file (.jfc)
|
||||
or a list of JFR events started with `+`
|
||||
|
||||
Example: `asprof -e cpu --jfrsync profile -f combined.jfr 8983`
|
||||
|
||||
* `--fdtransfer` - runs a background process that provides access to perf_events
|
||||
to an unprivileged process. `--fdtransfer` is useful for profiling a process
|
||||
in a container (which lacks access to perf_events) from the host.
|
||||
See [Profiling Java in a container](#profiling-java-in-a-container).
|
||||
|
||||
* `-v`, `--version` - prints the version of profiler library. If PID is specified,
|
||||
gets the version of the library loaded into the given process.
|
||||
|
||||
## Profiling Java in a container
|
||||
|
||||
It is possible to profile Java processes running in a Docker or LXC container
|
||||
both from within a container and from the host system.
|
||||
|
||||
When profiling from the host, `pid` should be the Java process ID in the host
|
||||
namespace. Use `ps aux | grep java` or `docker top <container>` to find
|
||||
the process ID.
|
||||
|
||||
async-profiler should be run from the host by a privileged user - it will
|
||||
automatically switch to the proper pid/mount namespace and change
|
||||
user credentials to match the target process. Also make sure that
|
||||
the target container can access `libasyncProfiler.so` by the same
|
||||
absolute path as on the host.
|
||||
|
||||
By default, Docker container restricts the access to `perf_event_open`
|
||||
syscall. There are 3 alternatives to allow profiling in a container:
|
||||
1. You can modify the [seccomp profile](https://docs.docker.com/engine/security/seccomp/)
|
||||
or disable it altogether with `--security-opt seccomp=unconfined` option. In
|
||||
addition, `--cap-add SYS_ADMIN` may be required.
|
||||
2. You can use "fdtransfer": see the help for `--fdtransfer`.
|
||||
3. Last, you may fall back to `-e ctimer` profiling mode, see [Troubleshooting](#troubleshooting).
|
||||
|
||||
## Restrictions/Limitations
|
||||
|
||||
* macOS profiling is limited to user space code only.
|
||||
|
||||
* On most Linux systems, `perf_events` captures call stacks with a maximum depth
|
||||
of 127 frames. On recent Linux kernels, this can be configured using
|
||||
`sysctl kernel.perf_event_max_stack` or by writing to the
|
||||
`/proc/sys/kernel/perf_event_max_stack` file.
|
||||
|
||||
* Profiler allocates 8kB perf_event buffer for each thread of the target process.
|
||||
Make sure `/proc/sys/kernel/perf_event_mlock_kb` value is large enough
|
||||
(more than `8 * threads`) when running under unprivileged user.
|
||||
Otherwise the message _"perf_event mmap failed: Operation not permitted"_
|
||||
will be printed, and no native stack traces will be collected.
|
||||
|
||||
* You will not see the non-Java frames _preceding_ the Java frames on the
|
||||
stack, unless `--cstack vm` is specified.
|
||||
For example, if `start_thread` called `JavaMain` and then your Java
|
||||
code started running, you will not see the first two frames in the resulting
|
||||
stack. On the other hand, you _will_ see non-Java frames (user and kernel)
|
||||
invoked by your Java code.
|
||||
|
||||
* No Java stacks will be collected if `-XX:MaxJavaStackTraceDepth` is zero
|
||||
or negative. The exception is `--cstack vm` mode, which does not take
|
||||
`MaxJavaStackTraceDepth` into account.
|
||||
|
||||
* Too short profiling interval may cause continuous interruption of heavy
|
||||
system calls like `clone()`, so that it will never complete;
|
||||
see [#97](https://github.com/async-profiler/async-profiler/issues/97).
|
||||
The workaround is simply to increase the interval.
|
||||
|
||||
* When agent is not loaded at JVM startup (by using -agentpath option) it is
|
||||
highly recommended to use `-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints` JVM flags.
|
||||
Without those flags the profiler will still work correctly but results might be
|
||||
less accurate. For example, without `-XX:+DebugNonSafepoints` there is a high chance
|
||||
that simple inlined methods will not appear in the profile. When the agent is attached at runtime,
|
||||
`CompiledMethodLoad` JVMTI event enables debug info, but only for methods compiled after attaching.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```
|
||||
Failed to change credentials to match the target process: Operation not permitted
|
||||
```
|
||||
Due to limitation of HotSpot Dynamic Attach mechanism, the profiler must be run
|
||||
by exactly the same user (and group) as the owner of target JVM process.
|
||||
If profiler is run by a different user, it will try to automatically change
|
||||
current user and group. This will likely succeed for `root`, but not for
|
||||
other users, resulting in the above error.
|
||||
|
||||
```
|
||||
Could not start attach mechanism: No such file or directory
|
||||
```
|
||||
The profiler cannot establish communication with the target JVM through UNIX domain socket.
|
||||
|
||||
Usually this happens in one of the following cases:
|
||||
1. Attach socket `/tmp/.java_pidNNN` has been deleted. It is a common
|
||||
practice to clean `/tmp` automatically with some scheduled script.
|
||||
Configure the cleanup software to exclude `.java_pid*` files from deletion.
|
||||
How to check: run `lsof -p PID | grep java_pid`
|
||||
If it lists a socket file, but the file does not exist, then this is exactly
|
||||
the described problem.
|
||||
2. JVM is started with `-XX:+DisableAttachMechanism` option.
|
||||
3. `/tmp` directory of Java process is not physically the same directory
|
||||
as `/tmp` of your shell, because Java is running in a container or in
|
||||
`chroot` environment. `jattach` attempts to solve this automatically,
|
||||
but it might lack the required permissions to do so.
|
||||
Check `strace build/jattach PID properties`
|
||||
4. JVM is busy and cannot reach a safepoint. For instance,
|
||||
JVM is in the middle of long-running garbage collection.
|
||||
How to check: run `kill -3 PID`. Healthy JVM process should print
|
||||
a thread dump and heap info in its console.
|
||||
|
||||
```
|
||||
Target JVM failed to load libasyncProfiler.so
|
||||
```
|
||||
The connection with the target JVM has been established, but JVM is unable to load profiler shared library.
|
||||
Make sure the user of JVM process has permissions to access `libasyncProfiler.so` by exactly the same absolute path.
|
||||
For more information see [#78](https://github.com/async-profiler/async-profiler/issues/78).
|
||||
|
||||
```
|
||||
No access to perf events. Try --fdtransfer or --all-user option or 'sysctl kernel.perf_event_paranoid=1'
|
||||
```
|
||||
or
|
||||
```
|
||||
Perf events unavailable
|
||||
```
|
||||
`perf_event_open()` syscall has failed.
|
||||
|
||||
Typical reasons include:
|
||||
1. `/proc/sys/kernel/perf_event_paranoid` is set to restricted mode (>=2).
|
||||
2. seccomp disables `perf_event_open` API in a container.
|
||||
3. OS runs under a hypervisor that does not virtualize performance counters.
|
||||
4. perf_event_open API is not supported on this system, e.g. WSL.
|
||||
|
||||
For permissions-related reasons (such as 1 and 2), using `--fdtransfer` while running the profiler
|
||||
as a privileged user may solve the issue.
|
||||
|
||||
If changing the configuration is not possible, you may fall back to
|
||||
`-e ctimer` profiling mode. It is similar to `cpu` mode, but does not
|
||||
require perf_events support. As a drawback, there will be no kernel
|
||||
stack traces.
|
||||
|
||||
```
|
||||
No AllocTracer symbols found. Are JDK debug symbols installed?
|
||||
```
|
||||
The OpenJDK debug symbols are required for allocation profiling.
|
||||
See [Installing Debug Symbols](#installing-debug-symbols) for more details.
|
||||
If the error message persists after a successful installation of the debug symbols,
|
||||
it is possible that the JDK was upgraded when installing the debug symbols.
|
||||
In this case, profiling any Java process which had started prior to the installation
|
||||
will continue to display this message, since the process had loaded
|
||||
the older version of the JDK which lacked debug symbols.
|
||||
Restarting the affected Java processes should resolve the issue.
|
||||
|
||||
```
|
||||
VMStructs unavailable. Unsupported JVM?
|
||||
```
|
||||
JVM shared library does not export `gHotSpotVMStructs*` symbols -
|
||||
apparently this is not a HotSpot JVM. Sometimes the same message
|
||||
can be also caused by an incorrectly built JDK
|
||||
(see [#218](https://github.com/async-profiler/async-profiler/issues/218)).
|
||||
In these cases installing JDK debug symbols may solve the problem.
|
||||
|
||||
```
|
||||
Could not parse symbols from <libname.so>
|
||||
```
|
||||
Async-profiler was unable to parse non-Java function names because of
|
||||
the corrupted contents in `/proc/[pid]/maps`. The problem is known to
|
||||
occur in a container when running Ubuntu with Linux kernel 5.x.
|
||||
This is the OS bug, see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018.
|
||||
|
||||
```
|
||||
Could not open output file
|
||||
```
|
||||
Output file is written by the target JVM process, not by the profiler script.
|
||||
Make sure the path specified in `-f` option is correct and is accessible by the JVM.
|
||||
|
||||
2247
demo/SwingSet2.svg
2247
demo/SwingSet2.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 141 KiB |
724
demo/flamegraph.html
Normal file
724
demo/flamegraph.html
Normal file
@@ -0,0 +1,724 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<style>
|
||||
body {margin: 0; padding: 10px 10px 22px 10px; background-color: #ffffff}
|
||||
h1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}
|
||||
header {margin: -24px 0 5px 0; line-height: 24px}
|
||||
button {font: 12px sans-serif; cursor: pointer}
|
||||
p {position: fixed; bottom: 0; margin: 0; padding: 2px 3px 2px 3px; outline: 1px solid #ffc000; display: none; overflow: hidden; white-space: nowrap; background-color: #ffffe0}
|
||||
a {color: #0366d6}
|
||||
#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}
|
||||
#hl span {padding: 0 3px 0 3px}
|
||||
#status {left: 0}
|
||||
#match {right: 0}
|
||||
#reset {cursor: pointer}
|
||||
#canvas {width: 100%; height: 576px}
|
||||
</style>
|
||||
</head>
|
||||
<body style='font: 12px Verdana, sans-serif'>
|
||||
<h1>CPU profile</h1>
|
||||
<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>
|
||||
<header style='text-align: right'>Produced by <a href='https://github.com/async-profiler/async-profiler'>async-profiler</a></header>
|
||||
<canvas id='canvas'></canvas>
|
||||
<div id='hl'><span></span></div>
|
||||
<p id='status'></p>
|
||||
<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>
|
||||
<script>
|
||||
// Copyright The async-profiler authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
'use strict';
|
||||
let root, rootLevel, px, pattern;
|
||||
let level0 = 0, left0 = 0, width0 = 0;
|
||||
let reverse = false;
|
||||
const levels = Array(36);
|
||||
for (let h = 0; h < levels.length; h++) {
|
||||
levels[h] = [];
|
||||
}
|
||||
|
||||
const canvas = document.getElementById('canvas');
|
||||
const c = canvas.getContext('2d');
|
||||
const hl = document.getElementById('hl');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
const canvasWidth = canvas.offsetWidth;
|
||||
const canvasHeight = canvas.offsetHeight;
|
||||
canvas.style.width = canvasWidth + 'px';
|
||||
canvas.width = canvasWidth * (devicePixelRatio || 1);
|
||||
canvas.height = canvasHeight * (devicePixelRatio || 1);
|
||||
if (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);
|
||||
c.font = document.body.style.font;
|
||||
|
||||
const palette = [
|
||||
[0xb2e1b2, 20, 20, 20],
|
||||
[0x50e150, 30, 30, 30],
|
||||
[0x50cccc, 30, 30, 30],
|
||||
[0xe15a5a, 30, 40, 40],
|
||||
[0xc8c83c, 30, 30, 10],
|
||||
[0xe17d00, 30, 30, 0],
|
||||
[0xcce880, 20, 20, 20],
|
||||
];
|
||||
|
||||
function getColor(p) {
|
||||
const v = Math.random();
|
||||
return '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);
|
||||
}
|
||||
|
||||
function f(key, level, left, width, inln, c1, int) {
|
||||
levels[level0 = level].push({left: left0 += left, width: width0 = width || width0,
|
||||
color: getColor(palette[key & 7]), title: cpool[key >>> 3],
|
||||
details: (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '')
|
||||
});
|
||||
}
|
||||
|
||||
function u(key, width, inln, c1, int) {
|
||||
f(key, level0 + 1, 0, width, inln, c1, int)
|
||||
}
|
||||
|
||||
function n(key, width, inln, c1, int) {
|
||||
f(key, level0, width0, width, inln, c1, int)
|
||||
}
|
||||
|
||||
function samples(n) {
|
||||
return n === 1 ? '1 sample' : n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' samples';
|
||||
}
|
||||
|
||||
function pct(a, b) {
|
||||
return a >= b ? '100' : (100 * a / b).toFixed(2);
|
||||
}
|
||||
|
||||
function findFrame(frames, x) {
|
||||
let left = 0;
|
||||
let right = frames.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = (left + right) >>> 1;
|
||||
const f = frames[mid];
|
||||
|
||||
if (f.left > x) {
|
||||
right = mid - 1;
|
||||
} else if (f.left + f.width <= x) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
if (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];
|
||||
if (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function search(r) {
|
||||
if (r === true && (r = prompt('Enter regexp to search:', '')) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
pattern = r ? RegExp(r) : undefined;
|
||||
const matched = render(root, rootLevel);
|
||||
document.getElementById('matchval').textContent = pct(matched, root.width) + '%';
|
||||
document.getElementById('match').style.display = r ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
function render(newRoot, newLevel) {
|
||||
if (root) {
|
||||
c.fillStyle = '#ffffff';
|
||||
c.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
}
|
||||
|
||||
root = newRoot || levels[0][0];
|
||||
rootLevel = newLevel || 0;
|
||||
px = canvasWidth / root.width;
|
||||
|
||||
const x0 = root.left;
|
||||
const x1 = x0 + root.width;
|
||||
const marked = [];
|
||||
|
||||
function mark(f) {
|
||||
return marked[f.left] >= f.width || (marked[f.left] = f.width);
|
||||
}
|
||||
|
||||
function totalMarked() {
|
||||
let total = 0;
|
||||
let left = 0;
|
||||
Object.keys(marked).sort(function(a, b) { return a - b; }).forEach(function(x) {
|
||||
if (+x >= left) {
|
||||
total += marked[x];
|
||||
left = +x + marked[x];
|
||||
}
|
||||
});
|
||||
return total;
|
||||
}
|
||||
|
||||
function drawFrame(f, y, alpha) {
|
||||
if (f.left < x1 && f.left + f.width > x0) {
|
||||
c.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;
|
||||
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
|
||||
|
||||
if (f.width * px >= 21) {
|
||||
const chars = Math.floor(f.width * px / 7);
|
||||
const title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';
|
||||
c.fillStyle = '#000000';
|
||||
c.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);
|
||||
}
|
||||
|
||||
if (alpha) {
|
||||
c.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let h = 0; h < levels.length; h++) {
|
||||
const y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;
|
||||
const frames = levels[h];
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
drawFrame(frames[i], y, h < rootLevel);
|
||||
}
|
||||
}
|
||||
|
||||
return totalMarked();
|
||||
}
|
||||
|
||||
function unpack(cpool) {
|
||||
for (let i = 1; i < cpool.length; i++) {
|
||||
cpool[i] = cpool[i - 1].substring(0, cpool[i].charCodeAt(0) - 32) + cpool[i].substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.onmousemove = function() {
|
||||
const h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);
|
||||
if (h >= 0 && h < levels.length) {
|
||||
const f = findFrame(levels[h], event.offsetX / px + root.left);
|
||||
if (f) {
|
||||
if (f !== root) getSelection().removeAllRanges();
|
||||
hl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';
|
||||
hl.style.width = (Math.min(f.width, root.width) * px) + 'px';
|
||||
hl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';
|
||||
hl.firstChild.textContent = f.title;
|
||||
hl.style.display = 'block';
|
||||
canvas.title = f.title + '\n(' + samples(f.width) + f.details + ', ' + pct(f.width, levels[0][0].width) + '%)';
|
||||
canvas.style.cursor = 'pointer';
|
||||
canvas.onclick = function() {
|
||||
if (f !== root) {
|
||||
render(f, h);
|
||||
canvas.onmousemove();
|
||||
}
|
||||
};
|
||||
status.textContent = 'Function: ' + canvas.title;
|
||||
status.style.display = 'inline-block';
|
||||
return;
|
||||
}
|
||||
}
|
||||
canvas.onmouseout();
|
||||
}
|
||||
|
||||
canvas.onmouseout = function() {
|
||||
hl.style.display = 'none';
|
||||
status.style.display = 'none';
|
||||
canvas.title = '';
|
||||
canvas.style.cursor = '';
|
||||
canvas.onclick = null;
|
||||
}
|
||||
|
||||
canvas.ondblclick = function() {
|
||||
getSelection().selectAllChildren(hl);
|
||||
}
|
||||
|
||||
document.getElementById('reverse').onclick = function() {
|
||||
reverse = !reverse;
|
||||
render();
|
||||
}
|
||||
|
||||
document.getElementById('search').onclick = function() {
|
||||
search(true);
|
||||
}
|
||||
|
||||
document.getElementById('reset').onclick = function() {
|
||||
search(false);
|
||||
}
|
||||
|
||||
window.onkeydown = function() {
|
||||
if (event.ctrlKey && event.keyCode === 70) {
|
||||
event.preventDefault();
|
||||
search(true);
|
||||
} else if (event.keyCode === 27) {
|
||||
search(false);
|
||||
}
|
||||
}
|
||||
|
||||
const cpool = [
|
||||
'all',
|
||||
' C2Compiler::compile_method',
|
||||
'!ompilation::Compilation',
|
||||
'-compile_java_method',
|
||||
'5method',
|
||||
'-emit_code_body',
|
||||
'&e::Code_Gen',
|
||||
'+mpile',
|
||||
')Optimize',
|
||||
'\'Broker::compiler_thread_loop',
|
||||
'/invoke_compiler_on_method',
|
||||
'\'r::compile_method',
|
||||
'"ntiguousSpace::allocate',
|
||||
' DefNewGeneration::FastEvacuateFollowersClosure::do_void',
|
||||
'2collect',
|
||||
'4py_to_survivor_space',
|
||||
' GenCollectedHeap::collect_generation',
|
||||
'2do_collection',
|
||||
'2satisfy_failed_allocation',
|
||||
'#eration::promote',
|
||||
' InstanceKlass::allocate_objArray',
|
||||
'"terpreterRuntime::anewarray',
|
||||
' JVM_ArrayCopy',
|
||||
'!avaThread::run',
|
||||
'$_sun_nio_ch_FileDispatcherImpl_read0',
|
||||
' Matcher::match',
|
||||
'!emAllocator::allocate',
|
||||
' ObjArrayAllocator::initialize',
|
||||
'!ffsetTableContigSpace::allocate',
|
||||
' Parse::Parse',
|
||||
'\'do_all_blocks',
|
||||
'*call',
|
||||
'*one_block',
|
||||
'/ytecode',
|
||||
'%Generator::generate',
|
||||
'!haseCFG::do_global_code_motion',
|
||||
'*global_code_motion',
|
||||
'*schedule_late',
|
||||
'4ocal',
|
||||
'&haitin::Register_Allocate',
|
||||
'.Split',
|
||||
'.build_ifg_physical',
|
||||
'.elide_copy',
|
||||
'.interfere_with_live',
|
||||
'.merge_multidefs',
|
||||
'.post_allocate_copy_removal',
|
||||
'%IdealLoop::Dominators',
|
||||
'0build_and_optimize',
|
||||
'6loop_early',
|
||||
';late',
|
||||
';tree',
|
||||
'0optimize',
|
||||
'0remix_address_expressions',
|
||||
'0split_if_with_blocks',
|
||||
'D_post',
|
||||
'Fre',
|
||||
'&terGVN::optimize',
|
||||
'.subsume_node',
|
||||
'.transform_old',
|
||||
'%Live::add_liveout',
|
||||
'+compute',
|
||||
'%MacroExpand::expand_macro_nodes',
|
||||
'!redictedCallGenerator::generate',
|
||||
' TenuredGeneration::allocate',
|
||||
'!hread::call_run',
|
||||
' VMThread::evaluate_operation',
|
||||
'*inner_execute',
|
||||
'*run',
|
||||
'"_GenCollectForAllocation::doit',
|
||||
'#Operation::evaluate',
|
||||
' __GI_read',
|
||||
'"handle_mm_fault',
|
||||
'"memcpy_sse2_unaligned_erms',
|
||||
'%set_avx2_unaligned_erms',
|
||||
' aci_CopyRight',
|
||||
'!sm_exc_page_fault',
|
||||
' clear_huge_page',
|
||||
'&page_erms',
|
||||
'&subpage',
|
||||
'"one3',
|
||||
'!opy_page_to_iter',
|
||||
'%user_enhanced_fast_string',
|
||||
' demo8/FileConverter$$Lambda$3.0x00007ffab9001000.apply',
|
||||
'<4.0x00007ffab9001240.applyAsInt',
|
||||
'4Entry.<init>',
|
||||
':equals',
|
||||
':hashCode',
|
||||
'3.convertFile',
|
||||
';List',
|
||||
'4main',
|
||||
'4readInput',
|
||||
'4saveResult',
|
||||
'!o_huge_pmd_anonymous_page',
|
||||
'#syscall_64',
|
||||
'#user_addr_fault',
|
||||
' entry_SYSCALL_64_after_hwframe',
|
||||
'!xc_page_fault',
|
||||
' filemap_read',
|
||||
' handle_mm_fault',
|
||||
' java/io/BufferedReader.fill',
|
||||
'7readLine',
|
||||
')yteArrayOutputStream.ensureCapacity',
|
||||
'>toByteArray',
|
||||
'>write',
|
||||
'(DataOutputStream.write',
|
||||
'>Int',
|
||||
'>UTF',
|
||||
'(InputStreamReader.read',
|
||||
'%lang/Integer.parseInt',
|
||||
'*String.<init>',
|
||||
'1decodeASCII',
|
||||
'1hashCode',
|
||||
'1length',
|
||||
'1substring',
|
||||
'0Latin1.hashCode',
|
||||
'7newString',
|
||||
'0UTF16.compress',
|
||||
'+ystem$2.decodeASCII',
|
||||
'0.arraycopy',
|
||||
'*ThreadLocal.get',
|
||||
'%nio/charset/CharsetDecoder.decode',
|
||||
')file/Files.readAllLines',
|
||||
'%util/ArrayList$ArrayListSpliterator.tryAdvance',
|
||||
'3.add',
|
||||
'4grow',
|
||||
'4sort',
|
||||
'/s.copyOf',
|
||||
'7Range',
|
||||
'1sort',
|
||||
'*Comparator$$Lambda$5.0x00007ffab90494b0.compare',
|
||||
'4.lambda$comparingInt$7b0bb60$1',
|
||||
'*HashMap$Node.<init>',
|
||||
'1.hash',
|
||||
'2newNode',
|
||||
'2put',
|
||||
'5Val',
|
||||
'2resize',
|
||||
'.Set.add',
|
||||
'*TimSort.binarySort',
|
||||
'2mergeAt',
|
||||
'7Collapse',
|
||||
'7ForceCollapse',
|
||||
'7Hi',
|
||||
'7Lo',
|
||||
'2sort',
|
||||
'*stream/AbstractPipeline.copyInto',
|
||||
'JWithCancel',
|
||||
'Bevaluate',
|
||||
'BwrapAndCopyInto',
|
||||
'1Collectors$$Lambda$7.0x00007ffab904a268.accept',
|
||||
'1DistinctOps$1$2.accept',
|
||||
'Aend',
|
||||
'1ReduceOps$3ReducingSink.accept',
|
||||
';ReduceOp.evaluateSequential',
|
||||
'3ferencePipeline$3$1.accept',
|
||||
'B.collect',
|
||||
'CforEachWithCancel',
|
||||
'1Sink$ChainedReference.end',
|
||||
'2liceOps$1$1.accept',
|
||||
'2ortedOps$RefSortingSink.accept',
|
||||
'Jend',
|
||||
'!long_disjoint_arraycopy',
|
||||
' ksys_read',
|
||||
' new_sync_read',
|
||||
' oop_arraycopy',
|
||||
' start_thread',
|
||||
'!un/nio/ch/ChannelInputStream.read',
|
||||
'+FileChannelImpl.read',
|
||||
'/DispatcherImpl.read',
|
||||
'B0',
|
||||
'+IOUtil.read',
|
||||
'6IntoNativeBuffer',
|
||||
'+Util.getTemporaryDirectBuffer',
|
||||
')s/StreamDecoder.implRead',
|
||||
'9read',
|
||||
'=Bytes',
|
||||
'+UTF_8$Decoder.decodeArrayLoop',
|
||||
'?Loop',
|
||||
' thread_native_entry',
|
||||
' vfs_read',
|
||||
'!oid ContiguousSpace::oop_since_save_marks_iterate<DefNewScanClosure>',
|
||||
'%OopOopIterateDispatch<DefNewScanClosure>::Table::oop_oop_iterate<InstanceKlass, narrowOop>',
|
||||
'fObjArrayKlass, narrowOop>',
|
||||
'AYoungerGenClosure>::Table::oop_oop_iterate<InstanceKlass, narrowOop>'
|
||||
];
|
||||
unpack(cpool);
|
||||
|
||||
n(3,584)
|
||||
f(635,1,1,178)
|
||||
u(1323)
|
||||
u(1428)
|
||||
u(516)
|
||||
u(188,70)
|
||||
u(76)
|
||||
f(84,7,2,68)
|
||||
f(12,8,2,63)
|
||||
u(60)
|
||||
u(52,36)
|
||||
f(204,11,3,2)
|
||||
n(284,7)
|
||||
u(292)
|
||||
f(300,13,1,2)
|
||||
n(308,4)
|
||||
f(316,11,4,24)
|
||||
f(324,12,6,2)
|
||||
n(332,9)
|
||||
f(348,13,5,4)
|
||||
f(356,12,4,2)
|
||||
n(364)
|
||||
u(340)
|
||||
f(484,12,2,3)
|
||||
u(476)
|
||||
f(68,10,3,23)
|
||||
f(412,11,2,16)
|
||||
u(380,15)
|
||||
f(372,13,1,3)
|
||||
n(388,2)
|
||||
n(396)
|
||||
n(404)
|
||||
n(428,5)
|
||||
f(436,14,1,2)
|
||||
n(444)
|
||||
u(420)
|
||||
f(452,11,3,3)
|
||||
u(468)
|
||||
f(460,13,1,2)
|
||||
f(492,11,2)
|
||||
u(452)
|
||||
u(468)
|
||||
f(276,10,2,4)
|
||||
u(236)
|
||||
u(244)
|
||||
u(260)
|
||||
u(268)
|
||||
u(252)
|
||||
f(500,16,1,3)
|
||||
f(500,17,1,2)
|
||||
u(276)
|
||||
u(236)
|
||||
u(244)
|
||||
u(260)
|
||||
u(268)
|
||||
u(252)
|
||||
f(92,8,2,3)
|
||||
u(20)
|
||||
u(36)
|
||||
u(28)
|
||||
f(44,12,1,2)
|
||||
f(540,5,2,108)
|
||||
u(532)
|
||||
u(524)
|
||||
u(556)
|
||||
u(548)
|
||||
u(148)
|
||||
u(140)
|
||||
u(132)
|
||||
u(116)
|
||||
u(108)
|
||||
f(1444,15,12,50)
|
||||
f(1452,16,1,22)
|
||||
f(124,17,2,20)
|
||||
f(156,18,9,8)
|
||||
f(228,19,2,2)
|
||||
n(508)
|
||||
n(605)
|
||||
u(773)
|
||||
u(757)
|
||||
u(789)
|
||||
u(573)
|
||||
u(741)
|
||||
u(613)
|
||||
u(629)
|
||||
u(621)
|
||||
f(579,18,2)
|
||||
f(1460,16,3,27)
|
||||
f(124,17,11,16)
|
||||
f(156,18,13,3)
|
||||
u(605)
|
||||
u(773)
|
||||
u(757)
|
||||
u(789)
|
||||
u(573)
|
||||
u(741)
|
||||
u(613)
|
||||
u(629)
|
||||
u(621)
|
||||
f(1468,15,3,46)
|
||||
f(124,16,19,27)
|
||||
f(100,17,10,4)
|
||||
n(156,13)
|
||||
f(228,18,1,2)
|
||||
n(605,10)
|
||||
u(773)
|
||||
u(757)
|
||||
u(789)
|
||||
u(573)
|
||||
u(741)
|
||||
u(613)
|
||||
u(629)
|
||||
f(621,26,1,9)
|
||||
f(713,1,9,405)
|
||||
u(697)
|
||||
u(705,229)
|
||||
f(1241,4,1,228)
|
||||
u(1177)
|
||||
u(1225)
|
||||
u(1185)
|
||||
u(1161)
|
||||
u(1169)
|
||||
u(1249,107)
|
||||
f(977,11,2,105)
|
||||
f(1233,12,2,103,2,0,0)
|
||||
u(657,13)
|
||||
f(674,14,4,9,8,0,0)
|
||||
f(866,15,1,3)
|
||||
u(866)
|
||||
f(906,15,3,5,4,0,0)
|
||||
u(906,3,2,0,0)
|
||||
u(922)
|
||||
f(1018,18,1,2)
|
||||
f(922,16,2)
|
||||
f(1201,13,2,90,2,0,0)
|
||||
u(1098,87,57,0,0)
|
||||
u(1074,87,59,0,0)
|
||||
u(1057,4)
|
||||
u(689)
|
||||
f(890,18,1,3)
|
||||
f(914,19,1,2)
|
||||
f(1082,16,2,83,59,1,0)
|
||||
f(682,17,41,2)
|
||||
n(1066,17)
|
||||
u(1050)
|
||||
f(1089,17,17,23,0,0,2)
|
||||
f(1265,14,23,3)
|
||||
u(1273)
|
||||
u(985)
|
||||
u(985)
|
||||
u(993)
|
||||
u(993)
|
||||
u(1009)
|
||||
u(1008)
|
||||
u(172)
|
||||
u(164)
|
||||
u(212)
|
||||
u(220)
|
||||
u(587)
|
||||
u(605)
|
||||
u(773)
|
||||
u(757)
|
||||
u(789)
|
||||
u(573)
|
||||
u(741)
|
||||
u(613)
|
||||
u(629)
|
||||
u(621)
|
||||
f(1257,10,3,121)
|
||||
u(1209)
|
||||
u(1257)
|
||||
u(1281)
|
||||
f(1001,14,1,117)
|
||||
u(1025)
|
||||
u(1153)
|
||||
u(1105,21,0,1,0)
|
||||
f(1034,18,13,4)
|
||||
u(1042)
|
||||
f(666,20,1,3)
|
||||
f(1315,18,3,4)
|
||||
f(1121,17,4,73)
|
||||
u(1113)
|
||||
u(1137,16)
|
||||
f(1034,20,14,2,1,0,0)
|
||||
u(1042)
|
||||
f(1145,19,2,57,0,2,1)
|
||||
f(1033,20,54,3)
|
||||
u(1042)
|
||||
u(666)
|
||||
f(1129,17,3,23)
|
||||
u(1113)
|
||||
u(1137)
|
||||
f(1033,20,18,5,1,0,0)
|
||||
u(1042)
|
||||
u(666)
|
||||
f(1218,14,5,3,1,0,0)
|
||||
u(1194,3,1,0,0)
|
||||
u(986,3,1,0,0)
|
||||
f(985,17,1,2)
|
||||
u(993)
|
||||
u(993)
|
||||
u(1009)
|
||||
u(1008)
|
||||
u(172)
|
||||
u(164)
|
||||
u(212)
|
||||
u(220)
|
||||
f(721,3,2,107)
|
||||
u(969)
|
||||
u(969)
|
||||
f(801,6,4,97)
|
||||
u(801,97,0,0,1)
|
||||
f(793,8,77,14)
|
||||
u(857,14,1,0,0)
|
||||
u(1393,14,1,0,0)
|
||||
f(1385,11,1,13)
|
||||
u(961,3)
|
||||
u(1417)
|
||||
u(1409)
|
||||
u(938)
|
||||
u(882)
|
||||
f(1401,12,3,10,2,0,0)
|
||||
u(1329,10,3,0,0)
|
||||
u(1329,10,3,0,0)
|
||||
u(1329,10,3,0,0)
|
||||
u(1337,10,3,1,0)
|
||||
f(1361,17,2,8,2,0,0)
|
||||
u(1361,8,2,0,0)
|
||||
u(1369,6)
|
||||
u(1345)
|
||||
u(1353)
|
||||
u(195)
|
||||
u(563)
|
||||
u(765)
|
||||
u(749)
|
||||
f(1301,26,1,5)
|
||||
u(1437)
|
||||
f(1309,28,1,4)
|
||||
u(781)
|
||||
f(645,30,1,3)
|
||||
u(653)
|
||||
f(1378,19,3,2)
|
||||
u(954)
|
||||
u(1291)
|
||||
f(874,8,2,6,5,0,0)
|
||||
u(874,6,5,0,0)
|
||||
u(930,6,5,0,0)
|
||||
f(985,6,6)
|
||||
u(985)
|
||||
u(993)
|
||||
u(993)
|
||||
u(1009)
|
||||
u(1008,6,0,1,4)
|
||||
f(172,12,2,4)
|
||||
u(164)
|
||||
u(212)
|
||||
u(220)
|
||||
u(587)
|
||||
f(605,17,1,3)
|
||||
u(773)
|
||||
u(757)
|
||||
u(789)
|
||||
u(573)
|
||||
u(741)
|
||||
u(613)
|
||||
u(629)
|
||||
u(621)
|
||||
f(729,3,3,69,0,2,1)
|
||||
f(817,4,21,2)
|
||||
u(1009)
|
||||
u(945)
|
||||
u(179)
|
||||
u(595)
|
||||
f(842,4,2,8,5,0,0)
|
||||
f(826,5,1,7,5,0,0)
|
||||
f(809,6,5,2)
|
||||
u(1008,2,0,0,1)
|
||||
f(849,4,2,38)
|
||||
u(849,38,0,0,2)
|
||||
f(834,6,12,23,21,0,0)
|
||||
f(826,7,6,17,15,1,0)
|
||||
f(898,6,17,3)
|
||||
|
||||
search();
|
||||
</script></body></html>
|
||||
BIN
demo/flamegraph.png
Normal file
BIN
demo/flamegraph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
119
pom-converter.xml
Normal file
119
pom-converter.xml
Normal file
@@ -0,0 +1,119 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler-converter</artifactId>
|
||||
<version>3.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
<url>https://profiler.tools</url>
|
||||
<description>Low overhead sampling profiler for Java</description>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>https://github.com/async-profiler/async-profiler</url>
|
||||
<connection>scm:git:git@github.com:async-profiler/async-profiler.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:async-profiler/async-profiler.git</developerConnection>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>apangin</id>
|
||||
<name>Andrei Pangin</name>
|
||||
<email>noreply@pangin.pro</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/converter</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/res</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>7</source>
|
||||
<target>7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
43
pom.xml
43
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler</artifactId>
|
||||
<version>1.8.3</version>
|
||||
<version>3.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
@@ -18,9 +18,9 @@
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>https://github.com/jvm-profiling-tools/async-profiler</url>
|
||||
<connection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</developerConnection>
|
||||
<url>https://github.com/async-profiler/async-profiler</url>
|
||||
<connection>scm:git:git@github.com:async-profiler/async-profiler.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:async-profiler/async-profiler.git</developerConnection>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
@@ -36,20 +36,51 @@
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/api</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>native</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<source>7</source>
|
||||
<target>7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<classifier>${native.platform}</classifier>
|
||||
<includes>
|
||||
<include>${native.platform}/*</include>
|
||||
<include>one/**</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>linux*/**</exclude>
|
||||
<exclude>macos*/**</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
|
||||
276
profiler.sh
276
profiler.sh
@@ -1,276 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [action] [options] <pid>"
|
||||
echo "Actions:"
|
||||
echo " start start profiling and return immediately"
|
||||
echo " resume resume profiling without resetting collected data"
|
||||
echo " stop stop profiling"
|
||||
echo " check check if the specified profiling event is available"
|
||||
echo " status print profiling status"
|
||||
echo " list list profiling events supported by the target JVM"
|
||||
echo " collect collect profile for the specified period of time"
|
||||
echo " and then stop (default action)"
|
||||
echo "Options:"
|
||||
echo " -e event profiling event: cpu|alloc|lock|cache-misses etc."
|
||||
echo " -d duration run profiling for <duration> seconds"
|
||||
echo " -f filename dump output to <filename>"
|
||||
echo " -i interval sampling interval in nanoseconds"
|
||||
echo " -j jstackdepth maximum Java stack depth"
|
||||
echo " -t profile different threads separately"
|
||||
echo " -s simple class names instead of FQN"
|
||||
echo " -g print method signatures"
|
||||
echo " -a annotate Java method names"
|
||||
echo " -o fmt output format: flat|collapsed|html|tree|jfr"
|
||||
echo " -I include output only stack traces containing the specified pattern"
|
||||
echo " -X exclude exclude stack traces with the specified pattern"
|
||||
echo " -v, --version display version string"
|
||||
echo ""
|
||||
echo " --title string FlameGraph title"
|
||||
echo " --minwidth pct skip frames smaller than pct%"
|
||||
echo " --reverse generate stack-reversed FlameGraph / Call tree"
|
||||
echo ""
|
||||
echo " --all-kernel only include kernel-mode events"
|
||||
echo " --all-user only include user-mode events"
|
||||
echo " --cstack mode how to traverse C stack: fp|lbr|no"
|
||||
echo " --begin function begin profiling when function is executed"
|
||||
echo " --end function end profiling when function is executed"
|
||||
echo ""
|
||||
echo "<pid> is a numeric process ID of the target JVM"
|
||||
echo " or 'jps' keyword to find running JVM automatically"
|
||||
echo " or the application's name as it would appear in the jps tool"
|
||||
echo ""
|
||||
echo "Example: $0 -d 30 -f profile.svg 3456"
|
||||
echo " $0 start -i 999000 jps"
|
||||
echo " $0 stop -o flat jps"
|
||||
echo " $0 -d 5 -e alloc MyAppName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
mirror_output() {
|
||||
# Mirror output from temporary file to local terminal
|
||||
if [ "$USE_TMP" = true ]; then
|
||||
if [ -f "$FILE" ]; then
|
||||
cat "$FILE"
|
||||
rm "$FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_if_terminated() {
|
||||
if ! kill -0 "$PID" 2> /dev/null; then
|
||||
mirror_output
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
jattach() {
|
||||
set +e
|
||||
"$JATTACH" "$PID" load "$PROFILER" true "$1" > /dev/null
|
||||
RET=$?
|
||||
set -e
|
||||
|
||||
# Check if jattach failed
|
||||
if [ $RET -ne 0 ]; then
|
||||
if [ $RET -eq 255 ]; then
|
||||
echo "Failed to inject profiler into $PID"
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
otool -L "$PROFILER"
|
||||
else
|
||||
ldd "$PROFILER"
|
||||
fi
|
||||
fi
|
||||
exit $RET
|
||||
fi
|
||||
|
||||
mirror_output
|
||||
}
|
||||
|
||||
OPTIND=1
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" > /dev/null 2>&1; pwd -P)"
|
||||
JATTACH=$SCRIPT_DIR/build/jattach
|
||||
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
|
||||
ACTION="collect"
|
||||
EVENT="cpu"
|
||||
DURATION="60"
|
||||
FILE=""
|
||||
USE_TMP="true"
|
||||
OUTPUT=""
|
||||
FORMAT=""
|
||||
PARAMS=""
|
||||
PID=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-h|"-?")
|
||||
usage
|
||||
;;
|
||||
start|resume|stop|check|status|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
-e)
|
||||
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
|
||||
shift
|
||||
;;
|
||||
-d)
|
||||
DURATION="$2"
|
||||
shift
|
||||
;;
|
||||
-f)
|
||||
FILE="$2"
|
||||
USE_TMP=false
|
||||
shift
|
||||
;;
|
||||
-i)
|
||||
PARAMS="$PARAMS,interval=$2"
|
||||
shift
|
||||
;;
|
||||
-j)
|
||||
PARAMS="$PARAMS,jstackdepth=$2"
|
||||
shift
|
||||
;;
|
||||
-t)
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-s)
|
||||
FORMAT="$FORMAT,simple"
|
||||
;;
|
||||
-g)
|
||||
FORMAT="$FORMAT,sig"
|
||||
;;
|
||||
-a)
|
||||
FORMAT="$FORMAT,ann"
|
||||
;;
|
||||
-o)
|
||||
OUTPUT="$2"
|
||||
shift
|
||||
;;
|
||||
-I|--include)
|
||||
FORMAT="$FORMAT,include=$2"
|
||||
shift
|
||||
;;
|
||||
-X|--exclude)
|
||||
FORMAT="$FORMAT,exclude=$2"
|
||||
shift
|
||||
;;
|
||||
--filter)
|
||||
FILTER="$(echo "$2" | sed 's/,/;/g')"
|
||||
FORMAT="$FORMAT,filter=$FILTER"
|
||||
shift
|
||||
;;
|
||||
--title)
|
||||
# escape XML special characters and comma
|
||||
TITLE="$(echo "$2" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/,/\,/g')"
|
||||
FORMAT="$FORMAT,title=$TITLE"
|
||||
shift
|
||||
;;
|
||||
--width|--height|--minwidth)
|
||||
FORMAT="$FORMAT,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--reverse)
|
||||
FORMAT="$FORMAT,reverse"
|
||||
;;
|
||||
--all-kernel)
|
||||
PARAMS="$PARAMS,allkernel"
|
||||
;;
|
||||
--all-user)
|
||||
PARAMS="$PARAMS,alluser"
|
||||
;;
|
||||
--cstack|--call-graph)
|
||||
PARAMS="$PARAMS,cstack=$2"
|
||||
shift
|
||||
;;
|
||||
--begin|--end)
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--safe-mode)
|
||||
PARAMS="$PARAMS,safemode=$2"
|
||||
shift
|
||||
;;
|
||||
[0-9]*)
|
||||
PID="$1"
|
||||
;;
|
||||
jps)
|
||||
# A shortcut for getting PID of a running Java application
|
||||
# -XX:+PerfDisableSharedMem prevents jps from appearing in its own list
|
||||
PID=$(pgrep -n java || jps -q -J-XX:+PerfDisableSharedMem)
|
||||
if [ "$PID" = "" ]; then
|
||||
echo "No Java process could be found!"
|
||||
fi
|
||||
;;
|
||||
-*)
|
||||
echo "Unrecognized option: $1"
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
if [ $# -eq 1 ]; then
|
||||
# the last argument is the application name as it would appear in the jps tool
|
||||
PID=$(jps -J-XX:+PerfDisableSharedMem | grep " $1$" | head -n 1 | cut -d ' ' -f 1)
|
||||
if [ "$PID" = "" ]; then
|
||||
echo "No Java process '$1' could be found!"
|
||||
fi
|
||||
else
|
||||
echo "Unrecognized option: $1"
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$PID" = "" ] && [ "$ACTION" != "version" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
# If no -f argument is given, use temporary file to transfer output to caller terminal.
|
||||
# Let the target process create the file in case this script is run by superuser.
|
||||
if [ "$USE_TMP" = true ]; then
|
||||
FILE=/tmp/async-profiler.$$.$PID
|
||||
else
|
||||
case "$FILE" in
|
||||
/*)
|
||||
# Path is absolute
|
||||
;;
|
||||
*)
|
||||
# Output file is written by the target process. Make the path absolute to avoid confusion.
|
||||
FILE=$PWD/$FILE
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case $ACTION in
|
||||
start|resume|check)
|
||||
jattach "$ACTION,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
;;
|
||||
stop)
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
status)
|
||||
jattach "status,file=$FILE"
|
||||
;;
|
||||
list)
|
||||
jattach "list,file=$FILE"
|
||||
;;
|
||||
collect)
|
||||
jattach "start,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
while [ "$DURATION" -gt 0 ]; do
|
||||
DURATION=$(( DURATION-1 ))
|
||||
check_if_terminated
|
||||
sleep 1
|
||||
done
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
version)
|
||||
if [ "$PID" = "" ]; then
|
||||
java "-agentpath:$PROFILER=version=full" -version 2> /dev/null
|
||||
else
|
||||
jattach "version=full,file=$FILE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
@@ -1,38 +1,26 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "allocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
int AllocTracer::_trap_kind;
|
||||
Trap AllocTracer::_in_new_tlab;
|
||||
Trap AllocTracer::_outside_tlab;
|
||||
Trap AllocTracer::_in_new_tlab(0);
|
||||
Trap AllocTracer::_outside_tlab(1);
|
||||
|
||||
u64 AllocTracer::_interval;
|
||||
volatile u64 AllocTracer::_allocated_bytes;
|
||||
|
||||
|
||||
// Called whenever our breakpoint trap is hit
|
||||
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
int event_type;
|
||||
EventType event_type;
|
||||
uintptr_t total_size;
|
||||
uintptr_t instance_size;
|
||||
|
||||
@@ -40,17 +28,18 @@ void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (_in_new_tlab.covers(frame.pc())) {
|
||||
// send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread)
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
event_type = BCI_ALLOC;
|
||||
event_type = ALLOC_SAMPLE;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = _trap_kind == 1 ? frame.arg3() : frame.arg2();
|
||||
} else if (_outside_tlab.covers(frame.pc())) {
|
||||
// send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread)
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
event_type = BCI_ALLOC_OUTSIDE_TLAB;
|
||||
event_type = ALLOC_OUTSIDE_TLAB;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = 0;
|
||||
} else {
|
||||
// Not our trap
|
||||
Profiler::instance()->trapHandler(signo, siginfo, ucontext);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,31 +47,13 @@ void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
uintptr_t klass = frame.arg0();
|
||||
frame.ret();
|
||||
|
||||
if (_enabled) {
|
||||
// TODO: _enabled also uses traps
|
||||
if (_enabled && updateCounter(_allocated_bytes, total_size, _interval)) {
|
||||
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
void AllocTracer::recordAllocation(void* ucontext, EventType event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size) {
|
||||
if (_interval) {
|
||||
// Do not record allocation unless allocated at least _interval bytes
|
||||
while (true) {
|
||||
u64 prev = _allocated_bytes;
|
||||
u64 next = prev + total_size;
|
||||
if (next < _interval) {
|
||||
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next % _interval)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AllocEvent event;
|
||||
event._class_id = 0;
|
||||
event._total_size = total_size;
|
||||
@@ -90,18 +61,22 @@ void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rkl
|
||||
|
||||
if (VMStructs::hasClassNames()) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(symbol->body(), symbol->length());
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(symbol->body(), symbol->length());
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(ucontext, total_size, event_type, &event);
|
||||
Profiler::instance()->recordSample(ucontext, total_size, event_type, &event);
|
||||
}
|
||||
|
||||
Error AllocTracer::check(Arguments& args) {
|
||||
if (args._live) {
|
||||
return Error("'live' option is supported on OpenJDK 11+");
|
||||
}
|
||||
|
||||
if (_in_new_tlab.entry() != 0 && _outside_tlab.entry() != 0) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
NativeCodeCache* libjvm = VMStructs::libjvm();
|
||||
CodeCache* libjvm = VMStructs::libjvm();
|
||||
const void* ne;
|
||||
const void* oe;
|
||||
|
||||
@@ -118,9 +93,9 @@ Error AllocTracer::check(Arguments& args) {
|
||||
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
|
||||
}
|
||||
|
||||
if (!_in_new_tlab.assign(ne) || !_outside_tlab.assign(oe)) {
|
||||
return Error("Unable to install allocation trap");
|
||||
}
|
||||
_in_new_tlab.assign(ne);
|
||||
_outside_tlab.assign(oe);
|
||||
_in_new_tlab.pair(_outside_tlab);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
@@ -131,13 +106,12 @@ Error AllocTracer::start(Arguments& args) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._interval;
|
||||
_interval = args._alloc > 0 ? args._alloc : 0;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
OS::installSignalHandler(SIGTRAP, signalHandler);
|
||||
|
||||
_in_new_tlab.install();
|
||||
_outside_tlab.install();
|
||||
if (!_in_new_tlab.install() || !_outside_tlab.install()) {
|
||||
return Error("Cannot install allocation breakpoints");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ALLOCTRACER_H
|
||||
@@ -20,6 +9,7 @@
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include "engine.h"
|
||||
#include "event.h"
|
||||
#include "trap.h"
|
||||
|
||||
|
||||
@@ -32,27 +22,23 @@ class AllocTracer : public Engine {
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
static void recordAllocation(void* ucontext, EventType event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "alloc";
|
||||
const char* title() {
|
||||
return "Allocation profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "bytes";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
};
|
||||
|
||||
#endif // _ALLOCTRACER_H
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Java API for in-process profiling. Serves as a wrapper around
|
||||
@@ -39,25 +31,93 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
return instance;
|
||||
}
|
||||
|
||||
if (libPath == null) {
|
||||
System.loadLibrary("asyncProfiler");
|
||||
} else {
|
||||
AsyncProfiler profiler = new AsyncProfiler();
|
||||
if (libPath != null) {
|
||||
System.load(libPath);
|
||||
} else {
|
||||
try {
|
||||
// No need to load library, if it has been preloaded with -agentpath
|
||||
profiler.getVersion();
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
File file = extractEmbeddedLib();
|
||||
if (file != null) {
|
||||
try {
|
||||
System.load(file.getPath());
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
} else {
|
||||
System.loadLibrary("asyncProfiler");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance = new AsyncProfiler();
|
||||
return instance;
|
||||
instance = profiler;
|
||||
return profiler;
|
||||
}
|
||||
|
||||
private static File extractEmbeddedLib() {
|
||||
String resourceName = "/" + getPlatformTag() + "/libasyncProfiler.so";
|
||||
InputStream in = AsyncProfiler.class.getResourceAsStream(resourceName);
|
||||
if (in == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String extractPath = System.getProperty("one.profiler.extractPath");
|
||||
File file = File.createTempFile("libasyncProfiler-", ".so",
|
||||
extractPath == null || extractPath.isEmpty() ? null : new File(extractPath));
|
||||
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||
byte[] buf = new byte[32000];
|
||||
for (int bytes; (bytes = in.read(buf)) >= 0; ) {
|
||||
out.write(buf, 0, bytes);
|
||||
}
|
||||
}
|
||||
return file;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPlatformTag() {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
String arch = System.getProperty("os.arch").toLowerCase();
|
||||
if (os.contains("linux")) {
|
||||
if (arch.equals("amd64") || arch.equals("x86_64") || arch.contains("x64")) {
|
||||
return "linux-x64";
|
||||
} else if (arch.equals("aarch64") || arch.contains("arm64")) {
|
||||
return "linux-arm64";
|
||||
} else if (arch.equals("aarch32") || arch.contains("arm")) {
|
||||
return "linux-arm32";
|
||||
} else if (arch.contains("86")) {
|
||||
return "linux-x86";
|
||||
} else if (arch.contains("ppc64")) {
|
||||
return "linux-ppc64le";
|
||||
}
|
||||
} else if (os.contains("mac")) {
|
||||
return "macos";
|
||||
}
|
||||
throw new UnsupportedOperationException("Unsupported platform: " + os + "-" + arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiling
|
||||
*
|
||||
* @param event Profiling event, see {@link Events}
|
||||
* @param event Profiling event, see {@link Events}
|
||||
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
|
||||
* @throws IllegalStateException If profiler is already running
|
||||
*/
|
||||
@Override
|
||||
public void start(String event, long interval) throws IllegalStateException {
|
||||
if (event == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
start0(event, interval, true);
|
||||
}
|
||||
|
||||
@@ -65,12 +125,15 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
* Start or resume profiling without resetting collected data.
|
||||
* Note that event and interval may change since the previous profiling session.
|
||||
*
|
||||
* @param event Profiling event, see {@link Events}
|
||||
* @param event Profiling event, see {@link Events}
|
||||
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
|
||||
* @throws IllegalStateException If profiler is already running
|
||||
*/
|
||||
@Override
|
||||
public void resume(String event, long interval) throws IllegalStateException {
|
||||
if (event == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
start0(event, interval, false);
|
||||
}
|
||||
|
||||
@@ -113,10 +176,13 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
* @param command Profiling command
|
||||
* @return The command result
|
||||
* @throws IllegalArgumentException If failed to parse the command
|
||||
* @throws IOException If failed to create output file
|
||||
* @throws IOException If failed to create output file
|
||||
*/
|
||||
@Override
|
||||
public String execute(String command) throws IllegalArgumentException, IOException {
|
||||
public String execute(String command) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
if (command == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return execute0(command);
|
||||
}
|
||||
|
||||
@@ -129,7 +195,22 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
@Override
|
||||
public String dumpCollapsed(Counter counter) {
|
||||
try {
|
||||
return execute0("collapsed,counter=" + counter.name().toLowerCase());
|
||||
return execute0("collapsed," + counter.name().toLowerCase());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump collected stack traces
|
||||
*
|
||||
* @param maxTraces Maximum number of stack traces to dump. 0 means no limit
|
||||
* @return Textual representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public String dumpTraces(int maxTraces) {
|
||||
try {
|
||||
return execute0(maxTraces == 0 ? "traces" : "traces=" + maxTraces);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
@@ -144,7 +225,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
@Override
|
||||
public String dumpFlat(int maxMethods) {
|
||||
try {
|
||||
return execute0("flat=" + maxMethods);
|
||||
return execute0(maxMethods == 0 ? "flat" : "flat=" + maxMethods);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
@@ -171,7 +252,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
}
|
||||
|
||||
private void filterThread(Thread thread, boolean enable) {
|
||||
if (thread == null) {
|
||||
if (thread == null || thread == Thread.currentThread()) {
|
||||
filterThread0(null, enable);
|
||||
} else {
|
||||
// Need to take lock to avoid race condition with a thread state change
|
||||
@@ -185,7 +266,10 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
}
|
||||
|
||||
private native void start0(String event, long interval, boolean reset) throws IllegalStateException;
|
||||
|
||||
private native void stop0() throws IllegalStateException;
|
||||
private native String execute0(String command) throws IllegalArgumentException, IOException;
|
||||
|
||||
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
|
||||
private native void filterThread0(Thread thread, boolean enable);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
@@ -35,8 +24,9 @@ public interface AsyncProfilerMXBean {
|
||||
long getSamples();
|
||||
String getVersion();
|
||||
|
||||
String execute(String command) throws IllegalArgumentException, java.io.IOException;
|
||||
String execute(String command) throws IllegalArgumentException, IllegalStateException, java.io.IOException;
|
||||
|
||||
String dumpCollapsed(Counter counter);
|
||||
String dumpTraces(int maxTraces);
|
||||
String dumpFlat(int maxMethods);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
@@ -24,5 +13,6 @@ public class Events {
|
||||
public static final String ALLOC = "alloc";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String WALL = "wall";
|
||||
public static final String CTIMER = "ctimer";
|
||||
public static final String ITIMER = "itimer";
|
||||
}
|
||||
|
||||
111
src/arch.h
111
src/arch.h
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ARCH_H
|
||||
@@ -31,28 +20,42 @@ static inline int atomicInc(volatile int& var, int increment = 1) {
|
||||
return __sync_fetch_and_add(&var, increment);
|
||||
}
|
||||
|
||||
static inline u64 loadAcquire(u64& var) {
|
||||
return __atomic_load_n(&var, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
static inline void storeRelease(u64& var, u64 value) {
|
||||
return __atomic_store_n(&var, value, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
|
||||
typedef unsigned char instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xcc;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = 2;
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int PROBE_SP_LIMIT = 4;
|
||||
const int PLT_HEADER_SIZE = 16;
|
||||
const int PLT_ENTRY_SIZE = 16;
|
||||
const int PERF_REG_PC = 8; // PERF_REG_X86_IP
|
||||
|
||||
#define spinPause() asm volatile("pause")
|
||||
#define rmb() asm volatile("lfence" : : : "memory")
|
||||
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r"(addr) : "memory")
|
||||
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r" (addr) : "memory")
|
||||
|
||||
#elif defined(__arm__) || defined(__thumb__)
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xe7f001f0;
|
||||
const instruction_t BREAKPOINT_THUMB = 0xde01de01;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 20;
|
||||
const int PLT_ENTRY_SIZE = 12;
|
||||
const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
|
||||
@@ -65,16 +68,76 @@ const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xd4200000;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 32;
|
||||
const int PLT_ENTRY_SIZE = 16;
|
||||
const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
|
||||
|
||||
#define spinPause() asm volatile("yield")
|
||||
#define spinPause() asm volatile("isb")
|
||||
#define rmb() asm volatile("dmb ish" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#elif defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0x7fe00008;
|
||||
// We place the break point in the third instruction slot on PPCLE as the first two are skipped if
|
||||
// the call comes from within the same compilation unit according to the LE ABI.
|
||||
const int BREAKPOINT_OFFSET = 8;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 2;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 24;
|
||||
const int PLT_ENTRY_SIZE = 24;
|
||||
const int PERF_REG_PC = 32; // PERF_REG_POWERPC_NIP
|
||||
|
||||
#define spinPause() asm volatile("yield") // does nothing, but using or 1,1,1 would lead to other problems
|
||||
#define rmb() asm volatile ("sync" : : : "memory") // lwsync would do but better safe than sorry
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#elif defined(__riscv) && (__riscv_xlen == 64)
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
#if defined(__riscv_compressed)
|
||||
const instruction_t BREAKPOINT = 0x9002; // EBREAK (compressed form)
|
||||
#else
|
||||
const instruction_t BREAKPOINT = 0x00100073; // EBREAK
|
||||
#endif
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1; // return address is at -1 from FP
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 24; // Best guess from examining readelf
|
||||
const int PLT_ENTRY_SIZE = 24; // ...same...
|
||||
const int PERF_REG_PC = 0; // PERF_REG_RISCV_PC
|
||||
|
||||
#define spinPause() // No architecture support
|
||||
#define rmb() asm volatile ("fence" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#elif defined(__loongarch_lp64)
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0x002a0005; // EBREAK
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 32;
|
||||
const int PLT_ENTRY_SIZE = 16;
|
||||
const int PERF_REG_PC = 0; // PERF_REG_LOONGARCH_PC
|
||||
|
||||
#define spinPause() asm volatile("ibar 0x0")
|
||||
#define rmb() asm volatile("dbar 0x0" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#else
|
||||
|
||||
#error "Compiling on unsupported arch"
|
||||
@@ -82,4 +145,22 @@ const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
|
||||
#endif
|
||||
|
||||
|
||||
// Return address signing support.
|
||||
// Apple M1 has 47 bit virtual addresses.
|
||||
#if defined(__aarch64__) && defined(__APPLE__)
|
||||
# define ADDRESS_BITS 47
|
||||
# define WX_MEMORY true
|
||||
#else
|
||||
# define WX_MEMORY false
|
||||
#endif
|
||||
|
||||
#ifdef ADDRESS_BITS
|
||||
static inline const void* stripPointer(const void* p) {
|
||||
return (const void*) ((unsigned long)p & ((1UL << ADDRESS_BITS) - 1));
|
||||
}
|
||||
#else
|
||||
# define stripPointer(p) (p)
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _ARCH_H
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
@@ -24,66 +13,97 @@
|
||||
#include "arguments.h"
|
||||
|
||||
|
||||
// Arguments of the last start/resume command; reused for shutdown and restart
|
||||
Arguments _global_args;
|
||||
|
||||
// Predefined value that denotes successful operation
|
||||
const Error Error::OK(NULL);
|
||||
|
||||
// Extra buffer space for expanding file pattern
|
||||
const size_t EXTRA_BUF_SIZE = 512;
|
||||
|
||||
// Statically compute hash code of a string containing up to 12 [a-z] letters
|
||||
#define HASH(s) (HASH12(s " "))
|
||||
static const Multiplier NANOS[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'s', 1000000000}, {0, 0}};
|
||||
static const Multiplier BYTES[] = {{'b', 1}, {'k', 1024}, {'m', 1048576}, {'g', 1073741824}, {0, 0}};
|
||||
static const Multiplier SECONDS[] = {{'s', 1}, {'m', 60}, {'h', 3600}, {'d', 86400}, {0, 0}};
|
||||
static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'s', 1000000000}, {'b', 1}, {'k', 1024}, {'g', 1073741824}, {0, 0}};
|
||||
|
||||
#define HASH12(s) (s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
|
||||
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
|
||||
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55
|
||||
|
||||
// Statically compute hash code of a string containing up to 12 [a-z] letters
|
||||
#define HASH(s) ((s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
|
||||
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
|
||||
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55)
|
||||
|
||||
// Simulate switch statement over string hashes
|
||||
#define SWITCH(arg) long long arg_hash = hash(arg); if (0)
|
||||
|
||||
#define CASE(s) } else if (arg_hash == HASH(s)) {
|
||||
#define CASE(s) } else if (arg_hash == HASH(s " ")) {
|
||||
|
||||
#define CASE2(s1, s2) } else if (arg_hash == HASH(s1) || arg_hash == HASH(s2)) {
|
||||
#define DEFAULT() } else {
|
||||
|
||||
|
||||
// Parses agent arguments.
|
||||
// The format of the string is:
|
||||
// arg[,arg...]
|
||||
// where arg is one of the following options:
|
||||
// start - start profiling
|
||||
// resume - start or resume profiling without resetting collected data
|
||||
// stop - stop profiling
|
||||
// check - check if the specified profiling event is available
|
||||
// status - print profiling status (inactive / running for X seconds)
|
||||
// list - show the list of available profiling events
|
||||
// version[=full] - display the agent version
|
||||
// event=EVENT - which event to trace (cpu, alloc, lock, cache-misses etc.)
|
||||
// collapsed[=C] - dump collapsed stacks (the format used by FlameGraph script)
|
||||
// html[=C] - produce Flame Graph in HTML format
|
||||
// tree[=C] - produce call tree in HTML format
|
||||
// C is counter type: 'samples' or 'total'
|
||||
// jfr - dump events in Java Flight Recorder format
|
||||
// flat[=N] - dump top N methods (aka flat profile)
|
||||
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
|
||||
// jstackdepth=N - maximum Java stack depth (default: 2048)
|
||||
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
|
||||
// file=FILENAME - output file name for dumping
|
||||
// filter=FILTER - thread filter
|
||||
// threads - profile different threads separately
|
||||
// cstack=MODE - how to collect C stack frames in addition to Java stack
|
||||
// MODE is 'fp' (Frame Pointer), 'lbr' (Last Branch Record) or 'no'
|
||||
// allkernel - include only kernel-mode events
|
||||
// alluser - include only user-mode events
|
||||
// simple - simple class names instead of FQN
|
||||
// dot - dotted class names
|
||||
// sig - print method signatures
|
||||
// ann - annotate Java method names
|
||||
// include=PATTERN - include stack traces containing PATTERN
|
||||
// exclude=PATTERN - exclude stack traces containing PATTERN
|
||||
// begin=FUNCTION - begin profiling when FUNCTION is executed
|
||||
// end=FUNCTION - end profiling when FUNCTION is executed
|
||||
// title=TITLE - FlameGraph title
|
||||
// minwidth=PCT - FlameGraph minimum frame width in percent
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree
|
||||
// start - start profiling
|
||||
// resume - start or resume profiling without resetting collected data
|
||||
// stop - stop profiling
|
||||
// dump - dump collected data without stopping profiling session
|
||||
// check - check if the specified profiling event is available
|
||||
// status - print profiling status (inactive / running for X seconds)
|
||||
// meminfo - print profiler memory stats
|
||||
// list - show the list of available profiling events
|
||||
// version - display the agent version
|
||||
// event=EVENT - which event to trace (cpu, wall, cache-misses, etc.)
|
||||
// alloc[=BYTES] - profile allocations with BYTES interval
|
||||
// live - build allocation profile from live objects only
|
||||
// lock[=DURATION] - profile contended locks longer than DURATION ns
|
||||
// wall[=NS] - run wall clock profiling together with CPU profiling
|
||||
// collapsed - dump collapsed stacks (the format used by FlameGraph script)
|
||||
// flamegraph - produce Flame Graph in HTML format
|
||||
// tree - produce call tree in HTML format
|
||||
// jfr - dump events in Java Flight Recorder format
|
||||
// jfrsync[=CONFIG] - start Java Flight Recording with the given config along with the profiler
|
||||
// traces[=N] - dump top N call traces
|
||||
// flat[=N] - dump top N methods (aka flat profile)
|
||||
// samples - count the number of samples (default)
|
||||
// total - count the total value (time, bytes, etc.) instead of samples
|
||||
// chunksize=N - approximate size of JFR chunk in bytes (default: 100 MB)
|
||||
// chunktime=N - duration of JFR chunk in seconds (default: 1 hour)
|
||||
// timeout=TIME - automatically stop profiler at TIME (absolute or relative)
|
||||
// loop=TIME - run profiler in a loop (continuous profiling)
|
||||
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
|
||||
// jstackdepth=N - maximum Java stack depth (default: 2048)
|
||||
// signal=N - use alternative signal for cpu or wall clock profiling
|
||||
// features=LIST - advanced stack trace features (vtable, comptask)"
|
||||
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
|
||||
// file=FILENAME - output file name for dumping
|
||||
// log=FILENAME - log warnings and errors to the given dedicated stream
|
||||
// loglevel=LEVEL - logging level: TRACE, DEBUG, INFO, WARN, ERROR, or NONE
|
||||
// server=ADDRESS - start insecure HTTP server at ADDRESS/PORT
|
||||
// filter=FILTER - thread filter
|
||||
// threads - profile different threads separately
|
||||
// sched - group threads by scheduling policy
|
||||
// cstack=MODE - how to collect C stack frames in addition to Java stack
|
||||
// MODE is 'fp', 'dwarf', 'lbr', 'vm' or 'no'
|
||||
// clock=SOURCE - clock source for JFR timestamps: 'tsc' or 'monotonic'
|
||||
// allkernel - include only kernel-mode events
|
||||
// alluser - include only user-mode events
|
||||
// fdtransfer - use fdtransfer to pass fds to the profiler
|
||||
// simple - simple class names instead of FQN
|
||||
// dot - dotted class names
|
||||
// norm - normalize names of hidden classes / lambdas
|
||||
// sig - print method signatures
|
||||
// ann - annotate Java methods
|
||||
// lib - prepend library names
|
||||
// mcache - max age of jmethodID cache (default: 0 = disabled)
|
||||
// include=PATTERN - include stack traces containing PATTERN
|
||||
// exclude=PATTERN - exclude stack traces containing PATTERN
|
||||
// begin=FUNCTION - begin profiling when FUNCTION is executed
|
||||
// end=FUNCTION - end profiling when FUNCTION is executed
|
||||
// title=TITLE - FlameGraph title
|
||||
// minwidth=PCT - FlameGraph minimum frame width in percent
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree
|
||||
//
|
||||
// It is possible to specify multiple dump options at the same time
|
||||
|
||||
@@ -94,13 +114,15 @@ Error Arguments::parse(const char* args) {
|
||||
|
||||
size_t len = strlen(args);
|
||||
free(_buf);
|
||||
_buf = (char*)malloc(len + EXTRA_BUF_SIZE);
|
||||
_buf = (char*)malloc(len + EXTRA_BUF_SIZE + 1);
|
||||
if (_buf == NULL) {
|
||||
return Error("Not enough memory to parse arguments");
|
||||
}
|
||||
strcpy(_buf, args);
|
||||
char* args_copy = strcpy(_buf + EXTRA_BUF_SIZE, args);
|
||||
|
||||
for (char* arg = strtok(_buf, ","); arg != NULL; arg = strtok(NULL, ",")) {
|
||||
const char* msg = NULL;
|
||||
|
||||
for (char* arg = strtok(args_copy, ","); arg != NULL; arg = strtok(NULL, ",")) {
|
||||
char* value = strchr(arg, '=');
|
||||
if (value != NULL) *value++ = 0;
|
||||
|
||||
@@ -115,80 +137,188 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("stop")
|
||||
_action = ACTION_STOP;
|
||||
|
||||
CASE("dump")
|
||||
_action = ACTION_DUMP;
|
||||
|
||||
CASE("check")
|
||||
_action = ACTION_CHECK;
|
||||
|
||||
CASE("status")
|
||||
_action = ACTION_STATUS;
|
||||
|
||||
CASE("meminfo")
|
||||
_action = ACTION_MEMINFO;
|
||||
|
||||
CASE("list")
|
||||
_action = ACTION_LIST;
|
||||
|
||||
CASE("version")
|
||||
_action = value == NULL ? ACTION_VERSION : ACTION_FULL_VERSION;
|
||||
_action = ACTION_VERSION;
|
||||
|
||||
// Output formats
|
||||
CASE2("collapsed", "folded")
|
||||
CASE("collapsed")
|
||||
_output = OUTPUT_COLLAPSED;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE2("flamegraph", "html")
|
||||
CASE("flamegraph")
|
||||
_output = OUTPUT_FLAMEGRAPH;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE("tree")
|
||||
_output = OUTPUT_TREE;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE("jfr")
|
||||
_output = OUTPUT_JFR;
|
||||
if (value != NULL) {
|
||||
_jfr_options = (int)strtol(value, NULL, 0);
|
||||
}
|
||||
|
||||
CASE("jfrsync")
|
||||
_output = OUTPUT_JFR;
|
||||
_jfr_options = JFR_SYNC_OPTS;
|
||||
_jfr_sync = value == NULL ? "default" : value;
|
||||
|
||||
CASE("traces")
|
||||
_output = OUTPUT_TEXT;
|
||||
_dump_traces = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
CASE("flat")
|
||||
_output = OUTPUT_FLAT;
|
||||
_output = OUTPUT_TEXT;
|
||||
_dump_flat = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
CASE("samples")
|
||||
_counter = COUNTER_SAMPLES;
|
||||
|
||||
CASE("total")
|
||||
_counter = COUNTER_TOTAL;
|
||||
|
||||
CASE("chunksize")
|
||||
if (value == NULL || (_chunk_size = parseUnits(value, BYTES)) < 0) {
|
||||
msg = "Invalid chunksize";
|
||||
}
|
||||
|
||||
CASE("chunktime")
|
||||
if (value == NULL || (_chunk_time = parseUnits(value, SECONDS)) < 0) {
|
||||
msg = "Invalid chunktime";
|
||||
}
|
||||
|
||||
// Basic options
|
||||
CASE("event")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("event must not be empty");
|
||||
msg = "event must not be empty";
|
||||
} else if (strcmp(value, EVENT_ALLOC) == 0) {
|
||||
if (_alloc < 0) _alloc = 0;
|
||||
} else if (strcmp(value, EVENT_LOCK) == 0) {
|
||||
if (_lock < 0) _lock = 0;
|
||||
} else if (_event != NULL) {
|
||||
msg = "Duplicate event argument";
|
||||
} else {
|
||||
_event = value;
|
||||
}
|
||||
|
||||
if (!addEvent(value)) {
|
||||
return Error("multiple incompatible events");
|
||||
CASE("timeout")
|
||||
if (value == NULL || (_timeout = parseTimeout(value)) == -1) {
|
||||
msg = "Invalid timeout";
|
||||
}
|
||||
|
||||
CASE("loop")
|
||||
_loop = true;
|
||||
if (value == NULL || (_timeout = parseTimeout(value)) == -1) {
|
||||
msg = "Invalid loop duration";
|
||||
}
|
||||
|
||||
CASE("alloc")
|
||||
_alloc = value == NULL ? 0 : parseUnits(value, BYTES);
|
||||
|
||||
CASE("lock")
|
||||
_lock = value == NULL ? 0 : parseUnits(value, NANOS);
|
||||
|
||||
CASE("wall")
|
||||
_wall = value == NULL ? 0 : parseUnits(value, NANOS);
|
||||
|
||||
CASE("interval")
|
||||
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
|
||||
return Error("Invalid interval");
|
||||
if (value == NULL || (_interval = parseUnits(value, UNIVERSAL)) <= 0) {
|
||||
msg = "Invalid interval";
|
||||
}
|
||||
|
||||
CASE("jstackdepth")
|
||||
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
|
||||
return Error("jstackdepth must be > 0");
|
||||
msg = "jstackdepth must be > 0";
|
||||
}
|
||||
|
||||
CASE("safemode")
|
||||
_safe_mode = value == NULL ? INT_MAX : atoi(value);
|
||||
CASE("signal")
|
||||
if (value == NULL || (_signal = atoi(value)) <= 0) {
|
||||
msg = "signal must be > 0";
|
||||
} else if ((value = strchr(value, '/')) != NULL) {
|
||||
// Two signals were specified: one for CPU profiling, another for wall clock
|
||||
_signal |= atoi(value + 1) << 8;
|
||||
}
|
||||
|
||||
CASE("features")
|
||||
if (value != NULL) {
|
||||
if (strstr(value, "probesp")) _features.probe_sp = 1;
|
||||
if (strstr(value, "vtable")) _features.vtable_target = 1;
|
||||
if (strstr(value, "comptask")) _features.comp_task = 1;
|
||||
}
|
||||
|
||||
CASE("safemode") {
|
||||
// Left for compatibility purpose; will be eventually migrated to 'features'
|
||||
int bits = value == NULL ? INT_MAX : (int)strtol(value, NULL, 0);
|
||||
_features.unknown_java = (bits & 1) ? 0 : 1;
|
||||
_features.unwind_stub = (bits & 2) ? 0 : 1;
|
||||
_features.unwind_comp = (bits & 4) ? 0 : 1;
|
||||
_features.unwind_native = (bits & 8) ? 0 : 1;
|
||||
_features.java_anchor = (bits & 16) ? 0 : 1;
|
||||
_features.gc_traces = (bits & 32) ? 0 : 1;
|
||||
}
|
||||
|
||||
CASE("file")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("file must not be empty");
|
||||
msg = "file must not be empty";
|
||||
}
|
||||
_file = value;
|
||||
|
||||
CASE("log")
|
||||
_log = value == NULL || value[0] == 0 ? NULL : value;
|
||||
|
||||
CASE("loglevel")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
msg = "loglevel must not be empty";
|
||||
}
|
||||
_loglevel = value;
|
||||
|
||||
CASE("server")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
msg = "server address must not be empty";
|
||||
}
|
||||
_server = value;
|
||||
|
||||
CASE("fdtransfer")
|
||||
_fdtransfer = true;
|
||||
if (value == NULL || value[0] == 0) {
|
||||
msg = "fdtransfer path must not be empty";
|
||||
}
|
||||
_fdtransfer_path = value;
|
||||
|
||||
// Filters
|
||||
CASE("filter")
|
||||
_filter = value == NULL ? "" : value;
|
||||
|
||||
CASE("include")
|
||||
if (value != NULL) appendToEmbeddedList(_include, value);
|
||||
// Workaround -Wstringop-overflow warning
|
||||
if (value == arg + 8) appendToEmbeddedList(_include, arg + 8);
|
||||
|
||||
CASE("exclude")
|
||||
if (value != NULL) appendToEmbeddedList(_exclude, value);
|
||||
// Workaround -Wstringop-overflow warning
|
||||
if (value == arg + 8) appendToEmbeddedList(_exclude, arg + 8);
|
||||
|
||||
CASE("threads")
|
||||
_threads = true;
|
||||
|
||||
CASE("sched")
|
||||
_sched = true;
|
||||
|
||||
CASE("live")
|
||||
_live = true;
|
||||
|
||||
CASE("allkernel")
|
||||
_ring = RING_KERNEL;
|
||||
|
||||
@@ -197,12 +327,21 @@ Error Arguments::parse(const char* args) {
|
||||
|
||||
CASE("cstack")
|
||||
if (value != NULL) {
|
||||
if (value[0] == 'n') {
|
||||
_cstack = CSTACK_NO;
|
||||
} else if (value[0] == 'l') {
|
||||
_cstack = CSTACK_LBR;
|
||||
} else {
|
||||
_cstack = CSTACK_FP;
|
||||
switch (value[0]) {
|
||||
case 'n': _cstack = CSTACK_NO; break;
|
||||
case 'd': _cstack = CSTACK_DWARF; break;
|
||||
case 'l': _cstack = CSTACK_LBR; break;
|
||||
case 'v': _cstack = CSTACK_VM; break;
|
||||
default: _cstack = CSTACK_FP;
|
||||
}
|
||||
}
|
||||
|
||||
CASE("clock")
|
||||
if (value != NULL) {
|
||||
if (value[0] == 't') {
|
||||
_clock = CLK_TSC;
|
||||
} else if (value[0] == 'm') {
|
||||
_clock = CLK_MONOTONIC;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,12 +352,21 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("dot")
|
||||
_style |= STYLE_DOTTED;
|
||||
|
||||
CASE("norm")
|
||||
_style |= STYLE_NORMALIZE;
|
||||
|
||||
CASE("sig")
|
||||
_style |= STYLE_SIGNATURES;
|
||||
|
||||
CASE("ann")
|
||||
_style |= STYLE_ANNOTATE;
|
||||
|
||||
CASE("lib")
|
||||
_style |= STYLE_LIB_NAMES;
|
||||
|
||||
CASE("mcache")
|
||||
_mcache = value == NULL ? 1 : (unsigned char)strtol(value, NULL, 0);
|
||||
|
||||
CASE("begin")
|
||||
_begin = value;
|
||||
|
||||
@@ -227,45 +375,54 @@ Error Arguments::parse(const char* args) {
|
||||
|
||||
// FlameGraph options
|
||||
CASE("title")
|
||||
if (value != NULL) _title = value;
|
||||
_title = value;
|
||||
|
||||
CASE("minwidth")
|
||||
if (value != NULL) _minwidth = atof(value);
|
||||
|
||||
CASE("reverse")
|
||||
_reverse = true;
|
||||
|
||||
DEFAULT()
|
||||
if (_unknown_arg == NULL) _unknown_arg = arg;
|
||||
}
|
||||
}
|
||||
|
||||
if (_file != NULL && strchr(_file, '%') != NULL) {
|
||||
_file = expandFilePattern(_buf + len + 1, EXTRA_BUF_SIZE - 1, _file);
|
||||
// Return error only after parsing all arguments, when 'log' is already set
|
||||
if (msg != NULL) {
|
||||
return Error(msg);
|
||||
}
|
||||
|
||||
if (_event == NULL && _alloc < 0 && _lock < 0 && _wall < 0) {
|
||||
_event = EVENT_CPU;
|
||||
}
|
||||
|
||||
if (_file != NULL && _output == OUTPUT_NONE) {
|
||||
_output = detectOutputFormat(_file);
|
||||
if (_output == OUTPUT_SVG) {
|
||||
return Error("SVG format is obsolete, use .html for FlameGraph");
|
||||
}
|
||||
_dump_traces = 100;
|
||||
_dump_flat = 200;
|
||||
}
|
||||
|
||||
if (_output != OUTPUT_NONE && (_action == ACTION_NONE || _action == ACTION_STOP)) {
|
||||
if (_action == ACTION_NONE && _output != OUTPUT_NONE) {
|
||||
_action = ACTION_DUMP;
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
bool Arguments::addEvent(const char* event) {
|
||||
if (strcmp(event, EVENT_ALLOC) == 0) {
|
||||
_events |= EK_ALLOC;
|
||||
} else if (strcmp(event, EVENT_LOCK) == 0) {
|
||||
_events |= EK_LOCK;
|
||||
} else {
|
||||
if (_events & EK_CPU) {
|
||||
return false;
|
||||
}
|
||||
_events |= EK_CPU;
|
||||
_event_desc = event;
|
||||
const char* Arguments::file() {
|
||||
if (_file != NULL && strchr(_file, '%') != NULL) {
|
||||
return expandFilePattern(_file);
|
||||
}
|
||||
return true;
|
||||
return _file;
|
||||
}
|
||||
|
||||
// Returns true if the log file is a temporary file of asprof launcher
|
||||
bool Arguments::hasTemporaryLog() const {
|
||||
return _log != NULL && strncmp(_log, "/tmp/asprof-log.", 16) == 0;
|
||||
}
|
||||
|
||||
// The linked list of string offsets is embedded right into _buf array
|
||||
@@ -283,11 +440,14 @@ long long Arguments::hash(const char* arg) {
|
||||
return h;
|
||||
}
|
||||
|
||||
// Expands %p to the process id
|
||||
// %t to the timestamp
|
||||
const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char* pattern) {
|
||||
char* ptr = dest;
|
||||
char* end = dest + max_size - 1;
|
||||
// Expands the following patterns:
|
||||
// %p process id
|
||||
// %t timestamp (yyyyMMdd-hhmmss)
|
||||
// %n{MAX} sequence number
|
||||
// %{ENV} environment variable
|
||||
const char* Arguments::expandFilePattern(const char* pattern) {
|
||||
char* ptr = _buf;
|
||||
char* end = _buf + EXTRA_BUF_SIZE - 1;
|
||||
|
||||
while (ptr < end && *pattern != 0) {
|
||||
char c = *pattern++;
|
||||
@@ -306,13 +466,35 @@ const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
|
||||
t.tm_hour, t.tm_min, t.tm_sec);
|
||||
continue;
|
||||
} else if (c == 'n') {
|
||||
unsigned int max_files = 0;
|
||||
const char* p;
|
||||
if (*pattern == '{' && (p = strchr(pattern, '}')) != NULL) {
|
||||
max_files = atoi(pattern + 1);
|
||||
pattern = p + 1;
|
||||
}
|
||||
ptr += snprintf(ptr, end - ptr, "%u", max_files > 0 ? _file_num % max_files : _file_num);
|
||||
continue;
|
||||
} else if (c == '{') {
|
||||
char env_key[128];
|
||||
const char* p = strchr(pattern, '}');
|
||||
if (p != NULL && p - pattern < sizeof(env_key)) {
|
||||
memcpy(env_key, pattern, p - pattern);
|
||||
env_key[p - pattern] = 0;
|
||||
const char* env_value = getenv(env_key);
|
||||
if (env_value != NULL) {
|
||||
ptr += snprintf(ptr, end - ptr, "%s", env_value);
|
||||
pattern = p + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*ptr++ = c;
|
||||
}
|
||||
|
||||
*ptr = 0;
|
||||
return dest;
|
||||
*(ptr < end ? ptr : end) = 0;
|
||||
return _buf;
|
||||
}
|
||||
|
||||
Output Arguments::detectOutputFormat(const char* file) {
|
||||
@@ -324,37 +506,57 @@ Output Arguments::detectOutputFormat(const char* file) {
|
||||
return OUTPUT_JFR;
|
||||
} else if (strcmp(ext, ".collapsed") == 0 || strcmp(ext, ".folded") == 0) {
|
||||
return OUTPUT_COLLAPSED;
|
||||
} else if (strcmp(ext, ".svg") == 0) {
|
||||
return OUTPUT_SVG;
|
||||
}
|
||||
}
|
||||
return OUTPUT_FLAT;
|
||||
return OUTPUT_TEXT;
|
||||
}
|
||||
|
||||
long Arguments::parseUnits(const char* str) {
|
||||
long Arguments::parseUnits(const char* str, const Multiplier* multipliers) {
|
||||
char* end;
|
||||
long result = strtol(str, &end, 0);
|
||||
if (end == str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (*end) {
|
||||
case 0:
|
||||
return result;
|
||||
case 'K': case 'k':
|
||||
case 'U': case 'u': // microseconds
|
||||
return result * 1000;
|
||||
case 'M': case 'm': // million, megabytes or milliseconds
|
||||
return result * 1000000;
|
||||
case 'G': case 'g':
|
||||
case 'S': case 's': // seconds
|
||||
return result * 1000000000;
|
||||
char c = *end;
|
||||
if (c == 0) {
|
||||
return result;
|
||||
}
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
|
||||
for (const Multiplier* m = multipliers; m->symbol; m++) {
|
||||
if (c == m->symbol) {
|
||||
return result * m->multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Arguments::parseTimeout(const char* str) {
|
||||
const char* p = strchr(str, ':');
|
||||
if (p == NULL) {
|
||||
return parseUnits(str, SECONDS);
|
||||
}
|
||||
|
||||
int hh = str[0] >= '0' && str[0] <= '2' ? atoi(str) : 0xff;
|
||||
int mm = p[1] >= '0' && p[1] <= '5' ? atoi(p + 1) : 0xff;
|
||||
int ss = (p = strchr(p + 1, ':')) != NULL && p[1] >= '0' && p[1] <= '5' ? atoi(p + 1) : 0xff;
|
||||
return 0xff000000 | hh << 16 | mm << 8 | ss;
|
||||
}
|
||||
|
||||
Arguments::~Arguments() {
|
||||
if (!_shared) free(_buf);
|
||||
}
|
||||
|
||||
void Arguments::save(Arguments& other) {
|
||||
if (!_shared) free(_buf);
|
||||
*this = other;
|
||||
other._shared = true;
|
||||
void Arguments::save() {
|
||||
if (this != &_global_args) {
|
||||
free(_global_args._buf);
|
||||
_global_args = *this;
|
||||
_shared = true;
|
||||
}
|
||||
}
|
||||
|
||||
187
src/arguments.h
187
src/arguments.h
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ARGUMENTS_H
|
||||
@@ -20,68 +9,115 @@
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
const long DEFAULT_INTERVAL = 10000000; // 10 ms
|
||||
const long DEFAULT_INTERVAL = 10000000; // 10 ms
|
||||
const long DEFAULT_ALLOC_INTERVAL = 524287; // 512 KiB
|
||||
const int DEFAULT_JSTACKDEPTH = 2048;
|
||||
|
||||
const char* const EVENT_CPU = "cpu";
|
||||
const char* const EVENT_ALLOC = "alloc";
|
||||
const char* const EVENT_LOCK = "lock";
|
||||
const char* const EVENT_WALL = "wall";
|
||||
const char* const EVENT_CTIMER = "ctimer";
|
||||
const char* const EVENT_ITIMER = "itimer";
|
||||
|
||||
enum Action {
|
||||
#define SHORT_ENUM __attribute__((__packed__))
|
||||
|
||||
enum SHORT_ENUM Action {
|
||||
ACTION_NONE,
|
||||
ACTION_START,
|
||||
ACTION_RESUME,
|
||||
ACTION_STOP,
|
||||
ACTION_DUMP,
|
||||
ACTION_CHECK,
|
||||
ACTION_STATUS,
|
||||
ACTION_MEMINFO,
|
||||
ACTION_LIST,
|
||||
ACTION_VERSION,
|
||||
ACTION_FULL_VERSION,
|
||||
ACTION_DUMP
|
||||
ACTION_VERSION
|
||||
};
|
||||
|
||||
enum Counter {
|
||||
enum SHORT_ENUM Counter {
|
||||
COUNTER_SAMPLES,
|
||||
COUNTER_TOTAL
|
||||
};
|
||||
|
||||
enum Ring {
|
||||
enum SHORT_ENUM Ring {
|
||||
RING_ANY,
|
||||
RING_KERNEL,
|
||||
RING_USER
|
||||
};
|
||||
|
||||
enum EventKind {
|
||||
EK_CPU = 1,
|
||||
EK_ALLOC = 2,
|
||||
EK_LOCK = 4
|
||||
};
|
||||
|
||||
enum Style {
|
||||
STYLE_SIMPLE = 1,
|
||||
STYLE_DOTTED = 2,
|
||||
STYLE_SIGNATURES = 4,
|
||||
STYLE_ANNOTATE = 8
|
||||
STYLE_SIMPLE = 0x1,
|
||||
STYLE_DOTTED = 0x2,
|
||||
STYLE_NORMALIZE = 0x4,
|
||||
STYLE_SIGNATURES = 0x8,
|
||||
STYLE_ANNOTATE = 0x10,
|
||||
STYLE_LIB_NAMES = 0x20,
|
||||
STYLE_NO_SEMICOLON = 0x40
|
||||
};
|
||||
|
||||
enum CStack {
|
||||
// Whenever enum changes, update SETTING_CSTACK in FlightRecorder
|
||||
enum SHORT_ENUM CStack {
|
||||
CSTACK_DEFAULT,
|
||||
CSTACK_NO,
|
||||
CSTACK_FP,
|
||||
CSTACK_LBR
|
||||
CSTACK_DWARF,
|
||||
CSTACK_LBR,
|
||||
CSTACK_VM
|
||||
};
|
||||
|
||||
enum Output {
|
||||
enum SHORT_ENUM Clock {
|
||||
CLK_DEFAULT,
|
||||
CLK_TSC,
|
||||
CLK_MONOTONIC
|
||||
};
|
||||
|
||||
enum SHORT_ENUM Output {
|
||||
OUTPUT_NONE,
|
||||
OUTPUT_FLAT,
|
||||
OUTPUT_TEXT,
|
||||
OUTPUT_SVG, // obsolete
|
||||
OUTPUT_COLLAPSED,
|
||||
OUTPUT_FLAMEGRAPH,
|
||||
OUTPUT_TREE,
|
||||
OUTPUT_JFR
|
||||
};
|
||||
|
||||
enum JfrOption {
|
||||
NO_SYSTEM_INFO = 0x1,
|
||||
NO_SYSTEM_PROPS = 0x2,
|
||||
NO_NATIVE_LIBS = 0x4,
|
||||
NO_CPU_LOAD = 0x8,
|
||||
NO_HEAP_SUMMARY = 0x10,
|
||||
|
||||
JFR_SYNC_OPTS = NO_SYSTEM_INFO | NO_SYSTEM_PROPS | NO_NATIVE_LIBS | NO_CPU_LOAD | NO_HEAP_SUMMARY
|
||||
};
|
||||
|
||||
struct StackWalkFeatures {
|
||||
// Stack recovery techniques used to workaround AsyncGetCallTrace flaws
|
||||
unsigned short unknown_java : 1;
|
||||
unsigned short unwind_stub : 1;
|
||||
unsigned short unwind_comp : 1;
|
||||
unsigned short unwind_native : 1;
|
||||
unsigned short java_anchor : 1;
|
||||
unsigned short gc_traces : 1;
|
||||
|
||||
// Additional HotSpot-specific features
|
||||
unsigned short probe_sp : 1;
|
||||
unsigned short vtable_target : 1;
|
||||
unsigned short comp_task : 1;
|
||||
unsigned short _reserved : 7;
|
||||
|
||||
StackWalkFeatures() : unknown_java(1), unwind_stub(1), unwind_comp(1), unwind_native(1), java_anchor(1), gc_traces(1),
|
||||
probe_sp(0), vtable_target(0), comp_task(0), _reserved(0) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct Multiplier {
|
||||
char symbol;
|
||||
long multiplier;
|
||||
};
|
||||
|
||||
|
||||
class Error {
|
||||
private:
|
||||
@@ -109,30 +145,53 @@ class Arguments {
|
||||
bool _shared;
|
||||
|
||||
void appendToEmbeddedList(int& list, char* value);
|
||||
const char* expandFilePattern(const char* pattern);
|
||||
|
||||
static long long hash(const char* arg);
|
||||
static const char* expandFilePattern(char* dest, size_t max_size, const char* pattern);
|
||||
static Output detectOutputFormat(const char* file);
|
||||
static long parseUnits(const char* str);
|
||||
static long parseUnits(const char* str, const Multiplier* multipliers);
|
||||
static int parseTimeout(const char* str);
|
||||
|
||||
public:
|
||||
Action _action;
|
||||
Counter _counter;
|
||||
Ring _ring;
|
||||
int _events;
|
||||
const char* _event_desc;
|
||||
const char* _event;
|
||||
int _timeout;
|
||||
long _interval;
|
||||
int _jstackdepth;
|
||||
int _safe_mode;
|
||||
long _alloc;
|
||||
long _lock;
|
||||
long _wall;
|
||||
int _jstackdepth;
|
||||
int _signal;
|
||||
const char* _file;
|
||||
const char* _log;
|
||||
const char* _loglevel;
|
||||
const char* _unknown_arg;
|
||||
const char* _server;
|
||||
const char* _filter;
|
||||
int _include;
|
||||
int _exclude;
|
||||
unsigned char _mcache;
|
||||
bool _loop;
|
||||
bool _preloaded;
|
||||
bool _threads;
|
||||
bool _sched;
|
||||
bool _live;
|
||||
bool _fdtransfer;
|
||||
const char* _fdtransfer_path;
|
||||
int _style;
|
||||
StackWalkFeatures _features;
|
||||
CStack _cstack;
|
||||
Clock _clock;
|
||||
Output _output;
|
||||
long _chunk_size;
|
||||
long _chunk_time;
|
||||
const char* _jfr_sync;
|
||||
int _jfr_options;
|
||||
int _dump_traces;
|
||||
int _dump_flat;
|
||||
unsigned int _file_num;
|
||||
const char* _begin;
|
||||
const char* _end;
|
||||
// FlameGraph parameters
|
||||
@@ -146,36 +205,72 @@ class Arguments {
|
||||
_action(ACTION_NONE),
|
||||
_counter(COUNTER_SAMPLES),
|
||||
_ring(RING_ANY),
|
||||
_events(0),
|
||||
_event_desc(NULL),
|
||||
_event(NULL),
|
||||
_timeout(0),
|
||||
_interval(0),
|
||||
_alloc(-1),
|
||||
_lock(-1),
|
||||
_wall(-1),
|
||||
_jstackdepth(DEFAULT_JSTACKDEPTH),
|
||||
_safe_mode(0),
|
||||
_signal(0),
|
||||
_file(NULL),
|
||||
_log(NULL),
|
||||
_loglevel(NULL),
|
||||
_unknown_arg(NULL),
|
||||
_server(NULL),
|
||||
_filter(NULL),
|
||||
_include(0),
|
||||
_exclude(0),
|
||||
_mcache(0),
|
||||
_loop(false),
|
||||
_preloaded(false),
|
||||
_threads(false),
|
||||
_sched(false),
|
||||
_live(false),
|
||||
_fdtransfer(false),
|
||||
_fdtransfer_path(NULL),
|
||||
_style(0),
|
||||
_features(),
|
||||
_cstack(CSTACK_DEFAULT),
|
||||
_clock(CLK_DEFAULT),
|
||||
_output(OUTPUT_NONE),
|
||||
_chunk_size(100 * 1024 * 1024),
|
||||
_chunk_time(3600),
|
||||
_jfr_sync(NULL),
|
||||
_jfr_options(0),
|
||||
_dump_traces(0),
|
||||
_dump_flat(0),
|
||||
_file_num(0),
|
||||
_begin(NULL),
|
||||
_end(NULL),
|
||||
_title("Flame Graph"),
|
||||
_title(NULL),
|
||||
_minwidth(0),
|
||||
_reverse(false) {
|
||||
}
|
||||
|
||||
~Arguments();
|
||||
|
||||
void save(Arguments& other);
|
||||
void save();
|
||||
|
||||
Error parse(const char* args);
|
||||
|
||||
bool addEvent(const char* event);
|
||||
const char* file();
|
||||
|
||||
bool hasTemporaryLog() const;
|
||||
|
||||
bool hasOutputFile() const {
|
||||
return _file != NULL &&
|
||||
(_action == ACTION_STOP || _action == ACTION_DUMP ? _output != OUTPUT_JFR : _action >= ACTION_CHECK);
|
||||
}
|
||||
|
||||
bool hasOption(JfrOption option) const {
|
||||
return (_jfr_options & option) != 0;
|
||||
}
|
||||
|
||||
friend class FrameName;
|
||||
friend class Recording;
|
||||
};
|
||||
|
||||
extern Arguments _global_args;
|
||||
|
||||
#endif // _ARGUMENTS_H
|
||||
|
||||
58
src/asprof.cpp
Normal file
58
src/asprof.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "asprof.h"
|
||||
#include "hooks.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
static asprof_error_t asprof_error(const char* msg) {
|
||||
return (asprof_error_t)msg;
|
||||
}
|
||||
|
||||
|
||||
DLLEXPORT void asprof_init() {
|
||||
Hooks::init(true);
|
||||
}
|
||||
|
||||
DLLEXPORT const char* asprof_error_str(asprof_error_t err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
DLLEXPORT asprof_error_t asprof_execute(const char* command, asprof_writer_t output_callback) {
|
||||
Arguments args;
|
||||
Error error = args.parse(command);
|
||||
if (error) {
|
||||
return asprof_error(error.message());
|
||||
}
|
||||
|
||||
Log::open(args);
|
||||
|
||||
if (!args.hasOutputFile()) {
|
||||
// FIXME: get rid of stream
|
||||
std::ostringstream out;
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
if (!error) {
|
||||
if (output_callback != NULL) {
|
||||
output_callback(out.str().data(), out.str().size());
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
std::ofstream out(args.file(), std::ios::out | std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
return asprof_error("Could not open output file");
|
||||
}
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
out.close();
|
||||
if (!error) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return asprof_error(error.message());
|
||||
}
|
||||
43
src/asprof.h
Normal file
43
src/asprof.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ASPROF_H
|
||||
#define _ASPROF_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __clang__
|
||||
# define DLLEXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
# define DLLEXPORT __attribute__((visibility("default"),externally_visible))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
typedef const char* asprof_error_t;
|
||||
typedef void (*asprof_writer_t)(const char* buf, size_t size);
|
||||
|
||||
// Should be called once prior to any other API functions
|
||||
DLLEXPORT void asprof_init();
|
||||
typedef void (*asprof_init_t)();
|
||||
|
||||
// Returns an error message for the given error code or NULL if there is no error
|
||||
DLLEXPORT const char* asprof_error_str(asprof_error_t err);
|
||||
typedef const char* (*asprof_error_str_t)(asprof_error_t err);
|
||||
|
||||
// Executes async-profiler command using output_callback as an optional sink
|
||||
// for the profiler output. Returning an error code or NULL on success.
|
||||
DLLEXPORT asprof_error_t asprof_execute(const char* command, asprof_writer_t output_callback);
|
||||
typedef asprof_error_t (*asprof_execute_t)(const char* command, asprof_writer_t output_callback);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _ASPROF_H
|
||||
@@ -1,28 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "callTraceStorage.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
static const u32 INITIAL_CAPACITY = 65536;
|
||||
static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024;
|
||||
static const size_t PAGE_ALIGNMENT = sysconf(_SC_PAGESIZE) - 1;
|
||||
static const u32 OVERFLOW_TRACE_ID = 0x7fffffff;
|
||||
|
||||
|
||||
class LongHashTable {
|
||||
@@ -36,7 +24,7 @@ class LongHashTable {
|
||||
|
||||
static size_t getSize(u32 capacity) {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
|
||||
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
|
||||
return (size + OS::page_mask) & ~OS::page_mask;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -56,6 +44,10 @@ class LongHashTable {
|
||||
return prev;
|
||||
}
|
||||
|
||||
size_t usedMemory() {
|
||||
return getSize(_capacity);
|
||||
}
|
||||
|
||||
LongHashTable* prev() {
|
||||
return _prev;
|
||||
}
|
||||
@@ -87,8 +79,11 @@ class LongHashTable {
|
||||
};
|
||||
|
||||
|
||||
CallTrace CallTraceStorage::_overflow_trace = {1, {BCI_ERROR, (jmethodID)"storage_overflow"}};
|
||||
|
||||
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
|
||||
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
|
||||
_overflow = 0;
|
||||
}
|
||||
|
||||
CallTraceStorage::~CallTraceStorage() {
|
||||
@@ -103,6 +98,15 @@ void CallTraceStorage::clear() {
|
||||
}
|
||||
_current_table->clear();
|
||||
_allocator.clear();
|
||||
_overflow = 0;
|
||||
}
|
||||
|
||||
size_t CallTraceStorage::usedMemory() {
|
||||
size_t bytes = _allocator.usedMemory();
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
bytes += table->usedMemory();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
@@ -112,11 +116,20 @@ void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
|
||||
if (keys[slot] != 0 && loadAcquire(values[slot].samples) != 0) {
|
||||
// Reset samples to avoid duplication of call traces between JFR chunks
|
||||
values[slot].samples = 0;
|
||||
CallTrace* trace = values[slot].acquireTrace();
|
||||
if (trace != NULL) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_overflow > 0) {
|
||||
map[OVERFLOW_TRACE_ID] = &_overflow_trace;
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
@@ -133,6 +146,20 @@ void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::map<u64, CallTraceSample>& map) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0 && values[slot].acquireTrace() != NULL) {
|
||||
map[keys[slot]] += values[slot];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptation of MurmurHash64A by Austin Appleby
|
||||
u64 CallTraceStorage::calcHash(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const u64 M = 0xc6a4a7935bd1e995ULL;
|
||||
@@ -225,22 +252,40 @@ u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter)
|
||||
if (trace == NULL) {
|
||||
trace = storeCallTrace(num_frames, frames);
|
||||
}
|
||||
table->values()[slot].trace = trace;
|
||||
table->values()[slot].setTrace(trace);
|
||||
break;
|
||||
}
|
||||
|
||||
if (++step >= capacity) {
|
||||
// Very unlikely case of a table overflow
|
||||
return 0;
|
||||
atomicInc(_overflow);
|
||||
return OVERFLOW_TRACE_ID;
|
||||
}
|
||||
// Improved version of linear probing
|
||||
slot = (slot + step) & (capacity - 1);
|
||||
}
|
||||
|
||||
// TODO: check overhead
|
||||
CallTraceSample& s = table->values()[slot];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
if (counter != 0) {
|
||||
CallTraceSample& s = table->values()[slot];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
}
|
||||
|
||||
return capacity - (INITIAL_CAPACITY - 1) + slot;
|
||||
}
|
||||
|
||||
void CallTraceStorage::add(u32 call_trace_id, u64 counter) {
|
||||
if (call_trace_id == OVERFLOW_TRACE_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
call_trace_id += (INITIAL_CAPACITY - 1);
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
if (call_trace_id >= table->capacity()) {
|
||||
CallTraceSample& s = table->values()[call_trace_id - table->capacity()];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _CALLTRACESTORAGE_H
|
||||
@@ -35,12 +24,34 @@ struct CallTraceSample {
|
||||
CallTrace* trace;
|
||||
u64 samples;
|
||||
u64 counter;
|
||||
|
||||
CallTrace* acquireTrace() {
|
||||
return __atomic_load_n(&trace, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
void setTrace(CallTrace* value) {
|
||||
return __atomic_store_n(&trace, value, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
CallTraceSample& operator+=(const CallTraceSample& s) {
|
||||
trace = s.trace;
|
||||
samples += s.samples;
|
||||
counter += s.counter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator<(const CallTraceSample& other) const {
|
||||
return counter > other.counter;
|
||||
}
|
||||
};
|
||||
|
||||
class CallTraceStorage {
|
||||
private:
|
||||
static CallTrace _overflow_trace;
|
||||
|
||||
LinearAllocator _allocator;
|
||||
LongHashTable* _current_table;
|
||||
u64 _overflow;
|
||||
|
||||
u64 calcHash(int num_frames, ASGCT_CallFrame* frames);
|
||||
CallTrace* storeCallTrace(int num_frames, ASGCT_CallFrame* frames);
|
||||
@@ -51,10 +62,14 @@ class CallTraceStorage {
|
||||
~CallTraceStorage();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
|
||||
void collectTraces(std::map<u32, CallTrace*>& map);
|
||||
void collectSamples(std::vector<CallTraceSample*>& samples);
|
||||
void collectSamples(std::map<u64, CallTraceSample>& map);
|
||||
|
||||
u32 put(int num_frames, ASGCT_CallFrame* frames, u64 counter);
|
||||
void add(u32 call_trace_id, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _CALLTRACESTORAGE
|
||||
|
||||
@@ -1,42 +1,83 @@
|
||||
/*
|
||||
* Copyright 2016 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include "codeCache.h"
|
||||
#include "dwarf.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
char* NativeFunc::create(const char* name, short lib_index) {
|
||||
NativeFunc* f = (NativeFunc*)malloc(sizeof(NativeFunc) + 1 + strlen(name));
|
||||
f->_lib_index = lib_index;
|
||||
f->_mark = 0;
|
||||
return strcpy(f->_name, name);
|
||||
}
|
||||
|
||||
void NativeFunc::destroy(char* name) {
|
||||
free(from(name));
|
||||
}
|
||||
|
||||
size_t NativeFunc::usedMemory(const char* name) {
|
||||
return sizeof(NativeFunc) + 1 + strlen(from(name)->_name);
|
||||
}
|
||||
|
||||
|
||||
CodeCache::CodeCache(const char* name, short lib_index, bool imports_patchable,
|
||||
const void* min_address, const void* max_address) {
|
||||
_name = NativeFunc::create(name, -1);
|
||||
_lib_index = lib_index;
|
||||
_min_address = min_address;
|
||||
_max_address = max_address;
|
||||
_text_base = NULL;
|
||||
|
||||
_plt_offset = 0;
|
||||
_plt_size = 0;
|
||||
|
||||
memset(_imports, 0, sizeof(_imports));
|
||||
_imports_patchable = imports_patchable;
|
||||
_debug_symbols = false;
|
||||
|
||||
_dwarf_table = NULL;
|
||||
_dwarf_table_length = 0;
|
||||
|
||||
_capacity = INITIAL_CODE_CACHE_CAPACITY;
|
||||
_count = 0;
|
||||
_blobs = new CodeBlob[_capacity];
|
||||
}
|
||||
|
||||
CodeCache::~CodeCache() {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
NativeFunc::destroy(_blobs[i]._name);
|
||||
}
|
||||
NativeFunc::destroy(_name);
|
||||
delete[] _blobs;
|
||||
free(_dwarf_table);
|
||||
}
|
||||
|
||||
void CodeCache::expand() {
|
||||
CodeBlob* old_blobs = _blobs;
|
||||
CodeBlob* new_blobs = new CodeBlob[_capacity * 2];
|
||||
|
||||
int live = 0;
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (_blobs[i]._method != NULL) {
|
||||
new_blobs[live++] = _blobs[i];
|
||||
}
|
||||
}
|
||||
memcpy(new_blobs, old_blobs, _count * sizeof(CodeBlob));
|
||||
|
||||
_count = live;
|
||||
_capacity *= 2;
|
||||
_blobs = new_blobs;
|
||||
delete[] old_blobs;
|
||||
}
|
||||
|
||||
void CodeCache::add(const void* start, int length, jmethodID method, bool update_bounds) {
|
||||
void CodeCache::add(const void* start, int length, const char* name, bool update_bounds) {
|
||||
char* name_copy = NativeFunc::create(name, _lib_index);
|
||||
// Replace non-printable characters
|
||||
for (char* s = name_copy; *s != 0; s++) {
|
||||
if (*s < ' ') *s = '?';
|
||||
}
|
||||
|
||||
if (_count >= _capacity) {
|
||||
expand();
|
||||
}
|
||||
@@ -44,58 +85,20 @@ void CodeCache::add(const void* start, int length, jmethodID method, bool update
|
||||
const void* end = (const char*)start + length;
|
||||
_blobs[_count]._start = start;
|
||||
_blobs[_count]._end = end;
|
||||
_blobs[_count]._method = method;
|
||||
_blobs[_count]._name = name_copy;
|
||||
_count++;
|
||||
|
||||
if (update_bounds) {
|
||||
if (start < _min_address) _min_address = start;
|
||||
if (end > _max_address) _max_address = end;
|
||||
updateBounds(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::remove(const void* start, jmethodID method) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (_blobs[i]._start == start && _blobs[i]._method == method) {
|
||||
_blobs[i]._method = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
void CodeCache::updateBounds(const void* start, const void* end) {
|
||||
if (start < _min_address) _min_address = start;
|
||||
if (end > _max_address) _max_address = end;
|
||||
}
|
||||
|
||||
jmethodID CodeCache::find(const void* address) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
CodeBlob* cb = _blobs + i;
|
||||
if (address >= cb->_start && address < cb->_end && cb->_method != NULL) {
|
||||
return _blobs[i]._method;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
NativeCodeCache::NativeCodeCache(const char* name, const void* min_address, const void* max_address) {
|
||||
_name = strdup(name);
|
||||
_min_address = min_address;
|
||||
_max_address = max_address;
|
||||
}
|
||||
|
||||
NativeCodeCache::~NativeCodeCache() {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
free(_blobs[i]._method);
|
||||
}
|
||||
free(_name);
|
||||
}
|
||||
|
||||
void NativeCodeCache::add(const void* start, int length, const char* name, bool update_bounds) {
|
||||
char* name_copy = strdup(name);
|
||||
// Replace non-printable characters
|
||||
for (char* s = name_copy; *s != 0; s++) {
|
||||
if (*s < ' ') *s = '?';
|
||||
}
|
||||
CodeCache::add(start, length, (jmethodID)name_copy, update_bounds);
|
||||
}
|
||||
|
||||
void NativeCodeCache::sort() {
|
||||
void CodeCache::sort() {
|
||||
if (_count == 0) return;
|
||||
|
||||
qsort(_blobs, _count, sizeof(CodeBlob), CodeBlob::comparator);
|
||||
@@ -104,7 +107,35 @@ void NativeCodeCache::sort() {
|
||||
if (_max_address == NO_MAX_ADDRESS) _max_address = _blobs[_count - 1]._end;
|
||||
}
|
||||
|
||||
const char* NativeCodeCache::binarySearch(const void* address) {
|
||||
void CodeCache::mark(NamePredicate predicate, char value) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && predicate(blob_name)) {
|
||||
NativeFunc::mark(blob_name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeBlob* CodeCache::findBlob(const char* name) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && strcmp(blob_name, name) == 0) {
|
||||
return &_blobs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CodeBlob* CodeCache::findBlobByAddress(const void* address) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (address >= _blobs[i]._start && address < _blobs[i]._end) {
|
||||
return &_blobs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* CodeCache::binarySearch(const void* address) {
|
||||
int low = 0;
|
||||
int high = _count - 1;
|
||||
|
||||
@@ -115,38 +146,122 @@ const char* NativeCodeCache::binarySearch(const void* address) {
|
||||
} else if (_blobs[mid]._start > address) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
return (const char*)_blobs[mid]._method;
|
||||
return _blobs[mid]._name;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with zero size can be valid functions: e.g. ASM entry points or kernel code.
|
||||
// Also, in some cases (endless loop) the return address may point beyond the function.
|
||||
if (low > 0 && (_blobs[low - 1]._start == _blobs[low - 1]._end || _blobs[low - 1]._end == address)) {
|
||||
return (const char*)_blobs[low - 1]._method;
|
||||
return _blobs[low - 1]._name;
|
||||
}
|
||||
return _name;
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbol(const char* name) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = (const char*)_blobs[i]._method;
|
||||
if (blob_name != NULL && strcmp(blob_name, name) == 0) {
|
||||
return _blobs[i]._start;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
const void* CodeCache::findSymbol(const char* name) {
|
||||
CodeBlob* blob = findBlob(name);
|
||||
return blob == NULL ? NULL : blob->_start;
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix) {
|
||||
const void* CodeCache::findSymbolByPrefix(const char* prefix) {
|
||||
return findSymbolByPrefix(prefix, strlen(prefix));
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
|
||||
const void* CodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = (const char*)_blobs[i]._method;
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
|
||||
return _blobs[i]._start;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CodeCache::addImport(void** entry, const char* name) {
|
||||
switch (name[0]) {
|
||||
case 'd':
|
||||
if (strcmp(name, "dlopen") == 0) {
|
||||
_imports[im_dlopen] = entry;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
if (strcmp(name, "pthread_create") == 0) {
|
||||
_imports[im_pthread_create] = entry;
|
||||
} else if (strcmp(name, "pthread_exit") == 0) {
|
||||
_imports[im_pthread_exit] = entry;
|
||||
} else if (strcmp(name, "pthread_setspecific") == 0) {
|
||||
_imports[im_pthread_setspecific] = entry;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void** CodeCache::findImport(ImportId id) {
|
||||
if (!_imports_patchable) {
|
||||
makeImportsPatchable();
|
||||
_imports_patchable = true;
|
||||
}
|
||||
return _imports[id];
|
||||
}
|
||||
|
||||
void CodeCache::patchImport(ImportId id, void* hook_func) {
|
||||
void** entry = findImport(id);
|
||||
if (entry != NULL) {
|
||||
*entry = hook_func;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::makeImportsPatchable() {
|
||||
void** min_import = (void**)-1;
|
||||
void** max_import = NULL;
|
||||
for (int i = 0; i < NUM_IMPORTS; i++) {
|
||||
if (_imports[i] != NULL && _imports[i] < min_import) min_import = _imports[i];
|
||||
if (_imports[i] != NULL && _imports[i] > max_import) max_import = _imports[i];
|
||||
}
|
||||
|
||||
if (max_import != NULL) {
|
||||
uintptr_t patch_start = (uintptr_t)min_import & ~OS::page_mask;
|
||||
uintptr_t patch_end = (uintptr_t)max_import & ~OS::page_mask;
|
||||
mprotect((void*)patch_start, patch_end - patch_start + OS::page_size, PROT_READ | PROT_WRITE);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::setDwarfTable(FrameDesc* table, int length) {
|
||||
_dwarf_table = table;
|
||||
_dwarf_table_length = length;
|
||||
}
|
||||
|
||||
FrameDesc* CodeCache::findFrameDesc(const void* pc) {
|
||||
u32 target_loc = (const char*)pc - _text_base;
|
||||
int low = 0;
|
||||
int high = _dwarf_table_length - 1;
|
||||
|
||||
while (low <= high) {
|
||||
int mid = (unsigned int)(low + high) >> 1;
|
||||
if (_dwarf_table[mid].loc < target_loc) {
|
||||
low = mid + 1;
|
||||
} else if (_dwarf_table[mid].loc > target_loc) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
return &_dwarf_table[mid];
|
||||
}
|
||||
}
|
||||
|
||||
if (low > 0) {
|
||||
return &_dwarf_table[low - 1];
|
||||
} else if (target_loc - _plt_offset < _plt_size) {
|
||||
return &FrameDesc::empty_frame;
|
||||
} else {
|
||||
return &FrameDesc::default_frame;
|
||||
}
|
||||
}
|
||||
|
||||
size_t CodeCache::usedMemory() {
|
||||
size_t bytes = _capacity * sizeof(CodeBlob);
|
||||
bytes += _dwarf_table_length * sizeof(FrameDesc);
|
||||
bytes += NativeFunc::usedMemory(_name);
|
||||
for (int i = 0; i < _count; i++) {
|
||||
bytes += NativeFunc::usedMemory(_blobs[i]._name);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
195
src/codeCache.h
195
src/codeCache.h
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _CODECACHE_H
|
||||
@@ -23,14 +12,62 @@
|
||||
#define NO_MIN_ADDRESS ((const void*)-1)
|
||||
#define NO_MAX_ADDRESS ((const void*)0)
|
||||
|
||||
typedef bool (*NamePredicate)(const char* name);
|
||||
|
||||
const int INITIAL_CODE_CACHE_CAPACITY = 1000;
|
||||
const int MAX_NATIVE_LIBS = 2048;
|
||||
|
||||
|
||||
enum ImportId {
|
||||
im_dlopen,
|
||||
im_pthread_create,
|
||||
im_pthread_exit,
|
||||
im_pthread_setspecific,
|
||||
NUM_IMPORTS
|
||||
};
|
||||
|
||||
enum Mark {
|
||||
MARK_INTERPRETER = 1,
|
||||
MARK_COMPILER_ENTRY = 2
|
||||
};
|
||||
|
||||
|
||||
class NativeFunc {
|
||||
private:
|
||||
short _lib_index;
|
||||
char _mark;
|
||||
char _reserved;
|
||||
char _name[0];
|
||||
|
||||
static NativeFunc* from(const char* name) {
|
||||
return (NativeFunc*)(name - sizeof(NativeFunc));
|
||||
}
|
||||
|
||||
public:
|
||||
static char* create(const char* name, short lib_index);
|
||||
static void destroy(char* name);
|
||||
|
||||
static size_t usedMemory(const char* name);
|
||||
|
||||
static short libIndex(const char* name) {
|
||||
return from(name)->_lib_index;
|
||||
}
|
||||
|
||||
static char mark(const char* name) {
|
||||
return from(name)->_mark;
|
||||
}
|
||||
|
||||
static void mark(const char* name, char value) {
|
||||
from(name)->_mark = value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class CodeBlob {
|
||||
public:
|
||||
const void* _start;
|
||||
const void* _end;
|
||||
jmethodID _method;
|
||||
char* _name;
|
||||
|
||||
static int comparator(const void* c1, const void* c2) {
|
||||
CodeBlob* cb1 = (CodeBlob*)c1;
|
||||
@@ -48,60 +85,120 @@ class CodeBlob {
|
||||
};
|
||||
|
||||
|
||||
class FrameDesc;
|
||||
|
||||
class CodeCache {
|
||||
protected:
|
||||
private:
|
||||
char* _name;
|
||||
short _lib_index;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
const char* _text_base;
|
||||
|
||||
unsigned int _plt_offset;
|
||||
unsigned int _plt_size;
|
||||
|
||||
void** _imports[NUM_IMPORTS];
|
||||
bool _imports_patchable;
|
||||
bool _debug_symbols;
|
||||
|
||||
FrameDesc* _dwarf_table;
|
||||
int _dwarf_table_length;
|
||||
|
||||
int _capacity;
|
||||
int _count;
|
||||
CodeBlob* _blobs;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
|
||||
void expand();
|
||||
void makeImportsPatchable();
|
||||
|
||||
public:
|
||||
CodeCache() {
|
||||
_capacity = INITIAL_CODE_CACHE_CAPACITY;
|
||||
_count = 0;
|
||||
_blobs = new CodeBlob[_capacity];
|
||||
_min_address = NO_MIN_ADDRESS;
|
||||
_max_address = NO_MAX_ADDRESS;
|
||||
}
|
||||
CodeCache(const char* name,
|
||||
short lib_index = -1,
|
||||
bool imports_patchable = false,
|
||||
const void* min_address = NO_MIN_ADDRESS,
|
||||
const void* max_address = NO_MAX_ADDRESS);
|
||||
|
||||
~CodeCache() {
|
||||
delete[] _blobs;
|
||||
}
|
||||
~CodeCache();
|
||||
|
||||
bool contains(const void* address) {
|
||||
return address >= _min_address && address < _max_address;
|
||||
}
|
||||
|
||||
void add(const void* start, int length, jmethodID method, bool update_bounds = false);
|
||||
void remove(const void* start, jmethodID method);
|
||||
jmethodID find(const void* address);
|
||||
};
|
||||
|
||||
|
||||
class NativeCodeCache : public CodeCache {
|
||||
private:
|
||||
char* _name;
|
||||
|
||||
public:
|
||||
NativeCodeCache(const char* name,
|
||||
const void* min_address = NO_MIN_ADDRESS,
|
||||
const void* max_address = NO_MAX_ADDRESS);
|
||||
|
||||
~NativeCodeCache();
|
||||
|
||||
const char* name() {
|
||||
const char* name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
const void* minAddress() const {
|
||||
return _min_address;
|
||||
}
|
||||
|
||||
const void* maxAddress() const {
|
||||
return _max_address;
|
||||
}
|
||||
|
||||
bool contains(const void* address) const {
|
||||
return address >= _min_address && address < _max_address;
|
||||
}
|
||||
|
||||
void setTextBase(const char* text_base) {
|
||||
_text_base = text_base;
|
||||
}
|
||||
|
||||
void setPlt(unsigned int plt_offset, unsigned int plt_size) {
|
||||
_plt_offset = plt_offset;
|
||||
_plt_size = plt_size;
|
||||
}
|
||||
|
||||
bool hasDebugSymbols() const {
|
||||
return _debug_symbols;
|
||||
}
|
||||
|
||||
void setDebugSymbols(bool debug_symbols) {
|
||||
_debug_symbols = debug_symbols;
|
||||
}
|
||||
|
||||
void add(const void* start, int length, const char* name, bool update_bounds = false);
|
||||
void updateBounds(const void* start, const void* end);
|
||||
void sort();
|
||||
void mark(NamePredicate predicate, char value);
|
||||
|
||||
void addImport(void** entry, const char* name);
|
||||
void** findImport(ImportId id);
|
||||
void patchImport(ImportId, void* hook_func);
|
||||
|
||||
CodeBlob* findBlob(const char* name);
|
||||
CodeBlob* findBlobByAddress(const void* address);
|
||||
const char* binarySearch(const void* address);
|
||||
const void* findSymbol(const char* name);
|
||||
const void* findSymbolByPrefix(const char* prefix);
|
||||
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
|
||||
|
||||
void setDwarfTable(FrameDesc* table, int length);
|
||||
FrameDesc* findFrameDesc(const void* pc);
|
||||
|
||||
size_t usedMemory();
|
||||
};
|
||||
|
||||
|
||||
class CodeCacheArray {
|
||||
private:
|
||||
CodeCache* _libs[MAX_NATIVE_LIBS];
|
||||
int _count;
|
||||
|
||||
public:
|
||||
CodeCacheArray() : _count(0) {
|
||||
}
|
||||
|
||||
CodeCache* operator[](int index) {
|
||||
return _libs[index];
|
||||
}
|
||||
|
||||
int count() {
|
||||
return __atomic_load_n(&_count, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
void add(CodeCache* lib) {
|
||||
int index = __atomic_load_n(&_count, __ATOMIC_ACQUIRE);
|
||||
_libs[index] = lib;
|
||||
__atomic_store_n(&_count, index + 1, __ATOMIC_RELEASE);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _CODECACHE_H
|
||||
|
||||
97
src/converter/Arguments.java
Normal file
97
src/converter/Arguments.java
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Arguments {
|
||||
String title = "Flame Graph";
|
||||
String highlight;
|
||||
String state;
|
||||
Pattern include;
|
||||
Pattern exclude;
|
||||
double minwidth;
|
||||
int skip;
|
||||
boolean reverse;
|
||||
boolean alloc;
|
||||
boolean live;
|
||||
boolean lock;
|
||||
boolean threads;
|
||||
boolean classify;
|
||||
boolean total;
|
||||
boolean lines;
|
||||
boolean bci;
|
||||
boolean simple;
|
||||
boolean dot;
|
||||
boolean norm;
|
||||
boolean collapsed;
|
||||
long from;
|
||||
long to;
|
||||
String input;
|
||||
String output;
|
||||
|
||||
public Arguments(String... args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if (arg.startsWith("--")) {
|
||||
try {
|
||||
Field f = Arguments.class.getDeclaredField(arg.substring(2));
|
||||
if ((f.getModifiers() & (Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL)) != 0) {
|
||||
throw new IllegalStateException(arg);
|
||||
}
|
||||
|
||||
Class<?> type = f.getType();
|
||||
if (type == String.class) {
|
||||
f.set(this, args[++i]);
|
||||
} else if (type == boolean.class) {
|
||||
f.setBoolean(this, true);
|
||||
} else if (type == int.class) {
|
||||
f.setInt(this, Integer.parseInt(args[++i]));
|
||||
} else if (type == double.class) {
|
||||
f.setDouble(this, Double.parseDouble(args[++i]));
|
||||
} else if (type == long.class) {
|
||||
f.setLong(this, parseTimestamp(args[++i]));
|
||||
} else if (type == Pattern.class) {
|
||||
f.set(this, Pattern.compile(args[++i]));
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new IllegalArgumentException(arg);
|
||||
}
|
||||
} else if (!arg.isEmpty()) {
|
||||
if (input == null) {
|
||||
input = arg;
|
||||
} else {
|
||||
output = arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Milliseconds or HH:mm:ss.S or yyyy-MM-dd'T'HH:mm:ss.S
|
||||
private long parseTimestamp(String time) {
|
||||
if (time.indexOf(':') < 0) {
|
||||
return Long.parseLong(time);
|
||||
}
|
||||
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
StringTokenizer st = new StringTokenizer(time, "-:.T");
|
||||
|
||||
if (time.indexOf('T') > 0) {
|
||||
cal.set(Calendar.YEAR, Integer.parseInt(st.nextToken()));
|
||||
cal.set(Calendar.MONTH, Integer.parseInt(st.nextToken()) - 1);
|
||||
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(st.nextToken()));
|
||||
}
|
||||
cal.set(Calendar.HOUR_OF_DAY, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
cal.set(Calendar.MINUTE, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
cal.set(Calendar.SECOND, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
cal.set(Calendar.MILLISECOND, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
}
|
||||
138
src/converter/Classifier.java
Normal file
138
src/converter/Classifier.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.StackTrace;
|
||||
|
||||
class Classifier {
|
||||
|
||||
private static final String
|
||||
GC = "[gc]",
|
||||
JIT = "[jit]",
|
||||
VM = "[vm]",
|
||||
VTABLE_STUBS = "[vtable_stubs]",
|
||||
NATIVE = "[native]",
|
||||
INTERPRETER = "[Interpreter]_[0]",
|
||||
C1_COMP = "[c1_comp]_[1]",
|
||||
C2_COMP = "[c2_comp]_[i]",
|
||||
ADAPTER = "[c2i_adapter]_[i]",
|
||||
CLASS_INIT = "[class::init]",
|
||||
CLASS_LOAD = "[class::load]",
|
||||
CLASS_RESOLVE = "[class::resolve]",
|
||||
CLASS_VERIFY = "[class::verify]",
|
||||
LAMBDA_INIT = "[lambda::init]";
|
||||
|
||||
private final Dictionary<String> methodNames;
|
||||
|
||||
Classifier(Dictionary<String> methodNames) {
|
||||
this.methodNames = methodNames;
|
||||
}
|
||||
|
||||
public String getCategoryName(StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
|
||||
String category;
|
||||
if ((category = detectGcJit(methods, types)) == null &&
|
||||
(category = detectClassLoading(methods, types)) == null) {
|
||||
category = detectOther(methods, types);
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
private String detectGcJit(long[] methods, byte[] types) {
|
||||
boolean vmThread = false;
|
||||
for (int i = types.length; --i >= 0; ) {
|
||||
if (types[i] == FlameGraph.FRAME_CPP) {
|
||||
switch (methodNames.get(methods[i])) {
|
||||
case "CompileBroker::compiler_thread_loop":
|
||||
return JIT;
|
||||
case "GCTaskThread::run":
|
||||
case "WorkerThread::run":
|
||||
return GC;
|
||||
case "java_start":
|
||||
case "thread_native_entry":
|
||||
vmThread = true;
|
||||
break;
|
||||
}
|
||||
} else if (types[i] != FlameGraph.FRAME_NATIVE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return vmThread ? VM : null;
|
||||
}
|
||||
|
||||
private String detectClassLoading(long[] methods, byte[] types) {
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
String methodName = methodNames.get(methods[i]);
|
||||
if (methodName.equals("Verifier::verify")) {
|
||||
return CLASS_VERIFY;
|
||||
} else if (methodName.startsWith("InstanceKlass::initialize")) {
|
||||
return CLASS_INIT;
|
||||
} else if (methodName.startsWith("LinkResolver::") ||
|
||||
methodName.startsWith("InterpreterRuntime::resolve") ||
|
||||
methodName.startsWith("SystemDictionary::resolve")) {
|
||||
return CLASS_RESOLVE;
|
||||
} else if (methodName.endsWith("ClassLoader.loadClass")) {
|
||||
return CLASS_LOAD;
|
||||
} else if (methodName.endsWith("LambdaMetafactory.metafactory") ||
|
||||
methodName.endsWith("LambdaMetafactory.altMetafactory")) {
|
||||
return LAMBDA_INIT;
|
||||
} else if (methodName.endsWith("table stub")) {
|
||||
return VTABLE_STUBS;
|
||||
} else if (methodName.equals("Interpreter")) {
|
||||
return INTERPRETER;
|
||||
} else if (methodName.startsWith("I2C/C2I")) {
|
||||
return i + 1 < types.length && types[i + 1] == FlameGraph.FRAME_INTERPRETED ? INTERPRETER : ADAPTER;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String detectOther(long[] methods, byte[] types) {
|
||||
boolean inJava = true;
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
switch (types[i]) {
|
||||
case FlameGraph.FRAME_INTERPRETED:
|
||||
return inJava ? INTERPRETER : NATIVE;
|
||||
case FlameGraph.FRAME_JIT_COMPILED:
|
||||
return inJava ? C2_COMP : NATIVE;
|
||||
case FlameGraph.FRAME_INLINED:
|
||||
inJava = true;
|
||||
break;
|
||||
case FlameGraph.FRAME_NATIVE: {
|
||||
String methodName = methodNames.get(methods[i]);
|
||||
if (methodName.startsWith("JVM_") || methodName.startsWith("Unsafe_") ||
|
||||
methodName.startsWith("MHN_") || methodName.startsWith("jni_")) {
|
||||
return VM;
|
||||
}
|
||||
switch (methodName) {
|
||||
case "call_stub":
|
||||
case "deoptimization":
|
||||
case "unknown_Java":
|
||||
case "not_walkable_Java":
|
||||
case "InlineCacheBuffer":
|
||||
return VM;
|
||||
}
|
||||
if (methodName.endsWith("_arraycopy") || methodName.contains("pthread_cond")) {
|
||||
break;
|
||||
}
|
||||
inJava = false;
|
||||
break;
|
||||
}
|
||||
case FlameGraph.FRAME_CPP: {
|
||||
String methodName = methodNames.get(methods[i]);
|
||||
if (methodName.startsWith("Runtime1::")) {
|
||||
return C1_COMP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FlameGraph.FRAME_C1_COMPILED:
|
||||
return inJava ? C1_COMP : NATIVE;
|
||||
}
|
||||
}
|
||||
return NATIVE;
|
||||
}
|
||||
}
|
||||
39
src/converter/CollapsedStacks.java
Normal file
39
src/converter/CollapsedStacks.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
public class CollapsedStacks extends FlameGraph {
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
private final PrintStream out;
|
||||
|
||||
public CollapsedStacks(Arguments args) throws IOException {
|
||||
super(args);
|
||||
this.out = args.output == null ? System.out : new PrintStream(
|
||||
new BufferedOutputStream(new FileOutputStream(args.output), 32768), false, "UTF-8");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
for (String s : trace) {
|
||||
sb.append(s).append(';');
|
||||
}
|
||||
if (sb.length() > 0) sb.setCharAt(sb.length() - 1, ' ');
|
||||
sb.append(ticks);
|
||||
|
||||
out.println(sb.toString());
|
||||
sb.setLength(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump() {
|
||||
if (out != System.out) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,54 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FlameGraph {
|
||||
public String title = "Flame Graph";
|
||||
public boolean reverse;
|
||||
public double minwidth;
|
||||
public int skip;
|
||||
public String input;
|
||||
public String output;
|
||||
public class FlameGraph implements Comparator<FlameGraph.Frame> {
|
||||
public static final byte FRAME_INTERPRETED = 0;
|
||||
public static final byte FRAME_JIT_COMPILED = 1;
|
||||
public static final byte FRAME_INLINED = 2;
|
||||
public static final byte FRAME_NATIVE = 3;
|
||||
public static final byte FRAME_CPP = 4;
|
||||
public static final byte FRAME_KERNEL = 5;
|
||||
public static final byte FRAME_C1_COMPILED = 6;
|
||||
|
||||
private final Frame root = new Frame();
|
||||
private final Arguments args;
|
||||
private final Map<String, Integer> cpool = new HashMap<>();
|
||||
private final Frame root = new Frame(getFrameKey("", FRAME_NATIVE));
|
||||
private int[] order;
|
||||
private int depth;
|
||||
private int lastLevel;
|
||||
private long lastX;
|
||||
private long lastTotal;
|
||||
private long mintotal;
|
||||
|
||||
public FlameGraph(Arguments args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public FlameGraph(String... args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if (!arg.startsWith("--") && !arg.isEmpty()) {
|
||||
if (input == null) {
|
||||
input = arg;
|
||||
} else {
|
||||
output = arg;
|
||||
}
|
||||
} else if (arg.equals("--title")) {
|
||||
title = args[++i];
|
||||
} else if (arg.equals("--reverse")) {
|
||||
reverse = true;
|
||||
} else if (arg.equals("--minwidth")) {
|
||||
minwidth = Double.parseDouble(args[++i]);
|
||||
} else if (arg.equals("--skip")) {
|
||||
skip = Integer.parseInt(args[++i]);
|
||||
}
|
||||
}
|
||||
this(new Arguments(args));
|
||||
}
|
||||
|
||||
public void parse() throws IOException {
|
||||
parse(new InputStreamReader(new FileInputStream(input), StandardCharsets.UTF_8));
|
||||
parse(new InputStreamReader(new FileInputStream(args.input), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void parse(Reader in) throws IOException {
|
||||
@@ -77,16 +65,18 @@ public class FlameGraph {
|
||||
}
|
||||
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
if (excludeTrace(trace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Frame frame = root;
|
||||
if (reverse) {
|
||||
for (int i = trace.length; --i >= skip; ) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
if (args.reverse) {
|
||||
for (int i = trace.length; --i >= args.skip; ) {
|
||||
frame = addChild(frame, trace[i], ticks);
|
||||
}
|
||||
} else {
|
||||
for (int i = skip; i < trace.length; i++) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
for (int i = args.skip; i < trace.length; i++) {
|
||||
frame = addChild(frame, trace[i], ticks);
|
||||
}
|
||||
}
|
||||
frame.total += ticks;
|
||||
@@ -96,10 +86,10 @@ public class FlameGraph {
|
||||
}
|
||||
|
||||
public void dump() throws IOException {
|
||||
if (output == null) {
|
||||
if (args.output == null) {
|
||||
dump(System.out);
|
||||
} else {
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(args.output), 32768);
|
||||
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
|
||||
dump(out);
|
||||
}
|
||||
@@ -107,85 +97,209 @@ public class FlameGraph {
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
out.print(applyReplacements(HEADER,
|
||||
"{title}", title,
|
||||
"{height}", (depth + 1) * 16,
|
||||
"{depth}", depth + 1,
|
||||
"{reverse}", reverse));
|
||||
mintotal = (long) (root.total * args.minwidth / 100);
|
||||
int depth = mintotal > 1 ? root.depth(mintotal) : this.depth + 1;
|
||||
|
||||
mintotal = (long) (root.total * minwidth / 100);
|
||||
printFrame(out, "all", root, 0, 0);
|
||||
String tail = getResource("/flame.html");
|
||||
|
||||
out.print(FOOTER);
|
||||
tail = printTill(out, tail, "/*height:*/300");
|
||||
out.print(Math.min(depth * 16, 32767));
|
||||
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out.print(args.title);
|
||||
|
||||
tail = printTill(out, tail, "/*reverse:*/false");
|
||||
out.print(args.reverse);
|
||||
|
||||
tail = printTill(out, tail, "/*depth:*/0");
|
||||
out.print(depth);
|
||||
|
||||
tail = printTill(out, tail, "/*cpool:*/");
|
||||
printCpool(out);
|
||||
|
||||
tail = printTill(out, tail, "/*frames:*/");
|
||||
printFrame(out, root, 0, 0);
|
||||
|
||||
tail = printTill(out, tail, "/*highlight:*/");
|
||||
out.print(args.highlight != null ? "'" + escape(args.highlight) + "'" : "");
|
||||
|
||||
out.print(tail);
|
||||
}
|
||||
|
||||
// Replace ${variables} in the given string with field values
|
||||
private String applyReplacements(String s, Object... params) {
|
||||
StringBuilder result = new StringBuilder(s.length() + 256);
|
||||
private String printTill(PrintStream out, String data, String till) {
|
||||
int index = data.indexOf(till);
|
||||
out.print(data.substring(0, index));
|
||||
return data.substring(index + till.length());
|
||||
}
|
||||
|
||||
int p = 0;
|
||||
for (int q; (q = s.indexOf('$', p)) >= 0; ) {
|
||||
result.append(s, p, q);
|
||||
p = s.indexOf('}', q + 2) + 1;
|
||||
String var = s.substring(q + 1, p);
|
||||
for (int i = 0; i < params.length; i += 2) {
|
||||
if (var.equals(params[i])) {
|
||||
result.append(params[i + 1]);
|
||||
break;
|
||||
}
|
||||
private void printCpool(PrintStream out) {
|
||||
String[] strings = cpool.keySet().toArray(new String[0]);
|
||||
Arrays.sort(strings);
|
||||
out.print("'all'");
|
||||
|
||||
order = new int[strings.length];
|
||||
String s = "";
|
||||
for (int i = 1; i < strings.length; i++) {
|
||||
int prefixLen = Math.min(getCommonPrefix(s, s = strings[i]), 95);
|
||||
out.print(",\n'" + escape((char) (prefixLen + ' ') + s.substring(prefixLen)) + "'");
|
||||
order[cpool.get(s)] = i;
|
||||
}
|
||||
|
||||
// cpool is not used beyond this point
|
||||
cpool.clear();
|
||||
}
|
||||
|
||||
private void printFrame(PrintStream out, Frame frame, int level, long x) {
|
||||
int nameAndType = order[frame.getTitleIndex()] << 3 | frame.getType();
|
||||
boolean hasExtraTypes = (frame.inlined | frame.c1 | frame.interpreted) != 0 &&
|
||||
frame.inlined < frame.total && frame.interpreted < frame.total;
|
||||
|
||||
char func = 'f';
|
||||
if (level == lastLevel + 1 && x == lastX) {
|
||||
func = 'u';
|
||||
} else if (level == lastLevel && x == lastX + lastTotal) {
|
||||
func = 'n';
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(24).append(func).append('(').append(nameAndType);
|
||||
if (func == 'f') {
|
||||
sb.append(',').append(level).append(',').append(x - lastX);
|
||||
}
|
||||
if (frame.total != lastTotal || hasExtraTypes) {
|
||||
sb.append(',').append(frame.total);
|
||||
if (hasExtraTypes) {
|
||||
sb.append(',').append(frame.inlined).append(',').append(frame.c1).append(',').append(frame.interpreted);
|
||||
}
|
||||
}
|
||||
sb.append(')');
|
||||
out.println(sb.toString());
|
||||
|
||||
result.append(s, p, s.length());
|
||||
return result.toString();
|
||||
}
|
||||
lastLevel = level;
|
||||
lastX = x;
|
||||
lastTotal = frame.total;
|
||||
|
||||
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
|
||||
int type = frameType(title);
|
||||
title = stripSuffix(title);
|
||||
if (title.indexOf('\'') >= 0) {
|
||||
title = title.replace("'", "\\'");
|
||||
}
|
||||
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + title + "')");
|
||||
Frame[] children = frame.values().toArray(Frame.EMPTY_ARRAY);
|
||||
Arrays.sort(children, this);
|
||||
|
||||
x += frame.self;
|
||||
for (Map.Entry<String, Frame> e : frame.entrySet()) {
|
||||
Frame child = e.getValue();
|
||||
for (Frame child : children) {
|
||||
if (child.total >= mintotal) {
|
||||
printFrame(out, e.getKey(), child, level + 1, x);
|
||||
printFrame(out, child, level + 1, x);
|
||||
}
|
||||
x += child.total;
|
||||
}
|
||||
}
|
||||
|
||||
private String stripSuffix(String title) {
|
||||
int len = title.length();
|
||||
if (len >= 4 && title.charAt(len - 1) == ']' && title.regionMatches(len - 4, "_[", 0, 2)) {
|
||||
return title.substring(0, len - 4);
|
||||
private boolean excludeTrace(String[] trace) {
|
||||
Pattern include = args.include;
|
||||
Pattern exclude = args.exclude;
|
||||
if (include == null && exclude == null) {
|
||||
return false;
|
||||
}
|
||||
return title;
|
||||
|
||||
for (String frame : trace) {
|
||||
if (exclude != null && exclude.matcher(frame).matches()) {
|
||||
return true;
|
||||
}
|
||||
if (include != null && include.matcher(frame).matches()) {
|
||||
include = null;
|
||||
if (exclude == null) break;
|
||||
}
|
||||
}
|
||||
|
||||
return include != null;
|
||||
}
|
||||
|
||||
private int frameType(String title) {
|
||||
if (title.endsWith("_[j]")) {
|
||||
return 0;
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
return 1;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
return 2;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
return 3;
|
||||
} else if (title.indexOf('/') > 0 || title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
return 0;
|
||||
private int getFrameKey(String title, byte type) {
|
||||
Integer key = cpool.get(title);
|
||||
if (key != null) {
|
||||
return key | type << 28;
|
||||
} else {
|
||||
return 4;
|
||||
int size = cpool.size();
|
||||
cpool.put(title, size);
|
||||
return size | type << 28;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
private Frame getChild(Frame frame, String title, byte type) {
|
||||
int key = getFrameKey(title, type);
|
||||
Frame child = frame.get(key);
|
||||
if (child == null) {
|
||||
frame.put(key, child = new Frame(key));
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
private Frame addChild(Frame frame, String title, long ticks) {
|
||||
frame.total += ticks;
|
||||
|
||||
Frame child;
|
||||
if (title.endsWith("_[j]")) {
|
||||
child = getChild(frame, stripSuffix(title), FRAME_JIT_COMPILED);
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
(child = getChild(frame, stripSuffix(title), FRAME_JIT_COMPILED)).inlined += ticks;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
child = getChild(frame, stripSuffix(title), FRAME_KERNEL);
|
||||
} else if (title.endsWith("_[1]")) {
|
||||
(child = getChild(frame, stripSuffix(title), FRAME_JIT_COMPILED)).c1 += ticks;
|
||||
} else if (title.endsWith("_[0]")) {
|
||||
(child = getChild(frame, stripSuffix(title), FRAME_JIT_COMPILED)).interpreted += ticks;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
child = getChild(frame, title, FRAME_CPP);
|
||||
} else if (title.indexOf('/') > 0 && title.charAt(0) != '['
|
||||
|| title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
child = getChild(frame, title, FRAME_JIT_COMPILED);
|
||||
} else {
|
||||
child = getChild(frame, title, FRAME_NATIVE);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
static int getCommonPrefix(String a, String b) {
|
||||
int length = Math.min(a.length(), b.length());
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (a.charAt(i) != b.charAt(i) || a.charAt(i) > 127) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static String stripSuffix(String title) {
|
||||
return title.substring(0, title.length() - 4);
|
||||
}
|
||||
|
||||
static String escape(String s) {
|
||||
if (s.indexOf('\\') >= 0) s = s.replace("\\", "\\\\");
|
||||
if (s.indexOf('\'') >= 0) s = s.replace("'", "\\'");
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String getResource(String name) {
|
||||
try (InputStream stream = FlameGraph.class.getResourceAsStream(name)) {
|
||||
if (stream == null) {
|
||||
throw new IOException("No resource found");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[64 * 1024];
|
||||
for (int length; (length = stream.read(buffer)) != -1; ) {
|
||||
result.write(buffer, 0, length);
|
||||
}
|
||||
return result.toString("UTF-8");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Can't load resource with name " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Frame f1, Frame f2) {
|
||||
return order[f1.getTitleIndex()] - order[f2.getTitleIndex()];
|
||||
}
|
||||
|
||||
public static void main(String[] cmdline) throws IOException {
|
||||
Arguments args = new Arguments(cmdline);
|
||||
if (args.input == null) {
|
||||
System.out.println("Usage: java " + FlameGraph.class.getName() + " [options] input.collapsed [output.html]");
|
||||
System.out.println();
|
||||
System.out.println("Options:");
|
||||
@@ -193,250 +307,55 @@ public class FlameGraph {
|
||||
System.out.println(" --reverse");
|
||||
System.out.println(" --minwidth PERCENT");
|
||||
System.out.println(" --skip FRAMES");
|
||||
System.out.println(" --include PATTERN");
|
||||
System.out.println(" --exclude PATTERN");
|
||||
System.out.println(" --highlight PATTERN");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
fg.parse();
|
||||
fg.dump();
|
||||
}
|
||||
|
||||
static class Frame extends TreeMap<String, Frame> {
|
||||
static class Frame extends HashMap<Integer, Frame> {
|
||||
static final Frame[] EMPTY_ARRAY = {};
|
||||
|
||||
final int key;
|
||||
long total;
|
||||
long self;
|
||||
long inlined, c1, interpreted;
|
||||
|
||||
Frame child(String title) {
|
||||
Frame child = get(title);
|
||||
if (child == null) {
|
||||
put(title, child = new Frame());
|
||||
Frame(int key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
byte getType() {
|
||||
if (inlined * 3 >= total) {
|
||||
return FRAME_INLINED;
|
||||
} else if (c1 * 2 >= total) {
|
||||
return FRAME_C1_COMPILED;
|
||||
} else if (interpreted * 2 >= total) {
|
||||
return FRAME_INTERPRETED;
|
||||
} else {
|
||||
return (byte) (key >>> 28);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
int getTitleIndex() {
|
||||
return key & ((1 << 28) - 1);
|
||||
}
|
||||
|
||||
int depth(long cutoff) {
|
||||
int depth = 0;
|
||||
if (size() > 0) {
|
||||
for (Frame child : values()) {
|
||||
if (child.total >= cutoff) {
|
||||
depth = Math.max(depth, child.depth(cutoff));
|
||||
}
|
||||
}
|
||||
}
|
||||
return depth + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String HEADER = "<!DOCTYPE html>\n" +
|
||||
"<html lang='en'>\n" +
|
||||
"<head>\n" +
|
||||
"<meta charset='utf-8'>\n" +
|
||||
"<style>\n" +
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n" +
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n" +
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n" +
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n" +
|
||||
"\tp {margin: 5px 0 5px 0}\n" +
|
||||
"\ta {color: #0366d6}\n" +
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n" +
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n" +
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n" +
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n" +
|
||||
"\t#reset {cursor: pointer}\n" +
|
||||
"</style>\n" +
|
||||
"</head>\n" +
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n" +
|
||||
"<h1>${title}</h1>\n" +
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n" +
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n" +
|
||||
"<canvas id='canvas' style='width: 100%; height: ${height}px'></canvas>\n" +
|
||||
"<div id='hl'><span></span></div>\n" +
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n" +
|
||||
"<p id='status'> </p>\n" +
|
||||
"<script>\n" +
|
||||
"\t// Copyright 2020 Andrei Pangin\n" +
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n" +
|
||||
"\t'use strict';\n" +
|
||||
"\tvar root, rootLevel, px, pattern;\n" +
|
||||
"\tvar reverse = ${reverse};\n" +
|
||||
"\tconst levels = Array(${depth});\n" +
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n" +
|
||||
"\t\tlevels[h] = [];\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tconst canvas = document.getElementById('canvas');\n" +
|
||||
"\tconst c = canvas.getContext('2d');\n" +
|
||||
"\tconst hl = document.getElementById('hl');\n" +
|
||||
"\tconst status = document.getElementById('status');\n" +
|
||||
"\n" +
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n" +
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n" +
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n" +
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n" +
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n" +
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n" +
|
||||
"\tc.font = document.body.style.font;\n" +
|
||||
"\n" +
|
||||
"\tconst palette = [\n" +
|
||||
"\t\t[0x50e150, 30, 30, 30],\n" +
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n" +
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n" +
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n" +
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n" +
|
||||
"\t];\n" +
|
||||
"\n" +
|
||||
"\tfunction getColor(p) {\n" +
|
||||
"\t\tconst v = Math.random();\n" +
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction f(level, left, width, type, title) {\n" +
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction samples(n) {\n" +
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction pct(a, b) {\n" +
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction findFrame(frames, x) {\n" +
|
||||
"\t\tlet left = 0;\n" +
|
||||
"\t\tlet right = frames.length - 1;\n" +
|
||||
"\n" +
|
||||
"\t\twhile (left <= right) {\n" +
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n" +
|
||||
"\t\t\tconst f = frames[mid];\n" +
|
||||
"\n" +
|
||||
"\t\t\tif (f.left > x) {\n" +
|
||||
"\t\t\t\tright = mid - 1;\n" +
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n" +
|
||||
"\t\t\t\tleft = mid + 1;\n" +
|
||||
"\t\t\t} else {\n" +
|
||||
"\t\t\t\treturn f;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n" +
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n" +
|
||||
"\n" +
|
||||
"\t\treturn null;\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction search(r) {\n" +
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n" +
|
||||
"\t\t\treturn;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n" +
|
||||
"\t\tconst matched = render(root, rootLevel);\n" +
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%';\n" +
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction render(newRoot, newLevel) {\n" +
|
||||
"\t\tif (root) {\n" +
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n" +
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\troot = newRoot || levels[0][0];\n" +
|
||||
"\t\trootLevel = newLevel || 0;\n" +
|
||||
"\t\tpx = canvasWidth / root.width;\n" +
|
||||
"\n" +
|
||||
"\t\tconst x0 = root.left;\n" +
|
||||
"\t\tconst x1 = x0 + root.width;\n" +
|
||||
"\t\tconst marked = [];\n" +
|
||||
"\n" +
|
||||
"\t\tfunction mark(f) {\n" +
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfunction totalMarked() {\n" +
|
||||
"\t\t\tlet total = 0;\n" +
|
||||
"\t\t\tlet left = 0;\n" +
|
||||
"\t\t\tfor (let x in marked) {\n" +
|
||||
"\t\t\t\tif (+x >= left) {\n" +
|
||||
"\t\t\t\t\ttotal += marked[x];\n" +
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t\treturn total;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n" +
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n" +
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n" +
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
|
||||
"\n" +
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n" +
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n" +
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n" +
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n" +
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\t\t\tif (alpha) {\n" +
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n" +
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n" +
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n" +
|
||||
"\t\t\tconst frames = levels[h];\n" +
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n" +
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\treturn totalMarked();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tcanvas.onmousemove = function() {\n" +
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n" +
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n" +
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n" +
|
||||
"\t\t\tif (f) {\n" +
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n" +
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n" +
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n" +
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n" +
|
||||
"\t\t\t\thl.style.display = 'block';\n" +
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%)';\n" +
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n" +
|
||||
"\t\t\t\tcanvas.onclick = function() {\n" +
|
||||
"\t\t\t\t\tif (f != root) {\n" +
|
||||
"\t\t\t\t\t\trender(f, h);\n" +
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n" +
|
||||
"\t\t\t\t\t}\n" +
|
||||
"\t\t\t\t};\n" +
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n" +
|
||||
"\t\t\t\treturn;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\t\tcanvas.onmouseout();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tcanvas.onmouseout = function() {\n" +
|
||||
"\t\thl.style.display = 'none';\n" +
|
||||
"\t\tstatus.textContent = '\\xa0';\n" +
|
||||
"\t\tcanvas.title = '';\n" +
|
||||
"\t\tcanvas.style.cursor = '';\n" +
|
||||
"\t\tcanvas.onclick = '';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n" +
|
||||
"\t\treverse = !reverse;\n" +
|
||||
"\t\trender();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('search').onclick = function() {\n" +
|
||||
"\t\tsearch(true);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n" +
|
||||
"\t\tsearch(false);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\twindow.onkeydown = function() {\n" +
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n" +
|
||||
"\t\t\tevent.preventDefault();\n" +
|
||||
"\t\t\tsearch(true);\n" +
|
||||
"\t\t} else if (event.keyCode === 27) {\n" +
|
||||
"\t\t\tsearch(false);\n" +
|
||||
"\t\t}\n" +
|
||||
"\t}\n";
|
||||
|
||||
private static final String FOOTER = "render();\n" +
|
||||
"</script></body></html>\n";
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Main-Class: Main
|
||||
@@ -1,31 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Main entry point of jar.
|
||||
* Lists available converters.
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
String[] converterArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
switch (args[0]) {
|
||||
case "FlameGraph":
|
||||
FlameGraph.main(converterArgs);
|
||||
break;
|
||||
case "jfr2flame":
|
||||
jfr2flame.main(converterArgs);
|
||||
break;
|
||||
case "jfr2nflx":
|
||||
jfr2nflx.main(converterArgs);
|
||||
break;
|
||||
case "jfr2pprof":
|
||||
jfr2pprof.main(converterArgs);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Unknown converter: " + args[0] + "\n");
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void usage() {
|
||||
System.out.println("Usage: java -cp converter.jar <Converter> [options] <input> <output>");
|
||||
System.out.println();
|
||||
System.out.println("Available converters:");
|
||||
System.out.println(" FlameGraph input.collapsed output.html");
|
||||
System.out.println(" jfr2flame input.jfr output.html");
|
||||
System.out.println(" jfr2nflx input.jfr output.nflx");
|
||||
System.out.println(" FlameGraph input.collapsed output.html");
|
||||
System.out.println(" jfr2flame input.jfr output.html");
|
||||
System.out.println(" jfr2nflx input.jfr output.nflx");
|
||||
System.out.println(" jfr2pprof input.jfr output.pprof");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
@@ -19,7 +8,9 @@ import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
@@ -27,64 +18,265 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
public class jfr2flame {
|
||||
|
||||
private static final int FRAME_KERNEL = 5;
|
||||
private static final String[] FRAME_SUFFIX = {"_[0]", "_[j]", "_[i]", "", "", "_[k]", "_[1]"};
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final Dictionary<String> methodNames = new Dictionary<>();
|
||||
private final Arguments args;
|
||||
|
||||
public jfr2flame(JfrReader jfr) {
|
||||
public jfr2flame(JfrReader jfr, Arguments args) {
|
||||
this.jfr = jfr;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void convert(final FlameGraph fg) {
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
String[] trace = new String[methods.length];
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
trace[trace.length - 1 - i] = getMethodName(methods[i], types[i]);
|
||||
public void convert(FlameGraph fg) throws IOException {
|
||||
Class<? extends Event> eventClass =
|
||||
args.live ? LiveObject.class :
|
||||
args.alloc ? AllocationSample.class :
|
||||
args.lock ? ContendedLock.class : ExecutionSample.class;
|
||||
|
||||
jfr.stopAtNewChunk = true;
|
||||
while (!jfr.eof()) {
|
||||
convertChunk(fg, eventClass);
|
||||
}
|
||||
}
|
||||
|
||||
public void convertChunk(final FlameGraph fg, Class<? extends Event> eventClass) throws IOException {
|
||||
EventAggregator agg = new EventAggregator(args.threads, args.total);
|
||||
|
||||
long threadStates = 0;
|
||||
if (args.state != null) {
|
||||
for (String state : args.state.split(",")) {
|
||||
int key = jfr.getEnumKey("jdk.types.ThreadState", "STATE_" + state.toUpperCase());
|
||||
if (key >= 0) threadStates |= 1L << key;
|
||||
}
|
||||
}
|
||||
|
||||
long startTicks = args.from != 0 ? toTicks(args.from) : Long.MIN_VALUE;
|
||||
long endTicks = args.to != 0 ? toTicks(args.to) : Long.MAX_VALUE;
|
||||
|
||||
for (Event event; (event = jfr.readEvent(eventClass)) != null; ) {
|
||||
if (event.time >= startTicks && event.time <= endTicks) {
|
||||
if (threadStates == 0 || (threadStates & (1L << ((ExecutionSample) event).threadState)) != 0) {
|
||||
agg.collect(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Dictionary<String> methodNames = new Dictionary<>();
|
||||
final Classifier classifier = new Classifier(methodNames);
|
||||
|
||||
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
|
||||
final boolean scale = args.total && args.lock && ticksToNanos != 1.0;
|
||||
|
||||
// Don't use lambda for faster startup
|
||||
agg.forEach(new EventAggregator.Visitor() {
|
||||
@Override
|
||||
public void visit(Event event, long value) {
|
||||
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
Arguments args = jfr2flame.this.args;
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
int[] locations = stackTrace.locations;
|
||||
String classFrame = getClassFrame(event);
|
||||
String[] trace = new String[methods.length
|
||||
+ (args.threads ? 1 : 0)
|
||||
+ (args.classify ? 1 : 0)
|
||||
+ (classFrame != null ? 1 : 0)];
|
||||
if (args.threads) {
|
||||
trace[0] = getThreadFrame(event.tid);
|
||||
}
|
||||
int idx = trace.length;
|
||||
if (classFrame != null) {
|
||||
trace[--idx] = classFrame;
|
||||
}
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
String methodName = getMethodName(methodNames, methods[i], types[i]);
|
||||
int location;
|
||||
if (args.lines && (location = locations[i] >>> 16) != 0) {
|
||||
methodName += ":" + location;
|
||||
} else if (args.bci && (location = locations[i] & 0xffff) != 0) {
|
||||
methodName += "@" + location;
|
||||
}
|
||||
trace[--idx] = methodName + FRAME_SUFFIX[types[i]];
|
||||
}
|
||||
if (args.classify) {
|
||||
trace[--idx] = classifier.getCategoryName(stackTrace);
|
||||
}
|
||||
fg.addSample(trace, scale ? (long) (value * ticksToNanos) : value);
|
||||
}
|
||||
fg.addSample(trace, stackTrace.samples);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getMethodName(long methodId, int type) {
|
||||
private String getThreadFrame(int tid) {
|
||||
String threadName = jfr.threads.get(tid);
|
||||
return threadName == null ? "[tid=" + tid + ']' :
|
||||
threadName.startsWith("[tid=") ? threadName : '[' + threadName + " tid=" + tid + ']';
|
||||
}
|
||||
|
||||
private String getClassFrame(Event event) {
|
||||
long classId;
|
||||
String suffix;
|
||||
if (event instanceof AllocationSample) {
|
||||
classId = ((AllocationSample) event).classId;
|
||||
suffix = ((AllocationSample) event).tlabSize == 0 ? "_[k]" : "_[i]";
|
||||
} else if (event instanceof ContendedLock) {
|
||||
classId = ((ContendedLock) event).classId;
|
||||
suffix = "_[i]";
|
||||
} else if (event instanceof LiveObject) {
|
||||
classId = ((LiveObject) event).classId;
|
||||
suffix = "_[i]";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassRef cls = jfr.classes.get(classId);
|
||||
if (cls == null) {
|
||||
return "null";
|
||||
}
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
|
||||
int arrayDepth = 0;
|
||||
while (className[arrayDepth] == '[') {
|
||||
arrayDepth++;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(toJavaClassName(className, arrayDepth, true));
|
||||
while (arrayDepth-- > 0) {
|
||||
sb.append("[]");
|
||||
}
|
||||
return sb.append(suffix).toString();
|
||||
}
|
||||
|
||||
private String getMethodName(Dictionary<String> methodNames, long methodId, byte methodType) {
|
||||
String result = methodNames.get(methodId);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0) {
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = type == FRAME_KERNEL ? methodStr + "_[k]" : methodStr;
|
||||
if (method == null) {
|
||||
result = "unknown";
|
||||
} else {
|
||||
String classStr = new String(className, StandardCharsets.UTF_8);
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = classStr + '.' + methodStr + "_[j]";
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0 || isNativeFrame(methodType)) {
|
||||
result = new String(methodName, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
String classStr = toJavaClassName(className, 0, args.dot);
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = methodStr.isEmpty() ? classStr : classStr + '.' + methodStr;
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.put(methodId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
private boolean isNativeFrame(byte methodType) {
|
||||
return methodType == FlameGraph.FRAME_NATIVE && jfr.getEnumValue("jdk.types.FrameType", FlameGraph.FRAME_KERNEL) != null
|
||||
|| methodType == FlameGraph.FRAME_CPP
|
||||
|| methodType == FlameGraph.FRAME_KERNEL;
|
||||
}
|
||||
|
||||
private String toJavaClassName(byte[] symbol, int start, boolean dotted) {
|
||||
int end = symbol.length;
|
||||
if (start > 0) {
|
||||
switch (symbol[start]) {
|
||||
case 'B':
|
||||
return "byte";
|
||||
case 'C':
|
||||
return "char";
|
||||
case 'S':
|
||||
return "short";
|
||||
case 'I':
|
||||
return "int";
|
||||
case 'J':
|
||||
return "long";
|
||||
case 'Z':
|
||||
return "boolean";
|
||||
case 'F':
|
||||
return "float";
|
||||
case 'D':
|
||||
return "double";
|
||||
case 'L':
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.norm) {
|
||||
for (int i = end - 2; i > start; i--) {
|
||||
if (symbol[i] == '/' || symbol[i] == '.') {
|
||||
if (symbol[i + 1] >= '0' && symbol[i + 1] <= '9') {
|
||||
end = i;
|
||||
if (i > start + 19 && symbol[i - 19] == '+' && symbol[i - 18] == '0') {
|
||||
// Original JFR transforms lambda names to something like
|
||||
// pkg.ClassName$$Lambda+0x00007f8177090218/543846639
|
||||
end = i - 19;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.simple) {
|
||||
for (int i = end - 2; i >= start; i--) {
|
||||
if (symbol[i] == '/' && (symbol[i + 1] < '0' || symbol[i + 1] > '9')) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String s = new String(symbol, start, end - start, StandardCharsets.UTF_8);
|
||||
return dotted ? s.replace('/', '.') : s;
|
||||
}
|
||||
|
||||
// millis can be an absolute timestamp or an offset from the beginning/end of the recording
|
||||
private long toTicks(long millis) {
|
||||
long nanos = millis * 1_000_000;
|
||||
if (millis < 0) {
|
||||
nanos += jfr.endNanos;
|
||||
} else if (millis < 1500000000000L) {
|
||||
nanos += jfr.startNanos;
|
||||
}
|
||||
return jfr.nanosToTicks(nanos);
|
||||
}
|
||||
|
||||
public static void main(String[] cmdline) throws Exception {
|
||||
Arguments args = new Arguments(cmdline);
|
||||
if (args.input == null) {
|
||||
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
|
||||
System.out.println();
|
||||
System.out.println("options include all supported FlameGraph options, plus the following:");
|
||||
System.out.println(" --alloc Allocation Flame Graph");
|
||||
System.out.println(" --live Include only live objects in allocation profile");
|
||||
System.out.println(" --lock Lock contention Flame Graph");
|
||||
System.out.println(" --threads Split profile by threads");
|
||||
System.out.println(" --state LIST Filter samples by thread states: RUNNABLE, SLEEPING, etc.");
|
||||
System.out.println(" --classify Classify samples into predefined categories");
|
||||
System.out.println(" --total Accumulate the total value (time, bytes, etc.)");
|
||||
System.out.println(" --lines Show line numbers");
|
||||
System.out.println(" --bci Show bytecode indices");
|
||||
System.out.println(" --simple Simple class names instead of FQN");
|
||||
System.out.println(" --dot Dotted class names");
|
||||
System.out.println(" --norm Normalize names of hidden classes / lambdas");
|
||||
System.out.println(" --from TIME Start time in ms (absolute or relative)");
|
||||
System.out.println(" --to TIME End time in ms (absolute or relative)");
|
||||
System.out.println(" --collapsed Use collapsed stacks output format");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try (JfrReader jfr = new JfrReader(fg.input)) {
|
||||
new jfr2flame(jfr).convert(fg);
|
||||
boolean collapsed = args.collapsed || args.output != null && args.output.endsWith(".collapsed");
|
||||
FlameGraph fg = collapsed ? new CollapsedStacks(args) : new FlameGraph(args);
|
||||
|
||||
try (JfrReader jfr = new JfrReader(args.input)) {
|
||||
new jfr2flame(jfr, args).convert(fg);
|
||||
}
|
||||
|
||||
fg.dump();
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.Sample;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.Event;
|
||||
import one.jfr.event.EventAggregator;
|
||||
import one.jfr.event.ExecutionSample;
|
||||
import one.proto.Proto;
|
||||
|
||||
import java.io.File;
|
||||
@@ -27,6 +17,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to nflxprofile format
|
||||
@@ -35,24 +26,27 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class jfr2nflx {
|
||||
|
||||
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel"};
|
||||
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel", "jit"};
|
||||
private static final byte[] NO_STACK = "[no_stack]".getBytes();
|
||||
private static final byte[] UNKNOWN = "[unknown]".getBytes();
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final List<ExecutionSample> samples;
|
||||
|
||||
public jfr2nflx(JfrReader jfr) {
|
||||
public jfr2nflx(JfrReader jfr) throws IOException {
|
||||
this.jfr = jfr;
|
||||
this.samples = jfr.readAllEvents(ExecutionSample.class);
|
||||
}
|
||||
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
int samples = jfr.samples.size();
|
||||
long durationTicks = samples == 0 ? 0 : jfr.samples.get(samples - 1).time - jfr.startTicks + 1;
|
||||
int size = samples.size();
|
||||
long durationTicks = size == 0 ? 0 : samples.get(size - 1).time - jfr.startTicks + 1;
|
||||
|
||||
final Proto profile = new Proto(200000)
|
||||
.field(1, 0.0)
|
||||
.field(2, Math.max(jfr.durationNanos / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(2, Math.max(jfr.durationNanos() / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(3, packSamples())
|
||||
.field(4, packDeltas())
|
||||
.field(6, "async-profiler")
|
||||
@@ -63,15 +57,23 @@ public class jfr2nflx {
|
||||
final Proto nodes = new Proto(10000);
|
||||
final Proto node = new Proto(10000);
|
||||
|
||||
EventAggregator agg = new EventAggregator(false, false);
|
||||
for (ExecutionSample sample : samples) {
|
||||
agg.collect(sample);
|
||||
}
|
||||
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
agg.forEach(new EventAggregator.Visitor() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
profile.field(5, nodes
|
||||
.field(1, (int) id)
|
||||
.field(2, packNode(node, stackTrace)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
public void visit(Event event, long value) {
|
||||
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
profile.field(5, nodes
|
||||
.field(1, event.stackTraceId)
|
||||
.field(2, packNode(node, stackTrace)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,13 +88,13 @@ public class jfr2nflx {
|
||||
byte[] types = stackTrace.types;
|
||||
int top = methods.length - 1;
|
||||
|
||||
node.field(1, top >= 0 ? getMethodName(methods[top]) : NO_STACK);
|
||||
node.field(1, top >= 0 ? getMethodName(methods[top], types[top]) : NO_STACK);
|
||||
node.field(2, 1);
|
||||
node.field(4, top >= 0 ? FRAME_TYPE[types[top]] : "user");
|
||||
|
||||
for (Proto frame = new Proto(100); --top >= 0; frame.reset()) {
|
||||
node.field(10, frame
|
||||
.field(1, getMethodName(methods[top]))
|
||||
.field(1, getMethodName(methods[top], types[top]))
|
||||
.field(2, FRAME_TYPE[types[top]]));
|
||||
}
|
||||
|
||||
@@ -101,7 +103,7 @@ public class jfr2nflx {
|
||||
|
||||
private Proto packSamples() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (Sample sample : jfr.samples) {
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeInt(sample.stackTraceId);
|
||||
}
|
||||
return proto;
|
||||
@@ -111,7 +113,7 @@ public class jfr2nflx {
|
||||
Proto proto = new Proto(10000);
|
||||
double ticksPerSec = jfr.ticksPerSec;
|
||||
long prevTime = jfr.startTicks;
|
||||
for (Sample sample : jfr.samples) {
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
|
||||
prevTime = sample.time;
|
||||
}
|
||||
@@ -120,19 +122,24 @@ public class jfr2nflx {
|
||||
|
||||
private Proto packTids() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (Sample sample : jfr.samples) {
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeInt(sample.tid);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private byte[] getMethodName(long methodId) {
|
||||
private byte[] getMethodName(long methodId, byte methodType) {
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
if (method == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0) {
|
||||
if ((methodType >= FlameGraph.FRAME_NATIVE && methodType <= FlameGraph.FRAME_KERNEL)
|
||||
|| className == null || className.length == 0) {
|
||||
return methodName;
|
||||
} else {
|
||||
byte[] fullName = Arrays.copyOf(className, className.length + 1 + methodName.length);
|
||||
|
||||
242
src/converter/jfr2pprof.java
Normal file
242
src/converter/jfr2pprof.java
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.ExecutionSample;
|
||||
import one.proto.Proto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Convert a JFR file to pprof
|
||||
* <p>
|
||||
* Protobuf definition: https://github.com/google/pprof/blob/44fc4e887b6b0cfb196973bcdb1fab95f0b3a75b/proto/profile.proto
|
||||
*/
|
||||
public class jfr2pprof {
|
||||
|
||||
public static class Method {
|
||||
final byte[] name;
|
||||
|
||||
public Method(final byte[] name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
return other instanceof Method && Arrays.equals(name, ((Method) other).name);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Location {
|
||||
final Method method;
|
||||
final int line;
|
||||
|
||||
public Location(final Method method, final int line) {
|
||||
this.method = method;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Location location = (Location) o;
|
||||
|
||||
if (line != location.line) return false;
|
||||
return method.equals(location.method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + line;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static final byte[] METHOD_UNKNOWN = "[unknown]".getBytes();
|
||||
|
||||
// Profile IDs
|
||||
public static final int PROFILE_SAMPLE_TYPE = 1;
|
||||
public static final int PROFILE_SAMPLE = 2;
|
||||
public static final int PROFILE_LOCATION = 4;
|
||||
public static final int PROFILE_FUNCTION = 5;
|
||||
public static final int PROFILE_STRING_TABLE = 6;
|
||||
public static final int PROFILE_TIME_NANOS = 9;
|
||||
public static final int PROFILE_DURATION_NANOS = 10;
|
||||
public static final int PROFILE_COMMENT = 13;
|
||||
public static final int PROFILE_DEFAULT_SAMPLE_TYPE = 14;
|
||||
|
||||
// ValueType IDs
|
||||
public static final int VALUETYPE_TYPE = 1;
|
||||
public static final int VALUETYPE_UNIT = 2;
|
||||
|
||||
// Sample IDs
|
||||
public static final int SAMPLE_LOCATION_ID = 1;
|
||||
public static final int SAMPLE_VALUE = 2;
|
||||
|
||||
// Location IDs
|
||||
public static final int LOCATION_ID = 1;
|
||||
public static final int LOCATION_LINE = 4;
|
||||
|
||||
// Line IDs
|
||||
public static final int LINE_FUNCTION_ID = 1;
|
||||
public static final int LINE_LINE = 2;
|
||||
|
||||
// Function IDs
|
||||
public static final int FUNCTION_ID = 1;
|
||||
public static final int FUNCTION_NAME = 2;
|
||||
|
||||
private final JfrReader reader;
|
||||
|
||||
public jfr2pprof(final JfrReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
// `Proto` instances are mutable, careful with reordering
|
||||
public void dump(final OutputStream out) throws Exception {
|
||||
// Mutable IDs, need to start at 1
|
||||
int functionId = 1;
|
||||
int locationId = 1;
|
||||
int stringId = 1;
|
||||
|
||||
// Used to de-dupe
|
||||
final Map<Method, Integer> functions = new HashMap<>();
|
||||
final Map<Location, Integer> locations = new HashMap<>();
|
||||
|
||||
final Proto profile = new Proto(200_000)
|
||||
.field(PROFILE_TIME_NANOS, reader.startNanos)
|
||||
.field(PROFILE_DURATION_NANOS, reader.durationNanos())
|
||||
.field(PROFILE_DEFAULT_SAMPLE_TYPE, 0L)
|
||||
.field(PROFILE_STRING_TABLE, "".getBytes(StandardCharsets.UTF_8)) // "" needs to be index 0
|
||||
.field(PROFILE_STRING_TABLE, "async-profiler".getBytes(StandardCharsets.UTF_8))
|
||||
.field(PROFILE_COMMENT, stringId++);
|
||||
|
||||
final Proto sampleType = new Proto(100);
|
||||
|
||||
profile.field(PROFILE_STRING_TABLE, "cpu".getBytes(StandardCharsets.UTF_8));
|
||||
sampleType.field(VALUETYPE_TYPE, stringId++);
|
||||
|
||||
profile.field(PROFILE_STRING_TABLE, "nanoseconds".getBytes(StandardCharsets.UTF_8));
|
||||
sampleType.field(VALUETYPE_UNIT, stringId++);
|
||||
|
||||
profile.field(PROFILE_SAMPLE_TYPE, sampleType);
|
||||
|
||||
final Dictionary<StackTrace> stackTraces = reader.stackTraces;
|
||||
long previousTime = reader.startTicks; // Mutate this to keep track of time deltas
|
||||
|
||||
// Iterate over samples
|
||||
for (ExecutionSample jfrSample; (jfrSample = reader.readEvent(ExecutionSample.class)) != null; ) {
|
||||
final StackTrace stackTrace = stackTraces.get(jfrSample.stackTraceId);
|
||||
final long[] methods = stackTrace.methods;
|
||||
final byte[] types = stackTrace.types;
|
||||
|
||||
final long nanosSinceLastSample = (jfrSample.time - previousTime) * 1_000_000_000 / reader.ticksPerSec;
|
||||
final Proto sample = new Proto(1_000).field(SAMPLE_VALUE, nanosSinceLastSample);
|
||||
|
||||
for (int current = 0; current < methods.length; current++) {
|
||||
final byte methodType = types[current];
|
||||
final long methodIdentifier = methods[current];
|
||||
final byte[] methodName = getMethodName(methodIdentifier, methodType);
|
||||
final Method method = new Method(methodName);
|
||||
final int line = stackTrace.locations[current] >>> 16;
|
||||
|
||||
final Integer methodId = functions.get(method);
|
||||
if (null == methodId) {
|
||||
final int funcId = functionId++;
|
||||
profile.field(PROFILE_STRING_TABLE, methodName);
|
||||
final Proto function = new Proto(16)
|
||||
.field(FUNCTION_ID, funcId)
|
||||
.field(FUNCTION_NAME, stringId++);
|
||||
|
||||
profile.field(PROFILE_FUNCTION, function);
|
||||
|
||||
functions.put(method, funcId);
|
||||
}
|
||||
final Location locKey = new Location(method, line);
|
||||
final Integer locaId = locations.get(locKey);
|
||||
if (null == locaId) {
|
||||
final int locId = locationId++;
|
||||
final Proto locLine = new Proto(16).field(LINE_FUNCTION_ID, functions.get(method));
|
||||
if (line > 0) {
|
||||
locLine.field(LINE_LINE, line);
|
||||
}
|
||||
|
||||
final Proto location = new Proto(16)
|
||||
.field(LOCATION_ID, locId)
|
||||
.field(LOCATION_LINE, locLine);
|
||||
|
||||
profile.field(PROFILE_LOCATION, location);
|
||||
|
||||
locations.put(locKey, locId);
|
||||
}
|
||||
|
||||
sample.field(SAMPLE_LOCATION_ID, locations.get(locKey));
|
||||
}
|
||||
|
||||
profile.field(PROFILE_SAMPLE, sample);
|
||||
|
||||
previousTime = jfrSample.time;
|
||||
}
|
||||
|
||||
out.write(profile.buffer(), 0, profile.size());
|
||||
}
|
||||
|
||||
private byte[] getMethodName(final long methodId, final byte methodType) {
|
||||
final MethodRef ref = reader.methods.get(methodId);
|
||||
if (null == ref) {
|
||||
return METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
final ClassRef classRef = reader.classes.get(ref.cls);
|
||||
final byte[] className = reader.symbols.get(classRef.name);
|
||||
final byte[] methodName = reader.symbols.get(ref.name);
|
||||
|
||||
if ((methodType >= FlameGraph.FRAME_NATIVE && methodType <= FlameGraph.FRAME_KERNEL) || className == null || className.length == 0) {
|
||||
// Native method
|
||||
return methodName;
|
||||
} else {
|
||||
// JVM method
|
||||
final byte[] fullName = new byte[className.length + 1 + methodName.length];
|
||||
System.arraycopy(className, 0, fullName, 0, className.length);
|
||||
fullName[className.length] = '.';
|
||||
System.arraycopy(methodName, 0, fullName, className.length + 1, methodName.length);
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Usage: java " + jfr2pprof.class.getName() + " input.jfr output.pprof");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File dst = new File(args[1]);
|
||||
if (dst.isDirectory()) {
|
||||
dst = new File(dst, new File(args[0]).getName().replace(".jfr", ".pprof"));
|
||||
}
|
||||
|
||||
try (final JfrReader jfr = new JfrReader(args[0]);
|
||||
final FileOutputStream out = new FileOutputStream(dst)) {
|
||||
new jfr2pprof(jfr).dump(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
@@ -31,22 +20,32 @@ public class Dictionary<T> {
|
||||
this.values = new Object[INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
keys = new long[INITIAL_CAPACITY];
|
||||
values = new Object[INITIAL_CAPACITY];
|
||||
size = 0;
|
||||
}
|
||||
|
||||
public void put(long key, T value) {
|
||||
if (key == 0) {
|
||||
throw new IllegalArgumentException("Zero key not allowed");
|
||||
}
|
||||
|
||||
if (++size * 2 > keys.length) {
|
||||
resize(keys.length * 2);
|
||||
}
|
||||
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(key) & mask;
|
||||
while (keys[i] != 0 && keys[i] != key) {
|
||||
while (keys[i] != 0) {
|
||||
if (keys[i] == key) {
|
||||
values[i] = value;
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
keys[i] = key;
|
||||
values[i] = value;
|
||||
|
||||
if (++size * 2 > keys.length) {
|
||||
resize(keys.length * 2);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -69,9 +68,8 @@ public class Dictionary<T> {
|
||||
}
|
||||
|
||||
public int preallocate(int count) {
|
||||
int newSize = size + count;
|
||||
if (newSize * 2 > keys.length) {
|
||||
resize(Integer.highestOneBit(newSize * 4 - 1));
|
||||
if (count * 2 > keys.length) {
|
||||
resize(Integer.highestOneBit(count * 4 - 1));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -98,6 +96,7 @@ public class Dictionary<T> {
|
||||
}
|
||||
|
||||
private static int hashCode(long key) {
|
||||
key *= 0xc6a4a7935bd1e995L;
|
||||
return (int) (key ^ (key >>> 32));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
@@ -22,11 +11,13 @@ import java.util.Map;
|
||||
|
||||
class JfrClass extends Element {
|
||||
final int id;
|
||||
final boolean simpleType;
|
||||
final String name;
|
||||
final List<JfrField> fields;
|
||||
|
||||
JfrClass(Map<String, String> attributes) {
|
||||
this.id = Integer.parseInt(attributes.get("id"));
|
||||
this.simpleType = "true".equals(attributes.get("simpleType"));
|
||||
this.name = attributes.get("name");
|
||||
this.fields = new ArrayList<>(2);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import one.jfr.event.*;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
@@ -33,17 +26,25 @@ import java.util.Map;
|
||||
* Parses JFR output produced by async-profiler.
|
||||
*/
|
||||
public class JfrReader implements Closeable {
|
||||
private static final int BUFFER_SIZE = 2 * 1024 * 1024;
|
||||
private static final int CHUNK_HEADER_SIZE = 68;
|
||||
private static final int CPOOL_OFFSET = 16;
|
||||
private static final int META_OFFSET = 24;
|
||||
private static final int CHUNK_SIGNATURE = 0x464c5200;
|
||||
|
||||
private static final byte STATE_NEW_CHUNK = 0;
|
||||
private static final byte STATE_READING = 1;
|
||||
private static final byte STATE_EOF = 2;
|
||||
private static final byte STATE_INCOMPLETE = 3;
|
||||
|
||||
private final FileChannel ch;
|
||||
private final ByteBuffer buf;
|
||||
private ByteBuffer buf;
|
||||
private long filePosition;
|
||||
private byte state;
|
||||
|
||||
public final long startNanos;
|
||||
public final long durationNanos;
|
||||
public final long startTicks;
|
||||
public final long ticksPerSec;
|
||||
public long startNanos = Long.MAX_VALUE;
|
||||
public long endNanos = Long.MIN_VALUE;
|
||||
public long startTicks = Long.MAX_VALUE;
|
||||
public long ticksPerSec;
|
||||
public boolean stopAtNewChunk;
|
||||
|
||||
public final Dictionary<JfrClass> types = new Dictionary<>();
|
||||
public final Map<String, JfrClass> typesByName = new HashMap<>();
|
||||
@@ -52,41 +53,235 @@ public class JfrReader implements Closeable {
|
||||
public final Dictionary<byte[]> symbols = new Dictionary<>();
|
||||
public final Dictionary<MethodRef> methods = new Dictionary<>();
|
||||
public final Dictionary<StackTrace> stackTraces = new Dictionary<>();
|
||||
public final Map<Integer, String> frameTypes = new HashMap<>();
|
||||
public final Map<Integer, String> threadStates = new HashMap<>();
|
||||
public final List<Sample> samples = new ArrayList<>();
|
||||
public final Map<String, String> settings = new HashMap<>();
|
||||
public final Map<String, Map<Integer, String>> enums = new HashMap<>();
|
||||
|
||||
private final Dictionary<Constructor<? extends Event>> customEvents = new Dictionary<>();
|
||||
|
||||
private int executionSample;
|
||||
private int nativeMethodSample;
|
||||
private int allocationInNewTLAB;
|
||||
private int allocationOutsideTLAB;
|
||||
private int allocationSample;
|
||||
private int liveObject;
|
||||
private int monitorEnter;
|
||||
private int threadPark;
|
||||
private int activeSetting;
|
||||
|
||||
public JfrReader(String fileName) throws IOException {
|
||||
this.ch = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
|
||||
this.buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
|
||||
this.buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
||||
|
||||
if (buf.getInt(0) != 0x464c5200) {
|
||||
throw new IOException("Not a valid JFR file");
|
||||
buf.flip();
|
||||
ensureBytes(CHUNK_HEADER_SIZE);
|
||||
if (!readChunk(0)) {
|
||||
throw new IOException("Incomplete JFR file");
|
||||
}
|
||||
}
|
||||
|
||||
int version = buf.getInt(4);
|
||||
if (version < 0x20000 || version > 0x2ffff) {
|
||||
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
|
||||
public JfrReader(ByteBuffer buf) throws IOException {
|
||||
this.ch = null;
|
||||
this.buf = buf;
|
||||
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
if (!readChunk(0)) {
|
||||
throw new IOException("Incomplete JFR file");
|
||||
}
|
||||
|
||||
this.startNanos = buf.getLong(32);
|
||||
this.durationNanos = buf.getLong(40);
|
||||
this.startTicks = buf.getLong(48);
|
||||
this.ticksPerSec = buf.getLong(56);
|
||||
|
||||
readMeta();
|
||||
readConstantPool();
|
||||
readEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ch.close();
|
||||
if (ch != null) {
|
||||
ch.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void readMeta() {
|
||||
buf.position(buf.getInt(META_OFFSET + 4));
|
||||
getVarint();
|
||||
public boolean eof() {
|
||||
return state >= STATE_EOF;
|
||||
}
|
||||
|
||||
public boolean incomplete() {
|
||||
return state == STATE_INCOMPLETE;
|
||||
}
|
||||
|
||||
public long durationNanos() {
|
||||
return endNanos - startNanos;
|
||||
}
|
||||
|
||||
public long nanosToTicks(long nanos) {
|
||||
return (long) ((nanos - startNanos) * (ticksPerSec / 1e9)) + startTicks;
|
||||
}
|
||||
|
||||
public <E extends Event> void registerEvent(String name, Class<E> eventClass) {
|
||||
JfrClass type = typesByName.get(name);
|
||||
if (type != null) {
|
||||
try {
|
||||
customEvents.put(type.id, eventClass.getConstructor(JfrReader.class));
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException("No suitable constructor found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Event> readAllEvents() throws IOException {
|
||||
return readAllEvents(null);
|
||||
}
|
||||
|
||||
public <E extends Event> List<E> readAllEvents(Class<E> cls) throws IOException {
|
||||
ArrayList<E> events = new ArrayList<>();
|
||||
for (E event; (event = readEvent(cls)) != null; ) {
|
||||
events.add(event);
|
||||
}
|
||||
Collections.sort(events);
|
||||
return events;
|
||||
}
|
||||
|
||||
public Event readEvent() throws IOException {
|
||||
return readEvent(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends Event> E readEvent(Class<E> cls) throws IOException {
|
||||
while (ensureBytes(CHUNK_HEADER_SIZE)) {
|
||||
int pos = buf.position();
|
||||
int size = getVarint();
|
||||
int type = getVarint();
|
||||
|
||||
if (type == 'L' && buf.getInt(pos) == CHUNK_SIGNATURE) {
|
||||
if (state != STATE_NEW_CHUNK && stopAtNewChunk) {
|
||||
buf.position(pos);
|
||||
state = STATE_NEW_CHUNK;
|
||||
} else if (readChunk(pos)) {
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type == executionSample || type == nativeMethodSample) {
|
||||
if (cls == null || cls == ExecutionSample.class) return (E) readExecutionSample();
|
||||
} else if (type == allocationInNewTLAB) {
|
||||
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(true);
|
||||
} else if (type == allocationOutsideTLAB || type == allocationSample) {
|
||||
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(false);
|
||||
} else if (type == liveObject) {
|
||||
if (cls == null || cls == LiveObject.class) return (E) readLiveObject();
|
||||
} else if (type == monitorEnter) {
|
||||
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(false);
|
||||
} else if (type == threadPark) {
|
||||
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(true);
|
||||
} else if (type == activeSetting) {
|
||||
readActiveSetting();
|
||||
} else {
|
||||
Constructor<? extends Event> customEvent = customEvents.get(type);
|
||||
if (customEvent != null && (cls == null || cls == customEvent.getDeclaringClass())) {
|
||||
try {
|
||||
return (E) customEvent.newInstance(this);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seek(filePosition + pos + size);
|
||||
}
|
||||
|
||||
state = STATE_EOF;
|
||||
return null;
|
||||
}
|
||||
|
||||
private ExecutionSample readExecutionSample() {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int threadState = getVarint();
|
||||
return new ExecutionSample(time, tid, stackTraceId, threadState);
|
||||
}
|
||||
|
||||
private AllocationSample readAllocationSample(boolean tlab) {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int classId = getVarint();
|
||||
long allocationSize = getVarlong();
|
||||
long tlabSize = tlab ? getVarlong() : 0;
|
||||
return new AllocationSample(time, tid, stackTraceId, classId, allocationSize, tlabSize);
|
||||
}
|
||||
|
||||
private LiveObject readLiveObject() {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int classId = getVarint();
|
||||
long allocationSize = getVarlong();
|
||||
long allocatimeTime = getVarlong();
|
||||
return new LiveObject(time, tid, stackTraceId, classId, allocationSize, allocatimeTime);
|
||||
}
|
||||
|
||||
private ContendedLock readContendedLock(boolean hasTimeout) {
|
||||
long time = getVarlong();
|
||||
long duration = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int classId = getVarint();
|
||||
if (hasTimeout) getVarlong();
|
||||
long until = getVarlong();
|
||||
long address = getVarlong();
|
||||
return new ContendedLock(time, tid, stackTraceId, duration, classId);
|
||||
}
|
||||
|
||||
private void readActiveSetting() {
|
||||
for (JfrField field : typesByName.get("jdk.ActiveSetting").fields) {
|
||||
getVarlong();
|
||||
if ("id".equals(field.name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String name = getString();
|
||||
String value = getString();
|
||||
settings.put(name, value);
|
||||
}
|
||||
|
||||
private boolean readChunk(int pos) throws IOException {
|
||||
if (pos + CHUNK_HEADER_SIZE > buf.limit() || buf.getInt(pos) != CHUNK_SIGNATURE) {
|
||||
throw new IOException("Not a valid JFR file");
|
||||
}
|
||||
|
||||
int version = buf.getInt(pos + 4);
|
||||
if (version < 0x20000 || version > 0x2ffff) {
|
||||
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
|
||||
}
|
||||
|
||||
long cpOffset = buf.getLong(pos + 16);
|
||||
long metaOffset = buf.getLong(pos + 24);
|
||||
if (cpOffset == 0 || metaOffset == 0) {
|
||||
state = STATE_INCOMPLETE;
|
||||
return false;
|
||||
}
|
||||
|
||||
startNanos = Math.min(startNanos, buf.getLong(pos + 32));
|
||||
endNanos = Math.max(endNanos, buf.getLong(pos + 32) + buf.getLong(pos + 40));
|
||||
startTicks = Math.min(startTicks, buf.getLong(pos + 48));
|
||||
ticksPerSec = buf.getLong(pos + 56);
|
||||
|
||||
types.clear();
|
||||
typesByName.clear();
|
||||
|
||||
long chunkStart = filePosition + pos;
|
||||
readMeta(chunkStart + metaOffset);
|
||||
readConstantPool(chunkStart + cpOffset);
|
||||
cacheEventTypes();
|
||||
|
||||
seek(chunkStart + CHUNK_HEADER_SIZE);
|
||||
state = STATE_READING;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readMeta(long metaOffset) throws IOException {
|
||||
seek(metaOffset);
|
||||
ensureBytes(5);
|
||||
|
||||
int posBeforeSize = buf.position();
|
||||
ensureBytes(getVarint() - (buf.position() - posBeforeSize));
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
@@ -133,15 +328,18 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void readConstantPool() {
|
||||
int offset = buf.getInt(CPOOL_OFFSET + 4);
|
||||
while (true) {
|
||||
buf.position(offset);
|
||||
getVarint();
|
||||
private void readConstantPool(long cpOffset) throws IOException {
|
||||
long delta;
|
||||
do {
|
||||
seek(cpOffset);
|
||||
ensureBytes(5);
|
||||
|
||||
int posBeforeSize = buf.position();
|
||||
ensureBytes(getVarint() - (buf.position() - posBeforeSize));
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
long delta = getVarlong();
|
||||
delta = getVarlong();
|
||||
getVarint();
|
||||
|
||||
int poolCount = getVarint();
|
||||
@@ -149,12 +347,7 @@ public class JfrReader implements Closeable {
|
||||
int type = getVarint();
|
||||
readConstants(types.get(type));
|
||||
}
|
||||
|
||||
if (delta == 0) {
|
||||
break;
|
||||
}
|
||||
offset += delta;
|
||||
}
|
||||
} while (delta != 0 && (cpOffset += delta) > 0);
|
||||
}
|
||||
|
||||
private void readConstants(JfrClass type) {
|
||||
@@ -163,10 +356,10 @@ public class JfrReader implements Closeable {
|
||||
buf.position(buf.position() + (CHUNK_HEADER_SIZE + 3));
|
||||
break;
|
||||
case "java.lang.Thread":
|
||||
readThreads(type.field("group") != null);
|
||||
readThreads(type.fields.size());
|
||||
break;
|
||||
case "java.lang.Class":
|
||||
readClasses(type.field("hidden") != null);
|
||||
readClasses(type.fields.size());
|
||||
break;
|
||||
case "jdk.types.Symbol":
|
||||
readSymbols();
|
||||
@@ -177,18 +370,16 @@ public class JfrReader implements Closeable {
|
||||
case "jdk.types.StackTrace":
|
||||
readStackTraces();
|
||||
break;
|
||||
case "jdk.types.FrameType":
|
||||
readMap(frameTypes);
|
||||
break;
|
||||
case "jdk.types.ThreadState":
|
||||
readMap(threadStates);
|
||||
break;
|
||||
default:
|
||||
readOtherConstants(type.fields);
|
||||
if (type.simpleType && type.fields.size() == 1) {
|
||||
readEnumValues(type.name);
|
||||
} else {
|
||||
readOtherConstants(type.fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreads(boolean hasGroup) {
|
||||
private void readThreads(int fieldCount) {
|
||||
int count = threads.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
@@ -196,12 +387,12 @@ public class JfrReader implements Closeable {
|
||||
int osThreadId = getVarint();
|
||||
String javaName = getString();
|
||||
long javaThreadId = getVarlong();
|
||||
if (hasGroup) getVarlong();
|
||||
readFields(fieldCount - 4);
|
||||
threads.put(id, javaName != null ? javaName : osName);
|
||||
}
|
||||
}
|
||||
|
||||
private void readClasses(boolean hasHidden) {
|
||||
private void readClasses(int fieldCount) {
|
||||
int count = classes.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
@@ -209,7 +400,7 @@ public class JfrReader implements Closeable {
|
||||
long name = getVarlong();
|
||||
long pkg = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
if (hasHidden) getVarint();
|
||||
readFields(fieldCount - 4);
|
||||
classes.put(id, new ClassRef(name));
|
||||
}
|
||||
}
|
||||
@@ -241,13 +432,15 @@ public class JfrReader implements Closeable {
|
||||
int depth = getVarint();
|
||||
long[] methods = new long[depth];
|
||||
byte[] types = new byte[depth];
|
||||
int[] locations = new int[depth];
|
||||
for (int i = 0; i < depth; i++) {
|
||||
methods[i] = getVarlong();
|
||||
int line = getVarint();
|
||||
int bci = getVarint();
|
||||
locations[i] = line << 16 | (bci & 0xffff);
|
||||
types[i] = buf.get();
|
||||
}
|
||||
return new StackTrace(methods, types);
|
||||
return new StackTrace(methods, types, locations);
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
@@ -261,11 +454,13 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void readMap(Map<Integer, String> map) {
|
||||
private void readEnumValues(String typeName) {
|
||||
HashMap<Integer, String> map = new HashMap<>();
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
map.put(getVarint(), getString());
|
||||
map.put((int) getVarlong(), getString());
|
||||
}
|
||||
enums.put(typeName, map);
|
||||
}
|
||||
|
||||
private void readOtherConstants(List<JfrField> fields) {
|
||||
@@ -294,36 +489,27 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void readEvents() {
|
||||
int executionSample = getTypeId("jdk.ExecutionSample");
|
||||
int nativeMethodSample = getTypeId("jdk.NativeMethodSample");
|
||||
|
||||
buf.position(CHUNK_HEADER_SIZE);
|
||||
while (buf.hasRemaining()) {
|
||||
int position = buf.position();
|
||||
int size = getVarint();
|
||||
int type = getVarint();
|
||||
if (type == executionSample || type == nativeMethodSample) {
|
||||
readExecutionSample();
|
||||
} else {
|
||||
buf.position(position + size);
|
||||
}
|
||||
private void readFields(int count) {
|
||||
while (count-- > 0) {
|
||||
getVarlong();
|
||||
}
|
||||
|
||||
Collections.sort(samples);
|
||||
}
|
||||
|
||||
private void readExecutionSample() {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int threadState = getVarint();
|
||||
samples.add(new Sample(time, tid, stackTraceId, threadState));
|
||||
private void cacheEventTypes() {
|
||||
executionSample = getTypeId("jdk.ExecutionSample");
|
||||
nativeMethodSample = getTypeId("jdk.NativeMethodSample");
|
||||
allocationInNewTLAB = getTypeId("jdk.ObjectAllocationInNewTLAB");
|
||||
allocationOutsideTLAB = getTypeId("jdk.ObjectAllocationOutsideTLAB");
|
||||
allocationSample = getTypeId("jdk.ObjectAllocationSample");
|
||||
liveObject = getTypeId("profiler.LiveObject");
|
||||
monitorEnter = getTypeId("jdk.JavaMonitorEnter");
|
||||
threadPark = getTypeId("jdk.ThreadPark");
|
||||
activeSetting = getTypeId("jdk.ActiveSetting");
|
||||
|
||||
StackTrace stackTrace = stackTraces.get(stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
stackTrace.samples++;
|
||||
}
|
||||
registerEvent("jdk.CPULoad", CPULoad.class);
|
||||
registerEvent("jdk.GCHeapSummary", GCHeapSummary.class);
|
||||
registerEvent("jdk.ObjectCount", ObjectCount.class);
|
||||
registerEvent("jdk.ObjectCountAfterGC", ObjectCount.class);
|
||||
}
|
||||
|
||||
private int getTypeId(String typeName) {
|
||||
@@ -331,7 +517,23 @@ public class JfrReader implements Closeable {
|
||||
return type != null ? type.id : -1;
|
||||
}
|
||||
|
||||
private int getVarint() {
|
||||
public int getEnumKey(String typeName, String value) {
|
||||
Map<Integer, String> enumValues = enums.get(typeName);
|
||||
if (enumValues != null) {
|
||||
for (Map.Entry<Integer, String> entry : enumValues.entrySet()) {
|
||||
if (value.equals(entry.getValue())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String getEnumValue(String typeName, int key) {
|
||||
return enums.get(typeName).get(key);
|
||||
}
|
||||
|
||||
public int getVarint() {
|
||||
int result = 0;
|
||||
for (int shift = 0; ; shift += 7) {
|
||||
byte b = buf.get();
|
||||
@@ -342,7 +544,7 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private long getVarlong() {
|
||||
public long getVarlong() {
|
||||
long result = 0;
|
||||
for (int shift = 0; shift < 56; shift += 7) {
|
||||
byte b = buf.get();
|
||||
@@ -354,7 +556,15 @@ public class JfrReader implements Closeable {
|
||||
return result | (buf.get() & 0xffL) << 56;
|
||||
}
|
||||
|
||||
private String getString() {
|
||||
public float getFloat() {
|
||||
return buf.getFloat();
|
||||
}
|
||||
|
||||
public double getDouble() {
|
||||
return buf.getDouble();
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
switch (buf.get()) {
|
||||
case 0:
|
||||
return null;
|
||||
@@ -376,9 +586,46 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getBytes() {
|
||||
public byte[] getBytes() {
|
||||
byte[] bytes = new byte[getVarint()];
|
||||
buf.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void seek(long pos) throws IOException {
|
||||
long bufPosition = pos - filePosition;
|
||||
if (bufPosition >= 0 && bufPosition <= buf.limit()) {
|
||||
buf.position((int) bufPosition);
|
||||
} else {
|
||||
filePosition = pos;
|
||||
ch.position(pos);
|
||||
buf.rewind().flip();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureBytes(int needed) throws IOException {
|
||||
if (buf.remaining() >= needed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ch == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
filePosition += buf.position();
|
||||
|
||||
if (buf.capacity() < needed) {
|
||||
ByteBuffer newBuf = ByteBuffer.allocateDirect(needed);
|
||||
newBuf.put(buf);
|
||||
buf = newBuf;
|
||||
} else {
|
||||
buf.compact();
|
||||
}
|
||||
|
||||
while (ch.read(buf) > 0 && buf.position() < needed) {
|
||||
// keep reading
|
||||
}
|
||||
buf.flip();
|
||||
return buf.limit() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class Sample implements Comparable<Sample> {
|
||||
public final long time;
|
||||
public final int tid;
|
||||
public final int stackTraceId;
|
||||
public final int threadState;
|
||||
|
||||
public Sample(long time, int tid, int stackTraceId, int threadState) {
|
||||
this.time = time;
|
||||
this.tid = tid;
|
||||
this.stackTraceId = stackTraceId;
|
||||
this.threadState = threadState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Sample o) {
|
||||
return Long.compare(time, o.time);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
@@ -19,10 +8,11 @@ package one.jfr;
|
||||
public class StackTrace {
|
||||
public final long[] methods;
|
||||
public final byte[] types;
|
||||
public long samples;
|
||||
public final int[] locations;
|
||||
|
||||
public StackTrace(long[] methods, byte[] types) {
|
||||
public StackTrace(long[] methods, byte[] types, int[] locations) {
|
||||
this.methods = methods;
|
||||
this.types = types;
|
||||
this.locations = locations;
|
||||
}
|
||||
}
|
||||
|
||||
38
src/converter/one/jfr/event/AllocationSample.java
Normal file
38
src/converter/one/jfr/event/AllocationSample.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class AllocationSample extends Event {
|
||||
public final int classId;
|
||||
public final long allocationSize;
|
||||
public final long tlabSize;
|
||||
|
||||
public AllocationSample(long time, int tid, int stackTraceId, int classId, long allocationSize, long tlabSize) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.classId = classId;
|
||||
this.allocationSize = allocationSize;
|
||||
this.tlabSize = tlabSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return classId * 127 + stackTraceId + (tlabSize == 0 ? 17 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameGroup(Event o) {
|
||||
if (o instanceof AllocationSample) {
|
||||
AllocationSample a = (AllocationSample) o;
|
||||
return classId == a.classId && (tlabSize == 0) == (a.tlabSize == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long value() {
|
||||
return tlabSize != 0 ? tlabSize : allocationSize;
|
||||
}
|
||||
}
|
||||
21
src/converter/one/jfr/event/CPULoad.java
Normal file
21
src/converter/one/jfr/event/CPULoad.java
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
import one.jfr.JfrReader;
|
||||
|
||||
public class CPULoad extends Event {
|
||||
public final float jvmUser;
|
||||
public final float jvmSystem;
|
||||
public final float machineTotal;
|
||||
|
||||
public CPULoad(JfrReader jfr) {
|
||||
super(jfr.getVarlong(), 0, 0);
|
||||
this.jvmUser = jfr.getFloat();
|
||||
this.jvmSystem = jfr.getFloat();
|
||||
this.machineTotal = jfr.getFloat();
|
||||
}
|
||||
}
|
||||
36
src/converter/one/jfr/event/ContendedLock.java
Normal file
36
src/converter/one/jfr/event/ContendedLock.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class ContendedLock extends Event {
|
||||
public final long duration;
|
||||
public final int classId;
|
||||
|
||||
public ContendedLock(long time, int tid, int stackTraceId, long duration, int classId) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.duration = duration;
|
||||
this.classId = classId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return classId * 127 + stackTraceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameGroup(Event o) {
|
||||
if (o instanceof ContendedLock) {
|
||||
ContendedLock c = (ContendedLock) o;
|
||||
return classId == c.classId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long value() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
54
src/converter/one/jfr/event/Event.java
Normal file
54
src/converter/one/jfr/event/Event.java
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public abstract class Event implements Comparable<Event> {
|
||||
public final long time;
|
||||
public final int tid;
|
||||
public final int stackTraceId;
|
||||
|
||||
protected Event(long time, int tid, int stackTraceId) {
|
||||
this.time = time;
|
||||
this.tid = tid;
|
||||
this.stackTraceId = stackTraceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Event o) {
|
||||
return Long.compare(time, o.time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return stackTraceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(getClass().getSimpleName())
|
||||
.append("{time=").append(time)
|
||||
.append(",tid=").append(tid)
|
||||
.append(",stackTraceId=").append(stackTraceId);
|
||||
for (Field f : getClass().getDeclaredFields()) {
|
||||
try {
|
||||
sb.append(',').append(f.getName()).append('=').append(f.get(this));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.append('}').toString();
|
||||
}
|
||||
|
||||
public boolean sameGroup(Event o) {
|
||||
return getClass() == o.getClass();
|
||||
}
|
||||
|
||||
public long value() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
92
src/converter/one/jfr/event/EventAggregator.java
Normal file
92
src/converter/one/jfr/event/EventAggregator.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class EventAggregator {
|
||||
private static final int INITIAL_CAPACITY = 1024;
|
||||
|
||||
private final boolean threads;
|
||||
private final boolean total;
|
||||
private Event[] keys;
|
||||
private long[] values;
|
||||
private int size;
|
||||
|
||||
public EventAggregator(boolean threads, boolean total) {
|
||||
this.threads = threads;
|
||||
this.total = total;
|
||||
this.keys = new Event[INITIAL_CAPACITY];
|
||||
this.values = new long[INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void collect(Event e) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(e) & mask;
|
||||
while (keys[i] != null) {
|
||||
if (sameGroup(keys[i], e)) {
|
||||
values[i] += total ? e.value() : 1;
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
keys[i] = e;
|
||||
values[i] = total ? e.value() : 1;
|
||||
|
||||
if (++size * 2 > keys.length) {
|
||||
resize(keys.length * 2);
|
||||
}
|
||||
}
|
||||
|
||||
public long getValue(Event e) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(e) & mask;
|
||||
while (keys[i] != null && !sameGroup(keys[i], e)) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
return values[i];
|
||||
}
|
||||
|
||||
public void forEach(Visitor visitor) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != null) {
|
||||
visitor.visit(keys[i], values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hashCode(Event e) {
|
||||
return e.hashCode() + (threads ? e.tid * 31 : 0);
|
||||
}
|
||||
|
||||
private boolean sameGroup(Event e1, Event e2) {
|
||||
return e1.stackTraceId == e2.stackTraceId && (!threads || e1.tid == e2.tid) && e1.sameGroup(e2);
|
||||
}
|
||||
|
||||
private void resize(int newCapacity) {
|
||||
Event[] newKeys = new Event[newCapacity];
|
||||
long[] newValues = new long[newCapacity];
|
||||
int mask = newKeys.length - 1;
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != null) {
|
||||
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
|
||||
if (newKeys[j] == null) {
|
||||
newKeys[j] = keys[i];
|
||||
newValues[j] = values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = newKeys;
|
||||
values = newValues;
|
||||
}
|
||||
|
||||
public interface Visitor {
|
||||
void visit(Event event, long value);
|
||||
}
|
||||
}
|
||||
15
src/converter/one/jfr/event/ExecutionSample.java
Normal file
15
src/converter/one/jfr/event/ExecutionSample.java
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class ExecutionSample extends Event {
|
||||
public final int threadState;
|
||||
|
||||
public ExecutionSample(long time, int tid, int stackTraceId, int threadState) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.threadState = threadState;
|
||||
}
|
||||
}
|
||||
28
src/converter/one/jfr/event/GCHeapSummary.java
Normal file
28
src/converter/one/jfr/event/GCHeapSummary.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
import one.jfr.JfrReader;
|
||||
|
||||
public class GCHeapSummary extends Event {
|
||||
public final int gcId;
|
||||
public final boolean afterGC;
|
||||
public final long committed;
|
||||
public final long reserved;
|
||||
public final long used;
|
||||
|
||||
public GCHeapSummary(JfrReader jfr) {
|
||||
super(jfr.getVarlong(), 0, 0);
|
||||
this.gcId = jfr.getVarint();
|
||||
this.afterGC = jfr.getVarint() > 0;
|
||||
long start = jfr.getVarlong();
|
||||
long committedEnd = jfr.getVarlong();
|
||||
this.committed = jfr.getVarlong();
|
||||
long reservedEnd = jfr.getVarlong();
|
||||
this.reserved = jfr.getVarlong();
|
||||
this.used = jfr.getVarlong();
|
||||
}
|
||||
}
|
||||
38
src/converter/one/jfr/event/LiveObject.java
Normal file
38
src/converter/one/jfr/event/LiveObject.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class LiveObject extends Event {
|
||||
public final int classId;
|
||||
public final long allocationSize;
|
||||
public final long allocationTime;
|
||||
|
||||
public LiveObject(long time, int tid, int stackTraceId, int classId, long allocationSize, long allocationTime) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.classId = classId;
|
||||
this.allocationSize = allocationSize;
|
||||
this.allocationTime = allocationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return classId * 127 + stackTraceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameGroup(Event o) {
|
||||
if (o instanceof LiveObject) {
|
||||
LiveObject a = (LiveObject) o;
|
||||
return classId == a.classId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long value() {
|
||||
return allocationSize;
|
||||
}
|
||||
}
|
||||
23
src/converter/one/jfr/event/ObjectCount.java
Normal file
23
src/converter/one/jfr/event/ObjectCount.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
import one.jfr.JfrReader;
|
||||
|
||||
public class ObjectCount extends Event {
|
||||
public final int gcId;
|
||||
public final int classId;
|
||||
public final long count;
|
||||
public final long totalSize;
|
||||
|
||||
public ObjectCount(JfrReader jfr) {
|
||||
super(jfr.getVarlong(), 0, 0);
|
||||
this.gcId = jfr.getVarint();
|
||||
this.classId = jfr.getVarint();
|
||||
this.count = jfr.getVarlong();
|
||||
this.totalSize = jfr.getVarlong();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.proto;
|
||||
@@ -48,6 +37,12 @@ public class Proto {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, long n) {
|
||||
tag(index, 0);
|
||||
writeLong(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, double d) {
|
||||
tag(index, 1);
|
||||
writeDouble(d);
|
||||
@@ -83,6 +78,17 @@ public class Proto {
|
||||
buf[pos++] = (byte) n;
|
||||
}
|
||||
|
||||
public void writeLong(long n) {
|
||||
int length = n == 0 ? 1 : (70 - Long.numberOfLeadingZeros(n)) / 7;
|
||||
ensureCapacity(length);
|
||||
|
||||
while (n > 0x7f) {
|
||||
buf[pos++] = (byte) (0x80 | (n & 0x7f));
|
||||
n >>>= 7;
|
||||
}
|
||||
buf[pos++] = (byte) n;
|
||||
}
|
||||
|
||||
public void writeDouble(double d) {
|
||||
ensureCapacity(8);
|
||||
long n = Double.doubleToRawLongBits(d);
|
||||
|
||||
128
src/cpuEngine.cpp
Normal file
128
src/cpuEngine.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include "cpuEngine.h"
|
||||
#include "j9StackTraces.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackWalker.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
void** CpuEngine::_pthread_entry = NULL;
|
||||
CpuEngine* CpuEngine::_current = NULL;
|
||||
|
||||
long CpuEngine::_interval;
|
||||
CStack CpuEngine::_cstack;
|
||||
int CpuEngine::_signal;
|
||||
|
||||
// Intercept thread creation/termination by patching libjvm's GOT entry for pthread_setspecific().
|
||||
// HotSpot puts VMThread into TLS on thread start, and resets on thread end.
|
||||
static int pthread_setspecific_hook(pthread_key_t key, const void* value) {
|
||||
if (key != VMThread::key()) {
|
||||
return pthread_setspecific(key, value);
|
||||
}
|
||||
if (pthread_getspecific(key) == value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value != NULL) {
|
||||
int result = pthread_setspecific(key, value);
|
||||
CpuEngine::onThreadStart();
|
||||
return result;
|
||||
} else {
|
||||
CpuEngine::onThreadEnd();
|
||||
return pthread_setspecific(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void CpuEngine::onThreadStart() {
|
||||
CpuEngine* current = __atomic_load_n(&_current, __ATOMIC_ACQUIRE);
|
||||
if (current != NULL) {
|
||||
current->createForThread(OS::threadId());
|
||||
}
|
||||
}
|
||||
|
||||
void CpuEngine::onThreadEnd() {
|
||||
CpuEngine* current = __atomic_load_n(&_current, __ATOMIC_ACQUIRE);
|
||||
if (current != NULL) {
|
||||
current->destroyForThread(OS::threadId());
|
||||
}
|
||||
}
|
||||
|
||||
bool CpuEngine::setupThreadHook() {
|
||||
if (_pthread_entry != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!VM::loaded()) {
|
||||
static void* dummy_pthread_entry;
|
||||
_pthread_entry = &dummy_pthread_entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Depending on Zing version, pthread_setspecific is called either from libazsys.so or from libjvm.so
|
||||
if (VM::isZing()) {
|
||||
CodeCache* libazsys = Profiler::instance()->findLibraryByName("libazsys");
|
||||
if (libazsys != NULL && (_pthread_entry = libazsys->findImport(im_pthread_setspecific)) != NULL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CodeCache* lib = Profiler::instance()->findJvmLibrary("libj9thr");
|
||||
return lib != NULL && (_pthread_entry = lib->findImport(im_pthread_setspecific)) != NULL;
|
||||
}
|
||||
|
||||
void CpuEngine::enableThreadHook() {
|
||||
*_pthread_entry = (void*)pthread_setspecific_hook;
|
||||
__atomic_store_n(&_current, this, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
void CpuEngine::disableThreadHook() {
|
||||
*_pthread_entry = (void*)pthread_setspecific;
|
||||
__atomic_store_n(&_current, NULL, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
bool CpuEngine::isResourceLimit(int err) {
|
||||
return err == EMFILE || err == ENOMEM;
|
||||
}
|
||||
|
||||
int CpuEngine::createForAllThreads() {
|
||||
int result = EPERM;
|
||||
|
||||
ThreadList* thread_list = OS::listThreads();
|
||||
for (int tid; (tid = thread_list->next()) != -1; ) {
|
||||
int err = createForThread(tid);
|
||||
if (isResourceLimit(err)) {
|
||||
result = err;
|
||||
break;
|
||||
} else if (result != 0) {
|
||||
result = err;
|
||||
}
|
||||
}
|
||||
delete thread_list;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void CpuEngine::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordSample(ucontext, _interval, EXECUTION_SAMPLE, &event);
|
||||
}
|
||||
|
||||
void CpuEngine::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
J9StackTraceNotification notif;
|
||||
StackContext java_ctx;
|
||||
notif.num_frames = _cstack == CSTACK_NO ? 0 : _cstack == CSTACK_DWARF
|
||||
? StackWalker::walkDwarf(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx)
|
||||
: StackWalker::walkFP(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx);
|
||||
J9StackTraces::checkpoint(_interval, ¬if);
|
||||
}
|
||||
51
src/cpuEngine.h
Normal file
51
src/cpuEngine.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _CPUENGINE_H
|
||||
#define _CPUENGINE_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
// Base class for CPU sampling engines: PerfEvents, CTimer, ITimer
|
||||
class CpuEngine : public Engine {
|
||||
protected:
|
||||
static void** _pthread_entry;
|
||||
static CpuEngine* _current;
|
||||
|
||||
static long _interval;
|
||||
static CStack _cstack;
|
||||
static int _signal;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
static bool setupThreadHook();
|
||||
|
||||
void enableThreadHook();
|
||||
void disableThreadHook();
|
||||
|
||||
bool isResourceLimit(int err);
|
||||
|
||||
int createForAllThreads();
|
||||
|
||||
virtual int createForThread(int tid) { return -1; }
|
||||
virtual void destroyForThread(int tid) {}
|
||||
|
||||
public:
|
||||
const char* title() {
|
||||
return "CPU profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
static void onThreadStart();
|
||||
static void onThreadEnd();
|
||||
};
|
||||
|
||||
#endif // _CPUENGINE_H
|
||||
50
src/ctimer.h
Normal file
50
src/ctimer.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _CTIMER_H
|
||||
#define _CTIMER_H
|
||||
|
||||
#include "cpuEngine.h"
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
class CTimer : public CpuEngine {
|
||||
private:
|
||||
static int _max_timers;
|
||||
static int* _timers;
|
||||
|
||||
int createForThread(int tid);
|
||||
void destroyForThread(int tid);
|
||||
|
||||
public:
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static bool supported() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class CTimer : public CpuEngine {
|
||||
public:
|
||||
Error check(Arguments& args) {
|
||||
return Error("CTimer is not supported on this platform");
|
||||
}
|
||||
|
||||
Error start(Arguments& args) {
|
||||
return Error("CTimer is not supported on this platform");
|
||||
}
|
||||
|
||||
static bool supported() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _CTIMER_H
|
||||
141
src/ctimer_linux.cpp
Normal file
141
src/ctimer_linux.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "ctimer.h"
|
||||
#include "j9StackTraces.h"
|
||||
#include "profiler.h"
|
||||
#include "stackWalker.h"
|
||||
|
||||
|
||||
#ifndef SIGEV_THREAD_ID
|
||||
#define SIGEV_THREAD_ID 4
|
||||
#endif
|
||||
|
||||
|
||||
static inline clockid_t thread_cpu_clock(unsigned int tid) {
|
||||
return ((~tid) << 3) | 6; // CPUCLOCK_SCHED | CPUCLOCK_PERTHREAD_MASK
|
||||
}
|
||||
|
||||
|
||||
int CTimer::_max_timers = 0;
|
||||
int* CTimer::_timers = NULL;
|
||||
|
||||
int CTimer::createForThread(int tid) {
|
||||
if (tid >= _max_timers) {
|
||||
Log::warn("tid[%d] > pid_max[%d]. Restart profiler after changing pid_max", tid, _max_timers);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sigevent sev;
|
||||
sev.sigev_value.sival_ptr = NULL;
|
||||
sev.sigev_signo = _signal;
|
||||
sev.sigev_notify = SIGEV_THREAD_ID;
|
||||
((int*)&sev.sigev_notify)[1] = tid;
|
||||
|
||||
// Use raw syscalls, since libc wrapper allows only predefined clocks
|
||||
clockid_t clock = thread_cpu_clock(tid);
|
||||
int timer;
|
||||
if (syscall(__NR_timer_create, clock, &sev, &timer) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Kernel timer ID may start with zero, but we use zero as an empty slot
|
||||
if (!__sync_bool_compare_and_swap(&_timers[tid], 0, timer + 1)) {
|
||||
// Lost race
|
||||
syscall(__NR_timer_delete, timer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct itimerspec ts;
|
||||
ts.it_interval.tv_sec = (time_t)(_interval / 1000000000);
|
||||
ts.it_interval.tv_nsec = _interval % 1000000000;
|
||||
ts.it_value = ts.it_interval;
|
||||
syscall(__NR_timer_settime, timer, 0, &ts, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CTimer::destroyForThread(int tid) {
|
||||
if (tid >= _max_timers) {
|
||||
return;
|
||||
}
|
||||
|
||||
int timer = _timers[tid];
|
||||
if (timer != 0 && __sync_bool_compare_and_swap(&_timers[tid], timer--, 0)) {
|
||||
syscall(__NR_timer_delete, timer);
|
||||
}
|
||||
}
|
||||
|
||||
Error CTimer::check(Arguments& args) {
|
||||
if (!setupThreadHook()) {
|
||||
return Error("Could not set pthread hook");
|
||||
}
|
||||
|
||||
timer_t timer;
|
||||
if (timer_create(CLOCK_THREAD_CPUTIME_ID, NULL, &timer) < 0) {
|
||||
return Error("Failed to create CPU timer");
|
||||
}
|
||||
timer_delete(timer);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error CTimer::start(Arguments& args) {
|
||||
if (!setupThreadHook()) {
|
||||
return Error("Could not set pthread hook");
|
||||
}
|
||||
|
||||
if (args._interval < 0) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
|
||||
_cstack = args._cstack;
|
||||
_signal = args._signal == 0 ? SIGPROF : args._signal & 0xff;
|
||||
|
||||
int max_timers = OS::getMaxThreadId();
|
||||
if (max_timers != _max_timers) {
|
||||
free(_timers);
|
||||
_timers = (int*)calloc(max_timers, sizeof(int));
|
||||
_max_timers = max_timers;
|
||||
}
|
||||
|
||||
if (VM::isOpenJ9()) {
|
||||
if (_cstack == CSTACK_DEFAULT) _cstack = CSTACK_DWARF;
|
||||
OS::installSignalHandler(_signal, signalHandlerJ9);
|
||||
Error error = J9StackTraces::start(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
} else {
|
||||
OS::installSignalHandler(_signal, signalHandler);
|
||||
}
|
||||
|
||||
// Enable pthread hook before traversing currently running threads
|
||||
enableThreadHook();
|
||||
|
||||
// Create timers for all existing threads
|
||||
int err = createForAllThreads();
|
||||
if (err) {
|
||||
disableThreadHook();
|
||||
J9StackTraces::stop();
|
||||
return Error("Failed to create CPU timer");
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void CTimer::stop() {
|
||||
disableThreadHook();
|
||||
for (int i = 0; i < _max_timers; i++) {
|
||||
destroyForThread(i);
|
||||
}
|
||||
J9StackTraces::stop();
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
135
src/demangle.cpp
Normal file
135
src/demangle.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "demangle.h"
|
||||
|
||||
|
||||
char* Demangle::demangleCpp(const char* s) {
|
||||
int status;
|
||||
char* result = abi::__cxa_demangle(s, NULL, NULL, &status);
|
||||
if (result == NULL && status == -2) {
|
||||
// Strip compiler-specific suffix (e.g. ".part.123") and retry demangling
|
||||
char buf[512];
|
||||
const char* p = strchr(s, '.');
|
||||
if (p != NULL && p - s < sizeof(buf)) {
|
||||
memcpy(buf, s, p - s);
|
||||
buf[p - s] = 0;
|
||||
result = abi::__cxa_demangle(buf, NULL, NULL, &status);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Demangle::demangleRust(const char* s, const char* e) {
|
||||
// Demangled symbol can be 1.5x longer than original, e.g. 1A1B1C -> A::B::C
|
||||
char* result = (char*)malloc((e - s) * 3 / 2 + 1);
|
||||
if (result == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* r = result;
|
||||
char* tmp;
|
||||
|
||||
while (s < e) {
|
||||
unsigned long len = strtoul(s, &tmp, 10);
|
||||
const char* next = tmp + len;
|
||||
if (len == 0 || next > e) {
|
||||
break;
|
||||
}
|
||||
|
||||
s = tmp;
|
||||
if (s[0] == '_' && s[1] == '$') s++;
|
||||
|
||||
if (r > result) {
|
||||
*r++ = ':';
|
||||
*r++ = ':';
|
||||
}
|
||||
|
||||
while (s < next) {
|
||||
if (s[0] == '$') {
|
||||
if (s[1] == 'L' && s[2] == 'T' && s[3] == '$') {
|
||||
*r++ = '<';
|
||||
s += 4;
|
||||
} else if (s[1] == 'G' && s[2] == 'T' && s[3] == '$') {
|
||||
*r++ = '>';
|
||||
s += 4;
|
||||
} else if (s[1] == 'L' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = '(';
|
||||
s += 4;
|
||||
} else if (s[1] == 'R' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = ')';
|
||||
s += 4;
|
||||
} else if (s[1] == 'S' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = '@';
|
||||
s += 4;
|
||||
} else if (s[1] == 'B' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = '*';
|
||||
s += 4;
|
||||
} else if (s[1] == 'R' && s[2] == 'F' && s[3] == '$') {
|
||||
*r++ = '&';
|
||||
s += 4;
|
||||
} else if (s[1] == 'C' && s[2] == '$') {
|
||||
*r++ = ',';
|
||||
s += 3;
|
||||
} else if (s[1] == 'u') {
|
||||
*r++ = (char)strtoul(s + 2, &tmp, 16);
|
||||
s = tmp + 1;
|
||||
} else {
|
||||
*r++ = '$';
|
||||
s++;
|
||||
}
|
||||
} else if (s[0] == '.' && s[1] == '.') {
|
||||
*r++ = ':';
|
||||
*r++ = ':';
|
||||
s += 2;
|
||||
} else {
|
||||
*r++ = *s++;
|
||||
}
|
||||
}
|
||||
|
||||
if (s > next) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*r = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Demangle::cutArguments(char* s) {
|
||||
char* p = strrchr(s, ')');
|
||||
if (p == NULL) return;
|
||||
|
||||
int balance = 1;
|
||||
while (--p > s) {
|
||||
if (*p == '(' && --balance == 0) {
|
||||
*p = 0;
|
||||
return;
|
||||
} else if (*p == ')') {
|
||||
balance++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char* Demangle::demangle(const char* s, bool full_signature) {
|
||||
// Check if the mangled symbol ends with a Rust hash "17h<hex>E"
|
||||
const char* e = strrchr(s, 'E');
|
||||
if (e != NULL && e - s > 22 && e[-19] == '1' && e[-18] == '7' && e[-17] == 'h') {
|
||||
const char* h = e - 16;
|
||||
while ((*h >= '0' && *h <= '9') || (*h >= 'a' && *h <= 'f')) h++;
|
||||
if (h == e) {
|
||||
return demangleRust(s + 3, e - 19);
|
||||
}
|
||||
}
|
||||
|
||||
char* result = demangleCpp(s);
|
||||
if (result != NULL && !full_signature) {
|
||||
cutArguments(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
20
src/demangle.h
Normal file
20
src/demangle.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _DEMANGLE_H
|
||||
#define _DEMANGLE_H
|
||||
|
||||
|
||||
class Demangle {
|
||||
private:
|
||||
static char* demangleCpp(const char* s);
|
||||
static char* demangleRust(const char* s, const char* e);
|
||||
static void cutArguments(char* s);
|
||||
|
||||
public:
|
||||
static char* demangle(const char* s, bool full_signature);
|
||||
};
|
||||
|
||||
#endif // _DEMANGLE_H
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -34,7 +23,7 @@ static inline bool keyEquals(const char* candidate, const char* key, size_t leng
|
||||
|
||||
Dictionary::Dictionary() {
|
||||
_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
_base_index = _table->base_index = 1;
|
||||
}
|
||||
|
||||
Dictionary::~Dictionary() {
|
||||
@@ -45,7 +34,7 @@ Dictionary::~Dictionary() {
|
||||
void Dictionary::clear() {
|
||||
clear(_table);
|
||||
memset(_table, 0, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
_base_index = _table->base_index = 1;
|
||||
}
|
||||
|
||||
void Dictionary::clear(DictTable* table) {
|
||||
@@ -61,6 +50,21 @@ void Dictionary::clear(DictTable* table) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t Dictionary::usedMemory() {
|
||||
return _table != NULL ? usedMemory(_table) : 0;
|
||||
}
|
||||
|
||||
size_t Dictionary::usedMemory(DictTable* table) {
|
||||
size_t bytes = sizeof(DictTable);
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
if (row->next != NULL) {
|
||||
bytes += usedMemory(row->next);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// Many popular symbols are quite short, e.g. "[B", "()V" etc.
|
||||
// FNV-1a is reasonably fast and sufficiently random.
|
||||
unsigned int Dictionary::hash(const char* key, size_t length) {
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _DICTIONARY_H
|
||||
@@ -50,6 +39,7 @@ class Dictionary {
|
||||
volatile unsigned int _base_index;
|
||||
|
||||
static void clear(DictTable* table);
|
||||
static size_t usedMemory(DictTable* table);
|
||||
|
||||
static unsigned int hash(const char* key, size_t length);
|
||||
|
||||
@@ -60,6 +50,7 @@ class Dictionary {
|
||||
~Dictionary();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
|
||||
unsigned int lookup(const char* key);
|
||||
unsigned int lookup(const char* key, size_t length);
|
||||
|
||||
355
src/dwarf.cpp
Normal file
355
src/dwarf.cpp
Normal file
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "dwarf.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
enum {
|
||||
DW_CFA_nop = 0x0,
|
||||
DW_CFA_set_loc = 0x1,
|
||||
DW_CFA_advance_loc1 = 0x2,
|
||||
DW_CFA_advance_loc2 = 0x3,
|
||||
DW_CFA_advance_loc4 = 0x4,
|
||||
DW_CFA_offset_extended = 0x5,
|
||||
DW_CFA_restore_extended = 0x6,
|
||||
DW_CFA_undefined = 0x7,
|
||||
DW_CFA_same_value = 0x8,
|
||||
DW_CFA_register = 0x9,
|
||||
DW_CFA_remember_state = 0xa,
|
||||
DW_CFA_restore_state = 0xb,
|
||||
DW_CFA_def_cfa = 0xc,
|
||||
DW_CFA_def_cfa_register = 0xd,
|
||||
DW_CFA_def_cfa_offset = 0xe,
|
||||
DW_CFA_def_cfa_expression = 0xf,
|
||||
DW_CFA_expression = 0x10,
|
||||
DW_CFA_offset_extended_sf = 0x11,
|
||||
DW_CFA_def_cfa_sf = 0x12,
|
||||
DW_CFA_def_cfa_offset_sf = 0x13,
|
||||
DW_CFA_val_offset = 0x14,
|
||||
DW_CFA_val_offset_sf = 0x15,
|
||||
DW_CFA_val_expression = 0x16,
|
||||
DW_CFA_AARCH64_negate_ra_state = 0x2d,
|
||||
DW_CFA_GNU_args_size = 0x2e,
|
||||
|
||||
DW_CFA_advance_loc = 0x1,
|
||||
DW_CFA_offset = 0x2,
|
||||
DW_CFA_restore = 0x3,
|
||||
};
|
||||
|
||||
enum {
|
||||
DW_OP_breg_pc = 0x70 + DW_REG_PC,
|
||||
DW_OP_const1u = 0x08,
|
||||
DW_OP_const1s = 0x09,
|
||||
DW_OP_const2u = 0x0a,
|
||||
DW_OP_const2s = 0x0b,
|
||||
DW_OP_const4u = 0x0c,
|
||||
DW_OP_const4s = 0x0d,
|
||||
DW_OP_constu = 0x10,
|
||||
DW_OP_consts = 0x11,
|
||||
DW_OP_minus = 0x1c,
|
||||
DW_OP_plus = 0x22,
|
||||
};
|
||||
|
||||
|
||||
FrameDesc FrameDesc::empty_frame = {0, DW_REG_SP | EMPTY_FRAME_SIZE << 8, DW_SAME_FP, -EMPTY_FRAME_SIZE};
|
||||
FrameDesc FrameDesc::default_frame = {0, DW_REG_FP | LINKED_FRAME_SIZE << 8, -LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE + DW_STACK_SLOT};
|
||||
|
||||
|
||||
DwarfParser::DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr) {
|
||||
_name = name;
|
||||
_image_base = image_base;
|
||||
|
||||
_capacity = 128;
|
||||
_count = 0;
|
||||
_table = (FrameDesc*)malloc(_capacity * sizeof(FrameDesc));
|
||||
_prev = NULL;
|
||||
|
||||
_code_align = sizeof(instruction_t);
|
||||
_data_align = -(int)sizeof(void*);
|
||||
|
||||
parse(eh_frame_hdr);
|
||||
}
|
||||
|
||||
void DwarfParser::parse(const char* eh_frame_hdr) {
|
||||
u8 version = eh_frame_hdr[0];
|
||||
u8 eh_frame_ptr_enc = eh_frame_hdr[1];
|
||||
u8 fde_count_enc = eh_frame_hdr[2];
|
||||
u8 table_enc = eh_frame_hdr[3];
|
||||
|
||||
if (version != 1 || (eh_frame_ptr_enc & 0x7) != 0x3 || (fde_count_enc & 0x7) != 0x3 || (table_enc & 0xf7) != 0x33) {
|
||||
Log::warn("Unsupported .eh_frame_hdr [%02x%02x%02x%02x] in %s",
|
||||
version, eh_frame_ptr_enc, fde_count_enc, table_enc, _name);
|
||||
return;
|
||||
}
|
||||
|
||||
int fde_count = *(int*)(eh_frame_hdr + 8);
|
||||
int* table = (int*)(eh_frame_hdr + 16);
|
||||
for (int i = 0; i < fde_count; i++) {
|
||||
_ptr = eh_frame_hdr + table[i * 2];
|
||||
parseFde();
|
||||
}
|
||||
}
|
||||
|
||||
void DwarfParser::parseCie() {
|
||||
u32 cie_len = get32();
|
||||
if (cie_len == 0 || cie_len == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* cie_start = _ptr;
|
||||
_ptr += 5;
|
||||
while (*_ptr++) {}
|
||||
_code_align = getLeb();
|
||||
_data_align = getSLeb();
|
||||
_ptr = cie_start + cie_len;
|
||||
}
|
||||
|
||||
void DwarfParser::parseFde() {
|
||||
u32 fde_len = get32();
|
||||
if (fde_len == 0 || fde_len == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* fde_start = _ptr;
|
||||
u32 cie_offset = get32();
|
||||
if (_count == 0) {
|
||||
_ptr = fde_start - cie_offset;
|
||||
parseCie();
|
||||
_ptr = fde_start + 4;
|
||||
}
|
||||
|
||||
u32 range_start = getPtr() - _image_base;
|
||||
u32 range_len = get32();
|
||||
_ptr += getLeb();
|
||||
parseInstructions(range_start, fde_start + fde_len);
|
||||
addRecord(range_start + range_len, DW_REG_FP, LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE + DW_STACK_SLOT);
|
||||
}
|
||||
|
||||
void DwarfParser::parseInstructions(u32 loc, const char* end) {
|
||||
const u32 code_align = _code_align;
|
||||
const int data_align = _data_align;
|
||||
|
||||
u32 cfa_reg = DW_REG_SP;
|
||||
int cfa_off = EMPTY_FRAME_SIZE;
|
||||
int fp_off = DW_SAME_FP;
|
||||
int pc_off = -EMPTY_FRAME_SIZE;
|
||||
|
||||
u32 rem_cfa_reg;
|
||||
int rem_cfa_off;
|
||||
int rem_fp_off;
|
||||
int rem_pc_off;
|
||||
|
||||
while (_ptr < end) {
|
||||
u8 op = get8();
|
||||
switch (op >> 6) {
|
||||
case 0:
|
||||
switch (op) {
|
||||
case DW_CFA_nop:
|
||||
case DW_CFA_set_loc:
|
||||
_ptr = end;
|
||||
break;
|
||||
case DW_CFA_advance_loc1:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
|
||||
loc += get8() * code_align;
|
||||
break;
|
||||
case DW_CFA_advance_loc2:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
|
||||
loc += get16() * code_align;
|
||||
break;
|
||||
case DW_CFA_advance_loc4:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
|
||||
loc += get32() * code_align;
|
||||
break;
|
||||
case DW_CFA_offset_extended:
|
||||
switch (getLeb()) {
|
||||
case DW_REG_FP: fp_off = getLeb() * data_align; break;
|
||||
case DW_REG_PC: pc_off = getLeb() * data_align; break;
|
||||
default: skipLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_restore_extended:
|
||||
case DW_CFA_undefined:
|
||||
case DW_CFA_same_value:
|
||||
if (getLeb() == DW_REG_FP) {
|
||||
fp_off = DW_SAME_FP;
|
||||
}
|
||||
break;
|
||||
case DW_CFA_register:
|
||||
skipLeb();
|
||||
skipLeb();
|
||||
break;
|
||||
case DW_CFA_remember_state:
|
||||
rem_cfa_reg = cfa_reg;
|
||||
rem_cfa_off = cfa_off;
|
||||
rem_fp_off = fp_off;
|
||||
rem_pc_off = pc_off;
|
||||
break;
|
||||
case DW_CFA_restore_state:
|
||||
cfa_reg = rem_cfa_reg;
|
||||
cfa_off = rem_cfa_off;
|
||||
fp_off = rem_fp_off;
|
||||
pc_off = rem_pc_off;
|
||||
break;
|
||||
case DW_CFA_def_cfa:
|
||||
cfa_reg = getLeb();
|
||||
cfa_off = getLeb();
|
||||
break;
|
||||
case DW_CFA_def_cfa_register:
|
||||
cfa_reg = getLeb();
|
||||
break;
|
||||
case DW_CFA_def_cfa_offset:
|
||||
cfa_off = getLeb();
|
||||
break;
|
||||
case DW_CFA_def_cfa_expression: {
|
||||
u32 len = getLeb();
|
||||
cfa_reg = len == 11 ? DW_REG_PLT : DW_REG_INVALID;
|
||||
cfa_off = DW_STACK_SLOT;
|
||||
_ptr += len;
|
||||
break;
|
||||
}
|
||||
case DW_CFA_expression:
|
||||
skipLeb();
|
||||
_ptr += getLeb();
|
||||
break;
|
||||
case DW_CFA_offset_extended_sf:
|
||||
switch (getLeb()) {
|
||||
case DW_REG_FP: fp_off = getSLeb() * data_align; break;
|
||||
case DW_REG_PC: pc_off = getSLeb() * data_align; break;
|
||||
default: skipLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_def_cfa_sf:
|
||||
cfa_reg = getLeb();
|
||||
cfa_off = getSLeb() * data_align;
|
||||
break;
|
||||
case DW_CFA_def_cfa_offset_sf:
|
||||
cfa_off = getSLeb() * data_align;
|
||||
break;
|
||||
case DW_CFA_val_offset:
|
||||
case DW_CFA_val_offset_sf:
|
||||
skipLeb();
|
||||
skipLeb();
|
||||
break;
|
||||
case DW_CFA_val_expression:
|
||||
if (getLeb() == DW_REG_PC) {
|
||||
int pc_off = parseExpression();
|
||||
if (pc_off != 0) {
|
||||
fp_off = DW_PC_OFFSET | (pc_off << 1);
|
||||
}
|
||||
} else {
|
||||
_ptr += getLeb();
|
||||
}
|
||||
break;
|
||||
#ifdef __aarch64__
|
||||
case DW_CFA_AARCH64_negate_ra_state:
|
||||
break;
|
||||
#endif
|
||||
case DW_CFA_GNU_args_size:
|
||||
skipLeb();
|
||||
break;
|
||||
default:
|
||||
Log::warn("Unknown DWARF instruction 0x%x in %s", op, _name);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case DW_CFA_advance_loc:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
|
||||
loc += (op & 0x3f) * code_align;
|
||||
break;
|
||||
case DW_CFA_offset:
|
||||
switch (op & 0x3f) {
|
||||
case DW_REG_FP: fp_off = getLeb() * data_align; break;
|
||||
case DW_REG_PC: pc_off = getLeb() * data_align; break;
|
||||
default: skipLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_restore:
|
||||
if ((op & 0x3f) == DW_REG_FP) {
|
||||
fp_off = DW_SAME_FP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
|
||||
}
|
||||
|
||||
// Parse a limited subset of DWARF expressions, which is used in DW_CFA_val_expression
|
||||
// to point to the previous PC relative to the current PC.
|
||||
// Returns the offset of the previous PC from the current PC.
|
||||
int DwarfParser::parseExpression() {
|
||||
int pc_off = 0;
|
||||
int tos = 0;
|
||||
|
||||
u32 len = getLeb();
|
||||
const char* end = _ptr + len;
|
||||
|
||||
while (_ptr < end) {
|
||||
u8 op = get8();
|
||||
switch (op) {
|
||||
case DW_OP_breg_pc:
|
||||
pc_off = getSLeb();
|
||||
break;
|
||||
case DW_OP_const1u:
|
||||
tos = get8();
|
||||
break;
|
||||
case DW_OP_const1s:
|
||||
tos = (signed char)get8();
|
||||
break;
|
||||
case DW_OP_const2u:
|
||||
tos = get16();
|
||||
break;
|
||||
case DW_OP_const2s:
|
||||
tos = (short)get16();
|
||||
break;
|
||||
case DW_OP_const4u:
|
||||
case DW_OP_const4s:
|
||||
tos = get32();
|
||||
break;
|
||||
case DW_OP_constu:
|
||||
tos = getLeb();
|
||||
break;
|
||||
case DW_OP_consts:
|
||||
tos = getSLeb();
|
||||
break;
|
||||
case DW_OP_minus:
|
||||
pc_off -= tos;
|
||||
break;
|
||||
case DW_OP_plus:
|
||||
pc_off += tos;
|
||||
break;
|
||||
default:
|
||||
Log::warn("Unknown DWARF opcode 0x%x in %s", op, _name);
|
||||
_ptr = end;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pc_off;
|
||||
}
|
||||
|
||||
void DwarfParser::addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off, int pc_off) {
|
||||
int cfa = cfa_reg | cfa_off << 8;
|
||||
if (_prev == NULL || (_prev->loc == loc && --_count >= 0) ||
|
||||
_prev->cfa != cfa || _prev->fp_off != fp_off || _prev->pc_off != pc_off) {
|
||||
_prev = addRecordRaw(loc, cfa, fp_off, pc_off);
|
||||
}
|
||||
}
|
||||
|
||||
FrameDesc* DwarfParser::addRecordRaw(u32 loc, int cfa, int fp_off, int pc_off) {
|
||||
if (_count >= _capacity) {
|
||||
_capacity *= 2;
|
||||
_table = (FrameDesc*)realloc(_table, _capacity * sizeof(FrameDesc));
|
||||
}
|
||||
|
||||
FrameDesc* f = &_table[_count++];
|
||||
f->loc = loc;
|
||||
f->cfa = cfa;
|
||||
f->fp_off = fp_off;
|
||||
f->pc_off = pc_off;
|
||||
return f;
|
||||
}
|
||||
168
src/dwarf.h
Normal file
168
src/dwarf.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _DWARF_H
|
||||
#define _DWARF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
const int DW_REG_PLT = 128; // denotes special rule for PLT entries
|
||||
const int DW_REG_INVALID = 255; // denotes unsupported configuration
|
||||
|
||||
const int DW_PC_OFFSET = 1;
|
||||
const int DW_SAME_FP = 0x80000000;
|
||||
const int DW_STACK_SLOT = sizeof(void*);
|
||||
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
#define DWARF_SUPPORTED true
|
||||
|
||||
const int DW_REG_FP = 6;
|
||||
const int DW_REG_SP = 7;
|
||||
const int DW_REG_PC = 16;
|
||||
const int EMPTY_FRAME_SIZE = DW_STACK_SLOT;
|
||||
const int LINKED_FRAME_SIZE = 2 * DW_STACK_SLOT;
|
||||
|
||||
#elif defined(__i386__)
|
||||
|
||||
#define DWARF_SUPPORTED true
|
||||
|
||||
const int DW_REG_FP = 5;
|
||||
const int DW_REG_SP = 4;
|
||||
const int DW_REG_PC = 8;
|
||||
const int EMPTY_FRAME_SIZE = DW_STACK_SLOT;
|
||||
const int LINKED_FRAME_SIZE = 2 * DW_STACK_SLOT;
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
|
||||
#define DWARF_SUPPORTED true
|
||||
|
||||
const int DW_REG_FP = 29;
|
||||
const int DW_REG_SP = 31;
|
||||
const int DW_REG_PC = 30;
|
||||
const int EMPTY_FRAME_SIZE = 0;
|
||||
const int LINKED_FRAME_SIZE = 0;
|
||||
|
||||
#else
|
||||
|
||||
#define DWARF_SUPPORTED false
|
||||
|
||||
const int DW_REG_FP = 0;
|
||||
const int DW_REG_SP = 1;
|
||||
const int DW_REG_PC = 2;
|
||||
const int EMPTY_FRAME_SIZE = 0;
|
||||
const int LINKED_FRAME_SIZE = 0;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
struct FrameDesc {
|
||||
u32 loc;
|
||||
int cfa;
|
||||
int fp_off;
|
||||
int pc_off;
|
||||
|
||||
static FrameDesc empty_frame;
|
||||
static FrameDesc default_frame;
|
||||
|
||||
static int comparator(const void* p1, const void* p2) {
|
||||
FrameDesc* fd1 = (FrameDesc*)p1;
|
||||
FrameDesc* fd2 = (FrameDesc*)p2;
|
||||
return (int)(fd1->loc - fd2->loc);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class DwarfParser {
|
||||
private:
|
||||
const char* _name;
|
||||
const char* _image_base;
|
||||
const char* _ptr;
|
||||
|
||||
int _capacity;
|
||||
int _count;
|
||||
FrameDesc* _table;
|
||||
FrameDesc* _prev;
|
||||
|
||||
u32 _code_align;
|
||||
int _data_align;
|
||||
|
||||
const char* add(size_t size) {
|
||||
const char* ptr = _ptr;
|
||||
_ptr = ptr + size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
u8 get8() {
|
||||
return *_ptr++;
|
||||
}
|
||||
|
||||
u16 get16() {
|
||||
return *(u16*)add(2);
|
||||
}
|
||||
|
||||
u32 get32() {
|
||||
return *(u32*)add(4);
|
||||
}
|
||||
|
||||
u32 getLeb() {
|
||||
u32 result = 0;
|
||||
for (u32 shift = 0; ; shift += 7) {
|
||||
u8 b = *_ptr++;
|
||||
result |= (b & 0x7f) << shift;
|
||||
if ((b & 0x80) == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getSLeb() {
|
||||
int result = 0;
|
||||
for (u32 shift = 0; ; shift += 7) {
|
||||
u8 b = *_ptr++;
|
||||
result |= (b & 0x7f) << shift;
|
||||
if ((b & 0x80) == 0) {
|
||||
if ((b & 0x40) != 0 && (shift += 7) < 32) {
|
||||
result |= -1 << shift;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void skipLeb() {
|
||||
while (*_ptr++ & 0x80) {}
|
||||
}
|
||||
|
||||
const char* getPtr() {
|
||||
const char* ptr = _ptr;
|
||||
return ptr + *(int*)add(4);
|
||||
}
|
||||
|
||||
void parse(const char* eh_frame_hdr);
|
||||
void parseCie();
|
||||
void parseFde();
|
||||
void parseInstructions(u32 loc, const char* end);
|
||||
int parseExpression();
|
||||
|
||||
void addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off, int pc_off);
|
||||
FrameDesc* addRecordRaw(u32 loc, int cfa, int fp_off, int pc_off);
|
||||
|
||||
public:
|
||||
DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr);
|
||||
|
||||
FrameDesc* table() const {
|
||||
return _table;
|
||||
}
|
||||
|
||||
int count() const {
|
||||
return _count;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _DWARF_H
|
||||
@@ -1,74 +1,20 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "engine.h"
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
volatile bool Engine::_enabled;
|
||||
volatile bool Engine::_enabled = false;
|
||||
|
||||
Error Engine::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
CStack Engine::cstack() {
|
||||
return CSTACK_FP;
|
||||
Error Engine::start(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs) {
|
||||
const void* pc;
|
||||
uintptr_t fp;
|
||||
uintptr_t prev_fp = (uintptr_t)&fp;
|
||||
uintptr_t bottom = prev_fp + 0x100000;
|
||||
|
||||
if (ucontext == NULL) {
|
||||
pc = __builtin_return_address(0);
|
||||
fp = (uintptr_t)__builtin_frame_address(1);
|
||||
} else {
|
||||
StackFrame frame(ucontext);
|
||||
pc = (const void*)frame.pc();
|
||||
fp = frame.fp();
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
const void* const valid_pc = (const void* const)0x1000;
|
||||
|
||||
// Walk until the bottom of the stack or until the first Java frame
|
||||
while (depth < max_depth && pc >= valid_pc) {
|
||||
if (java_methods->contains(pc) || runtime_stubs->contains(pc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
callchain[depth++] = pc;
|
||||
|
||||
// Check if the next frame is below on the current stack
|
||||
if (fp <= prev_fp || fp >= prev_fp + 0x40000 || fp >= bottom) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Frame pointer must be word aligned
|
||||
if ((fp & (sizeof(uintptr_t) - 1)) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
prev_fp = fp;
|
||||
pc = ((const void**)fp)[1];
|
||||
fp = ((uintptr_t*)fp)[0];
|
||||
}
|
||||
|
||||
return depth;
|
||||
void Engine::stop() {
|
||||
}
|
||||
|
||||
75
src/engine.h
75
src/engine.h
@@ -1,67 +1,54 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ENGINE_H
|
||||
#define _ENGINE_H
|
||||
|
||||
#include "arguments.h"
|
||||
#include "codeCache.h"
|
||||
|
||||
|
||||
class Engine {
|
||||
protected:
|
||||
static volatile bool _enabled;
|
||||
|
||||
static bool updateCounter(volatile unsigned long long& counter, unsigned long long value, unsigned long long interval) {
|
||||
if (interval <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
unsigned long long prev = counter;
|
||||
unsigned long long next = prev + value;
|
||||
if (next < interval) {
|
||||
if (__sync_bool_compare_and_swap(&counter, prev, next)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (__sync_bool_compare_and_swap(&counter, prev, next % interval)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
virtual const char* name() = 0;
|
||||
virtual const char* units() = 0;
|
||||
virtual const char* title() {
|
||||
return "Flame Graph";
|
||||
}
|
||||
|
||||
virtual const char* units() {
|
||||
return "total";
|
||||
}
|
||||
|
||||
virtual Error check(Arguments& args);
|
||||
virtual Error start(Arguments& args) = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual CStack cstack();
|
||||
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs);
|
||||
virtual Error start(Arguments& args);
|
||||
virtual void stop();
|
||||
|
||||
void enableEvents(bool enabled) {
|
||||
_enabled = enabled;
|
||||
}
|
||||
};
|
||||
|
||||
class NoopEngine : public Engine {
|
||||
public:
|
||||
const char* name() {
|
||||
return "noop";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error start(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _ENGINE_H
|
||||
|
||||
43
src/event.h
43
src/event.h
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _EVENT_H
|
||||
@@ -21,6 +10,19 @@
|
||||
#include "os.h"
|
||||
|
||||
|
||||
// The order is important: look for event_type comparison
|
||||
enum EventType {
|
||||
PERF_SAMPLE,
|
||||
EXECUTION_SAMPLE,
|
||||
INSTRUMENTED_METHOD,
|
||||
ALLOC_SAMPLE,
|
||||
ALLOC_OUTSIDE_TLAB,
|
||||
LIVE_OBJECT,
|
||||
LOCK_SAMPLE,
|
||||
PARK_SAMPLE,
|
||||
PROFILING_WINDOW,
|
||||
};
|
||||
|
||||
class Event {
|
||||
public:
|
||||
u32 id() {
|
||||
@@ -32,7 +34,7 @@ class ExecutionEvent : public Event {
|
||||
public:
|
||||
ThreadState _thread_state;
|
||||
|
||||
ExecutionEvent() : _thread_state(THREAD_RUNNING) {
|
||||
ExecutionEvent() : _thread_state(THREAD_UNKNOWN) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,4 +54,17 @@ class LockEvent : public Event {
|
||||
long long _timeout;
|
||||
};
|
||||
|
||||
class LiveObject : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _alloc_size;
|
||||
u64 _alloc_time;
|
||||
};
|
||||
|
||||
class ProfilingWindow : public Event {
|
||||
public:
|
||||
u64 _start_time;
|
||||
u64 _end_time;
|
||||
};
|
||||
|
||||
#endif // _EVENT_H
|
||||
|
||||
68
src/fdtransfer.h
Normal file
68
src/fdtransfer.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _FDTRANSFER_H
|
||||
#define _FDTRANSFER_H
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <linux/perf_event.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
|
||||
#define RESTARTABLE(call) ({ ssize_t ret; while ((ret = call) < 0 && errno == EINTR); ret; })
|
||||
|
||||
|
||||
// base header for all requests
|
||||
enum request_type {
|
||||
PERF_FD,
|
||||
KALLSYMS_FD,
|
||||
};
|
||||
|
||||
struct fd_request {
|
||||
// of type "enum request_type"
|
||||
unsigned int type;
|
||||
};
|
||||
|
||||
struct perf_fd_request {
|
||||
struct fd_request header;
|
||||
int tid;
|
||||
struct perf_event_attr attr;
|
||||
};
|
||||
|
||||
struct fd_response {
|
||||
// of type "enum request_type"
|
||||
unsigned int type;
|
||||
// 0 on success, otherwise errno
|
||||
int error;
|
||||
};
|
||||
|
||||
struct perf_fd_response {
|
||||
struct fd_response header;
|
||||
int tid;
|
||||
};
|
||||
|
||||
static inline bool socketPath(const char *path, struct sockaddr_un *sun, socklen_t *addrlen) {
|
||||
const int path_len = strlen(path);
|
||||
if (path_len > sizeof(sun->sun_path)) {
|
||||
return false;
|
||||
}
|
||||
memcpy(sun->sun_path, path, path_len);
|
||||
if (sun->sun_path[0] == '@') {
|
||||
sun->sun_path[0] = '\0';
|
||||
}
|
||||
|
||||
sun->sun_family = AF_UNIX;
|
||||
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - path_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _FDTRANSFER_H
|
||||
44
src/fdtransferClient.h
Normal file
44
src/fdtransferClient.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _FDTRANSFER_CLIENT_H
|
||||
#define _FDTRANSFER_CLIENT_H
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include "fdtransfer.h"
|
||||
|
||||
class FdTransferClient {
|
||||
private:
|
||||
static int _peer;
|
||||
|
||||
static int recvFd(unsigned int request_id, struct fd_response *resp, size_t resp_size);
|
||||
|
||||
public:
|
||||
static bool connectToServer(const char *path);
|
||||
static bool hasPeer() { return _peer != -1; }
|
||||
static void closePeer() {
|
||||
if (_peer != -1) {
|
||||
close(_peer);
|
||||
_peer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int requestPerfFd(int *tid, struct perf_event_attr *attr);
|
||||
static int requestKallsymsFd();
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class FdTransferClient {
|
||||
public:
|
||||
static bool connectToServer(const char *path) { return false; }
|
||||
static bool hasPeer() { return false; }
|
||||
static void closePeer() { }
|
||||
};
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _FDTRANSFER_CLIENT_H
|
||||
138
src/fdtransferClient_linux.cpp
Normal file
138
src/fdtransferClient_linux.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fdtransferClient.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
int FdTransferClient::_peer = -1;
|
||||
|
||||
bool FdTransferClient::connectToServer(const char *path) {
|
||||
closePeer();
|
||||
|
||||
_peer = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (_peer == -1) {
|
||||
Log::warn("FdTransferClient socket(): %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_un sun;
|
||||
socklen_t addrlen;
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not block for more than 10 seconds when waiting for a response
|
||||
struct timeval tv = {10, 0};
|
||||
setsockopt(_peer, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
if (connect(_peer, (const struct sockaddr *)&sun, addrlen) == -1) {
|
||||
Log::warn("FdTransferClient connect(): %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int FdTransferClient::requestPerfFd(int *tid, struct perf_event_attr *attr) {
|
||||
struct perf_fd_request request;
|
||||
request.header.type = PERF_FD;
|
||||
request.tid = *tid;
|
||||
memcpy(&request.attr, attr, sizeof(request.attr));
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct perf_fd_response resp;
|
||||
int fd = recvFd(request.header.type, &resp.header, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
// Update errno for our caller.
|
||||
errno = resp.header.error;
|
||||
} else {
|
||||
// Update the TID of createForThread, in case the multiple threads' requests got mixed up and we're
|
||||
// now handling the response destined to another. It's alright - the other thread(s) will finish the
|
||||
// handling of our TID perf fd.
|
||||
*tid = resp.tid;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int FdTransferClient::requestKallsymsFd() {
|
||||
struct fd_request request;
|
||||
request.type = KALLSYMS_FD;
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct fd_response resp;
|
||||
int fd = recvFd(request.type, &resp, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
errno = resp.error;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int FdTransferClient::recvFd(unsigned int type, struct fd_response *resp, size_t resp_size) {
|
||||
struct msghdr msg = {0};
|
||||
|
||||
struct iovec iov[1];
|
||||
iov[0].iov_base = resp;
|
||||
iov[0].iov_len = resp_size;
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = ARRAY_SIZE(iov);
|
||||
|
||||
int newfd;
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(newfd))];
|
||||
struct cmsghdr align;
|
||||
} u;
|
||||
msg.msg_control = u.buf;
|
||||
msg.msg_controllen = sizeof(u.buf);
|
||||
|
||||
ssize_t ret = RESTARTABLE(recvmsg(_peer, &msg, 0));
|
||||
if (ret < 0) {
|
||||
Log::warn("FdTransferClient recvmsg(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (resp->type != type) {
|
||||
Log::warn("FdTransferClient recvmsg(): bad response type");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (resp->error == 0) {
|
||||
struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg);
|
||||
if (cmptr != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))
|
||||
&& cmptr->cmsg_level == SOL_SOCKET && cmptr->cmsg_type == SCM_RIGHTS) {
|
||||
|
||||
newfd = *((int*)CMSG_DATA(cmptr));
|
||||
} else {
|
||||
Log::warn("FdTransferClient recvmsg(): unexpected response with no SCM_RIGHTS: %s", strerror(errno));
|
||||
newfd = -1;
|
||||
}
|
||||
} else {
|
||||
newfd = -1;
|
||||
}
|
||||
|
||||
return newfd;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
@@ -1,422 +1,39 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "flameGraph.h"
|
||||
#include "incbin.h"
|
||||
|
||||
|
||||
static const char FLAMEGRAPH_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang='en'>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<style>\n"
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n"
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n"
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n"
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n"
|
||||
"\tp {margin: 5px 0 5px 0}\n"
|
||||
"\ta {color: #0366d6}\n"
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n"
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n"
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n"
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n"
|
||||
"\t#reset {cursor: pointer}\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n"
|
||||
"<h1>%s</h1>\n"
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n"
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n"
|
||||
"<canvas id='canvas' style='width: 100%%; height: %dpx'></canvas>\n"
|
||||
"<div id='hl'><span></span></div>\n"
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n"
|
||||
"<p id='status'> </p>\n"
|
||||
"<script>\n"
|
||||
"\t// Copyright 2020 Andrei Pangin\n"
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n"
|
||||
"\t'use strict';\n"
|
||||
"\tvar root, rootLevel, px, pattern;\n"
|
||||
"\tvar reverse = %s;\n"
|
||||
"\tconst levels = Array(%d);\n"
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\tlevels[h] = [];\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tconst canvas = document.getElementById('canvas');\n"
|
||||
"\tconst c = canvas.getContext('2d');\n"
|
||||
"\tconst hl = document.getElementById('hl');\n"
|
||||
"\tconst status = document.getElementById('status');\n"
|
||||
"\n"
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n"
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n"
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n"
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n"
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n"
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n"
|
||||
"\tc.font = document.body.style.font;\n"
|
||||
"\n"
|
||||
"\tconst palette = [\n"
|
||||
"\t\t[0x50e150, 30, 30, 30],\n"
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n"
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n"
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n"
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n"
|
||||
"\t];\n"
|
||||
"\n"
|
||||
"\tfunction getColor(p) {\n"
|
||||
"\t\tconst v = Math.random();\n"
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction f(level, left, width, type, title) {\n"
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction samples(n) {\n"
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction pct(a, b) {\n"
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction findFrame(frames, x) {\n"
|
||||
"\t\tlet left = 0;\n"
|
||||
"\t\tlet right = frames.length - 1;\n"
|
||||
"\n"
|
||||
"\t\twhile (left <= right) {\n"
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n"
|
||||
"\t\t\tconst f = frames[mid];\n"
|
||||
"\n"
|
||||
"\t\t\tif (f.left > x) {\n"
|
||||
"\t\t\t\tright = mid - 1;\n"
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n"
|
||||
"\t\t\t\tleft = mid + 1;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\treturn f;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n"
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n"
|
||||
"\n"
|
||||
"\t\treturn null;\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction search(r) {\n"
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n"
|
||||
"\t\tconst matched = render(root, rootLevel);\n"
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%%';\n"
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction render(newRoot, newLevel) {\n"
|
||||
"\t\tif (root) {\n"
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n"
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\troot = newRoot || levels[0][0];\n"
|
||||
"\t\trootLevel = newLevel || 0;\n"
|
||||
"\t\tpx = canvasWidth / root.width;\n"
|
||||
"\n"
|
||||
"\t\tconst x0 = root.left;\n"
|
||||
"\t\tconst x1 = x0 + root.width;\n"
|
||||
"\t\tconst marked = [];\n"
|
||||
"\n"
|
||||
"\t\tfunction mark(f) {\n"
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction totalMarked() {\n"
|
||||
"\t\t\tlet total = 0;\n"
|
||||
"\t\t\tlet left = 0;\n"
|
||||
"\t\t\tfor (let x in marked) {\n"
|
||||
"\t\t\t\tif (+x >= left) {\n"
|
||||
"\t\t\t\t\ttotal += marked[x];\n"
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\treturn total;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n"
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n"
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n"
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n"
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n"
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n"
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n"
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (alpha) {\n"
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n"
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n"
|
||||
"\t\t\tconst frames = levels[h];\n"
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n"
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\treturn totalMarked();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tcanvas.onmousemove = function() {\n"
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n"
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n"
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n"
|
||||
"\t\t\tif (f) {\n"
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n"
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n"
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n"
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n"
|
||||
"\t\t\t\thl.style.display = 'block';\n"
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%%)';\n"
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n"
|
||||
"\t\t\t\tcanvas.onclick = function() {\n"
|
||||
"\t\t\t\t\tif (f != root) {\n"
|
||||
"\t\t\t\t\t\trender(f, h);\n"
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t};\n"
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n"
|
||||
"\t\t\t\treturn;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tcanvas.onmouseout();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tcanvas.onmouseout = function() {\n"
|
||||
"\t\thl.style.display = 'none';\n"
|
||||
"\t\tstatus.textContent = '\\xa0';\n"
|
||||
"\t\tcanvas.title = '';\n"
|
||||
"\t\tcanvas.style.cursor = '';\n"
|
||||
"\t\tcanvas.onclick = '';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n"
|
||||
"\t\treverse = !reverse;\n"
|
||||
"\t\trender();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('search').onclick = function() {\n"
|
||||
"\t\tsearch(true);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n"
|
||||
"\t\tsearch(false);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\twindow.onkeydown = function() {\n"
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n"
|
||||
"\t\t\tevent.preventDefault();\n"
|
||||
"\t\t\tsearch(true);\n"
|
||||
"\t\t} else if (event.keyCode === 27) {\n"
|
||||
"\t\t\tsearch(false);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n";
|
||||
// Browsers refuse to draw on canvas larger than 32767 px
|
||||
const int MAX_CANVAS_HEIGHT = 32767;
|
||||
|
||||
static const char FLAMEGRAPH_FOOTER[] =
|
||||
"render();\n"
|
||||
"</script></body></html>\n";
|
||||
|
||||
|
||||
static const char TREE_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"en\">\n"
|
||||
"<head>\n"
|
||||
"<title>Tree view</title>\n"
|
||||
"<meta charset=\"utf-8\"/>\n"
|
||||
"<style>\n"
|
||||
"body {\n"
|
||||
" font-family: Arial;\n"
|
||||
"}\n"
|
||||
"ul.tree li {\n"
|
||||
" list-style-type: none;\n"
|
||||
" position: relative;\n"
|
||||
"}\n"
|
||||
"ul.tree ul {\n"
|
||||
" margin-left: 20px; padding-left: 0;\n"
|
||||
"}\n"
|
||||
"ul.tree li ul {\n"
|
||||
" display: none;\n"
|
||||
"}\n"
|
||||
"ul.tree li.open > ul {\n"
|
||||
" display: block;\n"
|
||||
"}\n"
|
||||
"ul.tree li div:before {\n"
|
||||
" height: 1em;\n"
|
||||
" padding:0 .1em;\n"
|
||||
" font-size: .8em;\n"
|
||||
" display: block;\n"
|
||||
" position: absolute;\n"
|
||||
" left: -1.3em;\n"
|
||||
" top: .2em;\n"
|
||||
"}\n"
|
||||
"ul.tree li > div:not(:nth-last-child(2)):before {\n"
|
||||
" content: '+';\n"
|
||||
"}\n"
|
||||
"ul.tree li.open > div:not(:nth-last-child(2)):before {\n"
|
||||
" content: '-';\n"
|
||||
"}\n"
|
||||
".sc {\n"
|
||||
" text-decoration: underline;\n"
|
||||
" text-decoration-color: black;\n"
|
||||
" font-weight: bold;\n"
|
||||
" background-color: #D9D9D9;\n"
|
||||
"}\n"
|
||||
".t0 {\n"
|
||||
" color: #32c832;\n"
|
||||
"}\n"
|
||||
".t1 {\n"
|
||||
" color: #32a5a5;\n"
|
||||
"}\n"
|
||||
".t2 {\n"
|
||||
" color: #be5a00;\n"
|
||||
"}\n"
|
||||
".t3 {\n"
|
||||
" color: #afaf32;\n"
|
||||
"}\n"
|
||||
".t4 {\n"
|
||||
" color: #c83232;\n"
|
||||
"}\n"
|
||||
"ul.tree li > div {\n"
|
||||
" display: inline;\n"
|
||||
" cursor: pointer;\n"
|
||||
" color: black;\n"
|
||||
" text-decoration: none;\n"
|
||||
"}\n"
|
||||
"</style>\n"
|
||||
"<script>\n"
|
||||
"function treeView(opt) {\n"
|
||||
" var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
|
||||
" for(var i = 0; i < tree.length; i++){\n"
|
||||
" var parent = tree[i].parentElement;\n"
|
||||
" var classList = parent.classList;\n"
|
||||
" if(opt == 0) {\n"
|
||||
" classList.add('open');\n"
|
||||
" } else {\n"
|
||||
" classList.remove('open');\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function openParent(p,t) {\n"
|
||||
" if(p.parentElement.classList.contains(\"tree\")) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" p.parentElement.classList.add('open');\n"
|
||||
" openParent(p.parentElement,t);\n"
|
||||
"}\n"
|
||||
"function search() {\n"
|
||||
" var tree = document.querySelectorAll('ul.tree span');\n"
|
||||
" var check = document.getElementById('check');\n"
|
||||
" for(var i = 0; i < tree.length; i++){\n"
|
||||
" tree[i].classList.remove('sc');\n"
|
||||
" if(tree[i].innerHTML.includes(document.getElementById(\"search\").value)) {\n"
|
||||
" tree[i].classList.add('sc');\n"
|
||||
" openParent(tree[i].parentElement,tree);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function openUL(n) {\n"
|
||||
" var children = n.children;\n"
|
||||
" if(children.length == 1) {\n"
|
||||
" openNode(children[0]);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function openNode(n) {\n"
|
||||
" var children = n.children;\n"
|
||||
" for(var i = 0; i < children.length; i++){\n"
|
||||
" if(children[i].nodeName == 'UL') {\n"
|
||||
" n.classList.add('open');\n"
|
||||
" openUL(children[i]);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function addClickActions() {\n"
|
||||
"var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
|
||||
"for(var i = 0; i < tree.length; i++){\n"
|
||||
" tree[i].addEventListener('click', function(e) {\n"
|
||||
" var parent = e.target.parentElement;\n"
|
||||
" var classList = parent.classList;\n"
|
||||
" if(classList.contains(\"open\")) {\n"
|
||||
" classList.remove('open');\n"
|
||||
" var opensubs = parent.querySelectorAll(':scope .open');\n"
|
||||
" for(var i = 0; i < opensubs.length; i++){\n"
|
||||
" opensubs[i].classList.remove('open');\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" if(e.altKey) {\n"
|
||||
" classList.add('open');\n"
|
||||
" var opensubs = parent.querySelectorAll('li');\n"
|
||||
" for(var i = 0; i < opensubs.length; i++){\n"
|
||||
" opensubs[i].classList.add('open');\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" openNode(parent);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
"}\n"
|
||||
"}\n"
|
||||
"</script>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<div style=\"padding-left: 25px;\">%s view, total %s: %s </div>\n"
|
||||
"<div style=\"padding-left: 25px;\"><button type='button' onclick='treeView(0)'>++</button><button type='button' onclick='treeView(1)'>--</button>\n"
|
||||
"<input type='text' id='search' value='' size='35' onkeypress=\"if(event.keyCode == 13) document.getElementById('searchBtn').click()\">\n"
|
||||
"<button type='button' id='searchBtn' onclick='search()'>search</button></div>\n"
|
||||
"<ul class=\"tree\">\n";
|
||||
|
||||
static const char TREE_FOOTER[] =
|
||||
"<script>\n"
|
||||
"addClickActions();\n"
|
||||
"</script>\n"
|
||||
"</ul>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
INCBIN(FLAMEGRAPH_TEMPLATE, "src/res/flame.html")
|
||||
INCBIN(TREE_TEMPLATE, "src/res/tree.html")
|
||||
|
||||
|
||||
class StringUtils {
|
||||
public:
|
||||
static bool endsWith(const std::string& s, const char* suffix, size_t suffixlen) {
|
||||
size_t len = s.length();
|
||||
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
|
||||
static void replace(std::string& s, char c, const char* replacement, size_t rlen) {
|
||||
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i += rlen) {
|
||||
s.replace(i, 1, replacement, rlen);
|
||||
}
|
||||
}
|
||||
|
||||
static void replace(std::string& s, char c, const char* replacement) {
|
||||
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
|
||||
s.replace(i, 1, replacement);
|
||||
static size_t getCommonPrefix(const std::string& a, const std::string& b) {
|
||||
size_t length = a.size() < b.size() ? a.size() : b.size();
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (a[i] != b[i] || a[i] > 127) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -450,89 +67,186 @@ class Format {
|
||||
|
||||
class Node {
|
||||
public:
|
||||
std::string _name;
|
||||
u32 _key;
|
||||
u32 _order;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(const std::string& name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
Node(u32 key, u32 order, const Trie& trie) : _key(key), _order(order), _trie(&trie) {
|
||||
}
|
||||
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
static bool orderByName(const Node& a, const Node& b) {
|
||||
return a._order < b._order;
|
||||
}
|
||||
|
||||
static bool orderByTotal(const Node& a, const Node& b) {
|
||||
return a._trie->_total > b._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Trie* FlameGraph::addChild(Trie* f, const char* name, FrameTypeId type, u64 value) {
|
||||
size_t len = strlen(name);
|
||||
bool has_suffix = len > 4 && name[len - 4] == '_' && name[len - 3] == '[' && name[len - 1] == ']';
|
||||
std::string s(name, has_suffix ? len - 4 : len);
|
||||
|
||||
u32 name_index = _cpool[s];
|
||||
if (name_index == 0) {
|
||||
name_index = _cpool[s] = _cpool.size();
|
||||
}
|
||||
|
||||
f->_total += value;
|
||||
|
||||
switch (type) {
|
||||
case FRAME_INLINED:
|
||||
(f = f->child(name_index, FRAME_JIT_COMPILED))->_inlined += value;
|
||||
return f;
|
||||
case FRAME_C1_COMPILED:
|
||||
(f = f->child(name_index, FRAME_JIT_COMPILED))->_c1_compiled += value;
|
||||
return f;
|
||||
case FRAME_INTERPRETED:
|
||||
(f = f->child(name_index, FRAME_JIT_COMPILED))->_interpreted += value;
|
||||
return f;
|
||||
default:
|
||||
return f->child(name_index, type);
|
||||
}
|
||||
}
|
||||
|
||||
void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
_name_order = new u32[_cpool.size() + 1]();
|
||||
_mintotal = _minwidth == 0 && tree ? _root._total / 1000 : (u64)(_root._total * _minwidth / 100);
|
||||
int depth = _root.depth(_mintotal);
|
||||
int depth = _root.depth(_mintotal, _name_order);
|
||||
|
||||
if (tree) {
|
||||
char buf[sizeof(TREE_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, TREE_HEADER,
|
||||
_reverse ? "Backtrace" : "Call tree",
|
||||
_counter == COUNTER_SAMPLES ? "samples" : "counter",
|
||||
Format().thousands(_root._total));
|
||||
out << buf;
|
||||
const char* tail = TREE_TEMPLATE;
|
||||
|
||||
printTreeFrame(out, _root, 0);
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out << (_reverse ? "Backtrace" : "Call tree");
|
||||
|
||||
out << TREE_FOOTER;
|
||||
tail = printTill(out, tail, "/*type:*/");
|
||||
out << (_counter == COUNTER_SAMPLES ? "samples" : "counter");
|
||||
|
||||
tail = printTill(out, tail, "/*count:*/");
|
||||
out << Format().thousands(_root._total);
|
||||
|
||||
tail = printTill(out, tail, "/*tree:*/");
|
||||
|
||||
const char** names = new const char*[_cpool.size() + 1];
|
||||
for (std::map<std::string, u32>::const_iterator it = _cpool.begin(); it != _cpool.end(); ++it) {
|
||||
names[it->second] = it->first.c_str();
|
||||
}
|
||||
printTreeFrame(out, _root, 0, names);
|
||||
delete[] names;
|
||||
|
||||
out << tail;
|
||||
} else {
|
||||
char buf[sizeof(FLAMEGRAPH_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title, depth * 16, _reverse ? "true" : "false", depth);
|
||||
out << buf;
|
||||
const char* tail = FLAMEGRAPH_TEMPLATE;
|
||||
|
||||
printFrame(out, "all", _root, 0, 0);
|
||||
tail = printTill(out, tail, "/*height:*/300");
|
||||
out << std::min(depth * 16, MAX_CANVAS_HEIGHT);
|
||||
|
||||
out << FLAMEGRAPH_FOOTER;
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out << _title;
|
||||
|
||||
tail = printTill(out, tail, "/*reverse:*/false");
|
||||
out << (_reverse ? "true" : "false");
|
||||
|
||||
tail = printTill(out, tail, "/*depth:*/0");
|
||||
out << depth;
|
||||
|
||||
tail = printTill(out, tail, "/*cpool:*/");
|
||||
printCpool(out);
|
||||
|
||||
tail = printTill(out, tail, "/*frames:*/");
|
||||
printFrame(out, FRAME_NATIVE << 28, _root, 0, 0);
|
||||
|
||||
tail = printTill(out, tail, "/*highlight:*/");
|
||||
|
||||
out << tail;
|
||||
}
|
||||
|
||||
delete[] _name_order;
|
||||
}
|
||||
|
||||
void FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x) {
|
||||
std::string name_copy = name;
|
||||
int type = frameType(name_copy);
|
||||
StringUtils::replace(name_copy, '\'', "\\'");
|
||||
void FlameGraph::printFrame(std::ostream& out, u32 key, const Trie& f, int level, u64 x) {
|
||||
u32 name_and_type = _name_order[f.nameIndex(key)] << 3 | f.type(key);
|
||||
bool has_extra_types = (f._inlined | f._c1_compiled | f._interpreted) &&
|
||||
f._inlined < f._total && f._interpreted < f._total;
|
||||
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n", level, x, f._total, type, name_copy.c_str());
|
||||
char* p = _buf;
|
||||
if (level == _last_level + 1 && x == _last_x) {
|
||||
p += snprintf(p, 100, "u(%u", name_and_type);
|
||||
} else if (level == _last_level && x == _last_x + _last_total) {
|
||||
p += snprintf(p, 100, "n(%u", name_and_type);
|
||||
} else {
|
||||
p += snprintf(p, 100, "f(%u,%d,%llu", name_and_type, level, x - _last_x);
|
||||
}
|
||||
|
||||
if (f._total != _last_total || has_extra_types) {
|
||||
p += snprintf(p, 100, ",%llu", f._total);
|
||||
if (has_extra_types) {
|
||||
p += snprintf(p, 100, ",%llu,%llu,%llu", f._inlined, f._c1_compiled, f._interpreted);
|
||||
}
|
||||
}
|
||||
|
||||
strcpy(p, ")\n");
|
||||
out << _buf;
|
||||
|
||||
_last_level = level;
|
||||
_last_x = x;
|
||||
_last_total = f._total;
|
||||
|
||||
if (f._children.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Node> children;
|
||||
children.reserve(f._children.size());
|
||||
for (std::map<u32, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
children.push_back(Node(it->first, _name_order[f.nameIndex(it->first)], it->second));
|
||||
}
|
||||
std::sort(children.begin(), children.end(), Node::orderByName);
|
||||
|
||||
x += f._self;
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
if (it->second._total >= _mintotal) {
|
||||
printFrame(out, it->first, it->second, level + 1, x);
|
||||
for (size_t i = 0; i < children.size(); i++) {
|
||||
u32 key = children[i]._key;
|
||||
const Trie* trie = children[i]._trie;
|
||||
if (trie->_total >= _mintotal) {
|
||||
printFrame(out, key, *trie, level + 1, x);
|
||||
}
|
||||
x += it->second._total;
|
||||
x += trie->_total;
|
||||
}
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
std::vector<Node> subnodes;
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
subnodes.push_back(Node(it->first, it->second));
|
||||
void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level, const char** names) {
|
||||
std::vector<Node> children;
|
||||
children.reserve(f._children.size());
|
||||
for (std::map<u32, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
children.push_back(Node(it->first, 0, it->second));
|
||||
}
|
||||
std::sort(subnodes.begin(), subnodes.end());
|
||||
std::sort(children.begin(), children.end(), Node::orderByTotal);
|
||||
|
||||
double pct = 100.0 / _root._total;
|
||||
for (size_t i = 0; i < subnodes.size(); i++) {
|
||||
std::string name = subnodes[i]._name;
|
||||
const Trie* trie = subnodes[i]._trie;
|
||||
for (size_t i = 0; i < children.size(); i++) {
|
||||
u32 key = children[i]._key;
|
||||
const Trie* trie = children[i]._trie;
|
||||
|
||||
int type = frameType(name);
|
||||
StringUtils::replace(name, '&', "&");
|
||||
StringUtils::replace(name, '<', "<");
|
||||
StringUtils::replace(name, '>', ">");
|
||||
u32 type = trie->type(key);
|
||||
std::string name = names[trie->nameIndex(key)];
|
||||
StringUtils::replace(name, '&', "&", 5);
|
||||
StringUtils::replace(name, '<', "<", 4);
|
||||
StringUtils::replace(name, '>', ">", 4);
|
||||
|
||||
const char* div_class = trie->_children.empty() ? " class=\"o\"" : "";
|
||||
|
||||
if (_reverse) {
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<li><div>[%d] %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
|
||||
level,
|
||||
trie->_total * pct, Format().thousands(trie->_total),
|
||||
"<li><div%s>%.2f%% [%s]</div> <span class=\"t%d\">%s</span>\n",
|
||||
div_class, trie->_total * pct, Format().thousands(trie->_total),
|
||||
type, name.c_str());
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
|
||||
level,
|
||||
trie->_total * pct, Format().thousands(trie->_total),
|
||||
"<li><div%s>%.2f%% [%s] • self: %.2f%% [%s]</div> <span class=\"t%d\">%s</span>\n",
|
||||
div_class, trie->_total * pct, Format().thousands(trie->_total),
|
||||
trie->_self * pct, Format().thousands(trie->_self),
|
||||
type, name.c_str());
|
||||
}
|
||||
@@ -541,7 +255,7 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
if (trie->_children.size() > 0) {
|
||||
out << "<ul>\n";
|
||||
if (trie->_total >= _mintotal) {
|
||||
printTreeFrame(out, *trie, level + 1);
|
||||
printTreeFrame(out, *trie, level + 1, names);
|
||||
} else {
|
||||
out << "<li>...\n";
|
||||
}
|
||||
@@ -550,27 +264,34 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
}
|
||||
}
|
||||
|
||||
int FlameGraph::frameType(std::string& name) {
|
||||
if (StringUtils::endsWith(name, "_[j]", 4)) {
|
||||
// Java compiled frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return 0;
|
||||
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
|
||||
// Java inlined frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return 1;
|
||||
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
|
||||
// Kernel function
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return 2;
|
||||
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
|
||||
// C++ function or Objective C method
|
||||
return 3;
|
||||
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
// Java regular method
|
||||
return 0;
|
||||
} else {
|
||||
// Other native code
|
||||
return 4;
|
||||
void FlameGraph::printCpool(std::ostream& out) {
|
||||
out << "'all'";
|
||||
|
||||
std::string prev;
|
||||
u32 index = 0;
|
||||
for (std::map<std::string, u32>::const_iterator it = _cpool.begin(); it != _cpool.end(); ++it) {
|
||||
if (_name_order[it->second]) {
|
||||
_name_order[it->second] = ++index;
|
||||
|
||||
size_t prefix_len = StringUtils::getCommonPrefix(prev, it->first);
|
||||
prev = it->first;
|
||||
|
||||
if (prefix_len > 95) prefix_len = 95;
|
||||
std::string s(1, (char)(prefix_len + ' '));
|
||||
s.append(it->first, prefix_len, std::string::npos);
|
||||
|
||||
StringUtils::replace(s, '\\', "\\\\", 2);
|
||||
StringUtils::replace(s, '\'', "\\'", 2);
|
||||
out << ",\n'" << s << "'";
|
||||
}
|
||||
}
|
||||
|
||||
// Release cpool memory, since frame names are never used beyond this point
|
||||
_cpool = std::map<std::string, u32>();
|
||||
}
|
||||
|
||||
const char* FlameGraph::printTill(std::ostream& out, const char* data, const char* till) {
|
||||
const char* pos = strstr(data, till);
|
||||
out.write(data, pos - data);
|
||||
return pos + strlen(till);
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _FLAMEGRAPH_H
|
||||
#define _FLAMEGRAPH_H
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
class Trie {
|
||||
public:
|
||||
std::map<std::string, Trie> _children;
|
||||
std::map<u32, Trie> _children;
|
||||
u64 _total;
|
||||
u64 _self;
|
||||
u64 _inlined, _c1_compiled, _interpreted;
|
||||
|
||||
Trie() : _children(), _total(0), _self(0) {
|
||||
}
|
||||
|
||||
Trie* addChild(const std::string& key, u64 value) {
|
||||
_total += value;
|
||||
return &_children[key];
|
||||
Trie() : _children(), _total(0), _self(0), _inlined(0), _c1_compiled(0), _interpreted(0) {
|
||||
}
|
||||
|
||||
void addLeaf(u64 value) {
|
||||
_total += value;
|
||||
_self += value;
|
||||
}
|
||||
|
||||
int depth(u64 cutoff) const {
|
||||
if (_total < cutoff) {
|
||||
return 0;
|
||||
FrameTypeId type(u32 key) const {
|
||||
if (_inlined * 3 >= _total) {
|
||||
return FRAME_INLINED;
|
||||
} else if (_c1_compiled * 2 >= _total) {
|
||||
return FRAME_C1_COMPILED;
|
||||
} else if (_interpreted * 2 >= _total) {
|
||||
return FRAME_INTERPRETED;
|
||||
} else {
|
||||
return (FrameTypeId)(key >> 28);
|
||||
}
|
||||
}
|
||||
|
||||
u32 nameIndex(u32 key) const {
|
||||
return key & ((1 << 28) - 1);
|
||||
}
|
||||
|
||||
Trie* child(u32 name_index, FrameTypeId type) {
|
||||
return &_children[name_index | type << 28];
|
||||
}
|
||||
|
||||
int depth(u64 cutoff, u32* name_order) const {
|
||||
int max_depth = 0;
|
||||
for (std::map<std::string, Trie>::const_iterator it = _children.begin(); it != _children.end(); ++it) {
|
||||
int d = it->second.depth(cutoff);
|
||||
if (d > max_depth) max_depth = d;
|
||||
for (std::map<u32, Trie>::const_iterator it = _children.begin(); it != _children.end(); ++it) {
|
||||
if (it->second._total >= cutoff) {
|
||||
name_order[nameIndex(it->first)] = 1;
|
||||
int d = it->second.depth(cutoff, name_order);
|
||||
if (d > max_depth) max_depth = d;
|
||||
}
|
||||
}
|
||||
return max_depth + 1;
|
||||
}
|
||||
@@ -61,25 +61,36 @@ class Trie {
|
||||
class FlameGraph {
|
||||
private:
|
||||
Trie _root;
|
||||
char _buf[4096];
|
||||
std::map<std::string, u32> _cpool;
|
||||
u32* _name_order;
|
||||
u64 _mintotal;
|
||||
char _buf[4096];
|
||||
|
||||
const char* _title;
|
||||
Counter _counter;
|
||||
double _minwidth;
|
||||
bool _reverse;
|
||||
|
||||
void printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x);
|
||||
void printTreeFrame(std::ostream& out, const Trie& f, int level);
|
||||
int frameType(std::string& name);
|
||||
int _last_level;
|
||||
u64 _last_x;
|
||||
u64 _last_total;
|
||||
|
||||
void printFrame(std::ostream& out, u32 key, const Trie& f, int level, u64 x);
|
||||
void printTreeFrame(std::ostream& out, const Trie& f, int level, const char** names);
|
||||
void printCpool(std::ostream& out);
|
||||
const char* printTill(std::ostream& out, const char* data, const char* till);
|
||||
|
||||
public:
|
||||
FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) :
|
||||
_root(),
|
||||
_cpool(),
|
||||
_title(title),
|
||||
_counter(counter),
|
||||
_minwidth(minwidth),
|
||||
_reverse(reverse) {
|
||||
_reverse(reverse),
|
||||
_last_level(0),
|
||||
_last_x(0),
|
||||
_last_total(0) {
|
||||
_buf[sizeof(_buf) - 1] = 0;
|
||||
}
|
||||
|
||||
@@ -87,6 +98,8 @@ class FlameGraph {
|
||||
return &_root;
|
||||
}
|
||||
|
||||
Trie* addChild(Trie* f, const char* name, FrameTypeId type, u64 value);
|
||||
|
||||
void dump(std::ostream& out, bool tree);
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _FLIGHTRECORDER_H
|
||||
@@ -20,6 +9,7 @@
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "event.h"
|
||||
#include "log.h"
|
||||
|
||||
class Recording;
|
||||
|
||||
@@ -27,19 +17,27 @@ class FlightRecorder {
|
||||
private:
|
||||
Recording* _rec;
|
||||
|
||||
Error startMasterRecording(Arguments& args, const char* filename);
|
||||
void stopMasterRecording();
|
||||
|
||||
public:
|
||||
FlightRecorder() : _rec(NULL) {
|
||||
}
|
||||
|
||||
Error start(Arguments& args, bool reset);
|
||||
void stop();
|
||||
void flush();
|
||||
size_t usedMemory();
|
||||
bool timerTick(u64 wall_time, u32 gc_id);
|
||||
|
||||
bool active() {
|
||||
bool active() const {
|
||||
return _rec != NULL;
|
||||
}
|
||||
|
||||
void recordEvent(int lock_index, int tid, u32 call_trace_id,
|
||||
int event_type, Event* event, u64 counter);
|
||||
EventType event_type, Event* event);
|
||||
|
||||
void recordLog(LogLevel level, const char* message, size_t len);
|
||||
};
|
||||
|
||||
#endif // _FLIGHTRECORDER_H
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "demangle.h"
|
||||
#include "frameName.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
static inline bool isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
|
||||
Matcher::Matcher(const char* pattern) {
|
||||
if (pattern[0] == '*') {
|
||||
_type = MATCH_ENDS_WITH;
|
||||
@@ -75,26 +69,42 @@ bool Matcher::matches(const char* s) {
|
||||
}
|
||||
|
||||
|
||||
FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_cache(),
|
||||
JMethodCache FrameName::_cache;
|
||||
|
||||
FrameName::FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_class_names(),
|
||||
_include(),
|
||||
_exclude(),
|
||||
_str(),
|
||||
_style(style),
|
||||
_cache_epoch((unsigned char)epoch),
|
||||
_cache_max_age(args._mcache),
|
||||
_thread_names_lock(thread_names_lock),
|
||||
_thread_names(thread_names)
|
||||
{
|
||||
// Require printf to use standard C format regardless of system locale
|
||||
_saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0));
|
||||
memset(_buf, 0, sizeof(_buf));
|
||||
|
||||
buildFilter(_include, args._buf, args._include);
|
||||
buildFilter(_exclude, args._buf, args._exclude);
|
||||
|
||||
Profiler::_instance.classMap()->collect(_class_names);
|
||||
Profiler::instance()->classMap()->collect(_class_names);
|
||||
}
|
||||
|
||||
FrameName::~FrameName() {
|
||||
if (_cache_max_age == 0) {
|
||||
_cache.clear();
|
||||
} else {
|
||||
// Remove stale methods from the cache, leave the fresh ones for the next profiling session
|
||||
for (JMethodCache::iterator it = _cache.begin(); it != _cache.end(); ) {
|
||||
if (_cache_epoch - (unsigned char)it->second[0] >= _cache_max_age) {
|
||||
_cache.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freelocale(uselocale(_saved_locale));
|
||||
}
|
||||
|
||||
@@ -105,32 +115,56 @@ void FrameName::buildFilter(std::vector<Matcher>& vector, const char* base, int
|
||||
}
|
||||
}
|
||||
|
||||
char* FrameName::truncate(char* name, int max_length) {
|
||||
if (strlen(name) > max_length && max_length >= 4) {
|
||||
strcpy(name + max_length - 4, "...)");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
const char* FrameName::decodeNativeSymbol(const char* name) {
|
||||
const char* lib_name = (_style & STYLE_LIB_NAMES) ? Profiler::instance()->getLibraryName(name) : NULL;
|
||||
|
||||
const char* FrameName::cppDemangle(const char* name) {
|
||||
if (name != NULL && name[0] == '_' && name[1] == 'Z') {
|
||||
int status;
|
||||
char* demangled = abi::__cxa_demangle(name, NULL, NULL, &status);
|
||||
if (name[0] == '_' && name[1] == 'Z') {
|
||||
char* demangled = Demangle::demangle(name, _style & STYLE_SIGNATURES);
|
||||
if (demangled != NULL) {
|
||||
strncpy(_buf, demangled, sizeof(_buf) - 1);
|
||||
if (lib_name != NULL) {
|
||||
_str.assign(lib_name).append("`").append(demangled);
|
||||
} else {
|
||||
_str.assign(demangled);
|
||||
}
|
||||
free(demangled);
|
||||
return _buf;
|
||||
return _str.c_str();
|
||||
}
|
||||
}
|
||||
return name;
|
||||
|
||||
if (lib_name != NULL) {
|
||||
return _str.assign(lib_name).append("`").append(name).c_str();
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
char* FrameName::javaMethodName(jmethodID method) {
|
||||
const char* FrameName::typeSuffix(FrameTypeId type) {
|
||||
if (_style & STYLE_ANNOTATE) {
|
||||
switch (type) {
|
||||
case FRAME_INTERPRETED: return "_[0]";
|
||||
case FRAME_JIT_COMPILED: return "_[j]";
|
||||
case FRAME_INLINED: return "_[i]";
|
||||
case FRAME_C1_COMPILED: return "_[1]";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void FrameName::javaMethodName(jmethodID method) {
|
||||
if (VMStructs::hasMethodStructs()) {
|
||||
// Workaround for JDK-8313816
|
||||
VMMethod* vm_method = VMMethod::fromMethodID(method);
|
||||
if (vm_method == NULL || vm_method->id() == NULL) {
|
||||
_str.assign("[stale_jmethodID]");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
jclass method_class;
|
||||
char* class_name = NULL;
|
||||
char* method_name = NULL;
|
||||
char* method_sig = NULL;
|
||||
char* result;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmtiError err;
|
||||
@@ -139,26 +173,28 @@ char* FrameName::javaMethodName(jmethodID method) {
|
||||
(err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
|
||||
(err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
|
||||
// Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;'
|
||||
result = javaClassName(class_name + 1, strlen(class_name) - 2, _style);
|
||||
strcat(result, ".");
|
||||
strcat(result, method_name);
|
||||
if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
|
||||
if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
|
||||
javaClassName(class_name + 1, strlen(class_name) - 2, _style);
|
||||
_str.append(".").append(method_name);
|
||||
if (_style & STYLE_SIGNATURES) {
|
||||
if (_style & STYLE_NO_SEMICOLON) {
|
||||
for (char* s = method_sig; *s; s++) {
|
||||
if (*s == ';') *s = '|';
|
||||
}
|
||||
}
|
||||
_str.append(method_sig);
|
||||
}
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[jvmtiError %d]", err);
|
||||
result = _buf;
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "[jvmtiError %d]", err);
|
||||
_str.assign(buf);
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
jvmti->Deallocate((unsigned char*)method_sig);
|
||||
jvmti->Deallocate((unsigned char*)method_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char* FrameName::javaClassName(const char* symbol, int length, int style) {
|
||||
char* result = _buf;
|
||||
|
||||
void FrameName::javaClassName(const char* symbol, size_t length, int style) {
|
||||
int array_dimension = 0;
|
||||
while (*symbol == '[') {
|
||||
array_dimension++;
|
||||
@@ -166,42 +202,52 @@ char* FrameName::javaClassName(const char* symbol, int length, int style) {
|
||||
}
|
||||
|
||||
if (array_dimension == 0) {
|
||||
strncpy(result, symbol, length);
|
||||
result[length] = 0;
|
||||
_str.assign(symbol, length);
|
||||
} else {
|
||||
switch (*symbol) {
|
||||
case 'B': strcpy(result, "byte"); break;
|
||||
case 'C': strcpy(result, "char"); break;
|
||||
case 'I': strcpy(result, "int"); break;
|
||||
case 'J': strcpy(result, "long"); break;
|
||||
case 'S': strcpy(result, "short"); break;
|
||||
case 'Z': strcpy(result, "boolean"); break;
|
||||
case 'F': strcpy(result, "float"); break;
|
||||
case 'D': strcpy(result, "double"); break;
|
||||
default:
|
||||
length -= array_dimension + 2;
|
||||
strncpy(result, symbol + 1, length);
|
||||
result[length] = 0;
|
||||
case 'B': _str.assign("byte"); break;
|
||||
case 'C': _str.assign("char"); break;
|
||||
case 'I': _str.assign("int"); break;
|
||||
case 'J': _str.assign("long"); break;
|
||||
case 'S': _str.assign("short"); break;
|
||||
case 'Z': _str.assign("boolean"); break;
|
||||
case 'F': _str.assign("float"); break;
|
||||
case 'D': _str.assign("double"); break;
|
||||
default: _str.assign(symbol + 1, length - array_dimension - 2);
|
||||
}
|
||||
|
||||
do {
|
||||
strcat(result, "[]");
|
||||
_str += "[]";
|
||||
} while (--array_dimension > 0);
|
||||
}
|
||||
|
||||
if (style & STYLE_SIMPLE) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') result = s + 1;
|
||||
if (style & STYLE_NORMALIZE) {
|
||||
size_t size = _str.size();
|
||||
for (ssize_t i = size - 2; i > 0; i--) {
|
||||
if (_str[i] == '/' || _str[i] == '.') {
|
||||
if (isDigit(_str[i + 1])) {
|
||||
_str.resize(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (style & STYLE_SIMPLE) {
|
||||
size_t start = 0;
|
||||
size_t size = _str.size();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (_str[i] == '/' && !isDigit(_str[i + 1])) start = i + 1;
|
||||
}
|
||||
_str.erase(0, start);
|
||||
}
|
||||
|
||||
if (style & STYLE_DOTTED) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') *s = '.';
|
||||
size_t size = _str.size();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (_str[i] == '/' && !isDigit(_str[i + 1])) _str[i] = '.';
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
@@ -211,18 +257,18 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
|
||||
switch (frame.bci) {
|
||||
case BCI_NATIVE_FRAME:
|
||||
return cppDemangle((const char*)frame.method_id);
|
||||
return decodeNativeSymbol((const char*)frame.method_id);
|
||||
|
||||
case BCI_ALLOC:
|
||||
case BCI_ALLOC_OUTSIDE_TLAB:
|
||||
case BCI_LOCK:
|
||||
case BCI_PARK: {
|
||||
const char* symbol = _class_names[(uintptr_t)frame.method_id];
|
||||
char* class_name = javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
|
||||
javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
|
||||
if (!for_matching && !(_style & STYLE_DOTTED)) {
|
||||
strcat(class_name, frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]");
|
||||
_str += frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]";
|
||||
}
|
||||
return class_name;
|
||||
return _str.c_str();
|
||||
}
|
||||
|
||||
case BCI_THREAD_ID: {
|
||||
@@ -231,32 +277,77 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
ThreadMap::iterator it = _thread_names.find(tid);
|
||||
if (for_matching) {
|
||||
return it != _thread_names.end() ? it->second.c_str() : "";
|
||||
} else if (it != _thread_names.end()) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[%s tid=%d]", it->second.c_str(), tid);
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[tid=%d]", tid);
|
||||
}
|
||||
return _buf;
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "tid=%d]", tid);
|
||||
if (it != _thread_names.end()) {
|
||||
return _str.assign("[").append(it->second).append(" ").append(buf).c_str();
|
||||
} else {
|
||||
return _str.assign("[").append(buf).c_str();
|
||||
}
|
||||
}
|
||||
|
||||
case BCI_ERROR: {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[%s]", (const char*)frame.method_id);
|
||||
return _buf;
|
||||
}
|
||||
case BCI_ERROR:
|
||||
return _str.assign("[").append((const char*)frame.method_id).append("]").c_str();
|
||||
|
||||
default: {
|
||||
const char* type_suffix = typeSuffix(FrameType::decode(frame.bci));
|
||||
|
||||
JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
|
||||
if (it != _cache.end() && it->first == frame.method_id) {
|
||||
return it->second.c_str();
|
||||
it->second[0] = _cache_epoch;
|
||||
if (type_suffix != NULL) {
|
||||
return _str.assign(it->second, 1, std::string::npos).append(type_suffix).c_str();
|
||||
}
|
||||
return it->second.c_str() + 1;
|
||||
}
|
||||
|
||||
const char* newName = javaMethodName(frame.method_id);
|
||||
_cache.insert(it, JMethodCache::value_type(frame.method_id, newName));
|
||||
return newName;
|
||||
javaMethodName(frame.method_id);
|
||||
_cache.insert(it, JMethodCache::value_type(frame.method_id, std::string(1, _cache_epoch) + _str));
|
||||
if (type_suffix != NULL) {
|
||||
_str += type_suffix;
|
||||
}
|
||||
return _str.c_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameTypeId FrameName::type(ASGCT_CallFrame& frame) {
|
||||
if (frame.method_id == NULL) {
|
||||
return FRAME_NATIVE;
|
||||
}
|
||||
|
||||
switch (frame.bci) {
|
||||
case BCI_NATIVE_FRAME: {
|
||||
const char* name = (const char*)frame.method_id;
|
||||
if ((name[0] == '_' && name[1] == 'Z') ||
|
||||
(name[0] == '+' && name[1] == '[') ||
|
||||
(name[0] == '-' && name[1] == '[')) {
|
||||
return FRAME_CPP;
|
||||
} else {
|
||||
size_t len = strlen(name);
|
||||
return len > 4 && strcmp(name + len - 4, "_[k]") == 0 ? FRAME_KERNEL : FRAME_NATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
case BCI_ALLOC:
|
||||
case BCI_LOCK:
|
||||
case BCI_PARK:
|
||||
return FRAME_INLINED;
|
||||
|
||||
case BCI_ALLOC_OUTSIDE_TLAB:
|
||||
return FRAME_KERNEL;
|
||||
|
||||
case BCI_THREAD_ID:
|
||||
case BCI_ERROR:
|
||||
return FRAME_NATIVE;
|
||||
|
||||
default:
|
||||
return FrameType::decode(frame.bci);
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameName::include(const char* frame_name) {
|
||||
for (int i = 0; i < _include.size(); i++) {
|
||||
if (_include[i].matches(frame_name)) {
|
||||
@@ -274,4 +365,3 @@ bool FrameName::exclude(const char* frame_name) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _FRAMENAME_H
|
||||
@@ -63,27 +52,31 @@ class Matcher {
|
||||
|
||||
class FrameName {
|
||||
private:
|
||||
JMethodCache _cache;
|
||||
static JMethodCache _cache;
|
||||
|
||||
ClassMap _class_names;
|
||||
std::vector<Matcher> _include;
|
||||
std::vector<Matcher> _exclude;
|
||||
char _buf[800]; // must be large enough for class name + method name + method signature
|
||||
std::string _str;
|
||||
int _style;
|
||||
unsigned char _cache_epoch;
|
||||
unsigned char _cache_max_age;
|
||||
Mutex& _thread_names_lock;
|
||||
ThreadMap& _thread_names;
|
||||
locale_t _saved_locale;
|
||||
|
||||
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
|
||||
char* truncate(char* name, int max_length);
|
||||
const char* cppDemangle(const char* name);
|
||||
char* javaMethodName(jmethodID method);
|
||||
char* javaClassName(const char* symbol, int length, int style);
|
||||
const char* decodeNativeSymbol(const char* name);
|
||||
const char* typeSuffix(FrameTypeId type);
|
||||
void javaMethodName(jmethodID method);
|
||||
void javaClassName(const char* symbol, size_t length, int style);
|
||||
|
||||
public:
|
||||
FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names);
|
||||
FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names);
|
||||
~FrameName();
|
||||
|
||||
const char* name(ASGCT_CallFrame& frame, bool for_matching = false);
|
||||
FrameTypeId type(ASGCT_CallFrame& frame);
|
||||
|
||||
bool hasIncludeList() { return !_include.empty(); }
|
||||
bool hasExcludeList() { return !_exclude.empty(); }
|
||||
|
||||
BIN
src/helper/one/profiler/Instrument.class
Normal file
BIN
src/helper/one/profiler/Instrument.class
Normal file
Binary file not shown.
17
src/helper/one/profiler/Instrument.java
Normal file
17
src/helper/one/profiler/Instrument.java
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
/**
|
||||
* Instrumentation helper for Java method profiling.
|
||||
*/
|
||||
public class Instrument {
|
||||
|
||||
private Instrument() {
|
||||
}
|
||||
|
||||
public static native void recordSample();
|
||||
}
|
||||
BIN
src/helper/one/profiler/JfrSync.class
Normal file
BIN
src/helper/one/profiler/JfrSync.class
Normal file
Binary file not shown.
115
src/helper/one/profiler/JfrSync.java
Normal file
115
src/helper/one/profiler/JfrSync.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
import jdk.jfr.Configuration;
|
||||
import jdk.jfr.FlightRecorder;
|
||||
import jdk.jfr.FlightRecorderListener;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.RecordingState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Synchronize async-profiler recording with an existing JFR recording.
|
||||
*/
|
||||
class JfrSync implements FlightRecorderListener {
|
||||
private static volatile Recording masterRecording;
|
||||
|
||||
private JfrSync() {
|
||||
}
|
||||
|
||||
static {
|
||||
FlightRecorder.addListener(new JfrSync());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordingStateChanged(Recording recording) {
|
||||
if (recording == masterRecording && recording.getState() == RecordingState.STOPPED) {
|
||||
masterRecording = null;
|
||||
stopProfiler();
|
||||
}
|
||||
}
|
||||
|
||||
public static void start(String fileName, String settings, int eventMask) throws IOException, ParseException {
|
||||
Recording recording;
|
||||
if (settings.startsWith("+")) {
|
||||
recording = new Recording();
|
||||
for (StringTokenizer st = new StringTokenizer(settings, "+"); st.hasMoreTokens(); ) {
|
||||
recording.enable(st.nextToken());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
recording = new Recording(Configuration.getConfiguration(settings));
|
||||
} catch (NoSuchFileException e) {
|
||||
recording = new Recording(Configuration.create(Paths.get(settings)));
|
||||
}
|
||||
disableBuiltinEvents(recording, eventMask);
|
||||
}
|
||||
|
||||
masterRecording = recording;
|
||||
|
||||
recording.setDestination(Paths.get(fileName));
|
||||
recording.setToDisk(true);
|
||||
recording.setDumpOnExit(true);
|
||||
recording.start();
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
Recording recording = masterRecording;
|
||||
if (recording != null) {
|
||||
// Disable state change notification before stopping
|
||||
masterRecording = null;
|
||||
recording.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void disableBuiltinEvents(Recording recording, int eventMask) {
|
||||
if ((eventMask & 1) != 0) {
|
||||
recording.disable("jdk.ExecutionSample");
|
||||
recording.disable("jdk.NativeMethodSample");
|
||||
}
|
||||
if ((eventMask & 2) != 0) {
|
||||
recording.disable("jdk.ObjectAllocationInNewTLAB");
|
||||
recording.disable("jdk.ObjectAllocationOutsideTLAB");
|
||||
recording.disable("jdk.ObjectAllocationSample");
|
||||
}
|
||||
if ((eventMask & 4) != 0) {
|
||||
recording.disable("jdk.JavaMonitorEnter");
|
||||
recording.disable("jdk.ThreadPark");
|
||||
}
|
||||
|
||||
// Shifted JfrOption values
|
||||
if ((eventMask & 0x10) != 0) {
|
||||
recording.disable("jdk.OSInformation");
|
||||
recording.disable("jdk.CPUInformation");
|
||||
recording.disable("jdk.JVMInformation");
|
||||
}
|
||||
if ((eventMask & 0x20) != 0) {
|
||||
recording.disable("jdk.InitialSystemProperty");
|
||||
}
|
||||
if ((eventMask & 0x40) != 0) {
|
||||
recording.disable("jdk.NativeLibrary");
|
||||
}
|
||||
if ((eventMask & 0x80) != 0) {
|
||||
recording.disable("jdk.CPULoad");
|
||||
}
|
||||
if ((eventMask & 0x100) != 0) {
|
||||
recording.disable("jdk.GCHeapSummary");
|
||||
}
|
||||
}
|
||||
|
||||
private static native void stopProfiler();
|
||||
|
||||
// JNI helper
|
||||
static Integer box(int n) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
BIN
src/helper/one/profiler/Server.class
Normal file
BIN
src/helper/one/profiler/Server.class
Normal file
Binary file not shown.
102
src/helper/one/profiler/Server.java
Normal file
102
src/helper/one/profiler/Server.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class Server extends Thread implements Executor, HttpHandler {
|
||||
private static final String[] COMMANDS = "start,resume,stop,dump,check,status,meminfo,list,version".split(",");
|
||||
|
||||
private final HttpServer server;
|
||||
private final AtomicInteger threadNum = new AtomicInteger();
|
||||
|
||||
private Server(String address) throws IOException {
|
||||
super("Async-profiler Server");
|
||||
setDaemon(true);
|
||||
|
||||
int p = address.lastIndexOf(':');
|
||||
InetSocketAddress socketAddress = p >= 0
|
||||
? new InetSocketAddress(address.substring(0, p), Integer.parseInt(address.substring(p + 1)))
|
||||
: new InetSocketAddress(Integer.parseInt(address));
|
||||
|
||||
server = HttpServer.create(socketAddress, 0);
|
||||
server.createContext("/", this);
|
||||
server.setExecutor(this);
|
||||
}
|
||||
|
||||
public static void start(String address) throws IOException {
|
||||
new Server(address).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Thread t = new Thread(command, "Async-profiler Request #" + threadNum.incrementAndGet());
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try {
|
||||
String command = getCommand(exchange.getRequestURI());
|
||||
if (command == null) {
|
||||
sendResponse(exchange, 404, "Unknown command");
|
||||
} else if (command.isEmpty()) {
|
||||
sendResponse(exchange, 200, "Async-profiler server");
|
||||
} else {
|
||||
String response = execute0(command);
|
||||
sendResponse(exchange, 200, response);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendResponse(exchange, 400, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
sendResponse(exchange, 500, e.getMessage());
|
||||
} finally {
|
||||
exchange.close();
|
||||
}
|
||||
}
|
||||
|
||||
private String getCommand(URI uri) {
|
||||
String path = uri.getPath();
|
||||
if (path.startsWith("/")) {
|
||||
if ((path = path.substring(1)).isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
for (String command : COMMANDS) {
|
||||
if (path.startsWith(command)) {
|
||||
String query = uri.getQuery();
|
||||
return query == null ? path : path + ',' + query.replace('&', ',');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void sendResponse(HttpExchange exchange, int code, String body) throws IOException {
|
||||
String contentType = body.startsWith("<!DOCTYPE html>") ? "text/html; charset=utf-8" : "text/plain";
|
||||
exchange.getResponseHeaders().add("Content-Type", contentType);
|
||||
|
||||
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(code, bodyBytes.length);
|
||||
exchange.getResponseBody().write(bodyBytes);
|
||||
}
|
||||
|
||||
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
}
|
||||
182
src/hooks.cpp
Normal file
182
src/hooks.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "hooks.h"
|
||||
#include "asprof.h"
|
||||
#include "cpuEngine.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
typedef void* (*ThreadFunc)(void*);
|
||||
|
||||
struct ThreadEntry {
|
||||
ThreadFunc start_routine;
|
||||
void* arg;
|
||||
};
|
||||
|
||||
typedef int (*pthread_create_t)(pthread_t*, const pthread_attr_t*, ThreadFunc, void*);
|
||||
static pthread_create_t _orig_pthread_create = NULL;
|
||||
|
||||
typedef void (*pthread_exit_t)(void*);
|
||||
static pthread_exit_t _orig_pthread_exit = NULL;
|
||||
|
||||
static void unblock_signals() {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
if (_global_args._signal == 0) {
|
||||
sigaddset(&set, SIGPROF);
|
||||
sigaddset(&set, SIGVTALRM);
|
||||
} else {
|
||||
for (int s = _global_args._signal; s > 0; s >>= 8) {
|
||||
sigaddset(&set, s & 0xff);
|
||||
}
|
||||
}
|
||||
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
|
||||
}
|
||||
|
||||
static void* thread_start_wrapper(void* e) {
|
||||
ThreadEntry* entry = (ThreadEntry*)e;
|
||||
ThreadFunc start_routine = entry->start_routine;
|
||||
void* arg = entry->arg;
|
||||
free(entry);
|
||||
|
||||
unblock_signals();
|
||||
|
||||
unsigned long current_thread = (unsigned long)(uintptr_t)pthread_self();
|
||||
Log::debug("thread_start: 0x%lx", current_thread);
|
||||
CpuEngine::onThreadStart();
|
||||
|
||||
void* result = start_routine(arg);
|
||||
|
||||
Log::debug("thread_end: 0x%lx", current_thread);
|
||||
CpuEngine::onThreadEnd();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int pthread_create_hook(pthread_t* thread, const pthread_attr_t* attr, ThreadFunc start_routine, void* arg) {
|
||||
ThreadEntry* entry = (ThreadEntry*) malloc(sizeof(ThreadEntry));
|
||||
entry->start_routine = start_routine;
|
||||
entry->arg = arg;
|
||||
|
||||
int result = _orig_pthread_create(thread, attr, thread_start_wrapper, entry);
|
||||
if (result != 0) {
|
||||
free(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void pthread_exit_hook(void* retval) {
|
||||
Log::debug("thread_exit: 0x%lx", (unsigned long)(uintptr_t)pthread_self());
|
||||
CpuEngine::onThreadEnd();
|
||||
|
||||
_orig_pthread_exit(retval);
|
||||
}
|
||||
|
||||
|
||||
typedef void* (*dlopen_t)(const char*, int);
|
||||
static dlopen_t _orig_dlopen = NULL;
|
||||
|
||||
static void* dlopen_hook_impl(const char* filename, int flags, bool patch) {
|
||||
Log::debug("dlopen: %s", filename);
|
||||
void* result = _orig_dlopen(filename, flags);
|
||||
if (result != NULL && filename != NULL) {
|
||||
Profiler::instance()->updateSymbols(false);
|
||||
if (patch) {
|
||||
Hooks::patchLibraries();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void* dlopen_hook(const char* filename, int flags) {
|
||||
return dlopen_hook_impl(filename, flags, true);
|
||||
}
|
||||
|
||||
|
||||
// LD_PRELOAD hooks
|
||||
|
||||
extern "C" DLLEXPORT
|
||||
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, ThreadFunc start_routine, void* arg) {
|
||||
if (_orig_pthread_create == NULL) {
|
||||
_orig_pthread_create = (pthread_create_t)dlsym(RTLD_NEXT, "pthread_create");
|
||||
}
|
||||
if (Hooks::initialized()) {
|
||||
return pthread_create_hook(thread, attr, start_routine, arg);
|
||||
}
|
||||
return _orig_pthread_create(thread, attr, start_routine, arg);
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT
|
||||
void pthread_exit(void* retval) {
|
||||
if (_orig_pthread_exit == NULL) {
|
||||
_orig_pthread_exit = (pthread_exit_t)dlsym(RTLD_NEXT, "pthread_exit");
|
||||
}
|
||||
if (Hooks::initialized()) {
|
||||
pthread_exit_hook(retval);
|
||||
} else {
|
||||
_orig_pthread_exit(retval);
|
||||
}
|
||||
abort(); // to suppress gcc warning
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT
|
||||
void* dlopen(const char* filename, int flags) {
|
||||
if (_orig_dlopen == NULL) {
|
||||
_orig_dlopen = (dlopen_t)dlsym(RTLD_NEXT, "dlopen");
|
||||
}
|
||||
if (Hooks::initialized()) {
|
||||
return dlopen_hook_impl(filename, flags, false);
|
||||
}
|
||||
return _orig_dlopen(filename, flags);
|
||||
}
|
||||
|
||||
|
||||
Mutex Hooks::_patch_lock;
|
||||
int Hooks::_patched_libs = 0;
|
||||
bool Hooks::_initialized = false;
|
||||
|
||||
bool Hooks::init(bool attach) {
|
||||
if (!__sync_bool_compare_and_swap(&_initialized, false, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Profiler::instance()->updateSymbols(false);
|
||||
Profiler::setupSignalHandlers();
|
||||
|
||||
if (attach) {
|
||||
_orig_pthread_create = pthread_create;
|
||||
_orig_pthread_exit = pthread_exit;
|
||||
_orig_dlopen = dlopen;
|
||||
patchLibraries();
|
||||
}
|
||||
|
||||
atexit(shutdown);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Hooks::shutdown() {
|
||||
Profiler::instance()->shutdown(_global_args);
|
||||
}
|
||||
|
||||
void Hooks::patchLibraries() {
|
||||
MutexLocker ml(_patch_lock);
|
||||
|
||||
CodeCacheArray* native_libs = Profiler::instance()->nativeLibs();
|
||||
int native_lib_count = native_libs->count();
|
||||
|
||||
while (_patched_libs < native_lib_count) {
|
||||
CodeCache* cc = (*native_libs)[_patched_libs++];
|
||||
cc->patchImport(im_dlopen, (void*)dlopen_hook);
|
||||
cc->patchImport(im_pthread_create, (void*)pthread_create_hook);
|
||||
cc->patchImport(im_pthread_exit, (void*)pthread_exit_hook);
|
||||
}
|
||||
}
|
||||
28
src/hooks.h
Normal file
28
src/hooks.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _HOOKS_H
|
||||
#define _HOOKS_H
|
||||
|
||||
#include "mutex.h"
|
||||
|
||||
|
||||
class Hooks {
|
||||
private:
|
||||
static Mutex _patch_lock;
|
||||
static int _patched_libs;
|
||||
static bool _initialized;
|
||||
|
||||
public:
|
||||
static bool init(bool attach);
|
||||
static void shutdown();
|
||||
static void patchLibraries();
|
||||
|
||||
static bool initialized() {
|
||||
return _initialized;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _HOOKS_H
|
||||
32
src/incbin.h
Normal file
32
src/incbin.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _INCBIN_H
|
||||
#define _INCBIN_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define INCBIN_SECTION ".const_data"
|
||||
# define INCBIN_SYMBOL "_"
|
||||
#else
|
||||
# define INCBIN_SECTION ".section \".rodata\", \"a\", @progbits"
|
||||
# define INCBIN_SYMBOL
|
||||
#endif
|
||||
|
||||
#define INCBIN(NAME, FILE) \
|
||||
extern const char NAME[];\
|
||||
extern const char NAME##_END[];\
|
||||
asm(INCBIN_SECTION "\n"\
|
||||
".global " INCBIN_SYMBOL #NAME "\n"\
|
||||
INCBIN_SYMBOL #NAME ":\n"\
|
||||
".incbin \"" FILE "\"\n"\
|
||||
".global " INCBIN_SYMBOL #NAME "_END\n"\
|
||||
INCBIN_SYMBOL #NAME "_END:\n"\
|
||||
".byte 0x00\n"\
|
||||
".previous\n"\
|
||||
);
|
||||
|
||||
#define INCBIN_SIZEOF(NAME) (NAME##_END - NAME)
|
||||
|
||||
#endif // _INCBIN_H
|
||||
@@ -1,51 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "arch.h"
|
||||
#include "incbin.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmEntry.h"
|
||||
#include "instrument.h"
|
||||
|
||||
|
||||
// A class with a single native recordSample() method
|
||||
static const char INSTRUMENT_CLASS[] =
|
||||
"\xCA\xFE\xBA\xBE" // magic
|
||||
"\x00\x00\x00\x32" // version: 50
|
||||
"\x00\x07" // constant_pool_count: 7
|
||||
"\x07\x00\x02" // #1 = CONSTANT_Class: #2
|
||||
"\x01\x00\x17one/profiler/Instrument" // #2 = CONSTANT_Utf8: "one/profiler/Instrument"
|
||||
"\x07\x00\x04" // #3 = CONSTANT_Class: #4
|
||||
"\x01\x00\x10java/lang/Object" // #4 = CONSTANT_Utf8: "java/lang/Object"
|
||||
"\x01\x00\x0CrecordSample" // #5 = CONSTANT_Utf8: "recordSample"
|
||||
"\x01\x00\x03()V" // #6 = CONSTANT_Utf8: "()V"
|
||||
"\x00\x21" // access_flags: public super
|
||||
"\x00\x01" // this_class: #1
|
||||
"\x00\x03" // super_class: #3
|
||||
"\x00\x00" // interfaces_count: 0
|
||||
"\x00\x00" // fields_count: 0
|
||||
"\x00\x01" // methods_count: 1
|
||||
"\x01\x09" // access_flags: public static native
|
||||
"\x00\x05" // name_index: #5
|
||||
"\x00\x06" // descriptor_index: #6
|
||||
"\x00\x00" // attributes_count: 0
|
||||
"\x00"; // attributes_count: 0
|
||||
INCBIN(INSTRUMENT_CLASS, "src/helper/one/profiler/Instrument.class")
|
||||
|
||||
|
||||
enum ConstantTag {
|
||||
@@ -255,6 +224,7 @@ class BytecodeRewriter {
|
||||
void rewriteCode();
|
||||
void rewriteBytecodeTable(int data_len);
|
||||
void rewriteStackMapTable();
|
||||
void rewriteVerificationTypeInfo();
|
||||
void rewriteAttributes(Scope scope);
|
||||
void rewriteMembers(Scope scope);
|
||||
bool rewriteClass();
|
||||
@@ -315,7 +285,7 @@ void BytecodeRewriter::rewriteCode() {
|
||||
put32(code_length + EXTRA_BYTECODES);
|
||||
|
||||
// invokestatic "one/profiler/Instrument.recordSample()V"
|
||||
// nop after invoke helps to prepend StackMapTable without rewriting
|
||||
// nop ensures that tableswitch/lookupswitch needs no realignment
|
||||
put8(0xb8);
|
||||
put16(_cpool_len);
|
||||
put8(0);
|
||||
@@ -366,7 +336,53 @@ void BytecodeRewriter::rewriteStackMapTable() {
|
||||
|
||||
// Prepend same_frame
|
||||
put8(EXTRA_BYTECODES - 1);
|
||||
put(get(attribute_length - 2), attribute_length - 2);
|
||||
|
||||
for (int i = 0; i < number_of_entries; i++) {
|
||||
u8 frame_type = get8();
|
||||
put8(frame_type);
|
||||
|
||||
if (frame_type <= 63) {
|
||||
// same_frame
|
||||
} else if (frame_type <= 127) {
|
||||
// same_locals_1_stack_item_frame
|
||||
rewriteVerificationTypeInfo();
|
||||
} else if (frame_type == 247) {
|
||||
// same_locals_1_stack_item_frame_extended
|
||||
put16(get16());
|
||||
rewriteVerificationTypeInfo();
|
||||
} else if (frame_type <= 251) {
|
||||
// chop_frame or same_frame_extended
|
||||
put16(get16());
|
||||
} else if (frame_type <= 254) {
|
||||
// append_frame
|
||||
put16(get16());
|
||||
for (int j = 0; j < frame_type - 251; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
} else {
|
||||
// full_frame
|
||||
put16(get16());
|
||||
u16 number_of_locals = get16();
|
||||
put16(number_of_locals);
|
||||
for (int j = 0; j < number_of_locals; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
u16 number_of_stack_items = get16();
|
||||
put16(number_of_stack_items);
|
||||
for (int j = 0; j < number_of_stack_items; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteVerificationTypeInfo() {
|
||||
u8 tag = get8();
|
||||
put8(tag);
|
||||
if (tag >= 7) {
|
||||
// Adjust ITEM_Uninitialized offset
|
||||
put16(tag == 8 ? EXTRA_BYTECODES + get16() : get16());
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteAttributes(Scope scope) {
|
||||
@@ -484,12 +500,16 @@ volatile bool Instrument::_running;
|
||||
|
||||
Error Instrument::check(Arguments& args) {
|
||||
if (!_instrument_class_loaded) {
|
||||
if (!VM::loaded()) {
|
||||
return Error("Profiling event is not supported with non-Java processes");
|
||||
}
|
||||
|
||||
JNIEnv* jni = VM::jni();
|
||||
const JNINativeMethod native_method = {(char*)"recordSample", (char*)"()V", (void*)recordSample};
|
||||
|
||||
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, sizeof(INSTRUMENT_CLASS));
|
||||
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, INCBIN_SIZEOF(INSTRUMENT_CLASS));
|
||||
if (cls == NULL || jni->RegisterNatives(cls, &native_method, 1) != 0) {
|
||||
jni->ExceptionClear();
|
||||
jni->ExceptionDescribe();
|
||||
return Error("Could not load Instrument class");
|
||||
}
|
||||
|
||||
@@ -509,7 +529,7 @@ Error Instrument::start(Arguments& args) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
|
||||
setupTargetClassAndMethod(args._event_desc);
|
||||
setupTargetClassAndMethod(args._event);
|
||||
_interval = args._interval ? args._interval : 1;
|
||||
_calls = 0;
|
||||
_running = true;
|
||||
@@ -588,6 +608,6 @@ void JNICALL Instrument::recordSample(JNIEnv* jni, jobject unused) {
|
||||
|
||||
if (_interval <= 1 || ((atomicInc(_calls) + 1) % _interval) == 0) {
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
|
||||
Profiler::instance()->recordSample(NULL, _interval, INSTRUMENTED_METHOD, &event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2019 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _INSTRUMENT_H
|
||||
@@ -30,18 +19,14 @@ class Instrument : public Engine {
|
||||
static volatile bool _running;
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "instrument";
|
||||
const char* title() {
|
||||
return "Java method profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "calls";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
@@ -1,35 +1,15 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <sys/time.h>
|
||||
#include "itimer.h"
|
||||
#include "j9StackTraces.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
long ITimer::_interval;
|
||||
|
||||
|
||||
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
Error ITimer::check(Arguments& args) {
|
||||
OS::installSignalHandler(SIGPROF, NULL, SIG_IGN);
|
||||
|
||||
@@ -49,11 +29,22 @@ Error ITimer::start(Arguments& args) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
|
||||
_cstack = args._cstack;
|
||||
_signal = SIGPROF;
|
||||
|
||||
OS::installSignalHandler(SIGPROF, signalHandler);
|
||||
if (VM::isOpenJ9()) {
|
||||
if (_cstack == CSTACK_DEFAULT) _cstack = CSTACK_DWARF;
|
||||
OS::installSignalHandler(SIGPROF, signalHandlerJ9);
|
||||
Error error = J9StackTraces::start(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
} else {
|
||||
OS::installSignalHandler(SIGPROF, signalHandler);
|
||||
}
|
||||
|
||||
long sec = _interval / 1000000000;
|
||||
long usec = (_interval % 1000000000) / 1000;
|
||||
time_t sec = _interval / 1000000000;
|
||||
suseconds_t usec = (_interval % 1000000000) / 1000;
|
||||
struct itimerval tv = {{sec, usec}, {sec, usec}};
|
||||
|
||||
if (setitimer(ITIMER_PROF, &tv, NULL) != 0) {
|
||||
@@ -66,4 +57,6 @@ Error ITimer::start(Arguments& args) {
|
||||
void ITimer::stop() {
|
||||
struct itimerval tv = {{0, 0}, {0, 0}};
|
||||
setitimer(ITIMER_PROF, &tv, NULL);
|
||||
|
||||
J9StackTraces::stop();
|
||||
}
|
||||
|
||||
33
src/itimer.h
33
src/itimer.h
@@ -1,41 +1,16 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ITIMER_H
|
||||
#define _ITIMER_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
#include "cpuEngine.h"
|
||||
|
||||
|
||||
class ITimer : public Engine {
|
||||
private:
|
||||
static long _interval;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
class ITimer : public CpuEngine {
|
||||
public:
|
||||
const char* name() {
|
||||
return "itimer";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
60
src/j9Ext.cpp
Normal file
60
src/j9Ext.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "j9Ext.h"
|
||||
#include "j9ObjectSampler.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
jvmtiEnv* J9Ext::_jvmti;
|
||||
|
||||
void* (*J9Ext::_j9thread_self)() = NULL;
|
||||
|
||||
jvmtiExtensionFunction J9Ext::_GetOSThreadID = NULL;
|
||||
jvmtiExtensionFunction J9Ext::_GetJ9vmThread = NULL;
|
||||
jvmtiExtensionFunction J9Ext::_GetStackTraceExtended = NULL;
|
||||
jvmtiExtensionFunction J9Ext::_GetAllStackTracesExtended = NULL;
|
||||
|
||||
int J9Ext::InstrumentableObjectAlloc_id = -1;
|
||||
|
||||
|
||||
// Look for OpenJ9-specific JVM TI extension
|
||||
bool J9Ext::initialize(jvmtiEnv* jvmti, const void* j9thread_self) {
|
||||
_jvmti = jvmti;
|
||||
_j9thread_self = (void* (*)())j9thread_self;
|
||||
|
||||
jint ext_count;
|
||||
jvmtiExtensionFunctionInfo* ext_functions;
|
||||
if (jvmti->GetExtensionFunctions(&ext_count, &ext_functions) == 0) {
|
||||
for (int i = 0; i < ext_count; i++) {
|
||||
if (strcmp(ext_functions[i].id, "com.ibm.GetOSThreadID") == 0) {
|
||||
_GetOSThreadID = ext_functions[i].func;
|
||||
} else if (strcmp(ext_functions[i].id, "com.ibm.GetJ9vmThread") == 0) {
|
||||
_GetJ9vmThread = ext_functions[i].func;
|
||||
} else if (strcmp(ext_functions[i].id, "com.ibm.GetStackTraceExtended") == 0) {
|
||||
_GetStackTraceExtended = ext_functions[i].func;
|
||||
} else if (strcmp(ext_functions[i].id, "com.ibm.GetAllStackTracesExtended") == 0) {
|
||||
_GetAllStackTracesExtended = ext_functions[i].func;
|
||||
}
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)ext_functions);
|
||||
}
|
||||
|
||||
jvmtiExtensionEventInfo* ext_events;
|
||||
if (jvmti->GetExtensionEvents(&ext_count, &ext_events) == 0) {
|
||||
for (int i = 0; i < ext_count; i++) {
|
||||
if (strcmp(ext_events[i].id, "com.ibm.InstrumentableObjectAlloc") == 0) {
|
||||
InstrumentableObjectAlloc_id = ext_events[i].extension_event_index;
|
||||
// If we don't set a callback now, we won't be able to enable it later in runtime
|
||||
jvmti->SetExtensionEventCallback(InstrumentableObjectAlloc_id, (jvmtiExtensionEvent)J9ObjectSampler::JavaObjectAlloc);
|
||||
jvmti->SetExtensionEventCallback(InstrumentableObjectAlloc_id, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)ext_events);
|
||||
}
|
||||
|
||||
return _GetOSThreadID != NULL && _GetStackTraceExtended != NULL && _GetAllStackTracesExtended != NULL;
|
||||
}
|
||||
80
src/j9Ext.h
Normal file
80
src/j9Ext.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _J9EXT_H
|
||||
#define _J9EXT_H
|
||||
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
#define JVMTI_EXT(f, ...) ((jvmtiError (*)(jvmtiEnv*, __VA_ARGS__))f)
|
||||
|
||||
struct jvmtiFrameInfoExtended {
|
||||
jmethodID method;
|
||||
jlocation location;
|
||||
jlocation machinepc;
|
||||
jint type;
|
||||
void* native_frame_address;
|
||||
};
|
||||
|
||||
struct jvmtiStackInfoExtended {
|
||||
jthread thread;
|
||||
jint state;
|
||||
jvmtiFrameInfoExtended* frame_buffer;
|
||||
jint frame_count;
|
||||
};
|
||||
|
||||
enum {
|
||||
SHOW_COMPILED_FRAMES = 4,
|
||||
SHOW_INLINED_FRAMES = 8
|
||||
};
|
||||
|
||||
|
||||
class J9Ext {
|
||||
private:
|
||||
static jvmtiEnv* _jvmti;
|
||||
|
||||
static void* (*_j9thread_self)();
|
||||
|
||||
static jvmtiExtensionFunction _GetOSThreadID;
|
||||
static jvmtiExtensionFunction _GetJ9vmThread;
|
||||
static jvmtiExtensionFunction _GetStackTraceExtended;
|
||||
static jvmtiExtensionFunction _GetAllStackTracesExtended;
|
||||
|
||||
public:
|
||||
static bool initialize(jvmtiEnv* jvmti, const void* j9thread_self);
|
||||
|
||||
static int GetOSThreadID(jthread thread) {
|
||||
jlong thread_id;
|
||||
return JVMTI_EXT(_GetOSThreadID, jthread, jlong*)(_jvmti, thread, &thread_id) == 0 ? (int)thread_id : -1;
|
||||
}
|
||||
|
||||
static JNIEnv* GetJ9vmThread(jthread thread) {
|
||||
JNIEnv* result;
|
||||
return JVMTI_EXT(_GetJ9vmThread, jthread, JNIEnv**)(_jvmti, thread, &result) == 0 ? result : NULL;
|
||||
}
|
||||
|
||||
static jvmtiError GetStackTraceExtended(jthread thread, jint start_depth, jint max_frame_count,
|
||||
void* frame_buffer, jint* count_ptr) {
|
||||
return JVMTI_EXT(_GetStackTraceExtended, jint, jthread, jint, jint, void*, jint*)(
|
||||
_jvmti, SHOW_COMPILED_FRAMES | SHOW_INLINED_FRAMES,
|
||||
thread, start_depth, max_frame_count, frame_buffer, count_ptr);
|
||||
}
|
||||
|
||||
static jvmtiError GetAllStackTracesExtended(jint max_frame_count, void** stack_info_ptr, jint* thread_count_ptr) {
|
||||
return JVMTI_EXT(_GetAllStackTracesExtended, jint, jint, void**, jint*)(
|
||||
_jvmti, SHOW_COMPILED_FRAMES | SHOW_INLINED_FRAMES,
|
||||
max_frame_count, stack_info_ptr, thread_count_ptr);
|
||||
}
|
||||
|
||||
static void* j9thread_self() {
|
||||
return _j9thread_self != NULL ? _j9thread_self() : NULL;
|
||||
}
|
||||
|
||||
static int InstrumentableObjectAlloc_id;
|
||||
};
|
||||
|
||||
|
||||
#endif // _J9EXT_H
|
||||
60
src/j9ObjectSampler.cpp
Normal file
60
src/j9ObjectSampler.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "j9ObjectSampler.h"
|
||||
#include "j9Ext.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
void J9ObjectSampler::JavaObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
if (_enabled && updateCounter(_allocated_bytes, size, _interval)) {
|
||||
recordAllocation(jvmti, jni, ALLOC_SAMPLE, object, object_klass, size);
|
||||
}
|
||||
}
|
||||
|
||||
void J9ObjectSampler::VMObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
if (_enabled && updateCounter(_allocated_bytes, size, _interval)) {
|
||||
recordAllocation(jvmti, jni, ALLOC_OUTSIDE_TLAB, object, object_klass, size);
|
||||
}
|
||||
}
|
||||
|
||||
Error J9ObjectSampler::check(Arguments& args) {
|
||||
if (J9Ext::InstrumentableObjectAlloc_id < 0) {
|
||||
return Error("InstrumentableObjectAlloc is not supported on this JVM");
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error J9ObjectSampler::start(Arguments& args) {
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._alloc > 0 ? args._alloc : DEFAULT_ALLOC_INTERVAL;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
initLiveRefs(args._live);
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
if (jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, (jvmtiExtensionEvent)JavaObjectAlloc) != 0) {
|
||||
return Error("Could not enable InstrumentableObjectAlloc callback");
|
||||
}
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9ObjectSampler::stop() {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
|
||||
jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, NULL);
|
||||
|
||||
dumpLiveRefs();
|
||||
}
|
||||
25
src/j9ObjectSampler.h
Normal file
25
src/j9ObjectSampler.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _J9OBJECTSAMPLER_H
|
||||
#define _J9OBJECTSAMPLER_H
|
||||
|
||||
#include "objectSampler.h"
|
||||
|
||||
|
||||
class J9ObjectSampler : public ObjectSampler {
|
||||
public:
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static void JNICALL JavaObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size);
|
||||
|
||||
static void JNICALL VMObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size);
|
||||
};
|
||||
|
||||
#endif // _J9OBJECTSAMPLER_H
|
||||
175
src/j9StackTraces.cpp
Normal file
175
src/j9StackTraces.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <map>
|
||||
#include "j9StackTraces.h"
|
||||
#include "j9Ext.h"
|
||||
#include "profiler.h"
|
||||
#include "perfEvents.h"
|
||||
|
||||
|
||||
enum {
|
||||
J9_STOPPED = 0x40,
|
||||
J9_HALT_THREAD_INSPECTION = 0x8000
|
||||
};
|
||||
|
||||
class J9VMThread {
|
||||
private:
|
||||
uintptr_t _unused1[10];
|
||||
uintptr_t _overflow_mark;
|
||||
uintptr_t _unused2[8];
|
||||
uintptr_t _flags;
|
||||
|
||||
public:
|
||||
uintptr_t getAndSetFlag(uintptr_t flag) {
|
||||
return __sync_fetch_and_or(&_flags, flag);
|
||||
}
|
||||
|
||||
void clearFlag(uintptr_t flag) {
|
||||
__sync_fetch_and_and(&_flags, ~flag);
|
||||
}
|
||||
|
||||
void setOverflowMark() {
|
||||
__atomic_store_n(&_overflow_mark, (uintptr_t)-1, __ATOMIC_RELEASE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
pthread_t J9StackTraces::_thread = 0;
|
||||
int J9StackTraces::_max_stack_depth;
|
||||
int J9StackTraces::_pipe[2];
|
||||
|
||||
static JNIEnv* _self_env = NULL;
|
||||
|
||||
|
||||
Error J9StackTraces::start(Arguments& args) {
|
||||
_max_stack_depth = args._jstackdepth;
|
||||
|
||||
if (pipe(_pipe) != 0) {
|
||||
return Error("Failed to create pipe");
|
||||
}
|
||||
fcntl(_pipe[1], F_SETFL, O_NONBLOCK);
|
||||
|
||||
if (pthread_create(&_thread, NULL, threadEntry, NULL) != 0) {
|
||||
close(_pipe[0]);
|
||||
close(_pipe[1]);
|
||||
return Error("Unable to create sampler thread");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9StackTraces::stop() {
|
||||
if (_thread != 0) {
|
||||
close(_pipe[1]);
|
||||
pthread_join(_thread, NULL);
|
||||
close(_pipe[0]);
|
||||
_thread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void J9StackTraces::timerLoop() {
|
||||
JNIEnv* jni = VM::attachThread("Async-profiler Sampler");
|
||||
__atomic_store_n(&_self_env, jni, __ATOMIC_RELEASE);
|
||||
|
||||
jni->PushLocalFrame(64);
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
char notification_buf[65536];
|
||||
std::map<void*, jthread> known_threads;
|
||||
|
||||
int max_frames = _max_stack_depth + MAX_J9_NATIVE_FRAMES + RESERVED_FRAMES;
|
||||
ASGCT_CallFrame* frames = (ASGCT_CallFrame*)malloc(max_frames * sizeof(ASGCT_CallFrame));
|
||||
jvmtiFrameInfoExtended* jvmti_frames = (jvmtiFrameInfoExtended*)malloc(max_frames * sizeof(jvmtiFrameInfoExtended));
|
||||
|
||||
while (true) {
|
||||
ssize_t bytes = read(_pipe[0], notification_buf, sizeof(notification_buf));
|
||||
if (bytes <= 0) {
|
||||
if (bytes < 0 && errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ssize_t ptr = 0;
|
||||
while (ptr < bytes) {
|
||||
J9StackTraceNotification* notif = (J9StackTraceNotification*)(notification_buf + ptr);
|
||||
|
||||
jthread thread = known_threads[notif->env];
|
||||
jint num_jvmti_frames;
|
||||
if (thread == NULL || J9Ext::GetStackTraceExtended(thread, 0, _max_stack_depth, jvmti_frames, &num_jvmti_frames) != 0) {
|
||||
jni->PopLocalFrame(NULL);
|
||||
jni->PushLocalFrame(64);
|
||||
|
||||
jint thread_count;
|
||||
jthread* threads;
|
||||
if (jvmti->GetAllThreads(&thread_count, &threads) == 0) {
|
||||
known_threads.clear();
|
||||
for (jint i = 0; i < thread_count; i++) {
|
||||
known_threads[J9Ext::GetJ9vmThread(threads[i])] = threads[i];
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)threads);
|
||||
}
|
||||
|
||||
if ((thread = known_threads[notif->env]) == NULL ||
|
||||
J9Ext::GetStackTraceExtended(thread, 0, _max_stack_depth, jvmti_frames, &num_jvmti_frames) != 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int num_frames = Profiler::instance()->convertNativeTrace(notif->num_frames, notif->addr, frames);
|
||||
|
||||
for (int j = 0; j < num_jvmti_frames; j++) {
|
||||
frames[num_frames].method_id = jvmti_frames[j].method;
|
||||
frames[num_frames].bci = FrameType::encode(jvmti_frames[j].type, jvmti_frames[j].location);
|
||||
num_frames++;
|
||||
}
|
||||
|
||||
int tid = J9Ext::GetOSThreadID(thread);
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordExternalSample(notif->counter, tid, EXECUTION_SAMPLE, &event, num_frames, frames);
|
||||
|
||||
ptr += notif->size();
|
||||
}
|
||||
}
|
||||
|
||||
free(jvmti_frames);
|
||||
free(frames);
|
||||
|
||||
__atomic_store_n(&_self_env, NULL, __ATOMIC_RELEASE);
|
||||
VM::detachThread();
|
||||
}
|
||||
|
||||
void J9StackTraces::checkpoint(u64 counter, J9StackTraceNotification* notif) {
|
||||
JNIEnv* self_env = __atomic_load_n(&_self_env, __ATOMIC_ACQUIRE);
|
||||
if (self_env == NULL) {
|
||||
// Sampler thread is not ready
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv* env = VM::jni();
|
||||
if (env != NULL && env != self_env) {
|
||||
J9VMThread* vm_thread = (J9VMThread*)env;
|
||||
uintptr_t flags = vm_thread->getAndSetFlag(J9_HALT_THREAD_INSPECTION);
|
||||
if (flags & J9_HALT_THREAD_INSPECTION) {
|
||||
// Thread is already scheduled for inspection, no need to notify again
|
||||
return;
|
||||
} else if (!(flags & J9_STOPPED)) {
|
||||
vm_thread->setOverflowMark();
|
||||
notif->env = env;
|
||||
notif->counter = counter;
|
||||
if (write(_pipe[1], notif, notif->size()) > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Something went wrong - rollback
|
||||
vm_thread->clearFlag(J9_HALT_THREAD_INSPECTION);
|
||||
}
|
||||
}
|
||||
49
src/j9StackTraces.h
Normal file
49
src/j9StackTraces.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _J9STACKTRACES_H
|
||||
#define _J9STACKTRACES_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
|
||||
|
||||
const int MAX_J9_NATIVE_FRAMES = 128;
|
||||
|
||||
struct J9StackTraceNotification {
|
||||
void* env;
|
||||
u64 counter;
|
||||
int num_frames;
|
||||
int reserved;
|
||||
const void* addr[MAX_J9_NATIVE_FRAMES];
|
||||
|
||||
size_t size() {
|
||||
return sizeof(*this) - sizeof(this->addr) + num_frames * sizeof(const void*);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class J9StackTraces {
|
||||
private:
|
||||
static pthread_t _thread;
|
||||
static int _max_stack_depth;
|
||||
static int _pipe[2];
|
||||
|
||||
static void* threadEntry(void* unused) {
|
||||
timerLoop();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void timerLoop();
|
||||
|
||||
public:
|
||||
static Error start(Arguments& args);
|
||||
static void stop();
|
||||
|
||||
static void checkpoint(u64 counter, J9StackTraceNotification* notif);
|
||||
};
|
||||
|
||||
#endif // _J9STACKTRACES_H
|
||||
75
src/j9WallClock.cpp
Normal file
75
src/j9WallClock.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "j9WallClock.h"
|
||||
#include "j9Ext.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
long J9WallClock::_interval;
|
||||
|
||||
Error J9WallClock::start(Arguments& args) {
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL * 5;
|
||||
_max_stack_depth = args._jstackdepth;
|
||||
|
||||
_running = true;
|
||||
|
||||
if (pthread_create(&_thread, NULL, threadEntry, this) != 0) {
|
||||
return Error("Unable to create timer thread");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9WallClock::stop() {
|
||||
_running = false;
|
||||
pthread_kill(_thread, WAKEUP_SIGNAL);
|
||||
pthread_join(_thread, NULL);
|
||||
}
|
||||
|
||||
void J9WallClock::timerLoop() {
|
||||
JNIEnv* jni = VM::attachThread("Async-profiler Sampler");
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
|
||||
int max_frames = _max_stack_depth + MAX_NATIVE_FRAMES + RESERVED_FRAMES;
|
||||
ASGCT_CallFrame* frames = (ASGCT_CallFrame*)malloc(max_frames * sizeof(ASGCT_CallFrame));
|
||||
|
||||
while (_running) {
|
||||
if (!_enabled) {
|
||||
OS::sleep(_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
jni->PushLocalFrame(64);
|
||||
|
||||
jvmtiStackInfoExtended* stack_infos;
|
||||
jint thread_count;
|
||||
if (J9Ext::GetAllStackTracesExtended(_max_stack_depth, (void**)&stack_infos, &thread_count) == 0) {
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
jvmtiStackInfoExtended* si = &stack_infos[i];
|
||||
for (int j = 0; j < si->frame_count; j++) {
|
||||
jvmtiFrameInfoExtended* fi = &si->frame_buffer[j];
|
||||
frames[j].method_id = fi->method;
|
||||
frames[j].bci = FrameType::encode(fi->type, fi->location);
|
||||
}
|
||||
|
||||
int tid = J9Ext::GetOSThreadID(si->thread);
|
||||
ExecutionEvent event;
|
||||
event._thread_state = (si->state & JVMTI_THREAD_STATE_RUNNABLE) ? THREAD_RUNNING : THREAD_SLEEPING;
|
||||
Profiler::instance()->recordExternalSample(_interval, tid, EXECUTION_SAMPLE, &event, si->frame_count, frames);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)stack_infos);
|
||||
}
|
||||
|
||||
jni->PopLocalFrame(NULL);
|
||||
|
||||
OS::sleep(_interval);
|
||||
}
|
||||
|
||||
free(frames);
|
||||
|
||||
VM::detachThread();
|
||||
}
|
||||
41
src/j9WallClock.h
Normal file
41
src/j9WallClock.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright The async-profiler authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _J9WALLCLOCK_H
|
||||
#define _J9WALLCLOCK_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class J9WallClock : public Engine {
|
||||
private:
|
||||
static long _interval;
|
||||
|
||||
int _max_stack_depth;
|
||||
volatile bool _running;
|
||||
pthread_t _thread;
|
||||
|
||||
static void* threadEntry(void* wall_clock) {
|
||||
((J9WallClock*)wall_clock)->timerLoop();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void timerLoop();
|
||||
|
||||
public:
|
||||
const char* title() {
|
||||
return "Wall clock profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif // _J9WALLCLOCK_H
|
||||
@@ -1,403 +1,41 @@
|
||||
/*
|
||||
* Copyright 2016 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Copyright The jattach authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAX_PATH 1024
|
||||
#define TMP_PATH (MAX_PATH - 64)
|
||||
|
||||
static char tmp_path[TMP_PATH] = {0};
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
extern int is_openj9_process(int pid);
|
||||
extern int jattach_openj9(int pid, int nspid, int argc, char** argv, int print_output);
|
||||
extern int jattach_hotspot(int pid, int nspid, int argc, char** argv, int print_output);
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
// Parse /proc/pid/status to find process credentials
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/status", pid);
|
||||
FILE* status_file = fopen(path, "r");
|
||||
if (status_file == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* line = NULL;
|
||||
size_t size;
|
||||
|
||||
while (getline(&line, &size, status_file) != -1) {
|
||||
if (strncmp(line, "Uid:", 4) == 0) {
|
||||
// Get the effective UID, which is the second value in the line
|
||||
*uid = (uid_t)atoi(strchr(line + 5, '\t'));
|
||||
} else if (strncmp(line, "Gid:", 4) == 0) {
|
||||
// Get the effective GID, which is the second value in the line
|
||||
*gid = (gid_t)atoi(strchr(line + 5, '\t'));
|
||||
} else if (strncmp(line, "NStgid:", 7) == 0) {
|
||||
// PID namespaces can be nested; the last one is the innermost one
|
||||
*nspid = atoi(strrchr(line, '\t'));
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(status_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int get_tmp_path(int pid) {
|
||||
// A process may have its own root path (when running in chroot environment)
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/root", pid);
|
||||
|
||||
// Append /tmp to the resolved root symlink
|
||||
ssize_t path_size = readlink(path, tmp_path, sizeof(tmp_path) - 10);
|
||||
strcpy(tmp_path + (path_size > 1 ? path_size : 0), "/tmp");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int enter_mount_ns(int pid) {
|
||||
#ifdef __NR_setns
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
|
||||
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat("/proc/self/ns/mnt", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
|
||||
// Don't try to call setns() if we're in the same namespace already
|
||||
if (oldns_stat.st_ino != newns_stat.st_ino) {
|
||||
int newns = open(path, O_RDONLY);
|
||||
if (newns < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Some ancient Linux distributions do not have setns() function
|
||||
int result = syscall(__NR_setns, newns, 0);
|
||||
close(newns);
|
||||
return result < 0 ? 0 : 1;
|
||||
}
|
||||
}
|
||||
#endif // __NR_setns
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The first line of /proc/pid/sched looks like
|
||||
// java (1234, #threads: 12)
|
||||
// where 1234 is the required host PID
|
||||
int sched_get_host_pid(const char* path) {
|
||||
static char* line = NULL;
|
||||
size_t size;
|
||||
int result = -1;
|
||||
|
||||
FILE* sched_file = fopen(path, "r");
|
||||
if (sched_file != NULL) {
|
||||
if (getline(&line, &size, sched_file) != -1) {
|
||||
char* c = strrchr(line, '(');
|
||||
if (c != NULL) {
|
||||
result = atoi(c + 1);
|
||||
}
|
||||
}
|
||||
fclose(sched_file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
|
||||
// Fortunately, /proc/pid/sched in a container exposes a host PID,
|
||||
// so the idea is to scan all container PIDs to find which one matches the host PID.
|
||||
int alt_lookup_nspid(int pid) {
|
||||
int namespace_differs = 0;
|
||||
char path[300];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
|
||||
|
||||
// Don't bother looking for container PID if we are already in the same PID namespace
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
|
||||
if (oldns_stat.st_ino == newns_stat.st_ino) {
|
||||
return pid;
|
||||
}
|
||||
namespace_differs = 1;
|
||||
}
|
||||
|
||||
// Otherwise browse all PIDs in the namespace of the target process
|
||||
// trying to find which one corresponds to the host PID
|
||||
snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
|
||||
DIR* dir = opendir(path);
|
||||
if (dir != NULL) {
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
|
||||
// Check if /proc/<container-pid>/sched points back to <host-pid>
|
||||
snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
|
||||
if (sched_get_host_pid(path) == pid) {
|
||||
closedir(dir);
|
||||
return atoi(entry->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
if (namespace_differs) {
|
||||
printf("WARNING: couldn't find container pid of the target process\n");
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
size_t len = sizeof(info);
|
||||
|
||||
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*uid = info.kp_eproc.e_ucred.cr_uid;
|
||||
*gid = info.kp_eproc.e_ucred.cr_gid;
|
||||
*nspid = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// macOS has a secure per-user temporary directory
|
||||
int get_tmp_path(int pid) {
|
||||
int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, tmp_path, sizeof(tmp_path));
|
||||
return path_size > 0 && path_size <= sizeof(tmp_path);
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_mount_ns(int pid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Not used on macOS and FreeBSD
|
||||
int alt_lookup_nspid(int pid) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
#else // __FreeBSD__
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
size_t len = sizeof(info);
|
||||
|
||||
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*uid = info.ki_uid;
|
||||
*gid = info.ki_groups[0];
|
||||
*nspid = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Use default /tmp path on FreeBSD
|
||||
int get_tmp_path(int pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_mount_ns(int pid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Not used on macOS and FreeBSD
|
||||
int alt_lookup_nspid(int pid) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
#endif
|
||||
int mnt_changed = 0;
|
||||
|
||||
|
||||
// Check if remote JVM has already opened socket for Dynamic Attach
|
||||
static int check_socket(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
|
||||
}
|
||||
|
||||
// Check if a file is owned by current user
|
||||
static int check_file_owner(const char* path) {
|
||||
struct stat stats;
|
||||
if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Some mounted filesystems may change the ownership of the file.
|
||||
// JVM will not trust such file, so it's better to remove it and try a different path
|
||||
unlink(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Force remote JVM to start Attach listener.
|
||||
// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
|
||||
static int start_attach_mechanism(int pid, int nspid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
|
||||
|
||||
int fd = creat(path, 0660);
|
||||
if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
|
||||
// Failed to create attach trigger in current directory. Retry in /tmp
|
||||
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
|
||||
fd = creat(path, 0660);
|
||||
if (fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// We have to still use the host namespace pid here for the kill() call
|
||||
kill(pid, SIGQUIT);
|
||||
|
||||
// Start with 20 ms sleep and increment delay each iteration
|
||||
struct timespec ts = {0, 20000000};
|
||||
int result;
|
||||
do {
|
||||
nanosleep(&ts, NULL);
|
||||
result = check_socket(nspid);
|
||||
} while (!result && (ts.tv_nsec += 20000000) < 300000000);
|
||||
|
||||
unlink(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Connect to UNIX domain socket created by JVM for Dynamic Attach
|
||||
static int connect_socket(int pid) {
|
||||
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
|
||||
if (bytes >= sizeof(addr.sun_path)) {
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
}
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Send command with arguments to socket
|
||||
static int write_command(int fd, int argc, char** argv) {
|
||||
// Protocol version
|
||||
if (write(fd, "1", 2) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
const char* arg = i < argc ? argv[i] : "";
|
||||
if (write(fd, arg, strlen(arg) + 1) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd) {
|
||||
char buf[8192];
|
||||
ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
|
||||
if (bytes <= 0) {
|
||||
perror("Error reading response");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// First line of response is the command result code
|
||||
buf[bytes] = 0;
|
||||
int result = atoi(buf);
|
||||
|
||||
do {
|
||||
fwrite(buf, 1, bytes, stdout);
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
} while (bytes > 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
|
||||
"Copyright 2018 Andrei Pangin\n"
|
||||
"\n"
|
||||
"Usage: jattach <pid> <cmd> [args ...]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pid = atoi(argv[1]);
|
||||
if (pid == 0) {
|
||||
perror("Invalid pid provided");
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int jattach(int pid, int argc, char** argv, int print_output) {
|
||||
uid_t my_uid = geteuid();
|
||||
gid_t my_gid = getegid();
|
||||
uid_t target_uid = my_uid;
|
||||
gid_t target_gid = my_gid;
|
||||
int nspid = -1;
|
||||
if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
|
||||
int nspid;
|
||||
if (get_process_info(pid, &target_uid, &target_gid, &nspid) < 0) {
|
||||
fprintf(stderr, "Process %d not found\n", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nspid < 0) {
|
||||
nspid = alt_lookup_nspid(pid);
|
||||
}
|
||||
// Container support: switch to the target namespaces.
|
||||
// Network and IPC namespaces are essential for OpenJ9 connection.
|
||||
enter_ns(pid, "net");
|
||||
enter_ns(pid, "ipc");
|
||||
mnt_changed = enter_ns(pid, "mnt");
|
||||
|
||||
// Get attach socket path of the target process (usually /tmp)
|
||||
char* jattach_path = getenv("JATTACH_PATH");
|
||||
if (jattach_path != NULL && strlen(jattach_path) < TMP_PATH) {
|
||||
strcpy(tmp_path, jattach_path);
|
||||
} else {
|
||||
// Make sure our /tmp and target /tmp is the same
|
||||
if (!get_tmp_path(pid)) {
|
||||
strcpy(tmp_path, "/tmp");
|
||||
}
|
||||
if (!enter_mount_ns(pid)) {
|
||||
printf("WARNING: couldn't enter target process mnt namespace\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic attach is allowed only for the clients with the same euid/egid.
|
||||
// In HotSpot, dynamic attach is allowed only for the clients with the same euid/egid.
|
||||
// If we are running under root, switch to the required euid/egid automatically.
|
||||
if ((my_gid != target_gid && setegid(target_gid) != 0) ||
|
||||
(my_uid != target_uid && seteuid(target_uid) != 0)) {
|
||||
@@ -405,33 +43,40 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make write() return EPIPE instead of silent process termination
|
||||
get_tmp_path(mnt_changed > 0 ? nspid : pid);
|
||||
|
||||
// Make write() return EPIPE instead of abnormal process termination
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
|
||||
perror("Could not start attach mechanism");
|
||||
return 1;
|
||||
if (is_openj9_process(nspid)) {
|
||||
return jattach_openj9(pid, nspid, argc, argv, print_output);
|
||||
} else {
|
||||
return jattach_hotspot(pid, nspid, argc, argv, print_output);
|
||||
}
|
||||
|
||||
int fd = connect_socket(nspid);
|
||||
if (fd == -1) {
|
||||
perror("Could not connect to socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Connected to remote JVM\n");
|
||||
if (!write_command(fd, argc - 2, argv + 2)) {
|
||||
perror("Error writing to socket");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Response code = ");
|
||||
fflush(stdout);
|
||||
|
||||
int result = read_response(fd);
|
||||
printf("\n");
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef JATTACH_VERSION
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
|
||||
"\n"
|
||||
"Usage: jattach <pid> <cmd> [args ...]\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
" load threaddump dumpheap setflag properties\n"
|
||||
" jcmd inspectheap datadump printflag agentProperties\n"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pid = atoi(argv[1]);
|
||||
if (pid <= 0) {
|
||||
fprintf(stderr, "%s is not a valid process ID\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return jattach(pid, argc - 2, argv + 2, 1);
|
||||
}
|
||||
|
||||
#endif // JATTACH_VERSION
|
||||
|
||||
193
src/jattach/jattach_hotspot.c
Normal file
193
src/jattach/jattach_hotspot.c
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright The jattach authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
extern int mnt_changed;
|
||||
|
||||
// Check if remote JVM has already opened socket for Dynamic Attach
|
||||
static int check_socket(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode) ? 0 : -1;
|
||||
}
|
||||
|
||||
// Check if a file is owned by current user
|
||||
static uid_t get_file_owner(const char* path) {
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 ? stats.st_uid : (uid_t)-1;
|
||||
}
|
||||
|
||||
// Force remote JVM to start Attach listener.
|
||||
// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
|
||||
static int start_attach_mechanism(int pid, int nspid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", mnt_changed > 0 ? nspid : pid, nspid);
|
||||
|
||||
int fd = creat(path, 0660);
|
||||
if (fd == -1 || (close(fd) == 0 && get_file_owner(path) != geteuid())) {
|
||||
// Some mounted filesystems may change the ownership of the file.
|
||||
// JVM will not trust such file, so it's better to remove it and try a different path
|
||||
unlink(path);
|
||||
|
||||
// Failed to create attach trigger in current directory. Retry in /tmp
|
||||
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
|
||||
fd = creat(path, 0660);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// We have to still use the host namespace pid here for the kill() call
|
||||
kill(pid, SIGQUIT);
|
||||
|
||||
// Start with 20 ms sleep and increment delay each iteration. Total timeout is 6000 ms
|
||||
struct timespec ts = {0, 20000000};
|
||||
int result;
|
||||
do {
|
||||
nanosleep(&ts, NULL);
|
||||
result = check_socket(nspid);
|
||||
} while (result != 0 && (ts.tv_nsec += 20000000) < 500000000);
|
||||
|
||||
unlink(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Connect to UNIX domain socket created by JVM for Dynamic Attach
|
||||
static int connect_socket(int pid) {
|
||||
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
|
||||
if (bytes >= sizeof(addr.sun_path)) {
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
}
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Send command with arguments to socket
|
||||
static int write_command(int fd, int argc, char** argv) {
|
||||
char buf[8192];
|
||||
const char* const limit = buf + sizeof(buf);
|
||||
|
||||
// jcmd has 2 arguments maximum; merge excessive arguments into one
|
||||
int cmd_args = argc >= 2 && strcmp(argv[0], "jcmd") == 0 ? 2 : argc >= 4 ? 4 : argc;
|
||||
|
||||
// Protocol version
|
||||
char* p = stpncpy(buf, "1", sizeof(buf)) + 1;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < argc && p < limit; i++) {
|
||||
if (i >= cmd_args) p[-1] = ' ';
|
||||
p = stpncpy(p, argv[i], limit - p) + 1;
|
||||
}
|
||||
for (i = cmd_args; i < 4 && p < limit; i++) {
|
||||
*p++ = 0;
|
||||
}
|
||||
|
||||
const char* q = p < limit ? p : limit;
|
||||
for (p = buf; p < q; ) {
|
||||
ssize_t bytes = write(fd, p, q - p);
|
||||
if (bytes <= 0) {
|
||||
return -1;
|
||||
}
|
||||
p += (size_t)bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd, int argc, char** argv, int print_output) {
|
||||
char buf[8192];
|
||||
ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
|
||||
if (bytes == 0) {
|
||||
fprintf(stderr, "Unexpected EOF reading response\n");
|
||||
return 1;
|
||||
} else if (bytes < 0) {
|
||||
perror("Error reading response");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// First line of response is the command result code
|
||||
buf[bytes] = 0;
|
||||
int result = atoi(buf);
|
||||
|
||||
// Special treatment of 'load' command
|
||||
if (result == 0 && argc > 0 && strcmp(argv[0], "load") == 0) {
|
||||
size_t total = bytes;
|
||||
while (total < sizeof(buf) - 1 && (bytes = read(fd, buf + total, sizeof(buf) - 1 - total)) > 0) {
|
||||
total += (size_t)bytes;
|
||||
}
|
||||
bytes = total;
|
||||
|
||||
// The second line is the result of 'load' command; since JDK 9 it starts from "return code: "
|
||||
buf[bytes] = 0;
|
||||
result = atoi(strncmp(buf + 2, "return code: ", 13) == 0 ? buf + 15 : buf + 2);
|
||||
}
|
||||
|
||||
if (print_output) {
|
||||
// Mirror JVM response to stdout
|
||||
printf("JVM response code = ");
|
||||
do {
|
||||
fwrite(buf, 1, bytes, stdout);
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
} while (bytes > 0);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int jattach_hotspot(int pid, int nspid, int argc, char** argv, int print_output) {
|
||||
if (check_socket(nspid) != 0 && start_attach_mechanism(pid, nspid) != 0) {
|
||||
perror("Could not start attach mechanism");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fd = connect_socket(nspid);
|
||||
if (fd == -1) {
|
||||
perror("Could not connect to socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_output) {
|
||||
printf("Connected to remote JVM\n");
|
||||
}
|
||||
|
||||
if (write_command(fd, argc, argv) != 0) {
|
||||
perror("Error writing to socket");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = read_response(fd, argc, argv, print_output);
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user