mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
477 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a1f7b8e4a | ||
|
|
8128696c0b | ||
|
|
dead47e0fb | ||
|
|
56730dc7b5 | ||
|
|
9fc0495f86 | ||
|
|
db1ca37a2e | ||
|
|
4ffdb05d3a | ||
|
|
26eab2e657 | ||
|
|
8fee26589d | ||
|
|
cdb8704156 | ||
|
|
c722b3972a | ||
|
|
48703edee7 | ||
|
|
63799a6055 | ||
|
|
ebda293a42 | ||
|
|
69c0340a08 | ||
|
|
45074592cf | ||
|
|
32601bccd9 | ||
|
|
8c4824be7f | ||
|
|
b08bf2d574 | ||
|
|
b4b2218782 | ||
|
|
e1f149f3ae | ||
|
|
80808a3f1b | ||
|
|
7eaefdb18f | ||
|
|
5e4a402c7e | ||
|
|
b0a44524ba | ||
|
|
ed092da71b | ||
|
|
58c62fe4e8 | ||
|
|
bdaefa9a3b | ||
|
|
8168c7dc91 | ||
|
|
98a2006386 | ||
|
|
389d6c5daa | ||
|
|
03e6fc5a17 | ||
|
|
9653401b4b | ||
|
|
f1e2b96a2f | ||
|
|
4f6c675504 | ||
|
|
cced4fcb51 | ||
|
|
904da2ac6b | ||
|
|
b5634b9d88 | ||
|
|
32b5fd8e3c | ||
|
|
b7dfd74a63 | ||
|
|
ed30401cc2 | ||
|
|
d4bee9647f | ||
|
|
d93477f680 | ||
|
|
696087c2ab | ||
|
|
1e8301e831 | ||
|
|
64d9f98a0f | ||
|
|
2613894b85 | ||
|
|
746166ccb4 | ||
|
|
b8c8db45d7 | ||
|
|
14f58ed2c7 | ||
|
|
7fa11e768b | ||
|
|
74ecedc671 | ||
|
|
63f2539e5e | ||
|
|
31261ea7be | ||
|
|
9cec0765cd | ||
|
|
55da899511 | ||
|
|
e9de87c0c3 | ||
|
|
56ae519224 | ||
|
|
733cf7c668 | ||
|
|
ee3ef243d3 | ||
|
|
28357c2fb4 | ||
|
|
2459d7eac4 | ||
|
|
cc1682d20a | ||
|
|
9ec48d9666 | ||
|
|
e2abcd2238 | ||
|
|
9ae31b0e91 | ||
|
|
a836ad6f89 | ||
|
|
c6f11d2673 | ||
|
|
989c3747e2 | ||
|
|
ce9ebc2b63 | ||
|
|
52cdc138d0 | ||
|
|
d09be06029 | ||
|
|
859c36ef9c | ||
|
|
cd084b5a97 | ||
|
|
1505345ee9 | ||
|
|
a53a141dd2 | ||
|
|
8547d642ab | ||
|
|
fa989850b6 | ||
|
|
1e4738ba7b | ||
|
|
c6b5c877f1 | ||
|
|
80a3c66969 | ||
|
|
e1d5df7ffd | ||
|
|
6ca5ad2c93 | ||
|
|
7af24609eb | ||
|
|
80302eb43d | ||
|
|
0396bc7d1b | ||
|
|
b29bfd3f3a | ||
|
|
93b8171601 | ||
|
|
721225393e | ||
|
|
58a3cb0d25 | ||
|
|
6754312e83 | ||
|
|
daf844397e | ||
|
|
5e0021a99f | ||
|
|
8d846a01e7 | ||
|
|
37c56c44bb | ||
|
|
88dd2345ea | ||
|
|
ded970fd50 | ||
|
|
a23400b85e | ||
|
|
796aff5555 | ||
|
|
d36e8e91f9 | ||
|
|
323c02e0d7 | ||
|
|
53cfd2b8b1 | ||
|
|
4431121760 | ||
|
|
4a88ee445f | ||
|
|
ce0fc0a2d9 | ||
|
|
768b437593 | ||
|
|
20fa8564c4 | ||
|
|
aaf24effe8 | ||
|
|
3bbeed6267 | ||
|
|
cb51be8eb3 | ||
|
|
7749b72e73 | ||
|
|
7112bc14e5 | ||
|
|
df5fb83354 | ||
|
|
90b7c78643 | ||
|
|
56ca88677b | ||
|
|
8a258d31a5 | ||
|
|
e33a01e0c6 | ||
|
|
5d5138ea61 | ||
|
|
10b5ad0ee5 | ||
|
|
80c3993b7e | ||
|
|
b81be14adf | ||
|
|
b5258cca2c | ||
|
|
0718a09e0e | ||
|
|
f86d1a415b | ||
|
|
c00b6c25de | ||
|
|
fe173c4101 | ||
|
|
4713106ac9 | ||
|
|
c89fea8de5 | ||
|
|
8032daa49d | ||
|
|
fa8b8f8072 | ||
|
|
85f3a68c56 | ||
|
|
60ce15569a | ||
|
|
9838ddb693 | ||
|
|
2f341043ef | ||
|
|
ee55fbe17b | ||
|
|
0309f3dfd3 | ||
|
|
5ec58fd873 | ||
|
|
1fda752243 | ||
|
|
d7a2a4fc8b | ||
|
|
e9b7747015 | ||
|
|
ff305e1d16 | ||
|
|
5cc4721e66 | ||
|
|
fee03218d1 | ||
|
|
dade9b2283 | ||
|
|
b96e07b001 | ||
|
|
ba00ca26c1 | ||
|
|
db9ec2e59e | ||
|
|
f0a7a36825 | ||
|
|
9ed175d73e | ||
|
|
b287816559 | ||
|
|
9a979a712d | ||
|
|
42442ed593 | ||
|
|
432d622aa4 | ||
|
|
f477f8d4c0 | ||
|
|
456ff57115 | ||
|
|
5ec28a86b2 | ||
|
|
e7cd6ee6fb | ||
|
|
8ba8fc748c | ||
|
|
6f869d3eff | ||
|
|
462988d8de | ||
|
|
5f37bf3ad6 | ||
|
|
26e9c7aef2 | ||
|
|
73b5e97c39 | ||
|
|
8efba10acc | ||
|
|
e894420119 | ||
|
|
41121aa64f | ||
|
|
2519bf4307 | ||
|
|
2ddd4d230c | ||
|
|
09bf0f025b | ||
|
|
1398e7ef75 | ||
|
|
3456dd3d90 | ||
|
|
12dfaba106 | ||
|
|
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 | ||
|
|
3a44bb6ba6 | ||
|
|
f73ac36c9c | ||
|
|
c94b1685cf | ||
|
|
90d4420d3f | ||
|
|
a96501a26a | ||
|
|
39f84be219 | ||
|
|
4af327e2c1 | ||
|
|
61919df2ff | ||
|
|
26880ecb22 | ||
|
|
af02f6b0fb | ||
|
|
c11d4ca487 | ||
|
|
b2dfe9b5b0 | ||
|
|
5585a77355 | ||
|
|
b5a67c2b95 | ||
|
|
9aea04a56a | ||
|
|
a48f77b380 | ||
|
|
8c5f6c1357 | ||
|
|
88730d4388 | ||
|
|
d132777a60 | ||
|
|
04dac10d41 | ||
|
|
5290b81190 | ||
|
|
93e1f963ef | ||
|
|
a18af69f8b | ||
|
|
60cac04c24 | ||
|
|
3d7e8efd3b | ||
|
|
d26d69e550 | ||
|
|
8160e49c14 | ||
|
|
731ac31064 | ||
|
|
013ceee55d | ||
|
|
f7ef0e97b2 | ||
|
|
c01fe588ce | ||
|
|
e498ad27d2 | ||
|
|
edb31a0f79 | ||
|
|
13394b7125 | ||
|
|
d227a83e42 | ||
|
|
7e8ad02ccb | ||
|
|
450f251732 | ||
|
|
53ca190457 | ||
|
|
683144a907 | ||
|
|
02b65627cd | ||
|
|
48e4fd5035 | ||
|
|
642a1ac7fb | ||
|
|
114e711fd6 | ||
|
|
f833f41b46 | ||
|
|
a82163b703 | ||
|
|
6b49cfa9be | ||
|
|
6c26e5ae69 | ||
|
|
1634380a16 | ||
|
|
1a6e582ad7 | ||
|
|
4b5a17b336 | ||
|
|
8392e568f4 | ||
|
|
d7d56c762b | ||
|
|
a4c6d42677 | ||
|
|
b7e907884b | ||
|
|
5b69492dba | ||
|
|
5a789bda42 | ||
|
|
a010f387b3 | ||
|
|
61d5cdcd68 | ||
|
|
2b14ee69ef | ||
|
|
048b54621d | ||
|
|
94d406c531 | ||
|
|
800580bb30 | ||
|
|
8cecd2df9b | ||
|
|
d86883043a | ||
|
|
d0772ba62c | ||
|
|
d6d4a3c2a3 | ||
|
|
49f9050bf5 | ||
|
|
67b77b9645 | ||
|
|
971fc85d1c | ||
|
|
50b9fe4d85 | ||
|
|
f9db1099f9 | ||
|
|
adce201837 | ||
|
|
a905d50e00 | ||
|
|
f006e00443 | ||
|
|
5ef449c2ed | ||
|
|
d9ca3e42a8 | ||
|
|
269bef2867 | ||
|
|
e62cb2cfd1 | ||
|
|
7135840f70 | ||
|
|
31ddc2f562 | ||
|
|
a5beee66ff | ||
|
|
c15439348f | ||
|
|
17fe36e43e | ||
|
|
5312a793ec | ||
|
|
4d43db91e1 | ||
|
|
0020af54a3 | ||
|
|
f67d392ad8 | ||
|
|
ff70da1736 | ||
|
|
07438daa70 | ||
|
|
e1e8aa068a | ||
|
|
a2691f919e | ||
|
|
f496a167fe | ||
|
|
d9a1252550 | ||
|
|
f3ca611267 | ||
|
|
119da0fcb2 | ||
|
|
6bb7f749c9 | ||
|
|
9593745098 | ||
|
|
e891ecd9da | ||
|
|
b8493976b6 | ||
|
|
54b85dc718 | ||
|
|
675a28fdc2 | ||
|
|
1a4437999b | ||
|
|
ee2438e25f | ||
|
|
cd062fead9 | ||
|
|
156389f11a | ||
|
|
8373224395 | ||
|
|
fe6c4ddeda | ||
|
|
98ac0c58d6 | ||
|
|
b57106b858 | ||
|
|
c204e28348 | ||
|
|
fc17386ec0 | ||
|
|
49d86abc6c | ||
|
|
9fc97fc681 | ||
|
|
9b24fdef99 | ||
|
|
e37059d409 | ||
|
|
869058b56b | ||
|
|
11b0f4598e | ||
|
|
cc9cee7bec | ||
|
|
5ae46d2312 | ||
|
|
776b5597bf | ||
|
|
e282b76880 | ||
|
|
e47d7f408f | ||
|
|
5044869ecd | ||
|
|
9516f54311 | ||
|
|
9bd414411f | ||
|
|
0334c5900e | ||
|
|
f502979135 | ||
|
|
d89fc3fbdc | ||
|
|
9279531cf8 | ||
|
|
9de1a63542 | ||
|
|
21af257716 | ||
|
|
28ed6f490e | ||
|
|
dc4f01dd14 | ||
|
|
1e3a4b77ee | ||
|
|
4cec7a3bb0 | ||
|
|
2557363892 | ||
|
|
78035134f4 | ||
|
|
0ef1122a3b | ||
|
|
8bb57de1d1 | ||
|
|
11d74b73af | ||
|
|
a759960bb0 | ||
|
|
7edcd2660a | ||
|
|
a97a5cae13 | ||
|
|
d2e7e2718c | ||
|
|
e1c3100c60 | ||
|
|
93c63d50d5 | ||
|
|
7e6db636d8 | ||
|
|
78a83a31b2 | ||
|
|
adcf89234b | ||
|
|
b7e9e6b955 | ||
|
|
19e16dc973 | ||
|
|
84602f8660 | ||
|
|
f5850e6f3b | ||
|
|
c14f9a9feb | ||
|
|
bbad5d835b | ||
|
|
f03fdae8df | ||
|
|
2159c7fd33 | ||
|
|
b66f920422 | ||
|
|
45e53b83f9 | ||
|
|
d265d142e6 | ||
|
|
5f94f6ee50 | ||
|
|
e7150f1b5e | ||
|
|
e89d41de54 | ||
|
|
7049adb202 | ||
|
|
99926e5c74 | ||
|
|
37777104fd | ||
|
|
a518d93ec8 | ||
|
|
936a9fea8d | ||
|
|
47c576e552 | ||
|
|
df05305642 | ||
|
|
d7a7a04684 | ||
|
|
9c04d76392 | ||
|
|
9df5518cd7 | ||
|
|
f77f2d1afb | ||
|
|
cef015d25f | ||
|
|
861598538a | ||
|
|
34da66f6fd | ||
|
|
cc4126911e |
21
.github/workflows/cpp.yml
vendored
Normal file
21
.github/workflows/cpp.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: C++ CI
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2.1.0
|
||||
with:
|
||||
distribution: "adopt"
|
||||
java-version: "11"
|
||||
- run: sudo sysctl kernel.perf_event_paranoid=1
|
||||
- run: make
|
||||
- run: make test
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: build/
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,8 @@
|
||||
/build/
|
||||
/nbproject/
|
||||
/out/
|
||||
/target/
|
||||
/.idea/
|
||||
/test/*.class
|
||||
.vscode
|
||||
*.iml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
language: cpp
|
||||
|
||||
dist: precise
|
||||
dist: bionic
|
||||
|
||||
sudo: required
|
||||
|
||||
|
||||
261
CHANGELOG.md
261
CHANGELOG.md
@@ -1,5 +1,266 @@
|
||||
# Changelog
|
||||
|
||||
## [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: `--ttsp`
|
||||
|
||||
### Improvements
|
||||
- Unlimited frame buffer. Removed `-b` option and 64K stack traces limit
|
||||
- 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
|
||||
- libasyncProfiler.dylib symlink on macOS
|
||||
|
||||
### Bug fixes
|
||||
- Fixed possible deadlock on non-HotSpot JVMs
|
||||
- Gracefully stop profiler when terminating JVM
|
||||
- Fixed GetStackTrace problem after RedefineClasses
|
||||
|
||||
## [1.8.2] - 2020-11-02
|
||||
|
||||
### Improvements
|
||||
- AArch64 build is now provided out of the box
|
||||
- Compatibility with JDK 15 and JDK 16
|
||||
|
||||
### Bug fixes
|
||||
- More careful native stack walking in wall-clock mode
|
||||
- `resume` command is not compatible with JFR format
|
||||
- Wrong allocation sizes on JDK 8u262
|
||||
|
||||
## [1.8.1] - 2020-09-05
|
||||
|
||||
### Improvements
|
||||
- Possibility to specify application name instead of `pid` (contributed by @yuzawa-san)
|
||||
|
||||
### Bug fixes
|
||||
- Fixed long attach time and slow class loading on JDK 8
|
||||
- `UnsatisfiedLinkError` during Java method profiling
|
||||
- Avoid reading `/proc/kallsyms` when `--all-user` is specified
|
||||
|
||||
## [1.8] - 2020-08-10
|
||||
|
||||
### Features
|
||||
- Converters between different output formats:
|
||||
- JFR -> nflx (FlameScope)
|
||||
- Collapsed stacks -> HTML 5 Flame Graph
|
||||
|
||||
### Improvements
|
||||
- `profiler.sh` no longer requires bash (contributed by @cfstras)
|
||||
- Fixed long attach time and slow class loading on JDK 8
|
||||
- Fixed deadlocks in wall-clock profiling mode
|
||||
- Per-thread reverse Flame Graph and Call Tree
|
||||
- ARM build now works with ARM and THUMB flavors of JDK
|
||||
|
||||
### Changes
|
||||
- Release package is extracted into a separate folder
|
||||
|
||||
## [1.7.1] - 2020-05-14
|
||||
|
||||
### Features
|
||||
- LBR call stack support (available since Haswell)
|
||||
|
||||
### Improvements
|
||||
- `--filter` to profile only specified thread IDs in wall-clock mode
|
||||
- `--safe-mode` to disable selected stack recovery techniques
|
||||
|
||||
## [1.7] - 2020-03-17
|
||||
|
||||
### Features
|
||||
- Profile invocations of arbitrary Java methods
|
||||
- Filter stack traces by the given name pattern
|
||||
- Java API to filter monitored threads
|
||||
- `--cstack`/`--no-cstack` option
|
||||
|
||||
### Improvements
|
||||
- Thread names and Java thread IDs in JFR output
|
||||
- Wall clock profiler distinguishes RUNNABLE vs. SLEEPING threads
|
||||
- Stable profiling interval in wall clock mode
|
||||
- C++ function names as events, e.g. `-e VMThread::execute`
|
||||
- `check` command to test event availability
|
||||
- Allow shading of AsyncProfiler API
|
||||
- Enable CPU profiling on WSL
|
||||
- Enable allocation profiling on Zing
|
||||
- Reduce the amount of `unknown_Java` samples
|
||||
|
||||
## [1.6] - 2019-09-09
|
||||
|
||||
### Features
|
||||
- Pause/resume profiling
|
||||
- Allocation profiling support for JDK 12, 13 (contributed by @rraptorr)
|
||||
|
||||
### Improvements
|
||||
- Include all AsyncGetCallTrace failures in the profile
|
||||
- Parse symbols of JNI libraries loaded in runtime
|
||||
- The agent autodetects output format by the file extension
|
||||
- Output file name patterns: `%p` and `%t`
|
||||
- `-g` option to print method signatures
|
||||
- `-j` can increase the maximum Java stack depth
|
||||
- Allocaton sampling rate can be adjusted with `-i`
|
||||
- Improved reliability on macOS
|
||||
|
||||
### Changes
|
||||
- `-f` file names are now relative to the current shell directory
|
||||
|
||||
## [1.5] - 2019-01-08
|
||||
|
||||
### Features
|
||||
|
||||
166
Makefile
166
Makefile
@@ -1,63 +1,171 @@
|
||||
PROFILER_VERSION=1.5
|
||||
JATTACH_VERSION=1.5
|
||||
LIB_PROFILER=libasyncProfiler.so
|
||||
PROFILER_VERSION=2.9-bpf
|
||||
|
||||
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
|
||||
PROFILER_JAR=async-profiler.jar
|
||||
CC=gcc
|
||||
CFLAGS=-O2
|
||||
CPP=g++
|
||||
CPPFLAGS=-O2
|
||||
INCLUDES=-I$(JAVA_HOME)/include
|
||||
API_JAR=async-profiler.jar
|
||||
CONVERTER_JAR=converter.jar
|
||||
|
||||
CFLAGS=-O3
|
||||
CXXFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
INCLUDES=-I$(JAVA_HOME)/include -Isrc/res -Isrc/helper
|
||||
LIBS=-ldl -lpthread
|
||||
MERGE=true
|
||||
|
||||
JAVAC=$(JAVA_HOME)/bin/javac
|
||||
JAR=$(JAVA_HOME)/bin/jar
|
||||
JAVA_TARGET=7
|
||||
JAVAC_OPTIONS=-source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options
|
||||
|
||||
SOURCES := $(wildcard src/*.cpp)
|
||||
HEADERS := $(wildcard src/*.h src/fdtransfer/*.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')
|
||||
|
||||
ifeq ($(JAVA_HOME),)
|
||||
export JAVA_HOME:=$(shell java -cp . JavaHome)
|
||||
endif
|
||||
|
||||
OS:=$(shell uname -s)
|
||||
ifeq ($(OS), Darwin)
|
||||
CPPFLAGS += -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
|
||||
RELEASE_TAG:=$(PROFILER_VERSION)-macos-x64
|
||||
FDTRANSFER_BIN=
|
||||
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
|
||||
RELEASE_TAG:=$(PROFILER_VERSION)-linux-x64
|
||||
FDTRANSFER_BIN=build/fdtransfer
|
||||
SOEXT=so
|
||||
PACKAGE_EXT=tar.gz
|
||||
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
|
||||
OS_TAG=linux-musl
|
||||
CXXFLAGS += -D__musl__
|
||||
else
|
||||
OS_TAG=linux
|
||||
endif
|
||||
endif
|
||||
|
||||
ARCH:=$(shell uname -m)
|
||||
ifeq ($(ARCH),x86_64)
|
||||
ARCH_TAG=x64
|
||||
else
|
||||
ifeq ($(findstring arm,$(ARCH)),arm)
|
||||
ifeq ($(findstring 64,$(ARCH)),64)
|
||||
ARCH_TAG=arm64
|
||||
else
|
||||
ARCH_TAG=arm32
|
||||
endif
|
||||
else
|
||||
ifeq ($(findstring aarch64,$(ARCH)),aarch64)
|
||||
ARCH_TAG=arm64
|
||||
else
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
ARCH_TAG=ppc64le
|
||||
else
|
||||
ARCH_TAG=x86
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(ARCH),ppc64le)
|
||||
ifneq ($(ARCH_TAG),arm32)
|
||||
CXXFLAGS += -momit-leaf-frame-pointer
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
.PHONY: all release test clean
|
||||
.PHONY: all release test native clean
|
||||
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(PROFILER_JAR)
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
|
||||
release: build async-profiler-$(RELEASE_TAG).tar.gz
|
||||
release: build $(PACKAGE_NAME).$(PACKAGE_EXT)
|
||||
|
||||
async-profiler-$(RELEASE_TAG).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(PROFILER_JAR) profiler.sh LICENSE *.md
|
||||
tar cvzf $@ $^
|
||||
$(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR)
|
||||
tar czf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
$(PACKAGE_NAME).zip: $(PACKAGE_DIR)
|
||||
codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/build/$(JATTACH) $(PACKAGE_DIR)/build/$(LIB_PROFILER_SO)
|
||||
ditto -c -k --keepParent $(PACKAGE_DIR) $@
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
$(PACKAGE_DIR): build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) \
|
||||
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
|
||||
|
||||
%.$(SOEXT): %.so
|
||||
rm -f $@
|
||||
-ln -s $(<F) $@
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
|
||||
build/$(LIB_PROFILER): src/*.cpp src/*.h
|
||||
$(CPP) $(CPPFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ src/*.cpp $(LIBS)
|
||||
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES)
|
||||
ifeq ($(MERGE),true)
|
||||
for f in src/*.cpp; do echo '#include "'$$f'"'; done |\
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ -xc++ - $(LIBS)
|
||||
else
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
|
||||
endif
|
||||
|
||||
build/$(JATTACH): src/jattach/jattach.c
|
||||
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
|
||||
build/$(JATTACH): src/jattach/*.c src/jattach/*.h
|
||||
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(PROFILER_VERSION)-ap\" -o $@ src/jattach/*.c
|
||||
|
||||
build/$(PROFILER_JAR): src/java/one/profiler/*.java
|
||||
mkdir -p build/classes
|
||||
$(JAVAC) -source 6 -target 6 -d build/classes $^
|
||||
$(JAR) cvf $@ -C build/classes .
|
||||
rm -rf build/classes
|
||||
build/fdtransfer: src/fdtransfer/*.cpp src/fdtransfer/*.h src/jattach/psutil.c src/jattach/psutil.h
|
||||
$(CXX) $(CFLAGS) -o $@ src/fdtransfer/*.cpp src/jattach/psutil.c
|
||||
|
||||
build/$(API_JAR): $(API_SOURCES)
|
||||
mkdir -p build/api
|
||||
$(JAVAC) $(JAVAC_OPTIONS) -d build/api $^
|
||||
$(JAR) cf $@ -C build/api .
|
||||
$(RM) -r build/api
|
||||
|
||||
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) $(RESOURCES)
|
||||
mkdir -p 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) $(JAVAC_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 -rf build
|
||||
$(RM) -r build
|
||||
|
||||
538
README.md
538
README.md
@@ -4,7 +4,7 @@ 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 HotSpot JVM.
|
||||
OpenJDK, Oracle JDK and other Java runtimes based on the HotSpot JVM.
|
||||
|
||||
async-profiler can trace the following kinds of events:
|
||||
- CPU cycles
|
||||
@@ -12,22 +12,36 @@ async-profiler can trace the following kinds of events:
|
||||
- Allocations in Java Heap
|
||||
- Contented lock attempts, including both Java object monitors and ReentrantLocks
|
||||
|
||||
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 features.
|
||||
|
||||
## Download
|
||||
|
||||
Latest release:
|
||||
Current release (2.9):
|
||||
|
||||
- Linux x64: [async-profiler-1.5-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-linux-x64.tar.gz)
|
||||
- Linux ARM: [async-profiler-1.5-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-linux-arm.tar.gz)
|
||||
- macOS x64: [async-profiler-1.5-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-macos-x64.tar.gz)
|
||||
- Linux x64 (glibc): [async-profiler-2.9-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-2.9-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-musl-x64.tar.gz)
|
||||
- Linux arm64: [async-profiler-2.9-linux-arm64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-arm64.tar.gz)
|
||||
- macOS x64/arm64: [async-profiler-2.9-macos.zip](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-macos.zip)
|
||||
- Converters between profile formats: [converter.jar](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/converter.jar)
|
||||
(JFR to Flame Graph, JFR to FlameScope, collapsed stacks to Flame Graph)
|
||||
|
||||
[Previous releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
|
||||
|
||||
Note: 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
|
||||
- **Linux** / x64 / x86 / arm64 / arm32 / ppc64le
|
||||
- **macOS** / x64 / arm64
|
||||
|
||||
Note: macOS profiling is limited to user space code only.
|
||||
### Community supported builds
|
||||
|
||||
- **Windows** / x64 - <img src="https://upload.wikimedia.org/wikipedia/commons/9/9c/IntelliJ_IDEA_Icon.svg" width="16" height="16"/> [IntelliJ IDEA](https://www.jetbrains.com/idea/) 2021.2 and later
|
||||
- [**ap-loader**](https://github.com/jvm-profiling-tools/ap-loader) -
|
||||
all-in-one JAR for using async-profiler in Java programs and as a CLI tool
|
||||
|
||||
## CPU profiling
|
||||
|
||||
@@ -45,18 +59,20 @@ This approach has the following advantages compared to using `perf_events`
|
||||
directly with a Java agent that translates addresses to Java method names:
|
||||
|
||||
* Works on older Java versions because it doesn't require
|
||||
`-XX:+PreserveFramePointer`, which is only available in JDK 8u60 and later.
|
||||
`-XX:+PreserveFramePointer`, which is only available in JDK 8u60 and later.
|
||||
|
||||
* Does not introduce the performance overhead from `-XX:+PreserveFramePointer`,
|
||||
which can in rare cases be as high as 10%.
|
||||
which can in rare cases be as high as 10%.
|
||||
|
||||
* Does not require generating a map file to map Java code addresses to method
|
||||
names.
|
||||
names.
|
||||
|
||||
* Works with interpreter frames.
|
||||
|
||||
* Does not require writing out a perf.data file for further processing in
|
||||
user space scripts.
|
||||
user space scripts.
|
||||
|
||||
If you wish to resolve frames within `libjvm`, the [debug symbols](#installing-debug-symbols) are required.
|
||||
|
||||
## ALLOCATION profiling
|
||||
|
||||
@@ -70,8 +86,8 @@ 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.
|
||||
- when an object is allocated in a newly created TLAB (aqua frames in a Flame Graph);
|
||||
- when an object is allocated on a slow path outside TLAB (brown frames).
|
||||
|
||||
This means not each allocation is counted, but only allocations every _N_ kB,
|
||||
where _N_ is the average size of TLAB. This makes heap sampling very cheap
|
||||
@@ -79,23 +95,44 @@ and suitable for production. On the other hand, the collected data
|
||||
may be incomplete, though in practice it will often reflect the top allocation
|
||||
sources.
|
||||
|
||||
Unlike Java Mission Control which uses similar approach, async-profiler
|
||||
does not require Java Flight Recorder or any other JDK commercial feature.
|
||||
It is completely based on open source technologies and it works with OpenJDK.
|
||||
Sampling interval can be adjusted with `--alloc` option.
|
||||
For example, `--alloc 500k` will take one sample after 500 KB of allocated
|
||||
space on average. However, intervals less than TLAB size will not take effect.
|
||||
|
||||
The minimum supported JDK version is 7u40 where the TLAB callbacks appeared.
|
||||
|
||||
Heap profiler requires HotSpot debug symbols. Oracle JDK already has them
|
||||
embedded in `libjvm.so`, but in OpenJDK builds they are typically shipped
|
||||
in a separate package. For example, to install OpenJDK debug symbols on
|
||||
Debian / Ubuntu, run
|
||||
```
|
||||
# apt-get install openjdk-8-dbg
|
||||
```
|
||||
On Gentoo the ``icedtea`` OpenJDK package can be built with the per-package setting
|
||||
``FEATURES="nostrip"`` to retain symbols.
|
||||
### Installing Debug Symbols
|
||||
|
||||
### Wall-clock profiling
|
||||
Prior to JDK 11, the allocation profiler required HotSpot debug symbols.
|
||||
Oracle JDK already has them embedded in `libjvm.so`, but in OpenJDK builds
|
||||
they are typically shipped in a separate package. For example, to install
|
||||
OpenJDK debug symbols on Debian / Ubuntu, run:
|
||||
```
|
||||
# apt install openjdk-8-dbg
|
||||
```
|
||||
or for OpenJDK 11:
|
||||
```
|
||||
# apt install openjdk-11-dbg
|
||||
```
|
||||
|
||||
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 the 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.
|
||||
@@ -103,27 +140,50 @@ For instance, this can be helpful when profiling application start-up time.
|
||||
|
||||
Wall-clock profiler is most useful in per-thread mode: `-t`.
|
||||
|
||||
Example: `./profiler.sh -e wall -t -i 5ms -f result.svg 8983`
|
||||
Example: `./profiler.sh -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 the _humongous allocation_ of the G1 GC,
|
||||
* ```JVM_StartThread``` - trace the new thread creation,
|
||||
* ```Java_java_lang_ClassLoader_defineClass1``` - trace class loading.
|
||||
|
||||
## Building
|
||||
|
||||
Build status: [](https://travis-ci.org/jvm-profiling-tools/async-profiler)
|
||||
Build status: [](https://github.com/jvm-profiling-tools/async-profiler/actions/workflows/cpp.yml)
|
||||
|
||||
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` subdirectory. If the build fails due to
|
||||
`Source option 7 is no longer supported. Use 8 or later.`, use `make JAVA_TARGET=8`.
|
||||
|
||||
## 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
|
||||
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:
|
||||
|
||||
```
|
||||
# echo 1 > /proc/sys/kernel/perf_event_paranoid
|
||||
# echo 0 > /proc/sys/kernel/kptr_restrict
|
||||
# sysctl kernel.perf_event_paranoid=1
|
||||
# sysctl kernel.kptr_restrict=0
|
||||
```
|
||||
|
||||
To run the agent and pass commands to it, the helper script `profiler.sh`
|
||||
@@ -142,6 +202,11 @@ $ ./profiler.sh start 8983
|
||||
$ ./profiler.sh 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.
|
||||
|
||||
@@ -183,30 +248,61 @@ If you need to profile some code as soon as the JVM starts up, instead of using
|
||||
it is possible to attach async-profiler as an agent on the command line. For example:
|
||||
|
||||
```
|
||||
$ java -agentpath:/path/to/libasyncProfiler.so=start,svg,file=profile.svg ...
|
||||
$ 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/jvm-profiling-tools/async-profiler/blob/af94b0e55178c46e17c573a65c498d25b58b641b/src/arguments.cpp#L26). The `profiler.sh` script actually
|
||||
converts command line arguments to the that format.
|
||||
Agent library is configured through the JVMTI argument interface.
|
||||
The format of the arguments string is described
|
||||
[in the source code](https://github.com/jvm-profiling-tools/async-profiler/blob/v2.9/src/arguments.cpp#L52).
|
||||
The `profiler.sh` script actually converts command line arguments to that format.
|
||||
|
||||
For instance, `-e alloc` is converted to `event=alloc`, `-f profile.svg` is converted to `file=profile.svg` and so on. But some arguments are processed directly by `profiler.sh` script. E.g. `-d 5` results in 3 actions: 1) attaching profiler agent with start command, sleeping for 5 seconds, and then attaching the agent again with stop command.
|
||||
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 `profiler.sh` script. 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.
|
||||
Or, 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
|
||||
```
|
||||
./profiler.sh -e cpu,alloc,lock -f profile.jfr ...
|
||||
```
|
||||
or use `--alloc` and `--lock` parameters with the desired threshold:
|
||||
```
|
||||
./profiler.sh -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 svg` argument to dump profiling results as an interactive SVG
|
||||
immediately viewable in all mainstream browsers.
|
||||
Also, SVG output format will be chosen automatically if the target
|
||||
filename ends with `.svg`.
|
||||
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
|
||||
$ ./profiler.sh -d 30 -f /tmp/flamegraph.svg 8983
|
||||
$ ./profiler.sh -d 30 -f /tmp/flamegraph.html 8983
|
||||
```
|
||||
|
||||

|
||||
[](https://htmlpreview.github.io/?https://github.com/jvm-profiling-tools/async-profiler/blob/master/demo/flamegraph.html)
|
||||
|
||||
## Profiler Options
|
||||
|
||||
@@ -214,150 +310,231 @@ The following is a complete list of the command-line options accepted by
|
||||
`profiler.sh` script.
|
||||
|
||||
* `start` - starts profiling in semi-automatic mode, i.e. profiler will run
|
||||
until `stop` command is explicitly called.
|
||||
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.
|
||||
for how long.
|
||||
|
||||
* `list` - show the list of available profiling events. This option still
|
||||
requires PID, since supported events may differ depending on JVM version.
|
||||
* `meminfo` - prints used memory statistics.
|
||||
|
||||
* `-d N` - the profiling duration, in seconds. If no `start`, `stop`
|
||||
or `status` option is given, the profiler will run for the specified period
|
||||
of time and then automatically stop.
|
||||
Example: `./profiler.sh -d 30 8983`
|
||||
* `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: `./profiler.sh -d 30 8983`
|
||||
|
||||
* `-e event` - the profiling event: `cpu`, `alloc`, `lock`, `cache-misses` etc.
|
||||
Use `list` to see the complete list of available events.
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
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: `./profiler.sh -i 500us 8983`
|
||||
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: `./profiler.sh -i 500us 8983`
|
||||
|
||||
* `-j N` - sets the Java stack profiling depth. This option will be ignored if N is greater
|
||||
than default MAX_STACK_FRAMES.
|
||||
Example: `./profiler.sh -j 30 8983`
|
||||
* `--alloc N` - allocation profiling interval in bytes or in other units,
|
||||
if N is followed by `k` (kilobytes), `m` (megabytes), or `g` (gigabytes).
|
||||
|
||||
* `-b N` - sets the frame buffer size, in the number of Java
|
||||
method ids that should fit in the buffer. If you receive messages about an
|
||||
insufficient frame buffer size, increase this value from the default.
|
||||
Example: `./profiler.sh -b 5000000 8983`
|
||||
* `--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: `./profiler.sh -j 30 8983`
|
||||
|
||||
* `-t` - profile threads separately. Each stack trace will end with a frame
|
||||
that denotes a single thread.
|
||||
Example: `./profiler.sh -t 8983`
|
||||
that denotes a single thread.
|
||||
Example: `./profiler.sh -t 8983`
|
||||
|
||||
* `-s` - print simple class names instead of FQN.
|
||||
|
||||
* `-a` - annotate Java method names by adding `_[j]` suffix.
|
||||
* `-g` - print method signatures.
|
||||
|
||||
* `-o fmt[,fmt...]` - specifies what information to dump when profiling ends.
|
||||
This is a comma-separated list of the following options:
|
||||
- `summary` - dump basic profiling statistics;
|
||||
- `traces[=N]` - dump call traces (at most N samples);
|
||||
- `flat[=N]` - dump flat profile (top N hot methods);
|
||||
- `jfr` - dump events in Java Flight Recorder format readable by Java Mission Control.
|
||||
This *does not* require JDK commercial features to be enabled.
|
||||
- `collapsed[=C]` - 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.
|
||||
- `svg[=C]` - produce Flame Graph in SVG format.
|
||||
- `tree[=C]` - produce call tree in HTML format.
|
||||
--reverse option will generate backtrace view.
|
||||
|
||||
`C` is a counter type:
|
||||
- `samples` - the counter is a number of samples for the given trace;
|
||||
- `total` - the counter is a total value of collected metric, e.g. total allocation size.
|
||||
|
||||
The default format is `summary,traces=200,flat=200`.
|
||||
* `-a` - annotate JIT compiled methods with `_[j]`, inlined methods with `_[i]`, interpreted methods with `_[0]` and C1 compiled methods with `_[1]`.
|
||||
|
||||
* `--title TITLE`, `--width PX`, `--height PX`, `--minwidth PX`, `--reverse` - FlameGraph parameters.
|
||||
Example: `./profiler.sh -f profile.svg --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
* `-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.
|
||||
Example: `./profiler.sh -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: `./profiler.sh -I 'Primes.*' -I 'java/*' -X '*Unsafe.park*' 8983`
|
||||
|
||||
* `--title TITLE`, `--minwidth PERCENT`, `--reverse` - FlameGraph parameters.
|
||||
Example: `./profiler.sh -f profile.html --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
|
||||
* `-f FILENAME` - the file name to dump the profile information to.
|
||||
Example: `./profiler.sh -o collapsed -f /tmp/traces.txt 8983`
|
||||
`%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: `./profiler.sh -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: `./profiler.sh --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.
|
||||
`--all-kernel` is its counterpart option for including only kernel-mode events.
|
||||
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),
|
||||
and `no` (do not collect C stack).
|
||||
|
||||
By default, C stack is shown in cpu, itimer, wall-clock and perf-events profiles.
|
||||
Java-level events like `alloc` and `lock` collect only Java stack.
|
||||
|
||||
* `--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).
|
||||
|
||||
Example: `./profiler.sh -e cpu --jfrsync profile -f combined.jfr 8983`
|
||||
|
||||
* `--fdtransfer` - runs "fdtransfer" alongside, which is a small program providing an interface
|
||||
for the profiler to access `perf_event_open` even while this syscall is unavailable for the
|
||||
profiled process (due to low privileges).
|
||||
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.
|
||||
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, async-profiler should be run by a privileged user -
|
||||
it will automatically switch to the proper pid/mount namespace and change
|
||||
user credentials to match the target process.
|
||||
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. You'll need to modify [seccomp profile](https://docs.docker.com/engine/security/seccomp/)
|
||||
or disable it altogether with `--security-opt=seccomp:unconfined` option.
|
||||
|
||||
Alternatively, if changing Docker configuration is not possible,
|
||||
you may fall back to `-e itimer` profiling mode, see [Troubleshooting](#troubleshooting).
|
||||
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 itimer` 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
* There is no bullet-proof guarantee that the `perf_events` overflow signal
|
||||
is delivered to the Java thread in a way that guarantees no other code has run,
|
||||
which means that in some rare cases, the captured Java stack might not match
|
||||
the captured native (user+kernel) stack.
|
||||
is delivered to the Java thread in a way that guarantees no other code has run,
|
||||
which means that in some rare cases, the captured Java stack might not match
|
||||
the captured native (user+kernel) stack.
|
||||
|
||||
* You will not see the non-Java frames _preceding_ the Java frames on the
|
||||
stack. 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.
|
||||
* You will not see the non-Java frames _preceding_ the Java frames on the
|
||||
stack. 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.
|
||||
or negative.
|
||||
|
||||
* 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/jvm-profiling-tools/async-profiler/issues/97).
|
||||
The workaround is simply to increase the interval.
|
||||
system calls like `clone()`, so that it will never complete;
|
||||
see [#97](https://github.com/jvm-profiling-tools/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 e.g. without `-XX:+DebugNonSafepoints` there is a high chance that simple inlined methods will not appear in the profile. When agent is attached at runtime `CompiledMethodLoad` JVMTI event
|
||||
enables debug info, but only for methods compiled after the event is turned on.
|
||||
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
|
||||
|
||||
@@ -376,22 +553,22 @@ 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.
|
||||
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.
|
||||
|
||||
```
|
||||
Failed to inject profiler into <pid>
|
||||
@@ -401,24 +578,59 @@ Make sure the user of JVM process has permissions to access `libasyncProfiler.so
|
||||
For more information see [#78](https://github.com/jvm-profiling-tools/async-profiler/issues/78).
|
||||
|
||||
```
|
||||
Perf events unavailble. See stderr of the target process.
|
||||
No access to perf events. Try --fdtransfer or --all-user option or 'sysctl kernel.perf_event_paranoid=1'
|
||||
```
|
||||
`perf_event_open()` syscall has failed. The error message is printed to the error stream
|
||||
of the target JVM.
|
||||
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.
|
||||
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 will allow using perf_events.
|
||||
|
||||
If changing the configuration is not possible, you may fall back to
|
||||
`-e itimer` 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.
|
||||
stack traces.
|
||||
|
||||
```
|
||||
[frame_buffer_overflow]
|
||||
No AllocTracer symbols found. Are JDK debug symbols installed?
|
||||
```
|
||||
This message in the output means there was not enough space to store all call traces.
|
||||
Consider increasing frame buffer size with `-b` option.
|
||||
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/jvm-profiling-tools/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 |
882
demo/flamegraph.html
Normal file
882
demo/flamegraph.html
Normal file
@@ -0,0 +1,882 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<style>
|
||||
body {margin: 0; padding: 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 {margin: 5px 0 5px 0}
|
||||
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 {overflow: hidden; white-space: nowrap}
|
||||
#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}
|
||||
#reset {cursor: pointer}
|
||||
</style>
|
||||
</head>
|
||||
<body style='font: 12px Verdana, sans-serif'>
|
||||
<h1>Flame Graph</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/jvm-profiling-tools/async-profiler'>async-profiler</a></header>
|
||||
<canvas id='canvas' style='width: 100%; height: 752px'></canvas>
|
||||
<div id='hl'><span></span></div>
|
||||
<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>
|
||||
<p id='status'> </p>
|
||||
<script>
|
||||
// Copyright 2020 Andrei Pangin
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
'use strict';
|
||||
var root, rootLevel, px, pattern;
|
||||
var reverse = false;
|
||||
const levels = Array(47);
|
||||
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 = [
|
||||
[0x50e150, 30, 30, 30],
|
||||
[0x50bebe, 30, 30, 30],
|
||||
[0xe17d00, 30, 30, 0],
|
||||
[0xc8c83c, 30, 30, 10],
|
||||
[0xe15a5a, 30, 40, 40],
|
||||
];
|
||||
|
||||
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(level, left, width, type, title) {
|
||||
levels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});
|
||||
}
|
||||
|
||||
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 && (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 ? 'inherit' : '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;
|
||||
for (let x in marked) {
|
||||
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();
|
||||
}
|
||||
|
||||
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) + ', ' + 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;
|
||||
return;
|
||||
}
|
||||
}
|
||||
canvas.onmouseout();
|
||||
}
|
||||
|
||||
canvas.onmouseout = function() {
|
||||
hl.style.display = 'none';
|
||||
status.textContent = '\xa0';
|
||||
canvas.title = '';
|
||||
canvas.style.cursor = '';
|
||||
canvas.onclick = '';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
f(0,0,641,4,'all')
|
||||
f(1,0,5,0,'com/sun/glass/ui/InvokeLaterDispatcher.run')
|
||||
f(2,0,4,0,'com/sun/glass/ui/gtk/GtkApplication.submitForLaterInvocation')
|
||||
f(3,0,4,0,'com/sun/glass/ui/gtk/GtkApplication._submitForLaterInvocation')
|
||||
f(4,0,4,4,'Java_com_sun_glass_ui_gtk_GtkApplication__1submitForLaterInvocation')
|
||||
f(5,0,4,4,'__write')
|
||||
f(6,0,4,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(7,0,4,2,'do_syscall_64')
|
||||
f(8,0,4,2,'__x64_sys_write')
|
||||
f(9,0,4,2,'ksys_write')
|
||||
f(10,0,4,2,'vfs_write')
|
||||
f(11,0,4,2,'__vfs_write')
|
||||
f(12,0,4,2,'eventfd_write')
|
||||
f(1,5,408,0,'java/lang/Thread.run')
|
||||
f(2,5,179,0,'com/sun/glass/ui/gtk/GtkApplication$$Lambda$42/1642360923.run')
|
||||
f(3,5,179,0,'com/sun/glass/ui/gtk/GtkApplication.lambda$null$48')
|
||||
f(4,5,179,0,'com/sun/glass/ui/gtk/GtkApplication._runLoop')
|
||||
f(5,5,4,4,'Java_com_sun_glass_ui_gtk_GtkApplication__1runLoop')
|
||||
f(6,6,2,4,'__writev')
|
||||
f(7,6,2,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(8,6,2,2,'do_syscall_64')
|
||||
f(9,6,2,2,'__x64_sys_writev')
|
||||
f(10,6,2,2,'do_writev')
|
||||
f(11,6,2,2,'vfs_writev')
|
||||
f(12,6,2,2,'do_iter_write')
|
||||
f(13,6,2,2,'do_iter_readv_writev')
|
||||
f(14,6,2,2,'sock_write_iter')
|
||||
f(15,6,2,2,'sock_sendmsg')
|
||||
f(16,6,2,2,'inet_sendmsg')
|
||||
f(17,6,2,2,'tcp_sendmsg')
|
||||
f(18,6,2,2,'tcp_sendmsg_locked')
|
||||
f(19,6,2,2,'tcp_push')
|
||||
f(20,6,2,2,'__tcp_push_pending_frames')
|
||||
f(21,6,2,2,'tcp_write_xmit')
|
||||
f(22,6,2,2,'__tcp_transmit_skb')
|
||||
f(23,6,2,2,'ip_queue_xmit')
|
||||
f(24,6,2,2,'__ip_queue_xmit')
|
||||
f(25,6,2,2,'ip_local_out')
|
||||
f(26,6,2,2,'ip_output')
|
||||
f(27,6,2,2,'ip_finish_output')
|
||||
f(28,6,2,2,'__ip_finish_output')
|
||||
f(29,6,2,2,'ip_finish_output2')
|
||||
f(30,6,2,2,'dev_queue_xmit')
|
||||
f(31,6,2,2,'__dev_queue_xmit')
|
||||
f(32,6,2,2,'sch_direct_xmit')
|
||||
f(33,6,2,2,'dev_hard_start_xmit')
|
||||
f(34,6,2,2,'e1000_xmit_frame?[e1000]')
|
||||
f(5,10,161,0,'com/sun/glass/ui/InvokeLaterDispatcher$Future.run')
|
||||
f(6,10,6,3,'InterpreterRuntime::monitorexit(JavaThread*, BasicObjectLock*)')
|
||||
f(7,10,6,3,'ObjectMonitor::ExitEpilog(Thread*, ObjectWaiter*)')
|
||||
f(8,10,6,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(9,10,6,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(10,10,6,2,'do_syscall_64')
|
||||
f(11,10,6,2,'__x64_sys_futex')
|
||||
f(12,10,6,2,'do_futex')
|
||||
f(13,10,6,2,'wake_up_q')
|
||||
f(14,10,6,2,'try_to_wake_up')
|
||||
f(15,10,6,2,'_raw_spin_unlock_irqrestore')
|
||||
f(6,16,58,0,'com/sun/javafx/application/PlatformImpl$$Lambda$53/233530418.run')
|
||||
f(7,16,58,0,'com/sun/javafx/application/PlatformImpl.lambda$runLater$173')
|
||||
f(8,17,57,0,'java/security/AccessController.doPrivileged')
|
||||
f(9,17,57,0,'com/sun/javafx/application/PlatformImpl$$Lambda$54/1140247440.run')
|
||||
f(10,17,57,0,'com/sun/javafx/application/PlatformImpl.lambda$null$172')
|
||||
f(11,17,55,0,'com/sun/javafx/application/PlatformImpl$$Lambda$52/1364335809.run')
|
||||
f(12,17,55,0,'com/sun/javafx/application/PlatformImpl.lambda$runAndWait$174')
|
||||
f(13,17,4,0,'com/sun/javafx/application/LauncherImpl$$Lambda$57/1790390841.run')
|
||||
f(14,17,4,0,'com/sun/javafx/application/LauncherImpl.lambda$launchApplication1$160')
|
||||
f(15,17,4,0,'java/lang/reflect/Constructor.newInstance')
|
||||
f(16,17,4,0,'sun/reflect/DelegatingConstructorAccessorImpl.newInstance')
|
||||
f(17,17,4,0,'sun/reflect/NativeConstructorAccessorImpl.newInstance')
|
||||
f(18,17,4,0,'sun/reflect/NativeConstructorAccessorImpl.newInstance0')
|
||||
f(19,17,4,0,'demo/parallel/Main.<init>')
|
||||
f(20,17,2,0,'java/lang/ClassLoader.loadClass')
|
||||
f(21,17,2,0,'java/lang/ClassLoader.loadClass')
|
||||
f(22,17,2,0,'java/net/URLClassLoader.findClass')
|
||||
f(23,17,2,0,'java/security/AccessController.doPrivileged')
|
||||
f(24,17,2,0,'java/net/URLClassLoader$1.run')
|
||||
f(25,17,2,0,'java/net/URLClassLoader$1.run')
|
||||
f(26,17,2,0,'java/net/URLClassLoader.access$100')
|
||||
f(27,17,2,0,'java/net/URLClassLoader.defineClass')
|
||||
f(28,17,2,0,'java/security/SecureClassLoader.defineClass')
|
||||
f(13,21,50,0,'com/sun/javafx/application/LauncherImpl$$Lambda$63/508611611.run')
|
||||
f(14,21,50,0,'com/sun/javafx/application/LauncherImpl.lambda$launchApplication1$161')
|
||||
f(15,21,49,0,'demo/parallel/Main.start')
|
||||
f(16,22,17,0,'demo/parallel/Main.createContent')
|
||||
f(17,23,14,0,'demo/parallel/Main.createControlPane')
|
||||
f(18,26,8,0,'javafx/scene/control/Control.<clinit>')
|
||||
f(19,26,8,0,'com/sun/javafx/application/PlatformImpl.setDefaultPlatformUserAgentStylesheet')
|
||||
f(20,26,8,0,'com/sun/javafx/application/PlatformImpl.setPlatformUserAgentStylesheet')
|
||||
f(21,26,8,0,'com/sun/javafx/application/PlatformImpl._setPlatformUserAgentStylesheet')
|
||||
f(22,27,7,0,'java/security/AccessController.doPrivileged')
|
||||
f(23,27,7,0,'com/sun/javafx/application/PlatformImpl$$Lambda$68/360857571.run')
|
||||
f(24,27,7,0,'com/sun/javafx/application/PlatformImpl.lambda$_setPlatformUserAgentStylesheet$181')
|
||||
f(25,27,7,0,'com/sun/javafx/css/StyleManager.setUserAgentStylesheets')
|
||||
f(26,27,6,0,'com/sun/javafx/css/StyleManager._setDefaultUserAgentStylesheet')
|
||||
f(27,28,5,0,'com/sun/javafx/css/StyleManager.loadStylesheet')
|
||||
f(28,28,5,0,'com/sun/javafx/css/StyleManager.loadStylesheetUnPrivileged')
|
||||
f(29,28,2,0,'com/sun/javafx/css/StyleManager.getURL')
|
||||
f(30,28,2,0,'java/lang/Class.forName')
|
||||
f(31,28,2,0,'java/lang/Class.forName0')
|
||||
f(32,28,2,0,'com/sun/javafx/scene/control/skin/Utils.<clinit>')
|
||||
f(29,30,3,0,'com/sun/javafx/css/Stylesheet.loadBinary')
|
||||
f(30,30,3,0,'com/sun/javafx/css/Stylesheet.readBinary')
|
||||
f(31,30,3,0,'com/sun/javafx/css/Rule.readBinary')
|
||||
f(32,30,3,0,'com/sun/javafx/css/Selector.readBinary')
|
||||
f(33,30,2,0,'com/sun/javafx/css/CompoundSelector.readBinary')
|
||||
f(16,40,11,0,'javafx/scene/Scene.<init>')
|
||||
f(17,40,11,0,'javafx/scene/Scene.<init>')
|
||||
f(18,40,11,0,'javafx/scene/Scene.setRoot')
|
||||
f(19,40,11,0,'javafx/beans/property/ObjectPropertyBase.set')
|
||||
f(20,40,11,0,'javafx/beans/property/ObjectPropertyBase.markInvalid')
|
||||
f(21,40,11,0,'javafx/scene/Scene$9.invalidated')
|
||||
f(22,40,11,0,'javafx/scene/Node.setScenes')
|
||||
f(23,40,11,0,'javafx/scene/Node.invalidatedScenes')
|
||||
f(24,40,11,0,'javafx/scene/Node.impl_reapplyCSS')
|
||||
f(25,41,10,0,'javafx/scene/Node.reapplyCss')
|
||||
f(26,42,3,0,'javafx/scene/CssStyleHelper.<clinit>')
|
||||
f(27,42,3,0,'javafx/scene/text/Font.getDefault')
|
||||
f(28,42,3,0,'javafx/scene/text/Font.<init>')
|
||||
f(29,42,3,0,'com/sun/javafx/font/PrismFontLoader.loadFont')
|
||||
f(30,42,2,0,'com/sun/javafx/font/PrismFontFactory.createFont')
|
||||
f(31,42,2,0,'com/sun/javafx/font/LogicalFont.getLogicalFont')
|
||||
f(32,42,2,0,'com/sun/javafx/font/LogicalFont.getLogicalFont')
|
||||
f(33,42,2,0,'com/sun/javafx/font/LogicalFont.<init>')
|
||||
f(34,42,2,0,'com/sun/javafx/font/FontConfigManager.getFontConfigFont')
|
||||
f(35,42,2,0,'com/sun/javafx/font/FontConfigManager.initFontConfigLogFonts')
|
||||
f(36,42,2,0,'com/sun/javafx/font/FontConfigManager.getFontConfig')
|
||||
f(26,45,2,0,'javafx/scene/CssStyleHelper.createStyleHelper')
|
||||
f(26,47,4,0,'javafx/scene/Node.reapplyCss')
|
||||
f(27,47,3,0,'javafx/scene/CssStyleHelper.createStyleHelper')
|
||||
f(28,47,3,0,'com/sun/javafx/css/StyleManager.findMatchingStyles')
|
||||
f(29,47,3,0,'com/sun/javafx/css/StyleManager.gatherParentStylesheets')
|
||||
f(30,47,3,0,'com/sun/javafx/css/StyleManager.processStylesheets')
|
||||
f(31,47,3,0,'com/sun/javafx/css/StyleManager.loadStylesheet')
|
||||
f(32,47,3,0,'com/sun/javafx/css/StyleManager.loadStylesheetUnPrivileged')
|
||||
f(33,48,2,0,'com/sun/javafx/css/parser/CSSParser.parse')
|
||||
f(34,48,2,0,'com/sun/javafx/css/parser/CSSParser.parse')
|
||||
f(16,51,19,0,'javafx/stage/Stage.show')
|
||||
f(17,51,19,0,'javafx/stage/Window.show')
|
||||
f(18,51,19,0,'javafx/stage/Window.setShowing')
|
||||
f(19,51,19,0,'javafx/beans/property/BooleanPropertyBase.set')
|
||||
f(20,51,19,0,'javafx/beans/property/BooleanPropertyBase.markInvalid')
|
||||
f(21,51,19,0,'javafx/stage/Window$9.invalidated')
|
||||
f(22,52,3,0,'javafx/scene/Scene.impl_initPeer')
|
||||
f(22,55,14,0,'javafx/scene/Scene.impl_preferredSize')
|
||||
f(23,55,14,0,'javafx/scene/Scene.preferredSize')
|
||||
f(24,55,11,0,'javafx/scene/Scene.doCSSPass')
|
||||
f(25,55,11,0,'javafx/scene/Node.processCSS')
|
||||
f(26,55,11,0,'javafx/scene/Parent.impl_processCSS')
|
||||
f(27,56,10,0,'javafx/scene/Parent.impl_processCSS')
|
||||
f(28,56,10,0,'javafx/scene/control/Control.impl_processCSS')
|
||||
f(29,56,3,0,'javafx/scene/Parent.impl_processCSS')
|
||||
f(30,56,3,0,'javafx/scene/Node.impl_processCSS')
|
||||
f(31,56,3,0,'javafx/scene/CssStyleHelper.transitionToState')
|
||||
f(29,59,4,0,'javafx/scene/control/Button.createDefaultSkin')
|
||||
f(30,59,4,0,'com/sun/javafx/scene/control/skin/ButtonSkin.<init>')
|
||||
f(31,59,4,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.<init>')
|
||||
f(32,61,2,0,'com/sun/javafx/scene/control/skin/LabeledText.<init>')
|
||||
f(33,61,2,0,'javafx/css/StyleableObjectProperty.bind')
|
||||
f(34,61,2,0,'javafx/beans/property/ObjectPropertyBase.bind')
|
||||
f(35,61,2,0,'javafx/beans/property/ObjectPropertyBase.markInvalid')
|
||||
f(36,61,2,0,'javafx/scene/text/Text$5.invalidated')
|
||||
f(37,61,2,0,'javafx/scene/text/Text.access$200')
|
||||
f(38,61,2,0,'javafx/scene/text/Text.needsFullTextLayout')
|
||||
f(39,61,2,0,'javafx/scene/text/Text.getTextLayout')
|
||||
f(40,61,2,0,'com/sun/javafx/text/PrismTextLayout.setContent')
|
||||
f(41,61,2,0,'com/sun/javafx/font/PrismFont.getStrike')
|
||||
f(42,61,2,0,'com/sun/javafx/font/LogicalFont.getStrike')
|
||||
f(43,61,2,0,'com/sun/javafx/font/LogicalFont.getDefaultAAMode')
|
||||
f(44,61,2,0,'com/sun/javafx/font/LogicalFont.getSlot0Resource')
|
||||
f(45,61,2,0,'com/sun/javafx/font/PrismFontFactory.getFontResource')
|
||||
f(46,61,2,0,'com/sun/javafx/font/PrismFontFactory.getFullNameToFileMap')
|
||||
f(29,64,2,0,'javafx/scene/control/ProgressIndicator.createDefaultSkin')
|
||||
f(24,67,2,0,'javafx/scene/Scene.resizeRootToPreferredSize')
|
||||
f(25,67,2,0,'javafx/scene/Scene.getPreferredWidth')
|
||||
f(26,67,2,0,'javafx/scene/layout/Region.prefWidth')
|
||||
f(27,67,2,0,'javafx/scene/Parent.prefWidth')
|
||||
f(28,67,2,0,'javafx/scene/layout/Region.computePrefWidth')
|
||||
f(29,67,2,0,'javafx/scene/Parent.computePrefWidth')
|
||||
f(30,67,2,0,'javafx/scene/layout/Region.prefWidth')
|
||||
f(31,67,2,0,'javafx/scene/Parent.prefWidth')
|
||||
f(32,67,2,0,'javafx/scene/layout/GridPane.computePrefWidth')
|
||||
f(33,67,2,0,'javafx/scene/layout/GridPane.computePrefWidths')
|
||||
f(34,67,2,0,'javafx/scene/layout/Region.computeChildPrefAreaWidth')
|
||||
f(35,67,2,0,'javafx/scene/layout/Region.minWidth')
|
||||
f(36,67,2,0,'javafx/scene/Parent.minWidth')
|
||||
f(37,67,2,0,'javafx/scene/control/Control.computeMinWidth')
|
||||
f(38,67,2,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.computeMinWidth')
|
||||
f(39,67,2,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.computeMinLabeledPartWidth')
|
||||
f(40,67,2,0,'com/sun/javafx/scene/control/skin/Utils.computeTextWidth')
|
||||
f(41,67,2,0,'com/sun/javafx/text/PrismTextLayout.getBounds')
|
||||
f(42,67,2,0,'com/sun/javafx/text/PrismTextLayout.ensureLayout')
|
||||
f(43,67,2,0,'com/sun/javafx/text/PrismTextLayout.layout')
|
||||
f(44,67,2,0,'com/sun/javafx/text/PrismTextLayout.buildRuns')
|
||||
f(45,67,2,0,'com/sun/javafx/text/GlyphLayout.breakRuns')
|
||||
f(6,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit$$Lambda$46/1696939523.run')
|
||||
f(7,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.lambda$runToolkit$403')
|
||||
f(8,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulseFromQueue')
|
||||
f(9,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulse')
|
||||
f(10,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulse')
|
||||
f(11,74,8,0,'com/sun/javafx/tk/Toolkit.firePulse')
|
||||
f(12,74,6,0,'com/sun/javafx/tk/Toolkit.runPulse')
|
||||
f(13,74,6,0,'java/security/AccessController.doPrivileged')
|
||||
f(14,74,6,0,'com/sun/javafx/tk/Toolkit$$Lambda$193/1813875389.run')
|
||||
f(15,74,6,0,'com/sun/javafx/tk/Toolkit.lambda$runPulse$29')
|
||||
f(16,74,6,0,'javafx/scene/Scene$ScenePulseListener.pulse')
|
||||
f(17,76,4,0,'javafx/scene/Scene.doLayoutPass')
|
||||
f(18,76,4,0,'javafx/scene/Parent.layout')
|
||||
f(19,76,4,0,'demo/parallel/Main$1.layoutChildren')
|
||||
f(20,76,4,0,'javafx/scene/Parent.layoutChildren')
|
||||
f(21,76,4,0,'javafx/scene/Node.autosize')
|
||||
f(22,76,2,0,'javafx/scene/layout/Region.prefHeight')
|
||||
f(23,76,2,0,'javafx/scene/Parent.prefHeight')
|
||||
f(24,76,2,0,'javafx/scene/layout/GridPane.computePrefHeight')
|
||||
f(25,76,2,0,'javafx/scene/layout/GridPane.computePrefHeights')
|
||||
f(22,78,2,0,'javafx/scene/layout/Region.prefWidth')
|
||||
f(23,78,2,0,'javafx/scene/Parent.prefWidth')
|
||||
f(24,78,2,0,'javafx/scene/layout/GridPane.computePrefWidth')
|
||||
f(25,78,2,0,'javafx/scene/layout/GridPane.computePrefWidths')
|
||||
f(11,82,3,0,'com/sun/javafx/tk/quantum/PaintCollector.renderAll')
|
||||
f(12,82,3,0,'com/sun/javafx/tk/quantum/ViewScene.repaint')
|
||||
f(13,82,3,0,'com/sun/javafx/tk/quantum/QuantumToolkit.addRenderJob')
|
||||
f(14,82,3,0,'com/sun/javafx/tk/quantum/QuantumRenderer.submitRenderJob')
|
||||
f(15,82,3,0,'java/util/concurrent/AbstractExecutorService.submit')
|
||||
f(16,82,3,0,'java/util/concurrent/ThreadPoolExecutor.execute')
|
||||
f(17,82,3,0,'java/util/concurrent/LinkedBlockingQueue.offer')
|
||||
f(18,82,3,0,'java/util/concurrent/LinkedBlockingQueue.signalNotEmpty')
|
||||
f(19,82,3,0,'java/util/concurrent/locks/ReentrantLock.unlock')
|
||||
f(20,82,3,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
|
||||
f(21,82,3,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
|
||||
f(22,82,3,0,'java/util/concurrent/locks/LockSupport.unpark')
|
||||
f(23,82,3,0,'sun/misc/Unsafe.unpark')
|
||||
f(24,82,3,4,'Unsafe_Unpark')
|
||||
f(25,82,3,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(26,82,3,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(27,82,3,2,'do_syscall_64')
|
||||
f(28,82,3,2,'__x64_sys_futex')
|
||||
f(29,82,3,2,'do_futex')
|
||||
f(30,82,3,2,'wake_up_q')
|
||||
f(31,82,3,2,'try_to_wake_up')
|
||||
f(32,82,3,2,'_raw_spin_unlock_irqrestore')
|
||||
f(6,86,85,0,'com/sun/javafx/tk/quantum/SceneState$$Lambda$205/1725489206.run')
|
||||
f(7,86,85,0,'com/sun/javafx/tk/quantum/SceneState.lambda$uploadPixels$305')
|
||||
f(8,86,85,0,'com/sun/javafx/tk/quantum/SceneState.access$001')
|
||||
f(9,86,85,0,'com/sun/prism/PresentableState.uploadPixels')
|
||||
f(10,86,85,0,'com/sun/glass/ui/View.uploadPixels')
|
||||
f(11,86,85,0,'com/sun/glass/ui/gtk/GtkView._uploadPixels')
|
||||
f(12,86,85,0,'com/sun/glass/ui/gtk/GtkView._uploadPixelsDirect')
|
||||
f(13,86,85,4,'Java_com_sun_glass_ui_gtk_GtkView__1uploadPixelsDirect')
|
||||
f(14,86,85,4,'__writev')
|
||||
f(15,86,85,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(16,86,85,2,'do_syscall_64')
|
||||
f(17,86,85,2,'__x64_sys_writev')
|
||||
f(18,86,85,2,'do_writev')
|
||||
f(19,86,85,2,'vfs_writev')
|
||||
f(20,86,85,2,'do_iter_write')
|
||||
f(21,86,85,2,'do_iter_readv_writev')
|
||||
f(22,86,85,2,'sock_write_iter')
|
||||
f(23,86,85,2,'sock_sendmsg')
|
||||
f(24,86,85,2,'inet_sendmsg')
|
||||
f(25,86,85,2,'tcp_sendmsg')
|
||||
f(26,86,3,2,'lock_sock_nested')
|
||||
f(27,86,3,2,'_raw_spin_lock_bh')
|
||||
f(28,86,3,2,'queued_spin_lock_slowpath')
|
||||
f(29,86,3,2,'native_queued_spin_lock_slowpath')
|
||||
f(26,90,81,2,'tcp_sendmsg_locked')
|
||||
f(27,90,3,2,'__sk_flush_backlog')
|
||||
f(28,90,3,2,'__release_sock')
|
||||
f(29,90,3,2,'tcp_v4_do_rcv')
|
||||
f(30,90,3,2,'tcp_rcv_established')
|
||||
f(31,90,3,2,'__tcp_push_pending_frames')
|
||||
f(32,90,3,2,'tcp_write_xmit')
|
||||
f(33,90,3,2,'__tcp_transmit_skb')
|
||||
f(34,90,3,2,'ip_queue_xmit')
|
||||
f(35,90,3,2,'__ip_queue_xmit')
|
||||
f(36,90,3,2,'ip_local_out')
|
||||
f(37,90,3,2,'ip_output')
|
||||
f(38,90,3,2,'ip_finish_output')
|
||||
f(39,90,3,2,'__ip_finish_output')
|
||||
f(40,90,3,2,'ip_finish_output2')
|
||||
f(41,90,3,2,'dev_queue_xmit')
|
||||
f(42,90,3,2,'__dev_queue_xmit')
|
||||
f(43,90,3,2,'sch_direct_xmit')
|
||||
f(44,90,3,2,'dev_hard_start_xmit')
|
||||
f(45,90,3,2,'e1000_xmit_frame?[e1000]')
|
||||
f(27,93,78,2,'tcp_push_one')
|
||||
f(28,93,78,2,'tcp_write_xmit')
|
||||
f(29,93,78,2,'__tcp_transmit_skb')
|
||||
f(30,93,78,2,'ip_queue_xmit')
|
||||
f(31,93,78,2,'__ip_queue_xmit')
|
||||
f(32,93,78,2,'ip_local_out')
|
||||
f(33,93,78,2,'ip_output')
|
||||
f(34,93,78,2,'ip_finish_output')
|
||||
f(35,93,78,2,'__ip_finish_output')
|
||||
f(36,93,78,2,'ip_finish_output2')
|
||||
f(37,93,9,2,'__local_bh_enable_ip')
|
||||
f(38,93,9,2,'do_softirq')
|
||||
f(39,93,9,2,'do_softirq_own_stack')
|
||||
f(40,93,9,2,'__softirqentry_text_start')
|
||||
f(41,93,9,2,'net_rx_action')
|
||||
f(42,93,9,2,'e1000_clean?[e1000]')
|
||||
f(37,102,69,2,'dev_queue_xmit')
|
||||
f(38,102,69,2,'__dev_queue_xmit')
|
||||
f(39,102,69,2,'sch_direct_xmit')
|
||||
f(40,102,69,2,'dev_hard_start_xmit')
|
||||
f(41,102,69,2,'e1000_xmit_frame?[e1000]')
|
||||
f(5,171,3,0,'com/sun/glass/ui/View.notifyMouse')
|
||||
f(6,171,3,0,'com/sun/glass/ui/View.handleMouseEvent')
|
||||
f(7,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler.handleMouseEvent')
|
||||
f(8,171,3,0,'com/sun/javafx/tk/quantum/QuantumToolkit.runWithoutRenderLock')
|
||||
f(9,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$$Lambda$181/241733715.get')
|
||||
f(10,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler.lambda$handleMouseEvent$353')
|
||||
f(11,171,3,0,'java/security/AccessController.doPrivileged')
|
||||
f(12,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$MouseEventNotification.run')
|
||||
f(13,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$MouseEventNotification.run')
|
||||
f(14,171,3,0,'javafx/scene/Scene$ScenePeerListener.mouseEvent')
|
||||
f(15,171,3,0,'javafx/scene/Scene.impl_processMouseEvent')
|
||||
f(16,171,3,0,'javafx/scene/Scene$MouseHandler.access$1500')
|
||||
f(17,171,3,0,'javafx/scene/Scene$MouseHandler.process')
|
||||
f(18,172,2,0,'javafx/scene/Scene.access$6700')
|
||||
f(19,172,2,0,'javafx/scene/Scene.pick')
|
||||
f(20,172,2,0,'javafx/scene/Scene$MouseHandler.access$1600')
|
||||
f(21,172,2,0,'javafx/scene/Scene$MouseHandler.pickNode')
|
||||
f(22,172,2,0,'javafx/scene/Node.impl_pickNode')
|
||||
f(23,172,2,0,'javafx/scene/layout/Region.impl_pickNodeLocal')
|
||||
f(5,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit$$Lambda$47/605813029.run')
|
||||
f(6,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit.lambda$runToolkit$404')
|
||||
f(7,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit.postPulse')
|
||||
f(8,174,2,0,'com/sun/glass/ui/Application.invokeLater')
|
||||
f(9,174,2,0,'com/sun/glass/ui/gtk/GtkApplication._invokeLater')
|
||||
f(10,174,2,0,'com/sun/glass/ui/InvokeLaterDispatcher.invokeLater')
|
||||
f(11,174,2,0,'java/util/concurrent/LinkedBlockingDeque.addLast')
|
||||
f(12,174,2,0,'java/util/concurrent/LinkedBlockingDeque.offerLast')
|
||||
f(13,174,2,0,'java/util/concurrent/locks/ReentrantLock.unlock')
|
||||
f(14,174,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
|
||||
f(15,174,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
|
||||
f(16,174,2,0,'java/util/concurrent/locks/LockSupport.unpark')
|
||||
f(17,174,2,0,'sun/misc/Unsafe.unpark')
|
||||
f(18,174,2,4,'Unsafe_Unpark')
|
||||
f(19,174,2,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(20,174,2,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(21,174,2,2,'do_syscall_64')
|
||||
f(22,174,2,2,'__x64_sys_futex')
|
||||
f(23,174,2,2,'do_futex')
|
||||
f(24,174,2,2,'wake_up_q')
|
||||
f(25,174,2,2,'try_to_wake_up')
|
||||
f(26,174,2,2,'_raw_spin_unlock_irqrestore')
|
||||
f(5,176,8,4,'recvmsg')
|
||||
f(6,176,8,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(7,176,8,2,'do_syscall_64')
|
||||
f(8,176,8,2,'__x64_sys_recvmsg')
|
||||
f(9,176,8,2,'__sys_recvmsg')
|
||||
f(10,176,8,2,'___sys_recvmsg')
|
||||
f(11,176,8,2,'sock_recvmsg')
|
||||
f(12,176,8,2,'inet_recvmsg')
|
||||
f(13,176,8,2,'tcp_recvmsg')
|
||||
f(14,176,3,2,'lock_sock_nested')
|
||||
f(15,176,3,2,'_raw_spin_lock_bh')
|
||||
f(16,176,3,2,'queued_spin_lock_slowpath')
|
||||
f(17,176,3,2,'native_queued_spin_lock_slowpath')
|
||||
f(14,180,4,2,'tcp_cleanup_rbuf')
|
||||
f(15,180,4,2,'tcp_send_ack')
|
||||
f(16,180,4,2,'__tcp_send_ack.part.45')
|
||||
f(17,180,4,2,'__tcp_transmit_skb')
|
||||
f(18,180,4,2,'ip_queue_xmit')
|
||||
f(19,180,4,2,'__ip_queue_xmit')
|
||||
f(20,180,4,2,'ip_local_out')
|
||||
f(21,180,4,2,'ip_output')
|
||||
f(22,180,4,2,'ip_finish_output')
|
||||
f(23,180,4,2,'__ip_finish_output')
|
||||
f(24,180,4,2,'ip_finish_output2')
|
||||
f(25,180,4,2,'dev_queue_xmit')
|
||||
f(26,180,4,2,'__dev_queue_xmit')
|
||||
f(27,180,4,2,'sch_direct_xmit')
|
||||
f(28,180,4,2,'dev_hard_start_xmit')
|
||||
f(29,180,4,2,'e1000_xmit_frame?[e1000]')
|
||||
f(2,184,45,0,'com/sun/javafx/tk/quantum/QuantumRenderer$PipelineRunnable.run')
|
||||
f(3,184,45,0,'java/util/concurrent/ThreadPoolExecutor$Worker.run')
|
||||
f(4,184,45,0,'java/util/concurrent/ThreadPoolExecutor.runWorker')
|
||||
f(5,184,45,0,'com/sun/javafx/tk/RenderJob.run')
|
||||
f(6,184,45,0,'java/util/concurrent/FutureTask.runAndReset')
|
||||
f(7,184,45,0,'java/util/concurrent/Executors$RunnableAdapter.call')
|
||||
f(8,184,45,0,'com/sun/javafx/tk/quantum/UploadingPainter.run')
|
||||
f(9,184,2,0,'com/sun/javafx/tk/quantum/SceneState.uploadPixels')
|
||||
f(10,184,2,0,'com/sun/glass/ui/Application.invokeLater')
|
||||
f(11,184,2,0,'com/sun/glass/ui/gtk/GtkApplication._invokeLater')
|
||||
f(12,184,2,0,'com/sun/glass/ui/InvokeLaterDispatcher.invokeLater')
|
||||
f(13,184,2,0,'java/util/concurrent/LinkedBlockingDeque.addLast')
|
||||
f(14,184,2,0,'java/util/concurrent/LinkedBlockingDeque.offerLast')
|
||||
f(15,184,2,0,'java/util/concurrent/locks/ReentrantLock.unlock')
|
||||
f(16,184,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
|
||||
f(17,184,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
|
||||
f(18,184,2,0,'java/util/concurrent/locks/LockSupport.unpark')
|
||||
f(19,184,2,0,'sun/misc/Unsafe.unpark')
|
||||
f(20,184,2,4,'Unsafe_Unpark')
|
||||
f(21,184,2,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(22,184,2,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(23,184,2,2,'do_syscall_64')
|
||||
f(24,184,2,2,'__x64_sys_futex')
|
||||
f(25,184,2,2,'do_futex')
|
||||
f(26,184,2,2,'wake_up_q')
|
||||
f(27,184,2,2,'try_to_wake_up')
|
||||
f(28,184,2,2,'_raw_spin_unlock_irqrestore')
|
||||
f(9,186,40,0,'com/sun/javafx/tk/quantum/ViewPainter.paintImpl')
|
||||
f(10,188,38,0,'com/sun/javafx/tk/quantum/ViewPainter.doPaint')
|
||||
f(11,188,38,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(12,188,38,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(13,188,38,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(14,188,38,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
|
||||
f(15,188,38,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(16,188,38,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(17,188,21,0,'com/sun/javafx/sg/prism/NGCanvas.renderContent')
|
||||
f(18,188,15,0,'com/sun/javafx/sg/prism/NGCanvas.renderStream')
|
||||
f(19,188,15,0,'com/sun/javafx/sg/prism/NGCanvas.handleRenderOp')
|
||||
f(20,188,11,0,'com/sun/prism/impl/BaseResourceFactory.getCachedTexture')
|
||||
f(21,188,11,0,'com/sun/prism/impl/BaseResourceFactory.getCachedTexture')
|
||||
f(22,189,10,0,'com/sun/prism/sw/SWTexture.update')
|
||||
f(23,189,10,0,'com/sun/prism/sw/SWArgbPreTexture.update')
|
||||
f(24,189,10,0,'com/sun/javafx/image/impl/BaseByteToIntConverter.convert')
|
||||
f(25,189,10,0,'com/sun/javafx/image/impl/BaseByteToIntConverter.convert')
|
||||
f(26,189,10,0,'com/sun/javafx/image/impl/ByteBgra$ToIntArgbSameConv.doConvert')
|
||||
f(20,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(21,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(22,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(23,199,3,0,'com/sun/pisces/PiscesRenderer.drawImage')
|
||||
f(24,199,3,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
|
||||
f(25,199,3,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
|
||||
f(26,199,3,4,'fillRect')
|
||||
f(27,200,2,4,'emitLinePTSourceOver8888_pre')
|
||||
f(18,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(19,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(20,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(21,203,6,0,'com/sun/pisces/PiscesRenderer.drawImage')
|
||||
f(22,203,6,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
|
||||
f(23,203,6,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
|
||||
f(24,203,6,4,'fillRect')
|
||||
f(25,204,5,4,'emitLinePTSourceOver8888_pre')
|
||||
f(17,209,17,0,'com/sun/javafx/sg/prism/NGNode.renderOpacity')
|
||||
f(18,210,10,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(19,210,10,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
|
||||
f(20,210,10,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(21,210,10,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(22,210,10,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(23,210,8,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
|
||||
f(24,210,8,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(25,210,8,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(26,210,3,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(27,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderAsRectangle')
|
||||
f(28,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectangle')
|
||||
f(29,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectanglesDirectly')
|
||||
f(30,211,2,0,'com/sun/prism/sw/SWGraphics.fill')
|
||||
f(31,211,2,0,'com/sun/prism/sw/SWGraphics.paintShape')
|
||||
f(32,211,2,0,'com/sun/prism/sw/SWGraphics.paintShapePaintAlreadySet')
|
||||
f(33,211,2,0,'com/sun/prism/sw/SWContext.renderShape')
|
||||
f(34,211,2,0,'com/sun/prism/sw/SWContext$JavaShapeRenderer.renderShape')
|
||||
f(35,211,2,0,'com/sun/prism/impl/shape/OpenPiscesPrismUtils.setupRenderer')
|
||||
f(26,213,5,0,'com/sun/javafx/sg/prism/NGShape.renderContent')
|
||||
f(27,213,5,0,'com/sun/javafx/sg/prism/NGText.renderContent2D')
|
||||
f(28,213,4,0,'com/sun/javafx/sg/prism/NGText.renderText')
|
||||
f(29,213,4,0,'com/sun/prism/sw/SWGraphics.drawString')
|
||||
f(30,214,3,0,'com/sun/prism/sw/SWGraphics.drawGlyph')
|
||||
f(23,218,2,0,'com/sun/javafx/sg/prism/NGRegion.renderAsRectangle')
|
||||
f(24,218,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectangle')
|
||||
f(18,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(19,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(20,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(21,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(22,220,5,0,'com/sun/pisces/PiscesRenderer.drawImage')
|
||||
f(23,220,5,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
|
||||
f(24,220,5,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
|
||||
f(25,220,5,4,'fillRect')
|
||||
f(26,220,3,4,'emitLinePTSourceOver8888_pre')
|
||||
f(26,223,2,4,'genTexturePaintMultiply')
|
||||
f(9,227,2,0,'java/nio/DirectIntBufferU.put')
|
||||
f(10,227,2,0,'java/nio/Bits.copyFromArray')
|
||||
f(11,227,2,0,'sun/misc/Unsafe.copyMemory')
|
||||
f(12,227,2,4,'acl_CopyRight')
|
||||
f(2,229,184,0,'java/util/concurrent/FutureTask.run')
|
||||
f(3,229,184,0,'javafx/concurrent/Task$TaskCallable.call')
|
||||
f(4,229,184,0,'demo/parallel/MandelbrotSetTask.call')
|
||||
f(5,229,184,0,'demo/parallel/MandelbrotSetTask.call')
|
||||
f(6,233,175,0,'java/util/stream/IntPipeline$Head.forEach')
|
||||
f(7,233,68,0,'java/util/stream/IntPipeline.forEach')
|
||||
f(8,233,68,0,'java/util/stream/AbstractPipeline.evaluate')
|
||||
f(9,233,68,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.evaluateParallel')
|
||||
f(10,233,68,0,'java/util/stream/ForEachOps$ForEachOp.evaluateParallel')
|
||||
f(11,233,68,0,'java/util/concurrent/ForkJoinTask.invoke')
|
||||
f(12,233,68,0,'java/util/concurrent/ForkJoinTask.doInvoke')
|
||||
f(13,233,25,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(14,233,25,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(15,233,25,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(16,233,24,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(17,233,24,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(18,233,24,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(19,233,24,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(20,233,24,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(21,233,24,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(22,234,2,3,'SharedRuntime::complete_monitor_locking_C(oopDesc*, BasicLock*, JavaThread*)')
|
||||
f(23,234,2,3,'ObjectMonitor::enter(Thread*)')
|
||||
f(24,234,2,3,'ObjectMonitor::TrySpin_VaryDuration(Thread*)')
|
||||
f(22,236,21,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(23,237,19,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(24,237,19,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(25,248,5,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(25,253,3,0,'demo/parallel/Complex.plus')
|
||||
f(13,258,43,0,'java/util/concurrent/ForkJoinTask.externalAwaitDone')
|
||||
f(14,258,43,0,'java/util/concurrent/ForkJoinPool.externalHelpComplete')
|
||||
f(15,258,43,0,'java/util/concurrent/ForkJoinPool.helpComplete')
|
||||
f(16,258,14,0,'java/util/concurrent/ForkJoinPool$WorkQueue.pollAndExecCC')
|
||||
f(17,258,14,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(18,258,14,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(19,258,14,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(20,258,14,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(21,258,14,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(22,258,14,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(23,258,14,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(24,258,14,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(25,258,14,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(26,258,14,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(27,259,13,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(28,259,13,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(29,267,4,0,'demo/parallel/Complex.plus')
|
||||
f(16,272,29,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(17,272,29,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(18,272,29,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(19,272,29,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(20,272,29,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(21,272,29,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(22,272,29,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(23,272,29,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(24,272,29,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(25,273,28,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(26,274,27,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(27,274,27,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(28,287,3,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(28,290,8,0,'demo/parallel/Complex.plus')
|
||||
f(28,298,3,0,'demo/parallel/Complex.times')
|
||||
f(7,301,107,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(8,301,107,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(9,301,107,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(10,301,105,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(11,309,97,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(12,312,94,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(13,357,23,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(13,380,16,0,'demo/parallel/Complex.plus')
|
||||
f(13,396,10,0,'demo/parallel/Complex.times')
|
||||
f(6,408,5,0,'javafx/scene/image/WritableImage$2.setColor')
|
||||
f(7,408,3,0,'java/lang/Math.round')
|
||||
f(7,411,2,0,'javafx/scene/image/WritableImage$2.setArgb')
|
||||
f(8,411,2,0,'com/sun/prism/Image.setArgb')
|
||||
f(1,413,163,0,'java/util/concurrent/ForkJoinWorkerThread.run')
|
||||
f(2,413,163,0,'java/util/concurrent/ForkJoinPool.runWorker')
|
||||
f(3,413,163,0,'java/util/concurrent/ForkJoinPool$WorkQueue.runTask')
|
||||
f(4,413,38,0,'java/util/concurrent/ForkJoinPool$WorkQueue.execLocalTasks')
|
||||
f(5,413,38,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(6,413,38,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(7,413,38,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(8,413,38,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(9,413,38,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(10,413,38,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(11,413,38,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(12,413,38,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(13,413,38,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(14,415,32,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(15,421,26,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(16,424,22,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(17,432,7,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(17,439,7,0,'demo/parallel/Complex.plus')
|
||||
f(14,447,4,0,'javafx/scene/image/WritableImage$2.setColor')
|
||||
f(15,448,3,0,'javafx/scene/image/WritableImage$2.setArgb')
|
||||
f(16,449,2,0,'javafx/scene/image/Image.getWritablePlatformImage')
|
||||
f(4,451,125,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(5,451,125,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(6,451,125,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(7,452,124,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(8,452,124,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(9,452,124,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(10,452,124,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(11,452,124,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(12,452,124,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(13,454,119,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(14,462,111,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(15,464,109,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(16,504,39,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(16,543,22,0,'demo/parallel/Complex.plus')
|
||||
f(16,565,8,0,'demo/parallel/Complex.times')
|
||||
f(13,573,3,0,'javafx/scene/image/WritableImage$2.setColor')
|
||||
f(1,576,65,4,'start_thread')
|
||||
f(2,576,65,4,'java_start(Thread*)')
|
||||
f(3,576,4,3,'GCTaskThread::run()')
|
||||
f(4,577,3,3,'OldToYoungRootsTask::do_it(GCTaskManager*, unsigned int)')
|
||||
f(5,577,3,3,'CardTableExtension::scavenge_contents_parallel(ObjectStartArray*, MutableSpace*, HeapWord*, PSPromotionManager*, unsigned int, unsigned int)')
|
||||
f(6,577,3,3,'PSPromotionManager::drain_stacks_depth(bool)')
|
||||
f(7,578,2,3,'oopDesc* PSPromotionManager::copy_to_survivor_space<false>(oopDesc*)')
|
||||
f(3,580,61,3,'JavaThread::run()')
|
||||
f(4,580,61,3,'JavaThread::thread_main_inner()')
|
||||
f(5,580,61,3,'CompileBroker::compiler_thread_loop()')
|
||||
f(6,580,58,3,'CompileBroker::invoke_compiler_on_method(CompileTask*)')
|
||||
f(7,580,58,3,'C2Compiler::compile_method(ciEnv*, ciMethod*, int)')
|
||||
f(8,580,58,3,'Compile::Compile(ciEnv*, C2Compiler*, ciMethod*, int, bool, bool, bool)')
|
||||
f(9,580,32,3,'Compile::Code_Gen()')
|
||||
f(10,582,3,3,'PhaseCFG::do_global_code_motion()')
|
||||
f(11,582,3,3,'PhaseCFG::global_code_motion()')
|
||||
f(10,585,27,3,'PhaseChaitin::Register_Allocate()')
|
||||
f(11,585,2,3,'PhaseChaitin::Select()')
|
||||
f(12,585,2,3,'PhaseIFG::re_insert(unsigned int)')
|
||||
f(11,589,9,3,'PhaseChaitin::build_ifg_physical(ResourceArea*)')
|
||||
f(12,592,3,3,'PhaseChaitin::interfere_with_live(unsigned int, IndexSet*)')
|
||||
f(12,596,2,3,'RegMask::smear_to_sets(int)')
|
||||
f(11,599,3,3,'PhaseChaitin::gather_lrg_masks(bool)')
|
||||
f(11,602,5,3,'PhaseChaitin::post_allocate_copy_removal()')
|
||||
f(12,604,2,3,'PhaseChaitin::elide_copy(Node*, int, Block*, Node_List&, Node_List&, bool)')
|
||||
f(11,609,2,3,'PhaseIFG::init(unsigned int)')
|
||||
f(12,609,2,3,'IndexSet::initialize(unsigned int)')
|
||||
f(9,613,15,3,'Compile::Optimize()')
|
||||
f(10,614,13,3,'PhaseIdealLoop::build_and_optimize(bool, bool)')
|
||||
f(11,617,2,3,'PhaseIdealLoop::build_loop_early(VectorSet&, Node_List&, Node_Stack&)')
|
||||
f(11,619,4,3,'PhaseIdealLoop::build_loop_late(VectorSet&, Node_List&, Node_Stack&)')
|
||||
f(11,625,2,3,'PhaseIterGVN::optimize()')
|
||||
f(12,625,2,3,'PhaseIterGVN::transform_old(Node*)')
|
||||
f(9,629,3,3,'ParseGenerator::generate(JVMState*)')
|
||||
f(10,629,3,3,'Parse::Parse(JVMState*, ciMethod*, float)')
|
||||
f(11,629,3,3,'Parse::do_all_blocks()')
|
||||
f(12,629,3,3,'Parse::do_one_block()')
|
||||
f(13,629,3,3,'Parse::do_one_bytecode()')
|
||||
f(14,629,3,3,'Parse::do_call()')
|
||||
f(15,630,2,3,'PredictedCallGenerator::generate(JVMState*)')
|
||||
f(16,630,2,3,'ParseGenerator::generate(JVMState*)')
|
||||
f(17,630,2,3,'Parse::Parse(JVMState*, ciMethod*, float)')
|
||||
f(18,630,2,3,'Parse::do_all_blocks()')
|
||||
f(19,630,2,3,'Parse::do_one_block()')
|
||||
f(20,630,2,3,'Parse::do_one_bytecode()')
|
||||
f(21,630,2,3,'Parse::do_call()')
|
||||
f(9,632,6,3,'ciEnv::register_method(ciMethod*, int, CodeOffsets*, int, CodeBuffer*, int, OopMapSet*, ExceptionHandlerTable*, ImplicitExceptionTable*, AbstractCompiler*, int, bool, bool, RTMState)')
|
||||
f(10,632,6,3,'nmethod::post_compiled_method_load_event()')
|
||||
f(11,632,6,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(12,632,6,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(13,632,6,2,'do_syscall_64')
|
||||
f(14,632,6,2,'__x64_sys_futex')
|
||||
f(15,632,6,2,'do_futex')
|
||||
f(16,632,6,2,'wake_up_q')
|
||||
f(17,632,6,2,'try_to_wake_up')
|
||||
f(18,632,6,2,'_raw_spin_unlock_irqrestore')
|
||||
f(6,638,3,3,'CompileQueue::get()')
|
||||
f(7,638,3,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(8,638,3,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(9,638,3,2,'do_syscall_64')
|
||||
f(10,638,3,2,'__x64_sys_futex')
|
||||
f(11,638,3,2,'do_futex')
|
||||
f(12,638,3,2,'wake_up_q')
|
||||
f(13,638,3,2,'try_to_wake_up')
|
||||
f(14,638,3,2,'_raw_spin_unlock_irqrestore')
|
||||
render();
|
||||
</script></body></html>
|
||||
BIN
demo/flamegraph.png
Normal file
BIN
demo/flamegraph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
358
docs/cddl1.txt
358
docs/cddl1.txt
@@ -1,358 +0,0 @@
|
||||
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
|
||||
|
||||
1. Definitions.
|
||||
|
||||
1.1. "Contributor" means each individual or entity that
|
||||
creates or contributes to the creation of Modifications.
|
||||
|
||||
1.2. "Contributor Version" means the combination of the
|
||||
Original Software, prior Modifications used by a
|
||||
Contributor (if any), and the Modifications made by that
|
||||
particular Contributor.
|
||||
|
||||
1.3. "Covered Software" means (a) the Original Software, or
|
||||
(b) Modifications, or (c) the combination of files
|
||||
containing Original Software with files containing
|
||||
Modifications, in each case including portions thereof.
|
||||
|
||||
1.4. "Executable" means the Covered Software in any form
|
||||
other than Source Code.
|
||||
|
||||
1.5. "Initial Developer" means the individual or entity
|
||||
that first makes Original Software available under this
|
||||
License.
|
||||
|
||||
1.6. "Larger Work" means a work which combines Covered
|
||||
Software or portions thereof with code not governed by the
|
||||
terms of this License.
|
||||
|
||||
1.7. "License" means this document.
|
||||
|
||||
1.8. "Licensable" means having the right to grant, to the
|
||||
maximum extent possible, whether at the time of the initial
|
||||
grant or subsequently acquired, any and all of the rights
|
||||
conveyed herein.
|
||||
|
||||
1.9. "Modifications" means the Source Code and Executable
|
||||
form of any of the following:
|
||||
A. Any file that results from an addition to,
|
||||
deletion from or modification of the contents of a
|
||||
file containing Original Software or previous
|
||||
Modifications;
|
||||
B. Any new file that contains any part of the
|
||||
Original Software or previous Modification; or
|
||||
C. Any new file that is contributed or otherwise made
|
||||
available under the terms of this License.
|
||||
|
||||
1.10. "Original Software" means the Source Code and
|
||||
Executable form of computer software code that is
|
||||
originally released under this License.
|
||||
|
||||
1.11. "Patent Claims" means any patent claim(s), now owned
|
||||
or hereafter acquired, including without limitation,
|
||||
method, process, and apparatus claims, in any patent
|
||||
Licensable by grantor.
|
||||
|
||||
1.12. "Source Code" means (a) the common form of computer
|
||||
software code in which modifications are made and (b)
|
||||
associated documentation included in or with such code.
|
||||
|
||||
1.13. "You" (or "Your") means an individual or a legal
|
||||
entity exercising rights under, and complying with all of
|
||||
the terms of, this License. For legal entities, "You"
|
||||
includes any entity which controls, is controlled by, or is
|
||||
under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or
|
||||
indirect, to cause the direction or management of such
|
||||
entity, whether by contract or otherwise, or (b) ownership
|
||||
of more than fifty percent (50%) of the outstanding shares
|
||||
or beneficial ownership of such entity.
|
||||
|
||||
2. License Grants.
|
||||
|
||||
2.1. The Initial Developer Grant.
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, the
|
||||
Initial Developer hereby grants You a world-wide,
|
||||
royalty-free, non-exclusive license:
|
||||
(a) under intellectual property rights (other than
|
||||
patent or trademark) Licensable by Initial Developer,
|
||||
to use, reproduce, modify, display, perform,
|
||||
sublicense and distribute the Original Software (or
|
||||
portions thereof), with or without Modifications,
|
||||
and/or as part of a Larger Work; and
|
||||
(b) under Patent Claims infringed by the making,
|
||||
using or selling of Original Software, to make, have
|
||||
made, use, practice, sell, and offer for sale, and/or
|
||||
otherwise dispose of the Original Software (or
|
||||
portions thereof).
|
||||
(c) The licenses granted in Sections 2.1(a) and (b)
|
||||
are effective on the date Initial Developer first
|
||||
distributes or otherwise makes the Original Software
|
||||
available to a third party under the terms of this
|
||||
License.
|
||||
(d) Notwithstanding Section 2.1(b) above, no patent
|
||||
license is granted: (1) for code that You delete from
|
||||
the Original Software, or (2) for infringements
|
||||
caused by: (i) the modification of the Original
|
||||
Software, or (ii) the combination of the Original
|
||||
Software with other software or devices.
|
||||
|
||||
2.2. Contributor Grant.
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, each
|
||||
Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
(a) under intellectual property rights (other than
|
||||
patent or trademark) Licensable by Contributor to
|
||||
use, reproduce, modify, display, perform, sublicense
|
||||
and distribute the Modifications created by such
|
||||
Contributor (or portions thereof), either on an
|
||||
unmodified basis, with other Modifications, as
|
||||
Covered Software and/or as part of a Larger Work; and
|
||||
(b) under Patent Claims infringed by the making,
|
||||
using, or selling of Modifications made by that
|
||||
Contributor either alone and/or in combination with
|
||||
its Contributor Version (or portions of such
|
||||
combination), to make, use, sell, offer for sale,
|
||||
have made, and/or otherwise dispose of: (1)
|
||||
Modifications made by that Contributor (or portions
|
||||
thereof); and (2) the combination of Modifications
|
||||
made by that Contributor with its Contributor Version
|
||||
(or portions of such combination).
|
||||
(c) The licenses granted in Sections 2.2(a) and
|
||||
2.2(b) are effective on the date Contributor first
|
||||
distributes or otherwise makes the Modifications
|
||||
available to a third party.
|
||||
(d) Notwithstanding Section 2.2(b) above, no patent
|
||||
license is granted: (1) for any code that Contributor
|
||||
has deleted from the Contributor Version; (2) for
|
||||
infringements caused by: (i) third party
|
||||
modifications of Contributor Version, or (ii) the
|
||||
combination of Modifications made by that Contributor
|
||||
with other software (except as part of the
|
||||
Contributor Version) or other devices; or (3) under
|
||||
Patent Claims infringed by Covered Software in the
|
||||
absence of Modifications made by that Contributor.
|
||||
|
||||
3. Distribution Obligations.
|
||||
|
||||
3.1. Availability of Source Code.
|
||||
Any Covered Software that You distribute or otherwise make
|
||||
available in Executable form must also be made available in
|
||||
Source Code form and that Source Code form must be
|
||||
distributed only under the terms of this License. You must
|
||||
include a copy of this License with every copy of the
|
||||
Source Code form of the Covered Software You distribute or
|
||||
otherwise make available. You must inform recipients of any
|
||||
such Covered Software in Executable form as to how they can
|
||||
obtain such Covered Software in Source Code form in a
|
||||
reasonable manner on or through a medium customarily used
|
||||
for software exchange.
|
||||
|
||||
3.2. Modifications.
|
||||
The Modifications that You create or to which You
|
||||
contribute are governed by the terms of this License. You
|
||||
represent that You believe Your Modifications are Your
|
||||
original creation(s) and/or You have sufficient rights to
|
||||
grant the rights conveyed by this License.
|
||||
|
||||
3.3. Required Notices.
|
||||
You must include a notice in each of Your Modifications
|
||||
that identifies You as the Contributor of the Modification.
|
||||
You may not remove or alter any copyright, patent or
|
||||
trademark notices contained within the Covered Software, or
|
||||
any notices of licensing or any descriptive text giving
|
||||
attribution to any Contributor or the Initial Developer.
|
||||
|
||||
3.4. Application of Additional Terms.
|
||||
You may not offer or impose any terms on any Covered
|
||||
Software in Source Code form that alters or restricts the
|
||||
applicable version of this License or the recipients'
|
||||
rights hereunder. You may choose to offer, and to charge a
|
||||
fee for, warranty, support, indemnity or liability
|
||||
obligations to one or more recipients of Covered Software.
|
||||
However, you may do so only on Your own behalf, and not on
|
||||
behalf of the Initial Developer or any Contributor. You
|
||||
must make it absolutely clear that any such warranty,
|
||||
support, indemnity or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify the Initial
|
||||
Developer and every Contributor for any liability incurred
|
||||
by the Initial Developer or such Contributor as a result of
|
||||
warranty, support, indemnity or liability terms You offer.
|
||||
|
||||
3.5. Distribution of Executable Versions.
|
||||
You may distribute the Executable form of the Covered
|
||||
Software under the terms of this License or under the terms
|
||||
of a license of Your choice, which may contain terms
|
||||
different from this License, provided that You are in
|
||||
compliance with the terms of this License and that the
|
||||
license for the Executable form does not attempt to limit
|
||||
or alter the recipient's rights in the Source Code form
|
||||
from the rights set forth in this License. If You
|
||||
distribute the Covered Software in Executable form under a
|
||||
different license, You must make it absolutely clear that
|
||||
any terms which differ from this License are offered by You
|
||||
alone, not by the Initial Developer or Contributor. You
|
||||
hereby agree to indemnify the Initial Developer and every
|
||||
Contributor for any liability incurred by the Initial
|
||||
Developer or such Contributor as a result of any such terms
|
||||
You offer.
|
||||
|
||||
3.6. Larger Works.
|
||||
You may create a Larger Work by combining Covered Software
|
||||
with other code not governed by the terms of this License
|
||||
and distribute the Larger Work as a single product. In such
|
||||
a case, You must make sure the requirements of this License
|
||||
are fulfilled for the Covered Software.
|
||||
|
||||
4. Versions of the License.
|
||||
|
||||
4.1. New Versions.
|
||||
Sun Microsystems, Inc. is the initial license steward and
|
||||
may publish revised and/or new versions of this License
|
||||
from time to time. Each version will be given a
|
||||
distinguishing version number. Except as provided in
|
||||
Section 4.3, no one other than the license steward has the
|
||||
right to modify this License.
|
||||
|
||||
4.2. Effect of New Versions.
|
||||
You may always continue to use, distribute or otherwise
|
||||
make the Covered Software available under the terms of the
|
||||
version of the License under which You originally received
|
||||
the Covered Software. If the Initial Developer includes a
|
||||
notice in the Original Software prohibiting it from being
|
||||
distributed or otherwise made available under any
|
||||
subsequent version of the License, You must distribute and
|
||||
make the Covered Software available under the terms of the
|
||||
version of the License under which You originally received
|
||||
the Covered Software. Otherwise, You may also choose to
|
||||
use, distribute or otherwise make the Covered Software
|
||||
available under the terms of any subsequent version of the
|
||||
License published by the license steward.
|
||||
|
||||
4.3. Modified Versions.
|
||||
When You are an Initial Developer and You want to create a
|
||||
new license for Your Original Software, You may create and
|
||||
use a modified version of this License if You: (a) rename
|
||||
the license and remove any references to the name of the
|
||||
license steward (except to note that the license differs
|
||||
from this License); and (b) otherwise make it clear that
|
||||
the license contains terms which differ from this License.
|
||||
|
||||
5. DISCLAIMER OF WARRANTY.
|
||||
|
||||
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
|
||||
SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
|
||||
PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
|
||||
COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
|
||||
INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF
|
||||
ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
|
||||
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
|
||||
ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
|
||||
DISCLAIMER.
|
||||
|
||||
6. TERMINATION.
|
||||
|
||||
6.1. This License and the rights granted hereunder will
|
||||
terminate automatically if You fail to comply with terms
|
||||
herein and fail to cure such breach within 30 days of
|
||||
becoming aware of the breach. Provisions which, by their
|
||||
nature, must remain in effect beyond the termination of
|
||||
this License shall survive.
|
||||
|
||||
6.2. If You assert a patent infringement claim (excluding
|
||||
declaratory judgment actions) against Initial Developer or
|
||||
a Contributor (the Initial Developer or Contributor against
|
||||
whom You assert such claim is referred to as "Participant")
|
||||
alleging that the Participant Software (meaning the
|
||||
Contributor Version where the Participant is a Contributor
|
||||
or the Original Software where the Participant is the
|
||||
Initial Developer) directly or indirectly infringes any
|
||||
patent, then any and all rights granted directly or
|
||||
indirectly to You by such Participant, the Initial
|
||||
Developer (if the Initial Developer is not the Participant)
|
||||
and all Contributors under Sections 2.1 and/or 2.2 of this
|
||||
License shall, upon 60 days notice from Participant
|
||||
terminate prospectively and automatically at the expiration
|
||||
of such 60 day notice period, unless if within such 60 day
|
||||
period You withdraw Your claim with respect to the
|
||||
Participant Software against such Participant either
|
||||
unilaterally or pursuant to a written agreement with
|
||||
Participant.
|
||||
|
||||
6.3. In the event of termination under Sections 6.1 or 6.2
|
||||
above, all end user licenses that have been validly granted
|
||||
by You or any distributor hereunder prior to termination
|
||||
(excluding licenses granted to You by any distributor)
|
||||
shall survive termination.
|
||||
|
||||
7. LIMITATION OF LIABILITY.
|
||||
|
||||
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
|
||||
(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
|
||||
INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
|
||||
COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
|
||||
LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
|
||||
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
|
||||
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
|
||||
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
|
||||
COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
|
||||
INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
|
||||
LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
|
||||
INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
|
||||
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
|
||||
NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
|
||||
APPLY TO YOU.
|
||||
|
||||
8. U.S. GOVERNMENT END USERS.
|
||||
|
||||
The Covered Software is a "commercial item," as that term is
|
||||
defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
|
||||
computer software" (as that term is defined at 48 C.F.R. ¤
|
||||
252.227-7014(a)(1)) and "commercial computer software
|
||||
documentation" as such terms are used in 48 C.F.R. 12.212 (Sept.
|
||||
1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1
|
||||
through 227.7202-4 (June 1995), all U.S. Government End Users
|
||||
acquire Covered Software with only those rights set forth herein.
|
||||
This U.S. Government Rights clause is in lieu of, and supersedes,
|
||||
any other FAR, DFAR, or other clause or provision that addresses
|
||||
Government rights in computer software under this License.
|
||||
|
||||
9. MISCELLANEOUS.
|
||||
|
||||
This License represents the complete agreement concerning subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the
|
||||
extent necessary to make it enforceable. This License shall be
|
||||
governed by the law of the jurisdiction specified in a notice
|
||||
contained within the Original Software (except to the extent
|
||||
applicable law, if any, provides otherwise), excluding such
|
||||
jurisdiction's conflict-of-law provisions. Any litigation
|
||||
relating to this License shall be subject to the jurisdiction of
|
||||
the courts located in the jurisdiction and venue specified in a
|
||||
notice contained within the Original Software, with the losing
|
||||
party responsible for costs, including, without limitation, court
|
||||
costs and reasonable attorneys' fees and expenses. The
|
||||
application of the United Nations Convention on Contracts for the
|
||||
International Sale of Goods is expressly excluded. Any law or
|
||||
regulation which provides that the language of a contract shall
|
||||
be construed against the drafter shall not apply to this License.
|
||||
You agree that You alone are responsible for compliance with the
|
||||
United States export administration regulations (and the export
|
||||
control laws and regulation of any other countries) when You use,
|
||||
distribute or otherwise make available any Covered Software.
|
||||
|
||||
10. RESPONSIBILITY FOR CLAIMS.
|
||||
|
||||
As between Initial Developer and the Contributors, each party is
|
||||
responsible for claims and damages arising, directly or
|
||||
indirectly, out of its utilization of rights under this License
|
||||
and You agree to work with Initial Developer and Contributors to
|
||||
distribute such responsibility on an equitable basis. Nothing
|
||||
herein is intended or shall be deemed to constitute any admission
|
||||
of liability.
|
||||
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>2.9</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/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>
|
||||
</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>
|
||||
133
pom.xml
Normal file
133
pom.xml
Normal file
@@ -0,0 +1,133 @@
|
||||
<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</artifactId>
|
||||
<version>2.9</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/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>
|
||||
</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/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>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>
|
||||
<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>
|
||||
312
profiler.sh
312
profiler.sh
@@ -1,11 +1,16 @@
|
||||
#!/bin/bash
|
||||
#!/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 " dump dump collected data without stopping profiling session"
|
||||
echo " check check if the specified profiling event is available"
|
||||
echo " status print profiling status"
|
||||
echo " meminfo print profiler memory stats"
|
||||
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)"
|
||||
@@ -15,108 +20,145 @@ usage() {
|
||||
echo " -f filename dump output to <filename>"
|
||||
echo " -i interval sampling interval in nanoseconds"
|
||||
echo " -j jstackdepth maximum Java stack depth"
|
||||
echo " -b bufsize frame buffer size"
|
||||
echo " -t profile different threads separately"
|
||||
echo " -s simple class names instead of FQN"
|
||||
echo " -a annotate Java method names"
|
||||
echo " -o fmt[,fmt...] output format: summary|traces|flat|collapsed|svg|tree|jfr"
|
||||
echo " -g print method signatures"
|
||||
echo " -a annotate Java methods"
|
||||
echo " -l prepend library names"
|
||||
echo " -o fmt output format: flat|traces|collapsed|flamegraph|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 SVG title"
|
||||
echo " --width px SVG width"
|
||||
echo " --height px SVG frame height"
|
||||
echo " --minwidth px skip frames smaller than px"
|
||||
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 " --loop time run profiler in a loop"
|
||||
echo " --alloc bytes allocation profiling interval in bytes"
|
||||
echo " --live build allocation profile from live objects only"
|
||||
echo " --lock duration lock profiling threshold in nanoseconds"
|
||||
echo " --total accumulate the total value (time, bytes, etc.)"
|
||||
echo " --all-user only include user-mode events"
|
||||
echo " --sched group threads by scheduling policy"
|
||||
echo " --cstack mode how to traverse C stack: fp|dwarf|lbr|no"
|
||||
echo " --begin function begin profiling when function is executed"
|
||||
echo " --end function end profiling when function is executed"
|
||||
echo " --ttsp time-to-safepoint profiling"
|
||||
echo " --jfrsync config synchronize profiler with JFR recording"
|
||||
echo " --lib path full path to libasyncProfiler.so in the container"
|
||||
echo " --fdtransfer use fdtransfer to serve perf requests"
|
||||
echo " from the non-privileged target"
|
||||
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 "Example: $0 -d 30 -f profile.html 3456"
|
||||
echo " $0 start -i 999000 jps"
|
||||
echo " $0 stop -o summary,flat 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 ]]; then
|
||||
if [[ -f $FILE ]]; then
|
||||
cat $FILE
|
||||
rm $FILE
|
||||
if [ "$USE_TMP" = true ]; then
|
||||
if [ -f "$FILE" ]; then
|
||||
cat "$FILE"
|
||||
rm "$FILE"
|
||||
elif [ -f "$ROOT_PREFIX$FILE" ]; then
|
||||
cat "$ROOT_PREFIX$FILE"
|
||||
rm "$ROOT_PREFIX$FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
mirror_log() {
|
||||
# Try to access the log file both directly and through /proc/[pid]/root,
|
||||
# in case the target namespace differs
|
||||
if [ -f "$LOG" ]; then
|
||||
cat "$LOG" >&2
|
||||
rm "$LOG"
|
||||
elif [ -f "$ROOT_PREFIX$LOG" ]; then
|
||||
cat "$ROOT_PREFIX$LOG" >&2
|
||||
rm "$ROOT_PREFIX$LOG"
|
||||
fi
|
||||
}
|
||||
|
||||
check_if_terminated() {
|
||||
if ! kill -0 $PID 2> /dev/null; then
|
||||
if ! kill -0 "$PID" 2> /dev/null; then
|
||||
mirror_output
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
fdtransfer() {
|
||||
if [ "$USE_FDTRANSFER" = "true" ]; then
|
||||
FDTRANSFER_PATH="@async-profiler-$(od -An -N3 -i /dev/random | xargs)"
|
||||
PARAMS="$PARAMS,fdtransfer=$FDTRANSFER_PATH"
|
||||
"$FDTRANSFER" "$FDTRANSFER_PATH" "$PID"
|
||||
fi
|
||||
}
|
||||
|
||||
jattach() {
|
||||
$JATTACH $PID load "$PROFILER" true "$1" > /dev/null
|
||||
set +e
|
||||
"$JATTACH" "$PID" load "$PROFILER" true "$1,log=$LOG" > /dev/null
|
||||
RET=$?
|
||||
|
||||
# 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
|
||||
if [ "$UNAME_S" = "Darwin" ]; then
|
||||
otool -L "$PROFILER"
|
||||
else
|
||||
ldd "$PROFILER"
|
||||
LD_PRELOAD="$PROFILER" /bin/true
|
||||
fi
|
||||
fi
|
||||
|
||||
mirror_log
|
||||
exit $RET
|
||||
fi
|
||||
|
||||
mirror_log
|
||||
mirror_output
|
||||
set -e
|
||||
}
|
||||
|
||||
function abspath() {
|
||||
if [ "$UNAME_S" == "Darwin" ]; then
|
||||
perl -MCwd -e 'print Cwd::abs_path shift' $1
|
||||
else
|
||||
readlink -f $1
|
||||
fi
|
||||
}
|
||||
SCRIPT_BIN="$0"
|
||||
while [ -h "$SCRIPT_BIN" ]; do
|
||||
SCRIPT_BIN="$(readlink "$SCRIPT_BIN")"
|
||||
done
|
||||
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_BIN")" > /dev/null 2>&1; pwd -P)"
|
||||
|
||||
|
||||
OPTIND=1
|
||||
UNAME_S=$(uname -s)
|
||||
SCRIPT_DIR=$(dirname $(abspath $0))
|
||||
JATTACH=$SCRIPT_DIR/build/jattach
|
||||
FDTRANSFER=$SCRIPT_DIR/build/fdtransfer
|
||||
USE_FDTRANSFER="false"
|
||||
FDTRANSFER_PATH=""
|
||||
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
|
||||
ACTION="collect"
|
||||
EVENT="cpu"
|
||||
DURATION="60"
|
||||
FILE=""
|
||||
USE_TMP="true"
|
||||
INTERVAL=""
|
||||
JSTACKDEPTH=""
|
||||
FRAMEBUF=""
|
||||
THREADS=""
|
||||
RING=""
|
||||
OUTPUT=""
|
||||
FORMAT=""
|
||||
PARAMS=""
|
||||
PID=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-h|"-?")
|
||||
usage
|
||||
;;
|
||||
start|stop|status|list|collect)
|
||||
start|resume|stop|dump|check|status|meminfo|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
-e)
|
||||
EVENT="$2"
|
||||
PARAMS="$PARAMS,event=$2"
|
||||
shift
|
||||
;;
|
||||
-d)
|
||||
@@ -125,55 +167,111 @@ while [[ $# -gt 0 ]]; do
|
||||
;;
|
||||
-f)
|
||||
FILE="$2"
|
||||
unset USE_TMP
|
||||
USE_TMP=false
|
||||
shift
|
||||
;;
|
||||
-i)
|
||||
INTERVAL=",interval=$2"
|
||||
PARAMS="$PARAMS,interval=$2"
|
||||
shift
|
||||
;;
|
||||
-j)
|
||||
JSTACKDEPTH=",jstackdepth=$2"
|
||||
shift
|
||||
;;
|
||||
-b)
|
||||
FRAMEBUF=",framebuf=$2"
|
||||
PARAMS="$PARAMS,jstackdepth=$2"
|
||||
shift
|
||||
;;
|
||||
-t)
|
||||
THREADS=",threads"
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-s)
|
||||
FORMAT="$FORMAT,simple"
|
||||
;;
|
||||
-g)
|
||||
FORMAT="$FORMAT,sig"
|
||||
;;
|
||||
-a)
|
||||
FORMAT="$FORMAT,ann"
|
||||
;;
|
||||
-l)
|
||||
FORMAT="$FORMAT,lib"
|
||||
;;
|
||||
-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=${2//&/&}
|
||||
TITLE=${TITLE//</<}
|
||||
TITLE=${TITLE//>/>}
|
||||
TITLE=${TITLE//,/,}
|
||||
TITLE="$(echo "$2" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/,/\,/g')"
|
||||
FORMAT="$FORMAT,title=$TITLE"
|
||||
shift
|
||||
;;
|
||||
--width|--height|--minwidth)
|
||||
FORMAT="$FORMAT,${1:2}=$2"
|
||||
FORMAT="$FORMAT,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--reverse)
|
||||
FORMAT="$FORMAT,reverse"
|
||||
;;
|
||||
--all-kernel)
|
||||
RING=",allkernel"
|
||||
--samples|--total)
|
||||
FORMAT="$FORMAT,${1#--}"
|
||||
;;
|
||||
--alloc|--lock|--chunksize|--chunktime)
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--timeout|--loop)
|
||||
if [ "$ACTION" = "collect" ]; then
|
||||
ACTION="start"
|
||||
fi
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--all-user)
|
||||
RING=",alluser"
|
||||
PARAMS="$PARAMS,alluser"
|
||||
;;
|
||||
--sched)
|
||||
PARAMS="$PARAMS,sched"
|
||||
;;
|
||||
--live)
|
||||
PARAMS="$PARAMS,live"
|
||||
;;
|
||||
--cstack|--call-graph)
|
||||
PARAMS="$PARAMS,cstack=$2"
|
||||
shift
|
||||
;;
|
||||
--begin|--end)
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--ttsp)
|
||||
PARAMS="$PARAMS,begin=SafepointSynchronize::begin,end=RuntimeService::record_safepoint_synchronized"
|
||||
;;
|
||||
--jfrsync)
|
||||
OUTPUT="jfr"
|
||||
PARAMS="$PARAMS,jfrsync=$2"
|
||||
shift
|
||||
;;
|
||||
--lib)
|
||||
PROFILER="$2"
|
||||
shift
|
||||
;;
|
||||
--fdtransfer)
|
||||
USE_FDTRANSFER="true"
|
||||
;;
|
||||
--safe-mode)
|
||||
PARAMS="$PARAMS,safemode=$2"
|
||||
shift
|
||||
;;
|
||||
[0-9]*)
|
||||
PID="$1"
|
||||
@@ -182,66 +280,102 @@ while [[ $# -gt 0 ]]; do
|
||||
# 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
|
||||
if [ "$PID" = "" ]; then
|
||||
case "$ACTION" in
|
||||
version)
|
||||
java "-agentpath:$PROFILER=version=full" -version 2> /dev/null
|
||||
;;
|
||||
list)
|
||||
java "-agentpath:$PROFILER=list" -version 2> /dev/null
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
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 ]]; then
|
||||
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
|
||||
LOG=/tmp/async-profiler-log.$$.$PID
|
||||
|
||||
# select default output format
|
||||
if [[ "$OUTPUT" == "" ]]; then
|
||||
if [[ $FILE == *.svg ]]; then
|
||||
OUTPUT="svg"
|
||||
elif [[ $FILE == *.html ]]; then
|
||||
OUTPUT="tree"
|
||||
elif [[ $FILE == *.jfr ]]; then
|
||||
OUTPUT="jfr"
|
||||
elif [[ $FILE == *.collapsed ]] || [[ $FILE == *.folded ]]; then
|
||||
OUTPUT="collapsed"
|
||||
else
|
||||
OUTPUT="summary,traces=200,flat=200"
|
||||
fi
|
||||
UNAME_S=$(uname -s)
|
||||
if [ "$UNAME_S" = "Linux" ]; then
|
||||
ROOT_PREFIX="/proc/$PID/root"
|
||||
else
|
||||
ROOT_PREFIX=""
|
||||
fi
|
||||
|
||||
case $ACTION in
|
||||
start)
|
||||
jattach "start,event=$EVENT,file=$FILE$INTERVAL$JSTACKDEPTH$FRAMEBUF$THREADS$RING,$OUTPUT$FORMAT"
|
||||
start|resume)
|
||||
fdtransfer
|
||||
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
;;
|
||||
stop)
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
check)
|
||||
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
;;
|
||||
status)
|
||||
jattach "status,file=$FILE"
|
||||
stop|dump)
|
||||
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
list)
|
||||
jattach "list,file=$FILE"
|
||||
status|meminfo|list)
|
||||
jattach "$ACTION,file=$FILE"
|
||||
;;
|
||||
version)
|
||||
jattach "version=full,file=$FILE"
|
||||
;;
|
||||
collect)
|
||||
jattach "start,event=$EVENT,file=$FILE$INTERVAL$JSTACKDEPTH$FRAMEBUF$THREADS$RING,$OUTPUT$FORMAT"
|
||||
while (( DURATION-- > 0 )); do
|
||||
fdtransfer
|
||||
jattach "start,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
echo Profiling for "$DURATION" seconds >&2
|
||||
set +e
|
||||
trap 'DURATION=0' INT
|
||||
|
||||
while [ "$DURATION" -gt 0 ]; do
|
||||
DURATION=$(( DURATION-1 ))
|
||||
check_if_terminated
|
||||
sleep 1
|
||||
done
|
||||
|
||||
set -e
|
||||
trap - INT
|
||||
echo Done >&2
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
version)
|
||||
if [[ "$PID" == "" ]]; then
|
||||
java "-agentpath:$PROFILER=version" -version 2> /dev/null
|
||||
else
|
||||
jattach "version,file=$FILE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -14,119 +14,155 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "allocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
// JDK 7-9
|
||||
Trap AllocTracer::_in_new_tlab("_ZN11AllocTracer33send_allocation_in_new_tlab_event");
|
||||
Trap AllocTracer::_outside_tlab("_ZN11AllocTracer34send_allocation_outside_tlab_event");
|
||||
// JDK 10+
|
||||
Trap AllocTracer::_in_new_tlab2("_ZN11AllocTracer27send_allocation_in_new_tlab");
|
||||
Trap AllocTracer::_outside_tlab2("_ZN11AllocTracer28send_allocation_outside_tlab");
|
||||
int AllocTracer::_trap_kind;
|
||||
Trap AllocTracer::_in_new_tlab(0);
|
||||
Trap AllocTracer::_outside_tlab(1);
|
||||
volatile bool AllocTracer::_use_hook = false;
|
||||
|
||||
|
||||
// Resolve the address of the intercepted function
|
||||
bool Trap::resolve(NativeCodeCache* libjvm) {
|
||||
if (_entry != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_entry = (instruction_t*)libjvm->findSymbolByPrefix(_func_name);
|
||||
if (_entry != NULL) {
|
||||
// Make the entry point writable, so we can rewrite instructions
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
uintptr_t page_start = (uintptr_t)_entry & -page_size;
|
||||
mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert breakpoint at the very first instruction
|
||||
void Trap::install() {
|
||||
if (_entry != NULL) {
|
||||
_saved_insn = *_entry;
|
||||
*_entry = BREAKPOINT;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear breakpoint - restore the original instruction
|
||||
void Trap::uninstall() {
|
||||
if (_entry != NULL) {
|
||||
*_entry = _saved_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
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;
|
||||
uintptr_t total_size;
|
||||
uintptr_t instance_size;
|
||||
|
||||
// PC points either to BREAKPOINT instruction or to the next one
|
||||
if (frame.pc() - (uintptr_t)_in_new_tlab._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg1(), false);
|
||||
} else if (frame.pc() - (uintptr_t)_outside_tlab._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg1(), true);
|
||||
} else if (frame.pc() - (uintptr_t)_in_new_tlab2._entry <= sizeof(instruction_t)) {
|
||||
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)
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg2(), false);
|
||||
} else if (frame.pc() - (uintptr_t)_outside_tlab2._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
event_type = BCI_ALLOC;
|
||||
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)
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg2(), true);
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
event_type = BCI_ALLOC_OUTSIDE_TLAB;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = 0;
|
||||
} else {
|
||||
// Not our trap; nothing to do
|
||||
// Not our trap
|
||||
Profiler::instance()->trapHandler(signo, siginfo, ucontext);
|
||||
return;
|
||||
}
|
||||
|
||||
// Leave the trapped function by simulating "ret" instruction
|
||||
uintptr_t klass = frame.arg0();
|
||||
frame.ret();
|
||||
|
||||
if (_enabled && updateCounter(_allocated_bytes, total_size, _interval)) {
|
||||
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, uintptr_t rklass, uintptr_t rsize, bool outside_tlab) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
if (outside_tlab) {
|
||||
// Invert the last bit to distinguish jmethodID from the allocation in new TLAB
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL_OUTSIDE_TLAB, (jmethodID)((uintptr_t)symbol ^ 1));
|
||||
void AllocTracer::inNewTLAB1(uintptr_t klass, void* obj, size_t tlab_size, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, tlab_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC, klass, tlab_size, alloc_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::outsideTLAB1(uintptr_t klass, void* obj, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, alloc_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC_OUTSIDE_TLAB, klass, alloc_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::inNewTLAB2(uintptr_t klass, size_t tlab_size, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, tlab_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC, klass, tlab_size, alloc_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::outsideTLAB2(uintptr_t klass, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, alloc_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC_OUTSIDE_TLAB, klass, alloc_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size) {
|
||||
AllocEvent event;
|
||||
event._class_id = 0;
|
||||
event._total_size = total_size;
|
||||
event._instance_size = instance_size;
|
||||
|
||||
if (VMStructs::hasClassNames()) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(symbol->body(), symbol->length());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
CodeCache* libjvm = VMStructs::libjvm();
|
||||
const void* ne;
|
||||
const void* oe;
|
||||
|
||||
if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer27send_allocation_in_new_tlab")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer28send_allocation_outside_tlab")) != NULL) {
|
||||
_trap_kind = 1; // JDK 10+
|
||||
} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandleP8HeapWord")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandleP8HeapWord")) != NULL) {
|
||||
_trap_kind = 1; // JDK 8u262+
|
||||
} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_event")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_event")) != NULL) {
|
||||
_trap_kind = 2; // JDK 7-9
|
||||
} else {
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL, (jmethodID)symbol);
|
||||
}
|
||||
}
|
||||
|
||||
Error AllocTracer::start(Arguments& args) {
|
||||
if (!VMStructs::available()) {
|
||||
return Error("VMStructs unavailable. Unsupported JVM?");
|
||||
}
|
||||
|
||||
NativeCodeCache* libjvm = Profiler::_instance.jvmLibrary();
|
||||
if (!(_in_new_tlab.resolve(libjvm) || _in_new_tlab2.resolve(libjvm)) ||
|
||||
!(_outside_tlab.resolve(libjvm) || _outside_tlab2.resolve(libjvm))) {
|
||||
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
|
||||
}
|
||||
|
||||
OS::installSignalHandler(SIGTRAP, signalHandler);
|
||||
|
||||
_in_new_tlab.install();
|
||||
_outside_tlab.install();
|
||||
_in_new_tlab2.install();
|
||||
_outside_tlab2.install();
|
||||
_in_new_tlab.assign(ne);
|
||||
_outside_tlab.assign(oe);
|
||||
_in_new_tlab.pair(_outside_tlab);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void AllocTracer::stop() {
|
||||
_in_new_tlab.uninstall();
|
||||
_outside_tlab.uninstall();
|
||||
_in_new_tlab2.uninstall();
|
||||
_outside_tlab2.uninstall();
|
||||
Error AllocTracer::start(Arguments& args) {
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._alloc > 0 ? args._alloc : 0;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
if (args._alloc_hook) {
|
||||
if ((_trap_kind == 1 && _in_new_tlab.install((void*)inNewTLAB1) && _outside_tlab.install((void*)outsideTLAB1)) ||
|
||||
(_trap_kind == 2 && _in_new_tlab.install((void*)inNewTLAB2) && _outside_tlab.install((void*)outsideTLAB2))) {
|
||||
_use_hook = true;
|
||||
return Error::OK;
|
||||
}
|
||||
} else if (_in_new_tlab.install() && _outside_tlab.install()) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
return Error("Cannot install allocation breakpoints");
|
||||
}
|
||||
|
||||
void AllocTracer::stop() {
|
||||
if (_use_hook) {
|
||||
_use_hook = false;
|
||||
} else {
|
||||
_in_new_tlab.uninstall();
|
||||
_outside_tlab.uninstall();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,53 +19,42 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include "arch.h"
|
||||
#include "codeCache.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
// Describes OpenJDK function being intercepted
|
||||
class Trap {
|
||||
private:
|
||||
const char* _func_name;
|
||||
instruction_t* _entry;
|
||||
instruction_t _saved_insn;
|
||||
|
||||
public:
|
||||
Trap(const char* func_name) : _func_name(func_name), _entry(NULL) {
|
||||
}
|
||||
|
||||
bool resolve(NativeCodeCache* libjvm);
|
||||
void install();
|
||||
void uninstall();
|
||||
|
||||
friend class AllocTracer;
|
||||
};
|
||||
#include "trap.h"
|
||||
|
||||
|
||||
class AllocTracer : public Engine {
|
||||
private:
|
||||
// JDK 7-9
|
||||
static int _trap_kind;
|
||||
static Trap _in_new_tlab;
|
||||
static Trap _outside_tlab;
|
||||
// JDK 10+
|
||||
static Trap _in_new_tlab2;
|
||||
static Trap _outside_tlab2;
|
||||
static volatile bool _use_hook;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void recordAllocation(void* ucontext, uintptr_t rklass, uintptr_t rsize, bool outside_tlab);
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void inNewTLAB1(uintptr_t klass, void* obj, size_t tlab_size, size_t alloc_size);
|
||||
static void outsideTLAB1(uintptr_t klass, void* obj, size_t alloc_size);
|
||||
static void inNewTLAB2(uintptr_t klass, size_t tlab_size, size_t alloc_size);
|
||||
static void outsideTLAB2(uintptr_t klass, size_t alloc_size);
|
||||
|
||||
static void recordAllocation(void* ucontext, int 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";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
};
|
||||
|
||||
#endif // _ALLOCTRACER_H
|
||||
|
||||
286
src/api/one/profiler/AsyncProfiler.java
Normal file
286
src/api/one/profiler/AsyncProfiler.java
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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
|
||||
* async-profiler native library. This class is a singleton.
|
||||
* The first call to {@link #getInstance()} initiates loading of
|
||||
* libasyncProfiler.so.
|
||||
*/
|
||||
public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
private static AsyncProfiler instance;
|
||||
|
||||
private AsyncProfiler() {
|
||||
}
|
||||
|
||||
public static AsyncProfiler getInstance() {
|
||||
return getInstance(null);
|
||||
}
|
||||
|
||||
public static synchronized AsyncProfiler getInstance(String libPath) {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
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 = 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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop profiling (without dumping results)
|
||||
*
|
||||
* @throws IllegalStateException If profiler is not running
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws IllegalStateException {
|
||||
stop0();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of samples collected during the profiling session
|
||||
*
|
||||
* @return Number of samples
|
||||
*/
|
||||
@Override
|
||||
public native long getSamples();
|
||||
|
||||
/**
|
||||
* Get profiler agent version, e.g. "1.0"
|
||||
*
|
||||
* @return Version string
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
try {
|
||||
return execute0("version");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an agent-compatible profiling command -
|
||||
* the comma-separated list of arguments described in arguments.cpp
|
||||
*
|
||||
* @param command Profiling command
|
||||
* @return The command result
|
||||
* @throws IllegalArgumentException If failed to parse the command
|
||||
* @throws IOException If failed to create output file
|
||||
*/
|
||||
@Override
|
||||
public String execute(String command) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
if (command == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return execute0(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump profile in 'collapsed stacktraces' format
|
||||
*
|
||||
* @param counter Which counter to display in the output
|
||||
* @return Textual representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public String dumpCollapsed(Counter counter) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump flat profile, i.e. the histogram of the hottest methods
|
||||
*
|
||||
* @param maxMethods Maximum number of methods to dump. 0 means no limit
|
||||
* @return Textual representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public String dumpFlat(int maxMethods) {
|
||||
try {
|
||||
return execute0(maxMethods == 0 ? "flat" : "flat=" + maxMethods);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given thread to the set of profiled threads.
|
||||
* 'filter' option must be enabled to use this method.
|
||||
*
|
||||
* @param thread Thread to include in profiling
|
||||
*/
|
||||
public void addThread(Thread thread) {
|
||||
filterThread(thread, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given thread from the set of profiled threads.
|
||||
* 'filter' option must be enabled to use this method.
|
||||
*
|
||||
* @param thread Thread to exclude from profiling
|
||||
*/
|
||||
public void removeThread(Thread thread) {
|
||||
filterThread(thread, false);
|
||||
}
|
||||
|
||||
private void filterThread(Thread thread, boolean enable) {
|
||||
if (thread == null || thread == Thread.currentThread()) {
|
||||
filterThread0(null, enable);
|
||||
} else {
|
||||
// Need to take lock to avoid race condition with a thread state change
|
||||
synchronized (thread) {
|
||||
Thread.State state = thread.getState();
|
||||
if (state != Thread.State.NEW && state != Thread.State.TERMINATED) {
|
||||
filterThread0(thread, enable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, IllegalStateException, IOException;
|
||||
|
||||
private native void filterThread0(Thread thread, boolean enable);
|
||||
}
|
||||
@@ -29,12 +29,13 @@ package one.profiler;
|
||||
*/
|
||||
public interface AsyncProfilerMXBean {
|
||||
void start(String event, long interval) throws IllegalStateException;
|
||||
void resume(String event, long interval) throws IllegalStateException;
|
||||
void stop() throws IllegalStateException;
|
||||
|
||||
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);
|
||||
@@ -20,8 +20,9 @@ package one.profiler;
|
||||
* Predefined event names to use in {@link AsyncProfiler#start(String, long)}
|
||||
*/
|
||||
public class Events {
|
||||
public static final String CPU = "cpu";
|
||||
public static final String ALLOC = "alloc";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String WALL = "wall";
|
||||
public static final String CPU = "cpu";
|
||||
public static final String ALLOC = "alloc";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String WALL = "wall";
|
||||
public static final String ITIMER = "itimer";
|
||||
}
|
||||
92
src/arch.h
92
src/arch.h
@@ -18,26 +18,60 @@
|
||||
#define _ARCH_H
|
||||
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
|
||||
static inline u64 atomicInc(u64& var, u64 increment = 1) {
|
||||
static inline u64 atomicInc(volatile u64& var, u64 increment = 1) {
|
||||
return __sync_fetch_and_add(&var, increment);
|
||||
}
|
||||
|
||||
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 ADJUST_RET = 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 ADJUST_RET = 0;
|
||||
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
|
||||
|
||||
#define spinPause() asm volatile("yield")
|
||||
#define rmb() asm volatile("dmb ish" : : : "memory")
|
||||
@@ -47,18 +81,62 @@ const instruction_t BREAKPOINT = 0xe7f001f0;
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xd4200000;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
#define spinPause() asm volatile("yield")
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int ADJUST_RET = 0;
|
||||
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("isb")
|
||||
#define rmb() asm volatile("dmb ish" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#else
|
||||
#warning "Compiling on unsupported arch"
|
||||
#elif defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
|
||||
#define spinPause()
|
||||
#define rmb() __sync_synchronize()
|
||||
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 ADJUST_RET = 0;
|
||||
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))
|
||||
|
||||
#else
|
||||
|
||||
#error "Compiling on unsupported arch"
|
||||
|
||||
#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
|
||||
|
||||
|
||||
|
||||
@@ -15,158 +15,536 @@
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "arguments.h"
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
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}};
|
||||
|
||||
|
||||
// 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 DEFAULT() } else {
|
||||
|
||||
|
||||
// Parses agent arguments.
|
||||
// The format of the string is:
|
||||
// arg[,arg...]
|
||||
// where arg is one of the following options:
|
||||
// start - start profiling
|
||||
// stop - stop profiling
|
||||
// status - print profiling status (inactive / running for X seconds)
|
||||
// list - show the list of available profiling events
|
||||
// version - 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)
|
||||
// svg[=C] - produce Flame Graph in SVG format
|
||||
// tree[=C] - produce call tree in HTML format
|
||||
// C is counter type: 'samples' or 'total'
|
||||
// jfr - dump events in Java Flight Recorder format
|
||||
// summary - dump profiling summary (number of collected samples of each type)
|
||||
// traces[=N] - dump top N call traces
|
||||
// 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: MAX_STACK_FRAMES)
|
||||
// framebuf=N - size of the buffer for stack frames (default: 1'000'000)
|
||||
// threads - profile different threads separately
|
||||
// allkernel - include only kernel-mode events
|
||||
// alluser - include only user-mode events
|
||||
// simple[=bool] - simple class names instead of FQN
|
||||
// ann[=bool] - annotate Java method names
|
||||
// title=TITLE - FlameGraph title
|
||||
// width=PX - FlameGraph image width
|
||||
// height=PX - FlameGraph frame height
|
||||
// minwidth=PX - FlameGraph minimum frame width
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree
|
||||
// file=FILENAME - output file name for dumping
|
||||
// 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[=full] - 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
|
||||
// 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)
|
||||
// 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' (Frame Pointer), 'dwarf', 'lbr' (Last Branch Record) or 'no'
|
||||
// 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
|
||||
// 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
|
||||
|
||||
Error Arguments::parse(const char* args) {
|
||||
if (args == NULL) {
|
||||
return Error::OK;
|
||||
} else if (strlen(args) >= sizeof(_buf)) {
|
||||
return Error("Argument list too long");
|
||||
}
|
||||
|
||||
strcpy(_buf, args);
|
||||
size_t len = strlen(args);
|
||||
free(_buf);
|
||||
_buf = (char*)malloc(len + EXTRA_BUF_SIZE + 1);
|
||||
if (_buf == NULL) {
|
||||
return Error("Not enough memory to parse arguments");
|
||||
}
|
||||
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;
|
||||
|
||||
if (strcmp(arg, "start") == 0) {
|
||||
_action = ACTION_START;
|
||||
} else if (strcmp(arg, "stop") == 0) {
|
||||
_action = ACTION_STOP;
|
||||
} else if (strcmp(arg, "status") == 0) {
|
||||
_action = ACTION_STATUS;
|
||||
} else if (strcmp(arg, "list") == 0) {
|
||||
_action = ACTION_LIST;
|
||||
} else if (strcmp(arg, "version") == 0) {
|
||||
_action = ACTION_VERSION;
|
||||
} else if (strcmp(arg, "event") == 0) {
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("event must not be empty");
|
||||
}
|
||||
_event = value;
|
||||
} else if (strcmp(arg, "collapsed") == 0 || strcmp(arg, "folded") == 0) {
|
||||
_dump_collapsed = true;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
} else if (strcmp(arg, "flamegraph") == 0 || strcmp(arg, "svg") == 0) {
|
||||
_dump_flamegraph = true;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
} else if (strcmp(arg, "tree") == 0) {
|
||||
_dump_tree = true;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
} else if (strcmp(arg, "jfr") == 0) {
|
||||
_dump_jfr = true;
|
||||
} else if (strcmp(arg, "summary") == 0) {
|
||||
_dump_summary = true;
|
||||
} else if (strcmp(arg, "traces") == 0) {
|
||||
_dump_traces = value == NULL ? INT_MAX : atoi(value);
|
||||
} else if (strcmp(arg, "flat") == 0) {
|
||||
_dump_flat = value == NULL ? INT_MAX : atoi(value);
|
||||
} else if (strcmp(arg, "interval") == 0) {
|
||||
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
|
||||
return Error("interval must be > 0");
|
||||
}
|
||||
} else if (strcmp(arg, "jstackdepth") == 0) {
|
||||
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
|
||||
return Error("jstackdepth must be > 0");
|
||||
}
|
||||
} else if (strcmp(arg, "framebuf") == 0) {
|
||||
if (value == NULL || (_framebuf = atoi(value)) <= 0) {
|
||||
return Error("framebuf must be > 0");
|
||||
}
|
||||
} else if (strcmp(arg, "threads") == 0) {
|
||||
_threads = true;
|
||||
} else if (strcmp(arg, "allkernel") == 0) {
|
||||
_ring = RING_KERNEL;
|
||||
} else if (strcmp(arg, "alluser") == 0) {
|
||||
_ring = RING_USER;
|
||||
} else if (strcmp(arg, "simple") == 0) {
|
||||
_simple = value == NULL || strcmp(value, "true") == 0;
|
||||
} else if (strcmp(arg, "ann") == 0) {
|
||||
_annotate = value == NULL || strcmp(value, "true") == 0;
|
||||
} else if (strcmp(arg, "title") == 0 && value != NULL) {
|
||||
_title = value;
|
||||
} else if (strcmp(arg, "width") == 0 && value != NULL) {
|
||||
_width = atoi(value);
|
||||
} else if (strcmp(arg, "height") == 0 && value != NULL) {
|
||||
_height = atoi(value);
|
||||
} else if (strcmp(arg, "minwidth") == 0 && value != NULL) {
|
||||
_minwidth = atof(value);
|
||||
} else if (strcmp(arg, "reverse") == 0) {
|
||||
_reverse = true;
|
||||
} else if (strcmp(arg, "file") == 0) {
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("file must not be empty");
|
||||
}
|
||||
_file = value;
|
||||
SWITCH (arg) {
|
||||
// Actions
|
||||
CASE("start")
|
||||
_action = ACTION_START;
|
||||
|
||||
CASE("resume")
|
||||
_action = ACTION_RESUME;
|
||||
|
||||
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;
|
||||
|
||||
// Output formats
|
||||
CASE("collapsed")
|
||||
_output = OUTPUT_COLLAPSED;
|
||||
|
||||
CASE("flamegraph")
|
||||
_output = OUTPUT_FLAMEGRAPH;
|
||||
|
||||
CASE("tree")
|
||||
_output = OUTPUT_TREE;
|
||||
|
||||
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_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) {
|
||||
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;
|
||||
}
|
||||
|
||||
CASE("timeout")
|
||||
if (value == NULL || (_timeout = parseTimeout(value)) == -1 || !_persistent) {
|
||||
msg = "Invalid timeout";
|
||||
}
|
||||
|
||||
CASE("loop")
|
||||
_loop = true;
|
||||
if (value == NULL || (_timeout = parseTimeout(value)) == -1 || !_persistent) {
|
||||
msg = "Invalid loop duration";
|
||||
}
|
||||
|
||||
CASE("alloc")
|
||||
_alloc = value == NULL ? 0 : parseUnits(value, BYTES);
|
||||
|
||||
CASE("lock")
|
||||
_lock = value == NULL ? 0 : parseUnits(value, NANOS);
|
||||
|
||||
CASE("interval")
|
||||
if (value == NULL || (_interval = parseUnits(value, UNIVERSAL)) <= 0) {
|
||||
msg = "Invalid interval";
|
||||
}
|
||||
|
||||
CASE("jstackdepth")
|
||||
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
|
||||
msg = "jstackdepth must be > 0";
|
||||
}
|
||||
|
||||
CASE("safemode")
|
||||
_safe_mode = value == NULL ? INT_MAX : (int)strtol(value, NULL, 0);
|
||||
|
||||
CASE("file")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
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;
|
||||
|
||||
CASE("cloud")
|
||||
// Meta option for continuous eBPF-assisted cloud profiling
|
||||
if (_action == ACTION_NONE) {
|
||||
_action = ACTION_START;
|
||||
}
|
||||
if (_event == NULL) {
|
||||
_event = EVENT_BPF;
|
||||
_sched = true;
|
||||
_alloc_hook = true;
|
||||
}
|
||||
if (_fdtransfer_path == NULL) {
|
||||
_fdtransfer = true;
|
||||
_fdtransfer_path = "/one/profile/profile.sock";
|
||||
}
|
||||
if (_file == NULL) {
|
||||
_file = "/one/logs/%{cloud_image}-%t.jfr";
|
||||
}
|
||||
if (_timeout == 0) {
|
||||
_loop = true;
|
||||
_timeout = 0xff0000ff; // rotate at 00:00
|
||||
}
|
||||
if (_chunk_time == 0) {
|
||||
_chunk_time = 300; // 5 min
|
||||
}
|
||||
|
||||
// Filters
|
||||
CASE("filter")
|
||||
_filter = value == NULL ? "" : value;
|
||||
|
||||
CASE("include")
|
||||
// Workaround -Wstringop-overflow warning
|
||||
if (value == arg + 8) appendToEmbeddedList(_include, arg + 8);
|
||||
|
||||
CASE("exclude")
|
||||
// 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("allochook")
|
||||
_alloc_hook = true;
|
||||
|
||||
CASE("allkernel")
|
||||
_ring = RING_KERNEL;
|
||||
|
||||
CASE("alluser")
|
||||
_ring = RING_USER;
|
||||
|
||||
CASE("cstack")
|
||||
if (value != NULL) {
|
||||
if (value[0] == 'n') {
|
||||
_cstack = CSTACK_NO;
|
||||
} else if (value[0] == 'd') {
|
||||
_cstack = CSTACK_DWARF;
|
||||
} else if (value[0] == 'l') {
|
||||
_cstack = CSTACK_LBR;
|
||||
} else {
|
||||
_cstack = CSTACK_FP;
|
||||
}
|
||||
}
|
||||
|
||||
// Output style modifiers
|
||||
CASE("simple")
|
||||
_style |= STYLE_SIMPLE;
|
||||
|
||||
CASE("dot")
|
||||
_style |= STYLE_DOTTED;
|
||||
|
||||
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;
|
||||
|
||||
CASE("end")
|
||||
_end = value;
|
||||
|
||||
// FlameGraph options
|
||||
CASE("title")
|
||||
_title = value;
|
||||
|
||||
CASE("minwidth")
|
||||
if (value != NULL) _minwidth = atof(value);
|
||||
|
||||
CASE("reverse")
|
||||
_reverse = true;
|
||||
|
||||
DEFAULT()
|
||||
if (_unknown_arg == NULL) _unknown_arg = arg;
|
||||
}
|
||||
}
|
||||
|
||||
if (dumpRequested() && (_action == ACTION_NONE || _action == ACTION_STOP)) {
|
||||
// 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) {
|
||||
_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 (_action == ACTION_NONE && _output != OUTPUT_NONE) {
|
||||
_action = ACTION_DUMP;
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
long Arguments::parseUnits(const char* str) {
|
||||
const char* Arguments::file() {
|
||||
if (_file != NULL && strchr(_file, '%') != NULL) {
|
||||
return expandFilePattern(_file);
|
||||
}
|
||||
return _file;
|
||||
}
|
||||
|
||||
// The linked list of string offsets is embedded right into _buf array
|
||||
void Arguments::appendToEmbeddedList(int& list, char* value) {
|
||||
((int*)value)[-1] = list;
|
||||
list = (int)(value - _buf);
|
||||
}
|
||||
|
||||
// Should match statically computed HASH(arg)
|
||||
long long Arguments::hash(const char* arg) {
|
||||
long long h = 0;
|
||||
for (int shift = 0; *arg != 0; shift += 5) {
|
||||
h |= (*arg++ & 31LL) << shift;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// 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++;
|
||||
if (c == '%') {
|
||||
c = *pattern++;
|
||||
if (c == 0) {
|
||||
break;
|
||||
} else if (c == 'p') {
|
||||
ptr += snprintf(ptr, end - ptr, "%d", getpid());
|
||||
continue;
|
||||
} else if (c == 't') {
|
||||
time_t timestamp = time(NULL);
|
||||
struct tm t;
|
||||
localtime_r(×tamp, &t);
|
||||
ptr += snprintf(ptr, end - ptr, "%d%02d%02d-%02d%02d%02d",
|
||||
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 < end ? ptr : end) = 0;
|
||||
return _buf;
|
||||
}
|
||||
|
||||
Output Arguments::detectOutputFormat(const char* file) {
|
||||
const char* ext = strrchr(file, '.');
|
||||
if (ext != NULL) {
|
||||
if (strcmp(ext, ".html") == 0) {
|
||||
return OUTPUT_FLAMEGRAPH;
|
||||
} else if (strcmp(ext, ".jfr") == 0) {
|
||||
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_TEXT;
|
||||
}
|
||||
|
||||
long Arguments::parseUnits(const char* str, const Multiplier* multipliers) {
|
||||
char* end;
|
||||
long result = strtol(str, &end, 0);
|
||||
if (end == str) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*end) {
|
||||
switch (*end) {
|
||||
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 result;
|
||||
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;
|
||||
}
|
||||
|
||||
179
src/arguments.h
179
src/arguments.h
@@ -20,10 +20,12 @@
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
const long DEFAULT_INTERVAL = 10000000; // 10 ms
|
||||
const int DEFAULT_FRAMEBUF = 1000000;
|
||||
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_BPF = "bpf";
|
||||
const char* const EVENT_ALLOC = "alloc";
|
||||
const char* const EVENT_LOCK = "lock";
|
||||
const char* const EVENT_WALL = "wall";
|
||||
@@ -32,11 +34,15 @@ const char* const EVENT_ITIMER = "itimer";
|
||||
enum Action {
|
||||
ACTION_NONE,
|
||||
ACTION_START,
|
||||
ACTION_RESUME,
|
||||
ACTION_STOP,
|
||||
ACTION_DUMP,
|
||||
ACTION_CHECK,
|
||||
ACTION_STATUS,
|
||||
ACTION_MEMINFO,
|
||||
ACTION_LIST,
|
||||
ACTION_VERSION,
|
||||
ACTION_DUMP
|
||||
ACTION_FULL_VERSION
|
||||
};
|
||||
|
||||
enum Counter {
|
||||
@@ -50,6 +56,48 @@ enum Ring {
|
||||
RING_USER
|
||||
};
|
||||
|
||||
enum Style {
|
||||
STYLE_SIMPLE = 1,
|
||||
STYLE_DOTTED = 2,
|
||||
STYLE_SIGNATURES = 4,
|
||||
STYLE_ANNOTATE = 8,
|
||||
STYLE_LIB_NAMES = 16,
|
||||
STYLE_NO_SEMICOLON = 32
|
||||
};
|
||||
|
||||
enum CStack {
|
||||
CSTACK_DEFAULT,
|
||||
CSTACK_NO,
|
||||
CSTACK_FP,
|
||||
CSTACK_DWARF,
|
||||
CSTACK_LBR
|
||||
};
|
||||
|
||||
enum Output {
|
||||
OUTPUT_NONE,
|
||||
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,
|
||||
|
||||
JFR_SYNC_OPTS = NO_SYSTEM_INFO | NO_SYSTEM_PROPS | NO_NATIVE_LIBS | NO_CPU_LOAD
|
||||
};
|
||||
|
||||
|
||||
struct Multiplier {
|
||||
char symbol;
|
||||
long multiplier;
|
||||
};
|
||||
|
||||
|
||||
class Error {
|
||||
private:
|
||||
@@ -73,67 +121,128 @@ class Error {
|
||||
|
||||
class Arguments {
|
||||
private:
|
||||
char _buf[1024];
|
||||
char* _buf;
|
||||
bool _shared;
|
||||
bool _persistent;
|
||||
|
||||
long parseUnits(const char* str);
|
||||
void appendToEmbeddedList(int& list, char* value);
|
||||
const char* expandFilePattern(const char* pattern);
|
||||
|
||||
static long long hash(const char* arg);
|
||||
static Output detectOutputFormat(const char* file);
|
||||
static long parseUnits(const char* str, const Multiplier* multipliers);
|
||||
static int parseTimeout(const char* str);
|
||||
|
||||
public:
|
||||
Action _action;
|
||||
Counter _counter;
|
||||
Ring _ring;
|
||||
const char* _event;
|
||||
int _timeout;
|
||||
long _interval;
|
||||
long _alloc;
|
||||
long _lock;
|
||||
int _jstackdepth;
|
||||
int _framebuf;
|
||||
int _safe_mode;
|
||||
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 _threads;
|
||||
bool _simple;
|
||||
bool _annotate;
|
||||
char* _file;
|
||||
bool _dump_collapsed;
|
||||
bool _dump_flamegraph;
|
||||
bool _dump_tree;
|
||||
bool _dump_jfr;
|
||||
bool _dump_summary;
|
||||
bool _sched;
|
||||
bool _live;
|
||||
bool _alloc_hook;
|
||||
bool _fdtransfer;
|
||||
const char* _fdtransfer_path;
|
||||
int _style;
|
||||
CStack _cstack;
|
||||
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
|
||||
const char* _title;
|
||||
int _width;
|
||||
int _height;
|
||||
double _minwidth;
|
||||
bool _reverse;
|
||||
|
||||
Arguments() :
|
||||
Arguments(bool persistent = false) :
|
||||
_buf(NULL),
|
||||
_shared(false),
|
||||
_persistent(persistent),
|
||||
_action(ACTION_NONE),
|
||||
_counter(COUNTER_SAMPLES),
|
||||
_ring(RING_ANY),
|
||||
_event(EVENT_CPU),
|
||||
_event(NULL),
|
||||
_timeout(0),
|
||||
_interval(0),
|
||||
_jstackdepth(0),
|
||||
_framebuf(DEFAULT_FRAMEBUF),
|
||||
_threads(false),
|
||||
_simple(false),
|
||||
_annotate(false),
|
||||
_alloc(-1),
|
||||
_lock(-1),
|
||||
_jstackdepth(DEFAULT_JSTACKDEPTH),
|
||||
_safe_mode(0),
|
||||
_file(NULL),
|
||||
_dump_collapsed(false),
|
||||
_dump_flamegraph(false),
|
||||
_dump_tree(false),
|
||||
_dump_jfr(false),
|
||||
_dump_summary(false),
|
||||
_log(NULL),
|
||||
_loglevel(NULL),
|
||||
_unknown_arg(NULL),
|
||||
_server(NULL),
|
||||
_filter(NULL),
|
||||
_include(0),
|
||||
_exclude(0),
|
||||
_mcache(0),
|
||||
_loop(false),
|
||||
_threads(false),
|
||||
_sched(false),
|
||||
_live(false),
|
||||
_alloc_hook(false),
|
||||
_fdtransfer(false),
|
||||
_fdtransfer_path(NULL),
|
||||
_style(0),
|
||||
_cstack(CSTACK_DEFAULT),
|
||||
_output(OUTPUT_NONE),
|
||||
_chunk_size(100 * 1024 * 1024),
|
||||
_chunk_time(0),
|
||||
_jfr_sync(NULL),
|
||||
_jfr_options(0),
|
||||
_dump_traces(0),
|
||||
_dump_flat(0),
|
||||
_title("Flame Graph"),
|
||||
_width(1200),
|
||||
_height(16),
|
||||
_minwidth(1),
|
||||
_file_num(0),
|
||||
_begin(NULL),
|
||||
_end(NULL),
|
||||
_title(NULL),
|
||||
_minwidth(0),
|
||||
_reverse(false) {
|
||||
}
|
||||
|
||||
bool dumpRequested() {
|
||||
return _dump_collapsed || _dump_flamegraph || _dump_tree || _dump_jfr || _dump_summary || _dump_traces > 0 || _dump_flat > 0;
|
||||
}
|
||||
~Arguments();
|
||||
|
||||
void save(Arguments& other);
|
||||
|
||||
Error parse(const char* args);
|
||||
|
||||
const char* file();
|
||||
|
||||
bool hasOutputFile() const {
|
||||
return _file != NULL &&
|
||||
(_action == ACTION_STOP || _action == ACTION_DUMP ? _output != OUTPUT_JFR : _action >= ACTION_STATUS);
|
||||
}
|
||||
|
||||
bool hasOption(JfrOption option) const {
|
||||
return (_jfr_options & option) != 0;
|
||||
}
|
||||
|
||||
friend class FrameName;
|
||||
friend class Recording;
|
||||
};
|
||||
|
||||
#endif // _ARGUMENTS_H
|
||||
|
||||
138
src/bpfClient.cpp
Normal file
138
src/bpfClient.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "bpfClient.h"
|
||||
#include "fdtransferClient.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackWalker.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
// Use different profiling signal to allow running two profilers together
|
||||
const int BPF_SIGNAL = SIGSTKFLT;
|
||||
|
||||
struct BpfStackTrace {
|
||||
u32 pid;
|
||||
u32 tid;
|
||||
u64 counter;
|
||||
u16 event_type;
|
||||
u16 sched_policy;
|
||||
u32 depth;
|
||||
u64 ip[0];
|
||||
};
|
||||
|
||||
struct BpfMap {
|
||||
char* addr;
|
||||
size_t size;
|
||||
u32 salt;
|
||||
u32 mask;
|
||||
u32 entry_size;
|
||||
|
||||
BpfStackTrace* getStackForThread(u32 tid) const {
|
||||
char* base = __atomic_load_n(&addr, __ATOMIC_ACQUIRE);
|
||||
if (base == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
size_t index = (salt + tid) & mask;
|
||||
return (BpfStackTrace*)(base + index * entry_size);
|
||||
}
|
||||
};
|
||||
|
||||
static BpfMap _bpf_map = {0};
|
||||
|
||||
static unsigned int _interval;
|
||||
static unsigned int _counter;
|
||||
|
||||
|
||||
void BpfClient::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
if (_interval <= 1 || __sync_add_and_fetch(&_counter, 1) % _interval == 0) {
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
}
|
||||
|
||||
Error BpfClient::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error BpfClient::start(Arguments& args) {
|
||||
OS::installSignalHandler(BPF_SIGNAL, signalHandler);
|
||||
|
||||
struct bpfmap_params params;
|
||||
int fd = FdTransferClient::requestBpfMapFd(¶ms);
|
||||
if (fd < 0) {
|
||||
return Error("Failed to request bpf map");
|
||||
}
|
||||
|
||||
_interval = args._interval;
|
||||
_counter = 0;
|
||||
|
||||
_bpf_map.salt = params.salt;
|
||||
_bpf_map.mask = params.num_entries - 1;
|
||||
_bpf_map.entry_size = params.entry_size;
|
||||
_bpf_map.size = (size_t)params.entry_size * params.num_entries;
|
||||
|
||||
char* addr = (char*)mmap(NULL, _bpf_map.size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(fd);
|
||||
return Error("Failed to mmap stack trace buffer");
|
||||
}
|
||||
|
||||
close(fd);
|
||||
__atomic_store_n(&_bpf_map.addr, addr, __ATOMIC_RELEASE);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void BpfClient::stop() {
|
||||
OS::installSignalHandler(BPF_SIGNAL, NULL, SIG_IGN);
|
||||
|
||||
char* addr = __atomic_exchange_n(&_bpf_map.addr, NULL, __ATOMIC_ACQ_REL);
|
||||
munmap(addr, _bpf_map.size);
|
||||
}
|
||||
|
||||
int BpfClient::walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx) {
|
||||
int depth = 0;
|
||||
|
||||
// Fill kernel frames from bpf map
|
||||
BpfStackTrace* trace = _bpf_map.getStackForThread(tid);
|
||||
if (trace != NULL && trace->tid == tid) {
|
||||
int limit = trace->depth < max_depth ? trace->depth : max_depth;
|
||||
while (depth < limit && (intptr_t)trace->ip[depth] < 0) {
|
||||
callchain[depth] = (const void*)trace->ip[depth];
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add user-space frames by manual stack walking
|
||||
depth += StackWalker::walkDwarf(ucontext, callchain + depth, max_depth - depth, java_ctx);
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
const char* BpfClient::schedPolicy(int tid) {
|
||||
BpfStackTrace* trace = _bpf_map.getStackForThread(tid);
|
||||
if (trace == NULL || trace->tid != tid || trace->sched_policy < SCHED_BATCH) {
|
||||
return "SCHED_OTHER";
|
||||
}
|
||||
return trace->sched_policy >= SCHED_IDLE ? "SCHED_IDLE" : "SCHED_BATCH";
|
||||
}
|
||||
47
src/bpfClient.h
Normal file
47
src/bpfClient.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef _BPFCLIENT_H
|
||||
#define _BPFCLIENT_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class BpfClient : public Engine {
|
||||
private:
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
const char* title() {
|
||||
return "CPU profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "cycles";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static int walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx);
|
||||
|
||||
static const char* schedPolicy(int tid);
|
||||
};
|
||||
|
||||
#endif // _BPFCLIENT_H
|
||||
294
src/callTraceStorage.cpp
Normal file
294
src/callTraceStorage.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "callTraceStorage.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
static const u32 CAPACITY = 131072;
|
||||
static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024;
|
||||
static const u32 OVERFLOW_TRACE_ID = 0x7fffffff;
|
||||
|
||||
|
||||
class LongHashTable {
|
||||
private:
|
||||
LongHashTable* _prev;
|
||||
void* _padding0;
|
||||
u32 _base;
|
||||
u32 _padding1[15];
|
||||
volatile u32 _size;
|
||||
u32 _padding2[15];
|
||||
|
||||
static size_t getSize() {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * CAPACITY;
|
||||
return (size + OS::page_mask) & ~OS::page_mask;
|
||||
}
|
||||
|
||||
public:
|
||||
static LongHashTable* allocate(LongHashTable* prev, u32 base) {
|
||||
LongHashTable* table = (LongHashTable*)OS::safeAlloc(getSize());
|
||||
if (table != NULL) {
|
||||
table->_prev = prev;
|
||||
table->_base = base;
|
||||
table->_size = 0;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
LongHashTable* destroy() {
|
||||
LongHashTable* prev = _prev;
|
||||
OS::safeFree(this, getSize());
|
||||
return prev;
|
||||
}
|
||||
|
||||
size_t usedMemory() const {
|
||||
return getSize();
|
||||
}
|
||||
|
||||
LongHashTable* trim() {
|
||||
return __atomic_exchange_n(&_prev, NULL, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
LongHashTable* prev() const {
|
||||
return _prev;
|
||||
}
|
||||
|
||||
u32 base() const {
|
||||
return _base;
|
||||
}
|
||||
|
||||
u32 size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
u32 incSize() {
|
||||
return __sync_add_and_fetch(&_size, 1);
|
||||
}
|
||||
|
||||
u64* keys() {
|
||||
return (u64*)(this + 1);
|
||||
}
|
||||
|
||||
CallTraceSample* values() {
|
||||
return (CallTraceSample*)(keys() + CAPACITY);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(keys(), 0, (sizeof(u64) + sizeof(CallTraceSample)) * CAPACITY);
|
||||
_size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CallTrace CallTraceStorage::_overflow_trace = {1, {BCI_ERROR, (jmethodID)"storage_overflow"}};
|
||||
|
||||
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
|
||||
_current_table = LongHashTable::allocate(NULL, 1);
|
||||
_overflow = 0;
|
||||
}
|
||||
|
||||
CallTraceStorage::~CallTraceStorage() {
|
||||
while (_current_table != NULL) {
|
||||
_current_table = _current_table->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::clear() {
|
||||
while (_current_table->prev() != NULL) {
|
||||
_current_table = _current_table->destroy();
|
||||
}
|
||||
_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;
|
||||
}
|
||||
|
||||
Chunk* CallTraceStorage::trimAllocator() {
|
||||
return _allocator.trim();
|
||||
}
|
||||
|
||||
LongHashTable* CallTraceStorage::trimTable() {
|
||||
return _current_table->trim();
|
||||
}
|
||||
|
||||
void CallTraceStorage::freeMemory(Chunk* chunk, LongHashTable* table) {
|
||||
_allocator.freeChain(chunk);
|
||||
while (table != NULL) {
|
||||
table = table->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 base = table->base();
|
||||
|
||||
for (u32 slot = 0; slot < CAPACITY; slot++) {
|
||||
if (keys[slot] != 0 && loadAcquire(values[slot].counter) != 0) {
|
||||
CallTrace* trace = values[slot].acquireTrace();
|
||||
if (trace != NULL) {
|
||||
map[base + slot] = trace;
|
||||
// Reset to make sure each trace is dumped only once
|
||||
values[slot].setTrace(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_overflow > 0) {
|
||||
map[OVERFLOW_TRACE_ID] = &_overflow_trace;
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
|
||||
for (u32 slot = 0; slot < CAPACITY; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
samples.push_back(&values[slot]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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;
|
||||
const int R = 47;
|
||||
|
||||
int len = num_frames * sizeof(ASGCT_CallFrame);
|
||||
u64 h = len * M;
|
||||
|
||||
const u64* data = (const u64*)frames;
|
||||
const u64* end = data + len / 8;
|
||||
|
||||
while (data != end) {
|
||||
u64 k = *data++;
|
||||
k *= M;
|
||||
k ^= k >> R;
|
||||
k *= M;
|
||||
h ^= k;
|
||||
h *= M;
|
||||
}
|
||||
|
||||
if (len & 4) {
|
||||
h ^= *(u32*)data;
|
||||
h *= M;
|
||||
}
|
||||
|
||||
h ^= h >> R;
|
||||
h *= M;
|
||||
h ^= h >> R;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
CallTrace* CallTraceStorage::storeCallTrace(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const size_t header_size = sizeof(CallTrace) - sizeof(ASGCT_CallFrame);
|
||||
CallTrace* buf = (CallTrace*)_allocator.alloc(header_size + num_frames * sizeof(ASGCT_CallFrame));
|
||||
if (buf != NULL) {
|
||||
buf->num_frames = num_frames;
|
||||
// Do not use memcpy inside signal handler
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
buf->frames[i] = frames[i];
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter) {
|
||||
u64 hash = calcHash(num_frames, frames);
|
||||
|
||||
LongHashTable* table = _current_table;
|
||||
u64* keys = table->keys();
|
||||
u32 slot = hash & (CAPACITY - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
if (keys[slot] == 0) {
|
||||
if (!__sync_bool_compare_and_swap(&keys[slot], 0, hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Increment the table size, and if the load factor exceeds 0.75, reserve a new table
|
||||
if (table->incSize() == CAPACITY * 3 / 4) {
|
||||
LongHashTable* new_table = LongHashTable::allocate(table, table->base() + CAPACITY);
|
||||
if (new_table != NULL) {
|
||||
__sync_bool_compare_and_swap(&_current_table, table, new_table);
|
||||
}
|
||||
}
|
||||
|
||||
CallTrace* trace = storeCallTrace(num_frames, frames);
|
||||
table->values()[slot].setTrace(trace);
|
||||
break;
|
||||
}
|
||||
|
||||
if (++step >= CAPACITY) {
|
||||
// Very unlikely case of a table overflow
|
||||
atomicInc(_overflow);
|
||||
return OVERFLOW_TRACE_ID;
|
||||
}
|
||||
// Improved version of linear probing
|
||||
slot = (slot + step) & (CAPACITY - 1);
|
||||
}
|
||||
|
||||
if (counter != 0) {
|
||||
CallTraceSample& s = table->values()[slot];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
}
|
||||
|
||||
return table->base() + slot;
|
||||
}
|
||||
|
||||
void CallTraceStorage::add(u32 call_trace_id, u64 counter) {
|
||||
if (call_trace_id == OVERFLOW_TRACE_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
if (call_trace_id >= table->base()) {
|
||||
CallTraceSample& s = table->values()[call_trace_id - table->base()];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/callTraceStorage.h
Normal file
88
src/callTraceStorage.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _CALLTRACESTORAGE_H
|
||||
#define _CALLTRACESTORAGE_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "arch.h"
|
||||
#include "linearAllocator.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
class LongHashTable;
|
||||
|
||||
struct CallTrace {
|
||||
int num_frames;
|
||||
ASGCT_CallFrame frames[1];
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
public:
|
||||
CallTraceStorage();
|
||||
~CallTraceStorage();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
Chunk* trimAllocator();
|
||||
LongHashTable* trimTable();
|
||||
void freeMemory(Chunk* chunk, LongHashTable* table);
|
||||
|
||||
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
|
||||
@@ -14,77 +14,126 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#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, 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;
|
||||
|
||||
_got_start = NULL;
|
||||
_got_end = NULL;
|
||||
_got_patchable = false;
|
||||
_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];
|
||||
memcpy(new_blobs, old_blobs, _capacity * sizeof(CodeBlob));
|
||||
|
||||
memcpy(new_blobs, old_blobs, _count * sizeof(CodeBlob));
|
||||
|
||||
_capacity *= 2;
|
||||
_blobs = new_blobs;
|
||||
delete[] old_blobs;
|
||||
}
|
||||
|
||||
void CodeCache::add(const void* start, int length, jmethodID method) {
|
||||
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();
|
||||
}
|
||||
|
||||
const void* end = (const char*)start + length;
|
||||
_blobs[_count]._start = start;
|
||||
_blobs[_count]._end = (const char*)start + length;
|
||||
_blobs[_count]._method = method;
|
||||
_blobs[_count]._end = end;
|
||||
_blobs[_count]._name = name_copy;
|
||||
_count++;
|
||||
|
||||
if (update_bounds) {
|
||||
updateBounds(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::remove(const void* start, jmethodID method) {
|
||||
void CodeCache::updateBounds(const void* start, const void* end) {
|
||||
if (start < _min_address) _min_address = start;
|
||||
if (end > _max_address) _max_address = end;
|
||||
}
|
||||
|
||||
void CodeCache::sort() {
|
||||
if (_count == 0) return;
|
||||
|
||||
qsort(_blobs, _count, sizeof(CodeBlob), CodeBlob::comparator);
|
||||
|
||||
if (_min_address == NO_MIN_ADDRESS) _min_address = _blobs[0]._start;
|
||||
if (_max_address == NO_MAX_ADDRESS) _max_address = _blobs[_count - 1]._end;
|
||||
}
|
||||
|
||||
void CodeCache::mark(NamePredicate predicate) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (_blobs[i]._start == start && _blobs[i]._method == method) {
|
||||
_blobs[i]._method = NULL;
|
||||
return;
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && predicate(blob_name)) {
|
||||
NativeFunc::mark(blob_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jmethodID CodeCache::find(const void* address) {
|
||||
CodeBlob* CodeCache::find(const void* address) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (address >= _blobs[i]._start && address < _blobs[i]._end) {
|
||||
return _blobs[i]._method;
|
||||
return &_blobs[i];
|
||||
}
|
||||
}
|
||||
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) {
|
||||
CodeCache::add(start, length, (jmethodID)strdup(name));
|
||||
}
|
||||
|
||||
void NativeCodeCache::sort() {
|
||||
if (_count == 0) return;
|
||||
|
||||
qsort(_blobs, _count, sizeof(CodeBlob), CodeBlob::comparator);
|
||||
|
||||
if (_min_address == NULL) _min_address = _blobs[0]._start;
|
||||
if (_max_address == NULL) _max_address = _blobs[_count - 1]._end;
|
||||
}
|
||||
|
||||
const char* NativeCodeCache::binarySearch(const void* address) {
|
||||
const char* CodeCache::binarySearch(const void* address) {
|
||||
int low = 0;
|
||||
int high = _count - 1;
|
||||
|
||||
@@ -95,20 +144,21 @@ 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
|
||||
if (low > 0 && _blobs[low - 1]._start == _blobs[low - 1]._end) {
|
||||
return (const char*)_blobs[low - 1]._method;
|
||||
// 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 _blobs[low - 1]._name;
|
||||
}
|
||||
return _name;
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbol(const char* name) {
|
||||
const void* CodeCache::findSymbol(const char* name) {
|
||||
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 && strcmp(blob_name, name) == 0) {
|
||||
return _blobs[i]._start;
|
||||
}
|
||||
@@ -116,13 +166,86 @@ const void* NativeCodeCache::findSymbol(const char* name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix) {
|
||||
int prefix_len = strlen(prefix);
|
||||
const void* CodeCache::findSymbolByPrefix(const char* prefix) {
|
||||
return findSymbolByPrefix(prefix, strlen(prefix));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
CodeBlob* CodeCache::findBlobByPrefix(const char* prefix) {
|
||||
size_t prefix_len = strlen(prefix);
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
|
||||
return &_blobs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CodeCache::setGlobalOffsetTable(void** start, void** end, bool patchable) {
|
||||
_got_start = start;
|
||||
_got_end = end;
|
||||
_got_patchable = patchable;
|
||||
}
|
||||
|
||||
void** CodeCache::findGlobalOffsetEntry(void* address) {
|
||||
for (void** entry = _got_start; entry < _got_end; entry++) {
|
||||
if (*entry == address) {
|
||||
makeGotPatchable();
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CodeCache::makeGotPatchable() {
|
||||
if (!_got_patchable) {
|
||||
uintptr_t got_start = (uintptr_t)_got_start & ~OS::page_mask;
|
||||
uintptr_t got_size = ((uintptr_t)_got_end - got_start + OS::page_mask) & ~OS::page_mask;
|
||||
mprotect((void*)got_start, got_size, PROT_READ | PROT_WRITE);
|
||||
_got_patchable = true;
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
return low > 0 ? &_dwarf_table[low - 1] : NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
159
src/codeCache.h
159
src/codeCache.h
@@ -20,14 +20,51 @@
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
#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;
|
||||
|
||||
|
||||
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 bool isMarked(const char* name) {
|
||||
return from(name)->_mark != 0;
|
||||
}
|
||||
|
||||
static void mark(const char* name) {
|
||||
from(name)->_mark = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
@@ -45,8 +82,24 @@ class CodeBlob {
|
||||
};
|
||||
|
||||
|
||||
class FrameDesc;
|
||||
|
||||
class CodeCache {
|
||||
protected:
|
||||
char* _name;
|
||||
short _lib_index;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
const char* _text_base;
|
||||
|
||||
void** _got_start;
|
||||
void** _got_end;
|
||||
bool _got_patchable;
|
||||
bool _debug_symbols;
|
||||
|
||||
FrameDesc* _dwarf_table;
|
||||
int _dwarf_table_length;
|
||||
|
||||
int _capacity;
|
||||
int _count;
|
||||
CodeBlob* _blobs;
|
||||
@@ -54,46 +107,94 @@ class CodeCache {
|
||||
void expand();
|
||||
|
||||
public:
|
||||
CodeCache() {
|
||||
_capacity = INITIAL_CODE_CACHE_CAPACITY;
|
||||
_count = 0;
|
||||
_blobs = new CodeBlob[_capacity];
|
||||
}
|
||||
CodeCache(const char* name,
|
||||
short lib_index = -1,
|
||||
const void* min_address = NO_MIN_ADDRESS,
|
||||
const void* max_address = NO_MAX_ADDRESS);
|
||||
|
||||
~CodeCache() {
|
||||
delete[] _blobs;
|
||||
}
|
||||
~CodeCache();
|
||||
|
||||
void add(const void* start, int length, jmethodID method);
|
||||
void remove(const void* start, jmethodID method);
|
||||
jmethodID find(const void* address);
|
||||
};
|
||||
|
||||
|
||||
class NativeCodeCache : public CodeCache {
|
||||
private:
|
||||
char* _name;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
|
||||
public:
|
||||
NativeCodeCache(const char* name, const void* min_address = NULL, const void* max_address = NULL);
|
||||
|
||||
~NativeCodeCache();
|
||||
|
||||
const char* name() {
|
||||
const char* name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
bool contains(const void* address) {
|
||||
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 add(const void* start, int length, const char* name);
|
||||
void setTextBase(const char* text_base) {
|
||||
_text_base = text_base;
|
||||
}
|
||||
|
||||
void** gotStart() const {
|
||||
return _got_start;
|
||||
}
|
||||
|
||||
void** gotEnd() const {
|
||||
return _got_end;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
CodeBlob* find(const void* address);
|
||||
const char* binarySearch(const void* address);
|
||||
const void* findSymbol(const char* name);
|
||||
CodeBlob* findBlobByPrefix(const char* name);
|
||||
const void* findSymbolByPrefix(const char* prefix);
|
||||
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
|
||||
|
||||
void setGlobalOffsetTable(void** start, void** end, bool patchable);
|
||||
void** findGlobalOffsetEntry(void* address);
|
||||
void makeGotPatchable();
|
||||
|
||||
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
|
||||
|
||||
106
src/converter/Arguments.java
Normal file
106
src/converter/Arguments.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
class Arguments {
|
||||
String title = "Flame Graph";
|
||||
String highlight;
|
||||
Pattern include;
|
||||
Pattern exclude;
|
||||
double minwidth;
|
||||
int skip;
|
||||
boolean reverse;
|
||||
boolean cpu;
|
||||
boolean alloc;
|
||||
boolean live;
|
||||
boolean lock;
|
||||
boolean threads;
|
||||
boolean total;
|
||||
boolean lines;
|
||||
boolean bci;
|
||||
boolean simple;
|
||||
boolean dot;
|
||||
boolean collapsed;
|
||||
long from;
|
||||
long to;
|
||||
String input;
|
||||
String output;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
50
src/converter/CollapsedStacks.java
Normal file
50
src/converter/CollapsedStacks.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
299
src/converter/FlameGraph.java
Normal file
299
src/converter/FlameGraph.java
Normal file
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FlameGraph {
|
||||
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 Arguments args;
|
||||
private final Frame root = new Frame(FRAME_NATIVE);
|
||||
private int depth;
|
||||
private long mintotal;
|
||||
|
||||
public FlameGraph(Arguments args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public FlameGraph(String... args) {
|
||||
this(new Arguments(args));
|
||||
}
|
||||
|
||||
public void parse() throws IOException {
|
||||
parse(new InputStreamReader(new FileInputStream(args.input), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void parse(Reader in) throws IOException {
|
||||
try (BufferedReader br = new BufferedReader(in)) {
|
||||
for (String line; (line = br.readLine()) != null; ) {
|
||||
int space = line.lastIndexOf(' ');
|
||||
if (space <= 0) continue;
|
||||
|
||||
String[] trace = line.substring(0, space).split(";");
|
||||
long ticks = Long.parseLong(line.substring(space + 1));
|
||||
addSample(trace, ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
if (excludeTrace(trace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Frame frame = root;
|
||||
if (args.reverse) {
|
||||
for (int i = trace.length; --i >= args.skip; ) {
|
||||
frame = frame.addChild(trace[i], ticks);
|
||||
}
|
||||
} else {
|
||||
for (int i = args.skip; i < trace.length; i++) {
|
||||
frame = frame.addChild(trace[i], ticks);
|
||||
}
|
||||
}
|
||||
frame.addLeaf(ticks);
|
||||
|
||||
depth = Math.max(depth, trace.length);
|
||||
}
|
||||
|
||||
public void dump() throws IOException {
|
||||
if (args.output == null) {
|
||||
dump(System.out);
|
||||
} else {
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(args.output), 32768);
|
||||
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
|
||||
dump(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
mintotal = (long) (root.total * args.minwidth / 100);
|
||||
int depth = mintotal > 1 ? root.depth(mintotal) : this.depth + 1;
|
||||
|
||||
String tail = getResource("/flame.html");
|
||||
|
||||
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, "/*frames:*/");
|
||||
|
||||
printFrame(out, "all", root, 0, 0);
|
||||
|
||||
tail = printTill(out, tail, "/*highlight:*/");
|
||||
out.print(args.highlight != null ? "'" + escape(args.highlight) + "'" : "");
|
||||
|
||||
out.print(tail);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
|
||||
int type = frame.getType();
|
||||
if (type == FRAME_KERNEL) {
|
||||
title = stripSuffix(title);
|
||||
}
|
||||
|
||||
if ((frame.inlined | frame.c1 | frame.interpreted) != 0 && frame.inlined < frame.total && frame.interpreted < frame.total) {
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + escape(title) + "'," +
|
||||
frame.inlined + "," + frame.c1 + "," + frame.interpreted + ")");
|
||||
} else {
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + escape(title) + "')");
|
||||
}
|
||||
|
||||
x += frame.self;
|
||||
for (Map.Entry<String, Frame> e : frame.entrySet()) {
|
||||
Frame child = e.getValue();
|
||||
if (child.total >= mintotal) {
|
||||
printFrame(out, e.getKey(), child, level + 1, x);
|
||||
}
|
||||
x += child.total;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean excludeTrace(String[] trace) {
|
||||
Pattern include = args.include;
|
||||
Pattern exclude = args.exclude;
|
||||
if (include == null && exclude == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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:");
|
||||
System.out.println(" --title TITLE");
|
||||
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> {
|
||||
final byte type;
|
||||
long total;
|
||||
long self;
|
||||
long inlined, c1, interpreted;
|
||||
|
||||
Frame(byte type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
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 type;
|
||||
}
|
||||
}
|
||||
|
||||
private Frame getChild(String title, byte type) {
|
||||
Frame child = super.get(title);
|
||||
if (child == null) {
|
||||
super.put(title, child = new Frame(type));
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Frame addChild(String title, long ticks) {
|
||||
total += ticks;
|
||||
|
||||
Frame child;
|
||||
if (title.endsWith("_[j]")) {
|
||||
child = getChild(stripSuffix(title), FRAME_JIT_COMPILED);
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
(child = getChild(stripSuffix(title), FRAME_JIT_COMPILED)).inlined += ticks;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
child = getChild(title, FRAME_KERNEL);
|
||||
} else if (title.endsWith("_[1]")) {
|
||||
(child = getChild(stripSuffix(title), FRAME_JIT_COMPILED)).c1 += ticks;
|
||||
} else if (title.endsWith("_[0]")) {
|
||||
(child = getChild(stripSuffix(title), FRAME_JIT_COMPILED)).interpreted += ticks;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
child = getChild(title, FRAME_CPP);
|
||||
} else if (title.indexOf('/') > 0 && title.charAt(0) != '['
|
||||
|| title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
child = getChild(title, FRAME_JIT_COMPILED);
|
||||
} else {
|
||||
child = getChild(title, FRAME_NATIVE);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
void addLeaf(long ticks) {
|
||||
total += ticks;
|
||||
self += ticks;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/converter/Main.java
Normal file
32
src/converter/Main.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main entry point of jar.
|
||||
* Lists available converters.
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
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(" jfr2pprof input.jfr output.pprof");
|
||||
}
|
||||
}
|
||||
263
src/converter/jfr2flame.java
Normal file
263
src/converter/jfr2flame.java
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
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;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to HTML Flame Graph.
|
||||
*/
|
||||
public class jfr2flame {
|
||||
|
||||
private static final String[] FRAME_SUFFIX = {"_[0]", "_[j]", "_[i]", "", "", "_[k]", "_[1]"};
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final Arguments args;
|
||||
private final Dictionary<String> methodNames = new Dictionary<>();
|
||||
|
||||
public jfr2flame(JfrReader jfr, Arguments args) {
|
||||
this.jfr = jfr;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void convert(final FlameGraph fg) throws IOException {
|
||||
EventAggregator agg = new EventAggregator(args.threads, args.total);
|
||||
|
||||
Class<? extends Event> eventClass =
|
||||
args.live ? LiveObject.class :
|
||||
args.alloc ? AllocationSample.class :
|
||||
args.lock ? ContendedLock.class : ExecutionSample.class;
|
||||
int threadState = args.cpu ? getMapKey(jfr.threadStates, "STATE_RUNNABLE") : -1;
|
||||
|
||||
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 (threadState < 0 || ((ExecutionSample) event).threadState == threadState) {
|
||||
agg.collect(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
|
||||
final boolean scale = args.total && eventClass == ContendedLock.class && 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) + (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(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]];
|
||||
}
|
||||
fg.addSample(trace, scale ? (long) (value * ticksToNanos) : value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getThreadFrame(int tid) {
|
||||
String threadName = jfr.threads.get(tid);
|
||||
return threadName == null ? "[tid=" + tid + ']' : '[' + 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(long methodId, byte methodType) {
|
||||
String result = methodNames.get(methodId);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
if (method == null) {
|
||||
result = "unknown";
|
||||
} else {
|
||||
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 = classStr + '.' + methodStr;
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.put(methodId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isNativeFrame(byte methodType) {
|
||||
return methodType >= FlameGraph.FRAME_NATIVE && methodType <= FlameGraph.FRAME_KERNEL
|
||||
&& jfr.frameTypes.size() > FlameGraph.FRAME_NATIVE + 1;
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
|
||||
private static int getMapKey(Map<Integer, String> map, String value) {
|
||||
for (Map.Entry<Integer, String> entry : map.entrySet()) {
|
||||
if (value.equals(entry.getValue())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
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(" --cpu CPU Flame Graph");
|
||||
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(" --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(" --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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
179
src/converter/jfr2nflx.java
Normal file
179
src/converter/jfr2nflx.java
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
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;
|
||||
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
|
||||
* as described in https://github.com/Netflix/nflxprofile/blob/master/nflxprofile.proto.
|
||||
* The result nflxprofile can be opened and analyzed with FlameScope.
|
||||
*/
|
||||
public class jfr2nflx {
|
||||
|
||||
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) throws IOException {
|
||||
this.jfr = jfr;
|
||||
this.samples = jfr.readAllEvents(ExecutionSample.class);
|
||||
}
|
||||
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
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(3, packSamples())
|
||||
.field(4, packDeltas())
|
||||
.field(6, "async-profiler")
|
||||
.field(8, new Proto(32).field(1, "has_node_stack").field(2, "true"))
|
||||
.field(8, new Proto(32).field(1, "has_samples_tid").field(2, "true"))
|
||||
.field(11, packTids());
|
||||
|
||||
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
|
||||
agg.forEach(new EventAggregator.Visitor() {
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
out.write(profile.buffer(), 0, profile.size());
|
||||
|
||||
long endTime = System.nanoTime();
|
||||
System.out.println("Wrote " + profile.size() + " bytes in " + (endTime - startTime) / 1e9 + " s");
|
||||
}
|
||||
|
||||
private Proto packNode(Proto node, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
int top = methods.length - 1;
|
||||
|
||||
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], types[top]))
|
||||
.field(2, FRAME_TYPE[types[top]]));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private Proto packSamples() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeInt(sample.stackTraceId);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private Proto packDeltas() {
|
||||
Proto proto = new Proto(10000);
|
||||
double ticksPerSec = jfr.ticksPerSec;
|
||||
long prevTime = jfr.startTicks;
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
|
||||
prevTime = sample.time;
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private Proto packTids() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeInt(sample.tid);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
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 ((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);
|
||||
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 " + jfr2nflx.class.getName() + " input.jfr output.nflx");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File dst = new File(args[1]);
|
||||
if (dst.isDirectory()) {
|
||||
dst = new File(dst, new File(args[0]).getName().replace(".jfr", ".nflx"));
|
||||
}
|
||||
|
||||
try (JfrReader jfr = new JfrReader(args[0]);
|
||||
FileOutputStream out = new FileOutputStream(dst)) {
|
||||
new jfr2nflx(jfr).dump(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
227
src/converter/jfr2pprof.java
Normal file
227
src/converter/jfr2pprof.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
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.List;
|
||||
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 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<Method, 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 List<ExecutionSample> jfrSamples = reader.readAllEvents(ExecutionSample.class);
|
||||
final Dictionary<StackTrace> stackTraces = reader.stackTraces;
|
||||
long previousTime = reader.startTicks; // Mutate this to keep track of time deltas
|
||||
|
||||
// Iterate over samples
|
||||
for (final ExecutionSample jfrSample : jfrSamples) {
|
||||
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 Integer locaId = locations.get(method);
|
||||
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(method, locId);
|
||||
}
|
||||
|
||||
sample.field(SAMPLE_LOCATION_ID, locations.get(method));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/converter/one/jfr/ClassRef.java
Normal file
25
src/converter/one/jfr/ClassRef.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 ClassRef {
|
||||
public final long name;
|
||||
|
||||
public ClassRef(long name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
117
src/converter/one/jfr/Dictionary.java
Normal file
117
src/converter/one/jfr/Dictionary.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Fast and compact long->Object map.
|
||||
*/
|
||||
public class Dictionary<T> {
|
||||
private static final int INITIAL_CAPACITY = 16;
|
||||
|
||||
private long[] keys;
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
public Dictionary() {
|
||||
this.keys = new long[INITIAL_CAPACITY];
|
||||
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");
|
||||
}
|
||||
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(key) & mask;
|
||||
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")
|
||||
public T get(long key) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(key) & mask;
|
||||
while (keys[i] != key && keys[i] != 0) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
return (T) values[i];
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void forEach(Visitor<T> visitor) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != 0) {
|
||||
visitor.visit(keys[i], (T) values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int preallocate(int count) {
|
||||
if (count * 2 > keys.length) {
|
||||
resize(Integer.highestOneBit(count * 4 - 1));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void resize(int newCapacity) {
|
||||
long[] newKeys = new long[newCapacity];
|
||||
Object[] newValues = new Object[newCapacity];
|
||||
int mask = newKeys.length - 1;
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != 0) {
|
||||
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
|
||||
if (newKeys[j] == 0) {
|
||||
newKeys[j] = keys[i];
|
||||
newValues[j] = values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = newKeys;
|
||||
values = newValues;
|
||||
}
|
||||
|
||||
private static int hashCode(long key) {
|
||||
key *= 0xc6a4a7935bd1e995L;
|
||||
return (int) (key ^ (key >>> 32));
|
||||
}
|
||||
|
||||
public interface Visitor<T> {
|
||||
void visit(long key, T value);
|
||||
}
|
||||
}
|
||||
23
src/converter/one/jfr/Element.java
Normal file
23
src/converter/one/jfr/Element.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
class Element {
|
||||
|
||||
void addChild(Element e) {
|
||||
}
|
||||
}
|
||||
49
src/converter/one/jfr/JfrClass.java
Normal file
49
src/converter/one/jfr/JfrClass.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class JfrClass extends Element {
|
||||
final int id;
|
||||
final String name;
|
||||
final List<JfrField> fields;
|
||||
|
||||
JfrClass(Map<String, String> attributes) {
|
||||
this.id = Integer.parseInt(attributes.get("id"));
|
||||
this.name = attributes.get("name");
|
||||
this.fields = new ArrayList<>(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addChild(Element e) {
|
||||
if (e instanceof JfrField) {
|
||||
fields.add((JfrField) e);
|
||||
}
|
||||
}
|
||||
|
||||
JfrField field(String name) {
|
||||
for (JfrField field : fields) {
|
||||
if (field.name.equals(name)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
31
src/converter/one/jfr/JfrField.java
Normal file
31
src/converter/one/jfr/JfrField.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class JfrField extends Element {
|
||||
final String name;
|
||||
final int type;
|
||||
final boolean constantPool;
|
||||
|
||||
JfrField(Map<String, String> attributes) {
|
||||
this.name = attributes.get("name");
|
||||
this.type = Integer.parseInt(attributes.get("class"));
|
||||
this.constantPool = "true".equals(attributes.get("constantPool"));
|
||||
}
|
||||
}
|
||||
570
src/converter/one/jfr/JfrReader.java
Normal file
570
src/converter/one/jfr/JfrReader.java
Normal file
@@ -0,0 +1,570 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import one.jfr.event.*;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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 CHUNK_SIGNATURE = 0x464c5200;
|
||||
|
||||
private final FileChannel ch;
|
||||
private ByteBuffer buf;
|
||||
private long filePosition;
|
||||
|
||||
public boolean incomplete;
|
||||
public long startNanos = Long.MAX_VALUE;
|
||||
public long endNanos = Long.MIN_VALUE;
|
||||
public long startTicks = Long.MAX_VALUE;
|
||||
public long ticksPerSec;
|
||||
|
||||
public final Dictionary<JfrClass> types = new Dictionary<>();
|
||||
public final Map<String, JfrClass> typesByName = new HashMap<>();
|
||||
public final Dictionary<String> threads = new Dictionary<>();
|
||||
public final Dictionary<ClassRef> classes = new Dictionary<>();
|
||||
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 Map<String, String> settings = new HashMap<>();
|
||||
|
||||
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;
|
||||
private boolean activeSettingHasStack;
|
||||
|
||||
public JfrReader(String fileName) throws IOException {
|
||||
this.ch = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
|
||||
this.buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
||||
|
||||
buf.flip();
|
||||
ensureBytes(CHUNK_HEADER_SIZE);
|
||||
if (!readChunk(0)) {
|
||||
throw new IOException("Incomplete JFR file");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ch.close();
|
||||
}
|
||||
|
||||
public long durationNanos() {
|
||||
return endNanos - startNanos;
|
||||
}
|
||||
|
||||
public long nanosToTicks(long nanos) {
|
||||
return (long) ((nanos - startNanos) * (ticksPerSec / 1e9)) + startTicks;
|
||||
}
|
||||
|
||||
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 (readChunk(pos)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
seek(filePosition + pos + size);
|
||||
}
|
||||
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() {
|
||||
long time = getVarlong();
|
||||
long duration = getVarlong();
|
||||
int tid = getVarint();
|
||||
if (activeSettingHasStack) getVarint();
|
||||
long id = getVarlong();
|
||||
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) {
|
||||
incomplete = true;
|
||||
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);
|
||||
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();
|
||||
getVarlong();
|
||||
|
||||
String[] strings = new String[getVarint()];
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
strings[i] = getString();
|
||||
}
|
||||
readElement(strings);
|
||||
}
|
||||
|
||||
private Element readElement(String[] strings) {
|
||||
String name = strings[getVarint()];
|
||||
|
||||
int attributeCount = getVarint();
|
||||
Map<String, String> attributes = new HashMap<>(attributeCount);
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
attributes.put(strings[getVarint()], strings[getVarint()]);
|
||||
}
|
||||
|
||||
Element e = createElement(name, attributes);
|
||||
int childCount = getVarint();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
e.addChild(readElement(strings));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
private Element createElement(String name, Map<String, String> attributes) {
|
||||
switch (name) {
|
||||
case "class": {
|
||||
JfrClass type = new JfrClass(attributes);
|
||||
if (!attributes.containsKey("superType")) {
|
||||
types.put(type.id, type);
|
||||
}
|
||||
typesByName.put(type.name, type);
|
||||
return type;
|
||||
}
|
||||
case "field":
|
||||
return new JfrField(attributes);
|
||||
default:
|
||||
return new Element();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
delta = getVarlong();
|
||||
getVarint();
|
||||
|
||||
int poolCount = getVarint();
|
||||
for (int i = 0; i < poolCount; i++) {
|
||||
int type = getVarint();
|
||||
readConstants(types.get(type));
|
||||
}
|
||||
} while (delta != 0 && (cpOffset += delta) > 0);
|
||||
}
|
||||
|
||||
private void readConstants(JfrClass type) {
|
||||
switch (type.name) {
|
||||
case "jdk.types.ChunkHeader":
|
||||
buf.position(buf.position() + (CHUNK_HEADER_SIZE + 3));
|
||||
break;
|
||||
case "java.lang.Thread":
|
||||
readThreads(type.fields.size());
|
||||
break;
|
||||
case "java.lang.Class":
|
||||
readClasses(type.fields.size());
|
||||
break;
|
||||
case "jdk.types.Symbol":
|
||||
readSymbols();
|
||||
break;
|
||||
case "jdk.types.Method":
|
||||
readMethods();
|
||||
break;
|
||||
case "jdk.types.StackTrace":
|
||||
readStackTraces();
|
||||
break;
|
||||
case "jdk.types.FrameType":
|
||||
readMap(frameTypes);
|
||||
break;
|
||||
case "jdk.types.ThreadState":
|
||||
readMap(threadStates);
|
||||
break;
|
||||
default:
|
||||
readOtherConstants(type.fields);
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreads(int fieldCount) {
|
||||
int count = threads.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
String osName = getString();
|
||||
int osThreadId = getVarint();
|
||||
String javaName = getString();
|
||||
long javaThreadId = getVarlong();
|
||||
readFields(fieldCount - 4);
|
||||
threads.put(id, javaName != null ? javaName : osName);
|
||||
}
|
||||
}
|
||||
|
||||
private void readClasses(int fieldCount) {
|
||||
int count = classes.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
long loader = getVarlong();
|
||||
long name = getVarlong();
|
||||
long pkg = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
readFields(fieldCount - 4);
|
||||
classes.put(id, new ClassRef(name));
|
||||
}
|
||||
}
|
||||
|
||||
private void readMethods() {
|
||||
int count = methods.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
long cls = getVarlong();
|
||||
long name = getVarlong();
|
||||
long sig = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
int hidden = getVarint();
|
||||
methods.put(id, new MethodRef(cls, name, sig));
|
||||
}
|
||||
}
|
||||
|
||||
private void readStackTraces() {
|
||||
int count = stackTraces.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
int truncated = getVarint();
|
||||
StackTrace stackTrace = readStackTrace();
|
||||
stackTraces.put(id, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
private StackTrace readStackTrace() {
|
||||
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, locations);
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
int count = symbols.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
if (buf.get() != 3) {
|
||||
throw new IllegalArgumentException("Invalid symbol encoding");
|
||||
}
|
||||
symbols.put(id, getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private void readMap(Map<Integer, String> map) {
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
map.put(getVarint(), getString());
|
||||
}
|
||||
}
|
||||
|
||||
private void readOtherConstants(List<JfrField> fields) {
|
||||
int stringType = getTypeId("java.lang.String");
|
||||
|
||||
boolean[] numeric = new boolean[fields.size()];
|
||||
for (int i = 0; i < numeric.length; i++) {
|
||||
JfrField f = fields.get(i);
|
||||
numeric[i] = f.constantPool || f.type != stringType;
|
||||
}
|
||||
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
getVarlong();
|
||||
readFields(numeric);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFields(boolean[] numeric) {
|
||||
for (boolean n : numeric) {
|
||||
if (n) {
|
||||
getVarlong();
|
||||
} else {
|
||||
getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readFields(int count) {
|
||||
while (count-- > 0) {
|
||||
getVarlong();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
activeSettingHasStack = activeSetting >= 0 && typesByName.get("jdk.ActiveSetting").field("stackTrace") != null;
|
||||
}
|
||||
|
||||
private int getTypeId(String typeName) {
|
||||
JfrClass type = typesByName.get(typeName);
|
||||
return type != null ? type.id : -1;
|
||||
}
|
||||
|
||||
private int getVarint() {
|
||||
int result = 0;
|
||||
for (int shift = 0; ; shift += 7) {
|
||||
byte b = buf.get();
|
||||
result |= (b & 0x7f) << shift;
|
||||
if (b >= 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getVarlong() {
|
||||
long result = 0;
|
||||
for (int shift = 0; shift < 56; shift += 7) {
|
||||
byte b = buf.get();
|
||||
result |= (b & 0x7fL) << shift;
|
||||
if (b >= 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result | (buf.get() & 0xffL) << 56;
|
||||
}
|
||||
|
||||
private String getString() {
|
||||
switch (buf.get()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return "";
|
||||
case 3:
|
||||
return new String(getBytes(), StandardCharsets.UTF_8);
|
||||
case 4: {
|
||||
char[] chars = new char[getVarint()];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = (char) getVarint();
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
case 5:
|
||||
return new String(getBytes(), StandardCharsets.ISO_8859_1);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid string encoding");
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
29
src/converter/one/jfr/MethodRef.java
Normal file
29
src/converter/one/jfr/MethodRef.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 MethodRef {
|
||||
public final long cls;
|
||||
public final long name;
|
||||
public final long sig;
|
||||
|
||||
public MethodRef(long cls, long name, long sig) {
|
||||
this.cls = cls;
|
||||
this.name = name;
|
||||
this.sig = sig;
|
||||
}
|
||||
}
|
||||
29
src/converter/one/jfr/StackTrace.java
Normal file
29
src/converter/one/jfr/StackTrace.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 StackTrace {
|
||||
public final long[] methods;
|
||||
public final byte[] types;
|
||||
public final int[] locations;
|
||||
|
||||
public StackTrace(long[] methods, byte[] types, int[] locations) {
|
||||
this.methods = methods;
|
||||
this.types = types;
|
||||
this.locations = locations;
|
||||
}
|
||||
}
|
||||
49
src/converter/one/jfr/event/AllocationSample.java
Normal file
49
src/converter/one/jfr/event/AllocationSample.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 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.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;
|
||||
}
|
||||
}
|
||||
47
src/converter/one/jfr/event/ContendedLock.java
Normal file
47
src/converter/one/jfr/event/ContendedLock.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 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.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;
|
||||
}
|
||||
}
|
||||
47
src/converter/one/jfr/event/Event.java
Normal file
47
src/converter/one/jfr/event/Event.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 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.event;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean sameGroup(Event o) {
|
||||
return getClass() == o.getClass();
|
||||
}
|
||||
|
||||
public long value() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
103
src/converter/one/jfr/event/EventAggregator.java
Normal file
103
src/converter/one/jfr/event/EventAggregator.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2021 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.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);
|
||||
}
|
||||
}
|
||||
26
src/converter/one/jfr/event/ExecutionSample.java
Normal file
26
src/converter/one/jfr/event/ExecutionSample.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 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.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;
|
||||
}
|
||||
}
|
||||
49
src/converter/one/jfr/event/LiveObject.java
Normal file
49
src/converter/one/jfr/event/LiveObject.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2022 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.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;
|
||||
}
|
||||
}
|
||||
144
src/converter/one/proto/Proto.java
Normal file
144
src/converter/one/proto/Proto.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.proto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Simplified implementation of Protobuf writer, capable of encoding
|
||||
* varints, doubles, ASCII strings and embedded messages
|
||||
*/
|
||||
public class Proto {
|
||||
private byte[] buf;
|
||||
private int pos;
|
||||
|
||||
public Proto(int capacity) {
|
||||
this.buf = new byte[capacity];
|
||||
}
|
||||
|
||||
public byte[] buffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
public Proto field(int index, int n) {
|
||||
tag(index, 0);
|
||||
writeInt(n);
|
||||
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);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, String s) {
|
||||
tag(index, 2);
|
||||
writeString(s);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, byte[] bytes) {
|
||||
tag(index, 2);
|
||||
writeBytes(bytes, 0, bytes.length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, Proto proto) {
|
||||
tag(index, 2);
|
||||
writeBytes(proto.buf, 0, proto.pos);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void writeInt(int n) {
|
||||
int length = n == 0 ? 1 : (38 - Integer.numberOfLeadingZeros(n)) / 7;
|
||||
ensureCapacity(length);
|
||||
|
||||
while (n > 0x7f) {
|
||||
buf[pos++] = (byte) (0x80 | (n & 0x7f));
|
||||
n >>>= 7;
|
||||
}
|
||||
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);
|
||||
buf[pos] = (byte) n;
|
||||
buf[pos + 1] = (byte) (n >>> 8);
|
||||
buf[pos + 2] = (byte) (n >>> 16);
|
||||
buf[pos + 3] = (byte) (n >>> 24);
|
||||
buf[pos + 4] = (byte) (n >>> 32);
|
||||
buf[pos + 5] = (byte) (n >>> 40);
|
||||
buf[pos + 6] = (byte) (n >>> 48);
|
||||
buf[pos + 7] = (byte) (n >>> 56);
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
public void writeString(String s) {
|
||||
int length = s.length();
|
||||
writeInt(length);
|
||||
ensureCapacity(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
buf[pos++] = (byte) s.charAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBytes(byte[] bytes, int offset, int length) {
|
||||
writeInt(length);
|
||||
ensureCapacity(length);
|
||||
System.arraycopy(bytes, offset, buf, pos, length);
|
||||
pos += length;
|
||||
}
|
||||
|
||||
private void tag(int index, int type) {
|
||||
ensureCapacity(1);
|
||||
buf[pos++] = (byte) (index << 3 | type);
|
||||
}
|
||||
|
||||
private void ensureCapacity(int length) {
|
||||
if (pos + length > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(pos + length, buf.length * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/demangle.cpp
Normal file
127
src/demangle.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
char* Demangle::demangle(const char* s) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
return demangleCpp(s);
|
||||
}
|
||||
30
src/demangle.h
Normal file
30
src/demangle.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef _DEMANGLE_H
|
||||
#define _DEMANGLE_H
|
||||
|
||||
|
||||
class Demangle {
|
||||
private:
|
||||
static char* demangleCpp(const char* s);
|
||||
static char* demangleRust(const char* s, const char* e);
|
||||
|
||||
public:
|
||||
static char* demangle(const char* s);
|
||||
};
|
||||
|
||||
#endif // _DEMANGLE_H
|
||||
144
src/dictionary.cpp
Normal file
144
src/dictionary.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dictionary.h"
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
static inline DictKey* allocateKey(const char* key, size_t length) {
|
||||
DictKey* dk = (DictKey*)malloc(length + 2);
|
||||
dk->mark = false;
|
||||
memcpy(dk->key, key, length);
|
||||
dk->key[length] = 0;
|
||||
return dk;
|
||||
}
|
||||
|
||||
static inline bool keyEquals(DictKey* dk, const char* key, size_t length) {
|
||||
return strncmp(dk->key, key, length) == 0 && dk->key[length] == 0;
|
||||
}
|
||||
|
||||
|
||||
Dictionary::Dictionary() {
|
||||
_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
}
|
||||
|
||||
Dictionary::~Dictionary() {
|
||||
clear(_table);
|
||||
free(_table);
|
||||
}
|
||||
|
||||
void Dictionary::clear() {
|
||||
clear(_table);
|
||||
memset(_table, 0, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
}
|
||||
|
||||
void Dictionary::clear(DictTable* table) {
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
free(row->keys[j]);
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
clear(row->next);
|
||||
free(row->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
unsigned int h = 2166136261U;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
h = (h ^ key[i]) * 16777619;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
unsigned int Dictionary::lookup(const char* key) {
|
||||
return lookup(key, strlen(key));
|
||||
}
|
||||
|
||||
unsigned int Dictionary::lookup(const char* key, size_t length) {
|
||||
DictTable* table = _table;
|
||||
unsigned int h = hash(key, length);
|
||||
|
||||
while (true) {
|
||||
DictRow* row = &table->rows[h % ROWS];
|
||||
for (int c = 0; c < CELLS; c++) {
|
||||
if (row->keys[c] == NULL) {
|
||||
DictKey* new_dk = allocateKey(key, length);
|
||||
if (__sync_bool_compare_and_swap(&row->keys[c], NULL, new_dk)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
free(new_dk);
|
||||
}
|
||||
if (keyEquals(row->keys[c], key, length)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (row->next == NULL) {
|
||||
DictTable* new_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
new_table->base_index = __sync_add_and_fetch(&_base_index, TABLE_CAPACITY);
|
||||
if (!__sync_bool_compare_and_swap(&row->next, NULL, new_table)) {
|
||||
free(new_table);
|
||||
}
|
||||
}
|
||||
|
||||
table = row->next;
|
||||
h = (h >> ROW_BITS) | (h << (32 - ROW_BITS));
|
||||
}
|
||||
}
|
||||
|
||||
void Dictionary::collect(std::map<unsigned int, const char*>& map) {
|
||||
collect(map, _table);
|
||||
}
|
||||
|
||||
void Dictionary::collect(std::map<unsigned int, const char*>& map, DictTable* table) {
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
DictKey* dk = row->keys[j];
|
||||
if (dk != NULL && !dk->mark) {
|
||||
dk->mark = true;
|
||||
map[table->index(i, j)] = dk->key;
|
||||
}
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
collect(map, row->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/dictionary.h
Normal file
77
src/dictionary.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _DICTIONARY_H
|
||||
#define _DICTIONARY_H
|
||||
|
||||
#include <map>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#define ROW_BITS 7
|
||||
#define ROWS (1 << ROW_BITS)
|
||||
#define CELLS 3
|
||||
#define TABLE_CAPACITY (ROWS * CELLS)
|
||||
|
||||
|
||||
struct DictTable;
|
||||
|
||||
struct DictKey {
|
||||
bool mark;
|
||||
char key[0];
|
||||
};
|
||||
|
||||
struct DictRow {
|
||||
DictKey* keys[CELLS];
|
||||
DictTable* next;
|
||||
};
|
||||
|
||||
struct DictTable {
|
||||
DictRow rows[ROWS];
|
||||
unsigned int base_index;
|
||||
|
||||
unsigned int index(int row, int col) {
|
||||
return base_index + (col << ROW_BITS) + row;
|
||||
}
|
||||
};
|
||||
|
||||
// Append-only concurrent hash table based on multi-level arrays
|
||||
class Dictionary {
|
||||
private:
|
||||
DictTable* _table;
|
||||
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);
|
||||
|
||||
static void collect(std::map<unsigned int, const char*>& map, DictTable* table);
|
||||
|
||||
public:
|
||||
Dictionary();
|
||||
~Dictionary();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
|
||||
unsigned int lookup(const char* key);
|
||||
unsigned int lookup(const char* key, size_t length);
|
||||
|
||||
void collect(std::map<unsigned int, const char*>& map);
|
||||
};
|
||||
|
||||
#endif // _DICTIONARY_H
|
||||
353
src/dwarf.cpp
Normal file
353
src/dwarf.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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_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::default_frame = {0, DW_REG_FP | (2 * DW_STACK_SLOT) << 8, -2 * 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, 2 * DW_STACK_SLOT, -2 * 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 = DW_STACK_SLOT;
|
||||
int fp_off = DW_SAME_FP;
|
||||
int pc_off = -DW_STACK_SLOT;
|
||||
|
||||
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);
|
||||
loc += get8() * code_align;
|
||||
break;
|
||||
case DW_CFA_advance_loc2:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off);
|
||||
loc += get16() * code_align;
|
||||
break;
|
||||
case DW_CFA_advance_loc4:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_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:
|
||||
skipLeb();
|
||||
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;
|
||||
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);
|
||||
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:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_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 cfa = cfa_reg | cfa_off << 8;
|
||||
if (_prev == NULL || (_prev->loc == loc && --_count >= 0) || _prev->cfa != cfa || _prev->fp_off != fp_off) {
|
||||
_prev = addRecordRaw(loc, cfa, fp_off);
|
||||
}
|
||||
}
|
||||
|
||||
FrameDesc* DwarfParser::addRecordRaw(u32 loc, int cfa, int fp_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;
|
||||
return f;
|
||||
}
|
||||
160
src/dwarf.h
Normal file
160
src/dwarf.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef _DWARF_H
|
||||
#define _DWARF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
#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;
|
||||
|
||||
#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;
|
||||
|
||||
#else
|
||||
|
||||
#define DWARF_SUPPORTED false
|
||||
|
||||
const int DW_REG_FP = 0;
|
||||
const int DW_REG_SP = 1;
|
||||
const int DW_REG_PC = 2;
|
||||
|
||||
#endif
|
||||
|
||||
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*);
|
||||
|
||||
|
||||
struct FrameDesc {
|
||||
u32 loc;
|
||||
int cfa;
|
||||
int fp_off;
|
||||
|
||||
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);
|
||||
FrameDesc* addRecordRaw(u32 loc, int cfa, int fp_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
|
||||
@@ -15,32 +15,17 @@
|
||||
*/
|
||||
|
||||
#include "engine.h"
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address) {
|
||||
StackFrame frame(ucontext);
|
||||
const void* pc = (const void*)frame.pc();
|
||||
uintptr_t fp = frame.fp();
|
||||
uintptr_t prev_fp = (uintptr_t)&fp;
|
||||
volatile bool Engine::_enabled = false;
|
||||
|
||||
int depth = 0;
|
||||
const void* const valid_pc = (const void*)0x1000;
|
||||
|
||||
// Walk until the bottom of the stack or until the first Java frame
|
||||
while (depth < max_depth && pc >= valid_pc && !(pc >= jit_min_address && pc < jit_max_address)) {
|
||||
callchain[depth++] = pc;
|
||||
|
||||
// Check if the next frame is below on the current stack
|
||||
if (fp <= prev_fp || fp >= prev_fp + 0x40000) {
|
||||
break;
|
||||
}
|
||||
|
||||
prev_fp = fp;
|
||||
pc = ((const void**)fp)[1];
|
||||
fp = ((uintptr_t*)fp)[0];
|
||||
}
|
||||
|
||||
return depth;
|
||||
Error Engine::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error Engine::start(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void Engine::stop() {
|
||||
}
|
||||
|
||||
45
src/engine.h
45
src/engine.h
@@ -20,19 +20,48 @@
|
||||
#include "arguments.h"
|
||||
|
||||
|
||||
struct StackContext;
|
||||
|
||||
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 Error start(Arguments& args) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual const char* units() {
|
||||
return "total";
|
||||
}
|
||||
|
||||
virtual void onThreadStart() {}
|
||||
virtual void onThreadEnd() {}
|
||||
virtual Error check(Arguments& args);
|
||||
virtual Error start(Arguments& args);
|
||||
virtual void stop();
|
||||
|
||||
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address);
|
||||
void enableEvents(bool enabled) {
|
||||
_enabled = enabled;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _ENGINE_H
|
||||
|
||||
62
src/event.h
Normal file
62
src/event.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _EVENT_H
|
||||
#define _EVENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class Event {
|
||||
public:
|
||||
u32 id() {
|
||||
return *(u32*)this;
|
||||
}
|
||||
};
|
||||
|
||||
class ExecutionEvent : public Event {
|
||||
public:
|
||||
ThreadState _thread_state;
|
||||
|
||||
ExecutionEvent() : _thread_state(THREAD_RUNNING) {
|
||||
}
|
||||
};
|
||||
|
||||
class AllocEvent : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _total_size;
|
||||
u64 _instance_size;
|
||||
};
|
||||
|
||||
class LockEvent : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _start_time;
|
||||
u64 _end_time;
|
||||
uintptr_t _address;
|
||||
long long _timeout;
|
||||
};
|
||||
|
||||
class LiveObject : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _alloc_size;
|
||||
u64 _alloc_time;
|
||||
};
|
||||
|
||||
#endif // _EVENT_H
|
||||
98
src/fdtransfer/fdtransfer.h
Normal file
98
src/fdtransfer/fdtransfer.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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,
|
||||
BPFMAP_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 bpfmap_fd_request {
|
||||
struct fd_request header;
|
||||
int version;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct bpfmap_params {
|
||||
unsigned long interval;
|
||||
unsigned int num_entries;
|
||||
unsigned int entry_size;
|
||||
unsigned int salt;
|
||||
};
|
||||
|
||||
struct bpfmap_fd_response {
|
||||
struct fd_response header;
|
||||
struct bpfmap_params params;
|
||||
};
|
||||
|
||||
|
||||
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
|
||||
388
src/fdtransfer/fdtransferServer.cpp
Normal file
388
src/fdtransfer/fdtransferServer.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "fdtransfer.h"
|
||||
#include "../jattach/psutil.h"
|
||||
|
||||
|
||||
class FdTransferServer {
|
||||
private:
|
||||
static int _server;
|
||||
static int _peer;
|
||||
static int copyFile(const char* src_name, const char* dst_name, mode_t mode);
|
||||
static bool sendFd(int fd, struct fd_response *resp, size_t resp_size);
|
||||
|
||||
public:
|
||||
static void closeServer() { close(_server); }
|
||||
static void closePeer() { close(_peer); }
|
||||
static bool bindServer(struct sockaddr_un *sun, socklen_t addrlen, int accept_timeout);
|
||||
static bool acceptPeer(int *peer_pid);
|
||||
static bool serveRequests(int peer_pid);
|
||||
};
|
||||
|
||||
int FdTransferServer::_server;
|
||||
int FdTransferServer::_peer;
|
||||
|
||||
bool FdTransferServer::bindServer(struct sockaddr_un *sun, socklen_t addrlen, int accept_timeout) {
|
||||
_server = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (_server == -1) {
|
||||
perror("FdTransfer socket()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arbitrary timeout, to prevent it from listening forever.
|
||||
if (accept_timeout > 0) {
|
||||
const struct timeval timeout = {accept_timeout, 0};
|
||||
if (setsockopt(_server, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
|
||||
perror("FdTransfer setsockopt(SO_RCVTIMEO)");
|
||||
close(_server);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(_server, (const struct sockaddr*)sun, addrlen) < 0) {
|
||||
perror("FdTransfer bind()");
|
||||
close(_server);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(_server, 1) < 0) {
|
||||
perror("FdTransfer listen()");
|
||||
close(_server);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FdTransferServer::acceptPeer(int *peer_pid) {
|
||||
_peer = accept(_server, NULL, NULL);
|
||||
if (_peer == -1) {
|
||||
perror("FdTransfer accept()");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ucred cred;
|
||||
socklen_t len = sizeof(cred);
|
||||
if (getsockopt(_peer, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
|
||||
perror("getsockopt(SO_PEERCRED)");
|
||||
close(_peer);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*peer_pid != 0) {
|
||||
if (cred.pid != *peer_pid) {
|
||||
fprintf(stderr, "Unexpected connection from PID %d, expected from %d\n", cred.pid, *peer_pid);
|
||||
close(_peer);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
*peer_pid = cred.pid;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FdTransferServer::serveRequests(int peer_pid) {
|
||||
// Close the server side, don't need it anymore.
|
||||
FdTransferServer::closeServer();
|
||||
|
||||
void *perf_mmap_ringbuf[1024] = {};
|
||||
size_t ringbuf_index = 0;
|
||||
const size_t perf_mmap_size = 2 * sysconf(_SC_PAGESIZE);
|
||||
|
||||
while (1) {
|
||||
unsigned char request_buf[1024];
|
||||
struct fd_request *req = (struct fd_request *)request_buf;
|
||||
|
||||
ssize_t ret = RESTARTABLE(recv(_peer, req, sizeof(request_buf), 0));
|
||||
|
||||
if (ret == 0) {
|
||||
// EOF means done
|
||||
return true;
|
||||
} else if (ret < 0) {
|
||||
perror("recv()");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (req->type) {
|
||||
case PERF_FD: {
|
||||
struct perf_fd_request *request = (struct perf_fd_request*)req;
|
||||
int perf_fd = -1;
|
||||
int error;
|
||||
|
||||
// In pid == 0 mode, allow all perf_event_open requests.
|
||||
// Otherwise, verify the thread belongs to PID.
|
||||
if (peer_pid == 0 || syscall(__NR_tgkill, peer_pid, request->tid, 0) == 0) {
|
||||
perf_fd = syscall(__NR_perf_event_open, &request->attr, request->tid, -1, -1, 0);
|
||||
error = perf_fd < 0 ? errno : 0;
|
||||
} else {
|
||||
fprintf(stderr, "Target has requested perf_event_open for TID %d which is not a thread of process %d\n", request->tid, peer_pid);
|
||||
error = ESRCH;
|
||||
}
|
||||
|
||||
// Map the perf buffer here (mapping perf fds may require privileges, and fdtransfer has them while the target application does not
|
||||
// necessarily; if pages are already mapped, the same physical pages will be used when the profiler agent maps them again, requiring
|
||||
// no privileges this time)
|
||||
if (error == 0) {
|
||||
// Settings match the mmap() done in PerfEvents::createForThread().
|
||||
void *map_result = mmap(NULL, perf_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, perf_fd, 0);
|
||||
// Ignore errors - if this fails, let it fail again in the profiler again & produce a proper error for the user.
|
||||
|
||||
// Free next entry in the ring buffer, if it was previously allocated.
|
||||
if (perf_mmap_ringbuf[ringbuf_index] != NULL && perf_mmap_ringbuf[ringbuf_index] != MAP_FAILED) {
|
||||
(void)munmap(perf_mmap_ringbuf[ringbuf_index], perf_mmap_size);
|
||||
}
|
||||
// Store it in the ring buffer so we can free it later.
|
||||
perf_mmap_ringbuf[ringbuf_index] = map_result;
|
||||
|
||||
ringbuf_index++;
|
||||
ringbuf_index = ringbuf_index % ARRAY_SIZE(perf_mmap_ringbuf);
|
||||
}
|
||||
|
||||
struct perf_fd_response resp;
|
||||
resp.header.type = request->header.type;
|
||||
resp.header.error = error;
|
||||
resp.tid = request->tid;
|
||||
sendFd(perf_fd, &resp.header, sizeof(resp));
|
||||
close(perf_fd);
|
||||
break;
|
||||
}
|
||||
|
||||
case KALLSYMS_FD: {
|
||||
// can't directly pass the fd of /proc/kallsyms, because before Linux 4.15 the permission check
|
||||
// was conducted on each read.
|
||||
// it's simpler to copy the file to a temporary location and pass the fd of it (compared to passing the
|
||||
// entire contents over the peer socket)
|
||||
char tmp_path[256];
|
||||
snprintf(tmp_path, sizeof(tmp_path), "/tmp/async-profiler-kallsyms.%d", getpid());
|
||||
|
||||
int kallsyms_fd = -1;
|
||||
int error = copyFile("/proc/kallsyms", tmp_path, 0600);
|
||||
if (error == 0) {
|
||||
kallsyms_fd = open(tmp_path, O_RDONLY);
|
||||
if (kallsyms_fd == -1) {
|
||||
error = errno;
|
||||
} else {
|
||||
unlink(tmp_path);
|
||||
}
|
||||
}
|
||||
|
||||
struct fd_response resp;
|
||||
resp.type = req->type;
|
||||
resp.error = error;
|
||||
sendFd(kallsyms_fd, &resp, sizeof(resp));
|
||||
close(kallsyms_fd);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown request type %u\n", req->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int FdTransferServer::copyFile(const char* src_name, const char* dst_name, mode_t mode) {
|
||||
int src = open(src_name, O_RDONLY);
|
||||
if (src == -1) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
int dst = creat(dst_name, mode);
|
||||
if (dst == -1) {
|
||||
int result = errno;
|
||||
close(src);
|
||||
return result;
|
||||
}
|
||||
|
||||
// copy_file_range() doesn't exist in older kernels, sendfile() no longer works in newer ones
|
||||
char buf[65536];
|
||||
ssize_t r;
|
||||
while ((r = read(src, buf, sizeof(buf))) > 0) {
|
||||
ssize_t w = write(dst, buf, r);
|
||||
(void)w;
|
||||
}
|
||||
|
||||
close(dst);
|
||||
close(src);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FdTransferServer::sendFd(int fd, 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);
|
||||
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(fd))];
|
||||
struct cmsghdr align;
|
||||
} u;
|
||||
|
||||
if (fd != -1) {
|
||||
msg.msg_control = u.buf;
|
||||
msg.msg_controllen = sizeof(u.buf);
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
|
||||
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
|
||||
}
|
||||
|
||||
ssize_t ret = RESTARTABLE(sendmsg(_peer, &msg, 0));
|
||||
if (ret < 0) {
|
||||
perror("sendmsg()");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int single_pid_server(int pid, const char *path) {
|
||||
// get its nspid prior to moving to its PID namespace.
|
||||
int nspid;
|
||||
uid_t _target_uid;
|
||||
gid_t _target_gid;
|
||||
if (get_process_info(pid, &_target_uid, &_target_gid, &nspid)) {
|
||||
fprintf(stderr, "Process %d not found\n", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct sockaddr_un sun;
|
||||
socklen_t addrlen;
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
fprintf(stderr, "Path too long\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the server before forking, so w're ready to accept connections once our parent
|
||||
// exits.
|
||||
|
||||
// Abstract namespace UDS requires us to move network namespace.
|
||||
if (sun.sun_path[0] == '\0') {
|
||||
if (enter_ns(pid, "net") == -1) {
|
||||
fprintf(stderr, "Failed to enter the net NS of target process %d\n", pid);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FdTransferServer::bindServer(&sun, addrlen, 10)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!enter_ns(pid, "pid") == -1) {
|
||||
fprintf(stderr, "Failed to enter the PID NS of target process %d\n", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// CLONE_NEWPID affects children only - so we fork here.
|
||||
if (0 == fork()) {
|
||||
return FdTransferServer::acceptPeer(&nspid) && FdTransferServer::serveRequests(nspid) ? 0 : 1;
|
||||
} else {
|
||||
// Exit now, let our caller continue.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int path_server(const char *path) {
|
||||
struct sockaddr_un sun;
|
||||
socklen_t addrlen;
|
||||
|
||||
struct sigaction sigchld_action;
|
||||
sigchld_action.sa_handler = SIG_DFL;
|
||||
sigchld_action.sa_flags = SA_NOCLDWAIT;
|
||||
sigaction(SIGCHLD, &sigchld_action, NULL);
|
||||
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
fprintf(stderr, "Path '%s' is too long\n", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!FdTransferServer::bindServer(&sun, addrlen, 0)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Server ready at '%s'\n", path);
|
||||
|
||||
while (1) {
|
||||
int peer_pid = 0;
|
||||
if (!FdTransferServer::acceptPeer(&peer_pid)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enter its PID namespace.
|
||||
if (enter_ns(peer_pid, "pid") == -1) {
|
||||
fprintf(stderr, "Failed to enter the PID NS of target process %d\n", peer_pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Serving PID %d\n", peer_pid);
|
||||
|
||||
// We fork(), to actually move a PID namespace.
|
||||
if (0 == fork()) {
|
||||
return FdTransferServer::serveRequests(0) ? 0 : 1;
|
||||
} else {
|
||||
FdTransferServer::closePeer();
|
||||
}
|
||||
|
||||
// Move back to our original PID namespace (reverts pid_for_children)
|
||||
if (enter_ns(getpid(), "pid") == -1) {
|
||||
fprintf(stderr, "Failed to exit the PID NS of target process %d\n", peer_pid);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
int pid = 0;
|
||||
if (argc == 3) {
|
||||
pid = atoi(argv[2]);
|
||||
} else if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <path> [<pid>]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2 modes:
|
||||
// pid is not given - bind on a path and accept requests forever, from any PID, until being killed.
|
||||
// pid is given - bind on an path for that PID, accept requests only from that PID until the single connection is closed.
|
||||
if (pid != 0) {
|
||||
return single_pid_server(pid, argv[1]);
|
||||
} else {
|
||||
return path_server(argv[1]);
|
||||
}
|
||||
}
|
||||
56
src/fdtransferClient.h
Normal file
56
src/fdtransferClient.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef _FDTRANSFER_CLIENT_H
|
||||
#define _FDTRANSFER_CLIENT_H
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include "fdtransfer/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();
|
||||
static int requestBpfMapFd(struct bpfmap_params* params);
|
||||
};
|
||||
|
||||
#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
|
||||
169
src/fdtransferClient_linux.cpp
Normal file
169
src/fdtransferClient_linux.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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::requestBpfMapFd(struct bpfmap_params* params) {
|
||||
struct bpfmap_fd_request request;
|
||||
request.header.type = BPFMAP_FD;
|
||||
request.version = 1;
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct bpfmap_fd_response resp;
|
||||
int fd = recvFd(request.header.type, &resp.header, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
errno = resp.header.error;
|
||||
} else {
|
||||
*params = resp.params;
|
||||
}
|
||||
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,528 +1,33 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* This is a specialized C++ port of the FlameGraph script available at
|
||||
* https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl
|
||||
* 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
|
||||
*
|
||||
* Copyright 2016 Netflix, Inc.
|
||||
* Copyright 2011 Joyent, Inc. All rights reserved.
|
||||
* Copyright 2011 Brendan Gregg. All rights reserved.
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at docs/cddl1.txt or
|
||||
* http://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at docs/cddl1.txt.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "flameGraph.h"
|
||||
#include "incbin.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
static const char SVG_HEADER[] =
|
||||
"<?xml version=\"1.0\" standalone=\"no\"?>\n"
|
||||
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
|
||||
"<svg version=\"1.1\" width=\"%d\" height=\"%d\" onload=\"init(evt)\" viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
|
||||
"<style type=\"text/css\">\n"
|
||||
"\ttext { font-family:Verdana; font-size:12px; fill:black; }\n"
|
||||
"\t.func_g:hover { stroke:black; stroke-width:0.5; cursor:pointer; }\n"
|
||||
"</style>\n"
|
||||
"<script type=\"text/ecmascript\">\n"
|
||||
"<![CDATA[\n"
|
||||
"\tvar details, searchbtn, matchedtxt, svg;\n"
|
||||
"\tfunction init(evt) {\n"
|
||||
"\t\tdetails = document.getElementById(\"details\").firstChild;\n"
|
||||
"\t\tsearchbtn = document.getElementById(\"search\");\n"
|
||||
"\t\tmatchedtxt = document.getElementById(\"matched\");\n"
|
||||
"\t\tsvg = document.getElementsByTagName(\"svg\")[0];\n"
|
||||
"\t\tsearching = 0;\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// mouse-over for info\n"
|
||||
"\tfunction s(node) {\t\t// show\n"
|
||||
"\t\tinfo = g_to_text(node);\n"
|
||||
"\t\tdetails.nodeValue = \"Function: \" + info;\n"
|
||||
"\t}\n"
|
||||
"\tfunction c() {\t\t\t// clear\n"
|
||||
"\t\tdetails.nodeValue = ' ';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// ctrl-F for search\n"
|
||||
"\twindow.addEventListener(\"keydown\",function (e) {\n"
|
||||
"\t\tif (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {\n"
|
||||
"\t\t\te.preventDefault();\n"
|
||||
"\t\t\tsearch_prompt();\n"
|
||||
"\t\t}\n"
|
||||
"\t})\n"
|
||||
"\n"
|
||||
"\t// functions\n"
|
||||
"\tfunction find_child(parent, name, attr) {\n"
|
||||
"\t\tvar children = parent.childNodes;\n"
|
||||
"\t\tfor (var i=0; i<children.length;i++) {\n"
|
||||
"\t\t\tif (children[i].tagName == name)\n"
|
||||
"\t\t\t\treturn (attr != undefined) ? children[i].attributes[attr].value : children[i];\n"
|
||||
"\t\t}\n"
|
||||
"\t\treturn;\n"
|
||||
"\t}\n"
|
||||
"\tfunction orig_save(e, attr, val) {\n"
|
||||
"\t\tif (e.attributes[\"_orig_\"+attr] != undefined) return;\n"
|
||||
"\t\tif (e.attributes[attr] == undefined) return;\n"
|
||||
"\t\tif (val == undefined) val = e.attributes[attr].value;\n"
|
||||
"\t\te.setAttribute(\"_orig_\"+attr, val);\n"
|
||||
"\t}\n"
|
||||
"\tfunction orig_load(e, attr) {\n"
|
||||
"\t\tif (e.attributes[\"_orig_\"+attr] == undefined) return;\n"
|
||||
"\t\te.attributes[attr].value = e.attributes[\"_orig_\"+attr].value;\n"
|
||||
"\t\te.removeAttribute(\"_orig_\"+attr);\n"
|
||||
"\t}\n"
|
||||
"\tfunction g_to_text(e) {\n"
|
||||
"\t\tvar text = find_child(e, \"title\").firstChild.nodeValue;\n"
|
||||
"\t\treturn (text)\n"
|
||||
"\t}\n"
|
||||
"\tfunction g_to_func(e) {\n"
|
||||
"\t\tvar func = g_to_text(e);\n"
|
||||
"\t\t// if there's any manipulation we want to do to the function\n"
|
||||
"\t\t// name before it's searched, do it here before returning.\n"
|
||||
"\t\treturn (func);\n"
|
||||
"\t}\n"
|
||||
"\tfunction update_text(e) {\n"
|
||||
"\t\tvar r = find_child(e, \"rect\");\n"
|
||||
"\t\tvar t = find_child(e, \"text\");\n"
|
||||
"\t\tvar w = parseFloat(r.attributes[\"width\"].value) -3;\n"
|
||||
"\t\tvar txt = find_child(e, \"title\").textContent.replace(/\\([^(]*\\)$/,\"\");\n"
|
||||
"\t\tt.attributes[\"x\"].value = parseFloat(r.attributes[\"x\"].value) +3;\n"
|
||||
"\n"
|
||||
"\t\t// Smaller than this size won't fit anything\n"
|
||||
"\t\tif (w < 2*12*0.59) {\n"
|
||||
"\t\t\tt.textContent = \"\";\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tt.textContent = txt;\n"
|
||||
"\t\t// Fit in full text width\n"
|
||||
"\t\tif (/^ *$/.test(txt) || t.getSubStringLength(0, txt.length) < w)\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\n"
|
||||
"\t\tfor (var x=txt.length-2; x>0; x--) {\n"
|
||||
"\t\t\tif (t.getSubStringLength(0, x+2) <= w) {\n"
|
||||
"\t\t\t\tt.textContent = txt.substring(0,x) + \"..\";\n"
|
||||
"\t\t\t\treturn;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tt.textContent = \"\";\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// zoom\n"
|
||||
"\tfunction zoom_reset(e) {\n"
|
||||
"\t\tif (e.attributes != undefined) {\n"
|
||||
"\t\t\torig_load(e, \"x\");\n"
|
||||
"\t\t\torig_load(e, \"width\");\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor(var i=0, c=e.childNodes; i<c.length; i++) {\n"
|
||||
"\t\t\tzoom_reset(c[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom_child(e, x, ratio) {\n"
|
||||
"\t\tif (e.attributes != undefined) {\n"
|
||||
"\t\t\tif (e.attributes[\"x\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"x\");\n"
|
||||
"\t\t\t\te.attributes[\"x\"].value = (parseFloat(e.attributes[\"x\"].value) - x - 10) * ratio + 10;\n"
|
||||
"\t\t\t\tif(e.tagName == \"text\") e.attributes[\"x\"].value = find_child(e.parentNode, \"rect\", \"x\") + 3;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (e.attributes[\"width\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"width\");\n"
|
||||
"\t\t\t\te.attributes[\"width\"].value = parseFloat(e.attributes[\"width\"].value) * ratio;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor(var i=0, c=e.childNodes; i<c.length; i++) {\n"
|
||||
"\t\t\tzoom_child(c[i], x-10, ratio);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom_parent(e) {\n"
|
||||
"\t\tif (e.attributes) {\n"
|
||||
"\t\t\tif (e.attributes[\"x\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"x\");\n"
|
||||
"\t\t\t\te.attributes[\"x\"].value = 10;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (e.attributes[\"width\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"width\");\n"
|
||||
"\t\t\t\te.attributes[\"width\"].value = parseInt(svg.width.baseVal.value) - (10*2);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor(var i=0, c=e.childNodes; i<c.length; i++) {\n"
|
||||
"\t\t\tzoom_parent(c[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom(node) {\n"
|
||||
"\t\tvar attr = find_child(node, \"rect\").attributes;\n"
|
||||
"\t\tvar width = parseFloat(attr[\"width\"].value);\n"
|
||||
"\t\tvar xmin = parseFloat(attr[\"x\"].value);\n"
|
||||
"\t\tvar xmax = parseFloat(xmin + width);\n"
|
||||
"\t\tvar ymin = parseFloat(attr[\"y\"].value);\n"
|
||||
"\t\tvar ratio = (svg.width.baseVal.value - 2*10) / width;\n"
|
||||
"\n"
|
||||
"\t\t// XXX: Workaround for JavaScript float issues (fix me)\n"
|
||||
"\t\tvar fudge = 0.0001;\n"
|
||||
"\n"
|
||||
"\t\tvar unzoombtn = document.getElementById(\"unzoom\");\n"
|
||||
"\t\tunzoombtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"g\");\n"
|
||||
"\t\tfor(var i=0;i<el.length;i++){\n"
|
||||
"\t\t\tvar e = el[i];\n"
|
||||
"\t\t\tvar a = find_child(e, \"rect\").attributes;\n"
|
||||
"\t\t\tvar ex = parseFloat(a[\"x\"].value);\n"
|
||||
"\t\t\tvar ew = parseFloat(a[\"width\"].value);\n"
|
||||
"\t\t\t// Is it an ancestor\n"
|
||||
"\t\t\tif (%d == 0) {\n"
|
||||
"\t\t\t\tvar upstack = parseFloat(a[\"y\"].value) > ymin;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\tvar upstack = parseFloat(a[\"y\"].value) < ymin;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (upstack) {\n"
|
||||
"\t\t\t\t// Direct ancestor\n"
|
||||
"\t\t\t\tif (ex <= xmin && (ex+ew+fudge) >= xmax) {\n"
|
||||
"\t\t\t\t\te.style[\"opacity\"] = \"0.5\";\n"
|
||||
"\t\t\t\t\tzoom_parent(e);\n"
|
||||
"\t\t\t\t\te.onclick = function(e){unzoom(); zoom(this);};\n"
|
||||
"\t\t\t\t\tupdate_text(e);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\t// not in current path\n"
|
||||
"\t\t\t\telse\n"
|
||||
"\t\t\t\t\te.style[\"display\"] = \"none\";\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\t// Children maybe\n"
|
||||
"\t\t\telse {\n"
|
||||
"\t\t\t\t// no common path\n"
|
||||
"\t\t\t\tif (ex < xmin || ex + fudge >= xmax) {\n"
|
||||
"\t\t\t\t\te.style[\"display\"] = \"none\";\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\telse {\n"
|
||||
"\t\t\t\t\tzoom_child(e, xmin, ratio);\n"
|
||||
"\t\t\t\t\te.onclick = function(e){zoom(this);};\n"
|
||||
"\t\t\t\t\tupdate_text(e);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction unzoom() {\n"
|
||||
"\t\tvar unzoombtn = document.getElementById(\"unzoom\");\n"
|
||||
"\t\tunzoombtn.style[\"opacity\"] = \"0.0\";\n"
|
||||
"\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"g\");\n"
|
||||
"\t\tfor(i=0;i<el.length;i++) {\n"
|
||||
"\t\t\tel[i].style[\"display\"] = \"block\";\n"
|
||||
"\t\t\tel[i].style[\"opacity\"] = \"1\";\n"
|
||||
"\t\t\tzoom_reset(el[i]);\n"
|
||||
"\t\t\tupdate_text(el[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// search\n"
|
||||
"\tfunction reset_search() {\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"rect\");\n"
|
||||
"\t\tfor (var i=0; i < el.length; i++) {\n"
|
||||
"\t\t\torig_load(el[i], \"fill\")\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction search_prompt() {\n"
|
||||
"\t\tif (!searching) {\n"
|
||||
"\t\t\tvar term = prompt(\"Enter a search term (regexp \" +\n"
|
||||
"\t\t\t \"allowed, eg: ^ext4_)\", \"\");\n"
|
||||
"\t\t\tif (term != null) {\n"
|
||||
"\t\t\t\tsearch(term)\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t} else {\n"
|
||||
"\t\t\treset_search();\n"
|
||||
"\t\t\tsearching = 0;\n"
|
||||
"\t\t\tsearchbtn.style[\"opacity\"] = \"0.1\";\n"
|
||||
"\t\t\tsearchbtn.firstChild.nodeValue = \"Search\"\n"
|
||||
"\t\t\tmatchedtxt.style[\"opacity\"] = \"0.0\";\n"
|
||||
"\t\t\tmatchedtxt.firstChild.nodeValue = \"\"\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction search(term) {\n"
|
||||
"\t\tvar re = new RegExp(term);\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"g\");\n"
|
||||
"\t\tvar matches = new Object();\n"
|
||||
"\t\tvar maxwidth = 0;\n"
|
||||
"\t\tfor (var i = 0; i < el.length; i++) {\n"
|
||||
"\t\t\tvar e = el[i];\n"
|
||||
"\t\t\tif (e.attributes[\"class\"].value != \"func_g\")\n"
|
||||
"\t\t\t\tcontinue;\n"
|
||||
"\t\t\tvar func = g_to_func(e);\n"
|
||||
"\t\t\tvar rect = find_child(e, \"rect\");\n"
|
||||
"\t\t\tif (rect == null) {\n"
|
||||
"\t\t\t\t// the rect might be wrapped in an anchor\n"
|
||||
"\t\t\t\t// if nameattr href is being used\n"
|
||||
"\t\t\t\tif (rect = find_child(e, \"a\")) {\n"
|
||||
"\t\t\t\t rect = find_child(r, \"rect\");\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (func == null || rect == null)\n"
|
||||
"\t\t\t\tcontinue;\n"
|
||||
"\n"
|
||||
"\t\t\t// Save max width. Only works as we have a root frame\n"
|
||||
"\t\t\tvar w = parseFloat(rect.attributes[\"width\"].value);\n"
|
||||
"\t\t\tif (w > maxwidth)\n"
|
||||
"\t\t\t\tmaxwidth = w;\n"
|
||||
"\n"
|
||||
"\t\t\tif (func.match(re)) {\n"
|
||||
"\t\t\t\t// highlight\n"
|
||||
"\t\t\t\tvar x = parseFloat(rect.attributes[\"x\"].value);\n"
|
||||
"\t\t\t\torig_save(rect, \"fill\");\n"
|
||||
"\t\t\t\trect.attributes[\"fill\"].value =\n"
|
||||
"\t\t\t\t \"rgb(230,0,230)\";\n"
|
||||
"\n"
|
||||
"\t\t\t\t// remember matches\n"
|
||||
"\t\t\t\tif (matches[x] == undefined) {\n"
|
||||
"\t\t\t\t\tmatches[x] = w;\n"
|
||||
"\t\t\t\t} else {\n"
|
||||
"\t\t\t\t\tif (w > matches[x]) {\n"
|
||||
"\t\t\t\t\t\t// overwrite with parent\n"
|
||||
"\t\t\t\t\t\tmatches[x] = w;\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\tsearching = 1;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (!searching)\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\n"
|
||||
"\t\tsearchbtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t\tsearchbtn.firstChild.nodeValue = \"Reset Search\"\n"
|
||||
"\n"
|
||||
"\t\t// calculate percent matched, excluding vertical overlap\n"
|
||||
"\t\tvar count = 0;\n"
|
||||
"\t\tvar lastx = -1;\n"
|
||||
"\t\tvar lastw = 0;\n"
|
||||
"\t\tvar keys = Array();\n"
|
||||
"\t\tfor (k in matches) {\n"
|
||||
"\t\t\tif (matches.hasOwnProperty(k))\n"
|
||||
"\t\t\t\tkeys.push(k);\n"
|
||||
"\t\t}\n"
|
||||
"\t\t// sort the matched frames by their x location\n"
|
||||
"\t\t// ascending, then width descending\n"
|
||||
"\t\tkeys.sort(function(a, b){\n"
|
||||
"\t\t\treturn a - b;\n"
|
||||
"\t\t});\n"
|
||||
"\t\t// Step through frames saving only the biggest bottom-up frames\n"
|
||||
"\t\t// thanks to the sort order. This relies on the tree property\n"
|
||||
"\t\t// where children are always smaller than their parents.\n"
|
||||
"\t\tvar fudge = 0.0001;\t// JavaScript floating point\n"
|
||||
"\t\tfor (var k in keys) {\n"
|
||||
"\t\t\tvar x = parseFloat(keys[k]);\n"
|
||||
"\t\t\tvar w = matches[keys[k]];\n"
|
||||
"\t\t\tif (x >= lastx + lastw - fudge) {\n"
|
||||
"\t\t\t\tcount += w;\n"
|
||||
"\t\t\t\tlastx = x;\n"
|
||||
"\t\t\t\tlastw = w;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\t// display matched percent\n"
|
||||
"\t\tmatchedtxt.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t\tpct = 100 * count / maxwidth;\n"
|
||||
"\t\tif (pct == 100)\n"
|
||||
"\t\t\tpct = \"100\"\n"
|
||||
"\t\telse\n"
|
||||
"\t\t\tpct = pct.toFixed(1)\n"
|
||||
"\t\tmatchedtxt.firstChild.nodeValue = \"Matched: \" + pct + \"%%\";\n"
|
||||
"\t}\n"
|
||||
"\tfunction searchover(e) {\n"
|
||||
"\t\tsearchbtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t}\n"
|
||||
"\tfunction searchout(e) {\n"
|
||||
"\t\tif (searching) {\n"
|
||||
"\t\t\tsearchbtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t\t} else {\n"
|
||||
"\t\t\tsearchbtn.style[\"opacity\"] = \"0.1\";\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"]]>\n"
|
||||
"</script>\n"
|
||||
"<rect x=\"0\" y=\"0\" width=\"100%%\" height=\"100%%\" fill=\"rgb(240,240,220)\"/>\n"
|
||||
"<text x=\"%d\" y=\"%d\" text-anchor=\"middle\" style=\"font-size:17px\">%s</text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"details\"> </text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"unzoom\" onclick=\"unzoom()\" style=\"opacity:0.0;cursor:pointer\">Reset Zoom</text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"search\" onmouseover=\"searchover()\" onmouseout=\"searchout()\" onclick=\"search_prompt()\" style=\"opacity:0.1;cursor:pointer\">Search</text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"matched\"> </text>\n";
|
||||
// Browsers refuse to draw on canvas larger than 32767 px
|
||||
const int MAX_CANVAS_HEIGHT = 32767;
|
||||
|
||||
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"
|
||||
".green {\n"
|
||||
" color: #32c832;\n"
|
||||
"}\n"
|
||||
".aqua {\n"
|
||||
" color: #32a5a5;\n"
|
||||
"}\n"
|
||||
".brown {\n"
|
||||
" color: #be5a00;\n"
|
||||
"}\n"
|
||||
".yellow {\n"
|
||||
" color: #afaf32;\n"
|
||||
"}\n"
|
||||
".red {\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, "flame.html")
|
||||
INCBIN(TREE_TEMPLATE, "tree.html")
|
||||
|
||||
|
||||
class StringUtils {
|
||||
@@ -532,27 +37,11 @@ class StringUtils {
|
||||
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
|
||||
}
|
||||
|
||||
static std::string trim(const std::string& s, size_t maxchars) {
|
||||
if (maxchars < 3) {
|
||||
return "";
|
||||
} else if (s.length() > maxchars) {
|
||||
return s.substr(0, maxchars - 2) + "..";
|
||||
} else {
|
||||
return s;
|
||||
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 void escape(std::string& s) {
|
||||
replace(s, '&', "&");
|
||||
replace(s, '<', "<");
|
||||
replace(s, '>', ">");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -583,183 +72,165 @@ class Format {
|
||||
};
|
||||
|
||||
|
||||
class Palette {
|
||||
private:
|
||||
const char* _name;
|
||||
int _base;
|
||||
int _r, _g, _b;
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Palette(const char* name, int base, int r, int g, int b) : _name(name), _base(base), _r(r), _g(g), _b(b) {
|
||||
std::string _name;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(const std::string& name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
}
|
||||
|
||||
const char* name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
int pickColor() const {
|
||||
double value = double(rand()) / RAND_MAX;
|
||||
return _base + (int(_r * value) << 16 | int(_g * value) << 8 | int(_b * value));
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
_scale = (_imagewidth - 20) / (double)_root._total;
|
||||
_pct = 100 / (double)_root._total;
|
||||
|
||||
u64 cutoff = (u64)ceil(_minwidth / _scale);
|
||||
_imageheight = _frameheight * _root.depth(cutoff) + 70;
|
||||
_mintotal = _minwidth == 0 && tree ? _root._total / 1000 : (u64)(_root._total * _minwidth / 100);
|
||||
int depth = _root.depth(_mintotal);
|
||||
|
||||
if (tree) {
|
||||
printTreeHeader(out);
|
||||
const char* tail = TREE_TEMPLATE;
|
||||
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out << (_reverse ? "Backtrace" : "Call tree");
|
||||
|
||||
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:*/");
|
||||
|
||||
printTreeFrame(out, _root, 0);
|
||||
printTreeFooter(out);
|
||||
|
||||
out << tail;
|
||||
} else {
|
||||
printHeader(out);
|
||||
printFrame(out, "all", _root, 10, _reverse ? 35 : (_imageheight - _frameheight - 35));
|
||||
printFooter(out);
|
||||
const char* tail = FLAMEGRAPH_TEMPLATE;
|
||||
|
||||
tail = printTill(out, tail, "/*height:*/300");
|
||||
out << std::min(depth * 16, MAX_CANVAS_HEIGHT);
|
||||
|
||||
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, "/*frames:*/");
|
||||
|
||||
printFrame(out, "all", _root, 0, 0);
|
||||
|
||||
tail = printTill(out, tail, "/*highlight:*/");
|
||||
|
||||
out << tail;
|
||||
}
|
||||
}
|
||||
|
||||
void FlameGraph::printHeader(std::ostream& out) {
|
||||
char buf[sizeof(SVG_HEADER) + 256];
|
||||
int x0 = _imagewidth / 2;
|
||||
int x1 = 10;
|
||||
int x2 = _imagewidth - 110;
|
||||
int y0 = 24;
|
||||
int y1 = _imageheight - 17;
|
||||
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, f);
|
||||
StringUtils::replace(name_copy, '\'', "\\'", 2);
|
||||
|
||||
sprintf(buf, SVG_HEADER,
|
||||
_imagewidth, _imageheight, _imagewidth, _imageheight, _reverse,
|
||||
x0, y0, _title, x1, y1, x1, y0, x2, y0, x2, y1);
|
||||
out << buf;
|
||||
}
|
||||
if (f._inlined | f._c1_compiled | f._interpreted) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s',%llu,%llu,%llu)\n",
|
||||
level, x, f._total, type, name_copy.c_str(), f._inlined, f._c1_compiled, f._interpreted);
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n",
|
||||
level, x, f._total, type, name_copy.c_str());
|
||||
}
|
||||
out << _buf;
|
||||
|
||||
void FlameGraph::printFooter(std::ostream& out) {
|
||||
out << "</svg>\n";
|
||||
}
|
||||
|
||||
double FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y) {
|
||||
double framewidth = f._total * _scale;
|
||||
|
||||
// Skip too narrow frames, they are not important
|
||||
if (framewidth >= _minwidth) {
|
||||
std::string full_title = name;
|
||||
int color = selectFramePalette(full_title).pickColor();
|
||||
std::string short_title = StringUtils::trim(full_title, size_t(framewidth / 7));
|
||||
StringUtils::escape(full_title);
|
||||
StringUtils::escape(short_title);
|
||||
|
||||
// Compensate rounding error in frame width
|
||||
double w = (round((x + framewidth) * 10) - round(x * 10)) / 10.0;
|
||||
|
||||
snprintf(_buf, sizeof(_buf),
|
||||
"<g class=\"func_g\" onmouseover=\"s(this)\" onmouseout=\"c()\" onclick=\"zoom(this)\">\n"
|
||||
"<title>%s (%s samples, %.2f%%)</title><rect x=\"%.1f\" y=\"%.1f\" width=\"%.1f\" height=\"%d\" fill=\"#%06x\" rx=\"2\" ry=\"2\"/>\n"
|
||||
"<text x=\"%.1f\" y=\"%.1f\">%s</text>\n"
|
||||
"</g>\n",
|
||||
full_title.c_str(), Format().thousands(f._total), f._total * _pct, x, y, w, _frameheight - 1, color,
|
||||
x + 3, y + 3 + _frameheight * 0.5, short_title.c_str());
|
||||
out << _buf;
|
||||
|
||||
x += f._self * _scale;
|
||||
y += _reverse ? _frameheight : -_frameheight;
|
||||
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
x += printFrame(out, it->first, it->second, x, y);
|
||||
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);
|
||||
}
|
||||
x += it->second._total;
|
||||
}
|
||||
|
||||
return framewidth;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeHeader(std::ostream& out) {
|
||||
char buf[sizeof(TREE_HEADER) + 256];
|
||||
const char* title = _reverse ? "Backtrace" : "Call tree";
|
||||
const char* counter = _counter == COUNTER_SAMPLES ? "samples" : "counter";
|
||||
sprintf(buf, TREE_HEADER, title, counter, Format().thousands(_root._total));
|
||||
out << buf;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeFooter(std::ostream& out) {
|
||||
out << TREE_FOOTER;
|
||||
}
|
||||
|
||||
bool FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int depth) {
|
||||
double framewidth = f._total * _scale;
|
||||
if (framewidth < _minwidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
std::sort(subnodes.begin(), subnodes.end());
|
||||
|
||||
double pct = 100.0 / _root._total;
|
||||
for (size_t i = 0; i < subnodes.size(); i++) {
|
||||
std::string full_title = subnodes[i]._name;
|
||||
std::string name = subnodes[i]._name;
|
||||
const Trie* trie = subnodes[i]._trie;
|
||||
const char* color = selectFramePalette(full_title).name();
|
||||
StringUtils::escape(full_title);
|
||||
|
||||
int type = frameType(name, f);
|
||||
StringUtils::replace(name, '&', "&", 5);
|
||||
StringUtils::replace(name, '<', "<", 4);
|
||||
StringUtils::replace(name, '>', ">", 4);
|
||||
|
||||
if (_reverse) {
|
||||
snprintf(_buf, sizeof(_buf),
|
||||
"<li><div>[%d] %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
|
||||
depth,
|
||||
trie->_total * _pct, Format().thousands(trie->_total),
|
||||
color, full_title.c_str());
|
||||
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),
|
||||
type, name.c_str());
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf),
|
||||
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
|
||||
depth,
|
||||
trie->_total * _pct, Format().thousands(trie->_total),
|
||||
trie->_self * _pct, Format().thousands(trie->_self),
|
||||
color, full_title.c_str());
|
||||
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),
|
||||
trie->_self * pct, Format().thousands(trie->_self),
|
||||
type, name.c_str());
|
||||
}
|
||||
out << _buf;
|
||||
|
||||
if (trie->_children.size() > 0) {
|
||||
out << "<ul>\n";
|
||||
if (!printTreeFrame(out, *trie, depth + 1)) {
|
||||
if (trie->_total >= _mintotal) {
|
||||
printTreeFrame(out, *trie, level + 1);
|
||||
} else {
|
||||
out << "<li>...\n";
|
||||
}
|
||||
out << "</ul>\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Palette& FlameGraph::selectFramePalette(std::string& name) {
|
||||
static const Palette
|
||||
green ("green", 0x32c832, 60, 55, 60),
|
||||
aqua ("aqua", 0x32a5a5, 60, 55, 55),
|
||||
brown ("brown", 0xbe5a00, 65, 65, 0),
|
||||
yellow("yellow", 0xafaf32, 55, 55, 20),
|
||||
red ("red", 0xc83232, 55, 80, 80);
|
||||
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);
|
||||
}
|
||||
|
||||
// TODO: Reuse frame type embedded in ASGCT_CallFrame
|
||||
int FlameGraph::frameType(std::string& name, const Trie& f) {
|
||||
if (f._inlined * 3 >= f._total) {
|
||||
return FRAME_INLINED;
|
||||
} else if (f._c1_compiled * 2 >= f._total) {
|
||||
return FRAME_C1_COMPILED;
|
||||
} else if (f._interpreted * 2 >= f._total) {
|
||||
return FRAME_INTERPRETED;
|
||||
}
|
||||
|
||||
if (StringUtils::endsWith(name, "_[j]", 4)) {
|
||||
// Java compiled frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return green;
|
||||
return FRAME_JIT_COMPILED;
|
||||
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
|
||||
// Java inlined frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return aqua;
|
||||
return FRAME_INLINED;
|
||||
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
|
||||
// Kernel function
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return brown;
|
||||
return FRAME_KERNEL;
|
||||
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
|
||||
// C++ function or Objective C method
|
||||
return yellow;
|
||||
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
// Java regular method
|
||||
return green;
|
||||
return FRAME_CPP;
|
||||
} else if (((int)name.find('/') > 0 && name[0] != '[')
|
||||
|| ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
return FRAME_JIT_COMPILED;
|
||||
} else {
|
||||
// Other native code
|
||||
return red;
|
||||
return FRAME_NATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <iostream>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
class Trie {
|
||||
@@ -29,10 +30,11 @@ class Trie {
|
||||
std::map<std::string, Trie> _children;
|
||||
u64 _total;
|
||||
u64 _self;
|
||||
u64 _inlined, _c1_compiled, _interpreted;
|
||||
|
||||
Trie() : _children(), _total(0), _self(0) {
|
||||
Trie() : _children(), _total(0), _self(0), _inlined(0), _c1_compiled(0), _interpreted(0) {
|
||||
}
|
||||
|
||||
|
||||
Trie* addChild(const std::string& key, u64 value) {
|
||||
_total += value;
|
||||
return &_children[key];
|
||||
@@ -43,6 +45,15 @@ class Trie {
|
||||
_self += value;
|
||||
}
|
||||
|
||||
void addCompilationDetails(int bci, u64 counter) {
|
||||
switch (FrameType::decode(bci)) {
|
||||
case FRAME_INLINED: _inlined += counter; break;
|
||||
case FRAME_C1_COMPILED: _c1_compiled += counter; break;
|
||||
case FRAME_INTERPRETED: _interpreted += counter; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
int depth(u64 cutoff) const {
|
||||
if (_total < cutoff) {
|
||||
return 0;
|
||||
@@ -57,55 +68,31 @@ class Trie {
|
||||
}
|
||||
};
|
||||
|
||||
class Node {
|
||||
public:
|
||||
std::string _name;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(std::string name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
}
|
||||
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Palette;
|
||||
|
||||
|
||||
class FlameGraph {
|
||||
private:
|
||||
Trie _root;
|
||||
char _buf[4096];
|
||||
u64 _mintotal;
|
||||
|
||||
const char* _title;
|
||||
Counter _counter;
|
||||
int _imagewidth;
|
||||
int _imageheight;
|
||||
int _frameheight;
|
||||
double _minwidth;
|
||||
double _scale;
|
||||
double _pct;
|
||||
bool _reverse;
|
||||
|
||||
void printHeader(std::ostream& out);
|
||||
void printFooter(std::ostream& out);
|
||||
double printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y);
|
||||
void printTreeHeader(std::ostream& out);
|
||||
void printTreeFooter(std::ostream& out);
|
||||
bool printTreeFrame(std::ostream& out, const Trie& f, int depth);
|
||||
const Palette& selectFramePalette(std::string& name);
|
||||
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);
|
||||
const char* printTill(std::ostream& out, const char* data, const char* till);
|
||||
int frameType(std::string& name, const Trie& f);
|
||||
|
||||
public:
|
||||
FlameGraph(const char* title, Counter counter, int width, int height, double minwidth, bool reverse) :
|
||||
FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) :
|
||||
_root(),
|
||||
_title(title),
|
||||
_counter(counter),
|
||||
_imagewidth(width),
|
||||
_frameheight(height),
|
||||
_minwidth(minwidth),
|
||||
_reverse(reverse) {
|
||||
_buf[sizeof(_buf) - 1] = 0;
|
||||
}
|
||||
|
||||
Trie* root() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,8 +17,10 @@
|
||||
#ifndef _FLIGHTRECORDER_H
|
||||
#define _FLIGHTRECORDER_H
|
||||
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
|
||||
#include "event.h"
|
||||
#include "log.h"
|
||||
|
||||
class Recording;
|
||||
|
||||
@@ -26,14 +28,27 @@ class FlightRecorder {
|
||||
private:
|
||||
Recording* _rec;
|
||||
|
||||
Error startMasterRecording(Arguments& args, const char* filename);
|
||||
void stopMasterRecording();
|
||||
|
||||
public:
|
||||
FlightRecorder() : _rec(NULL) {
|
||||
}
|
||||
|
||||
Error start(const char* file);
|
||||
Error start(Arguments& args, bool reset);
|
||||
void stop();
|
||||
void flush();
|
||||
size_t usedMemory();
|
||||
bool timerTick(u64 wall_time);
|
||||
|
||||
void recordExecutionSample(int lock_index, int tid, int call_trace_id);
|
||||
bool active() const {
|
||||
return _rec != NULL;
|
||||
}
|
||||
|
||||
void recordEvent(int lock_index, int tid, u32 call_trace_id,
|
||||
int event_type, Event* event);
|
||||
|
||||
void recordLog(LogLevel level, const char* message, size_t len);
|
||||
};
|
||||
|
||||
#endif // _FLIGHTRECORDER_H
|
||||
|
||||
@@ -14,75 +14,189 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "demangle.h"
|
||||
#include "frameName.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
FrameName::FrameName(bool simple, bool annotate, bool dotted, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_cache(),
|
||||
_simple(simple),
|
||||
_annotate(annotate),
|
||||
_dotted(dotted),
|
||||
static inline bool isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
|
||||
Matcher::Matcher(const char* pattern) {
|
||||
if (pattern[0] == '*') {
|
||||
_type = MATCH_ENDS_WITH;
|
||||
_pattern = strdup(pattern + 1);
|
||||
} else {
|
||||
_type = MATCH_EQUALS;
|
||||
_pattern = strdup(pattern);
|
||||
}
|
||||
|
||||
_len = strlen(_pattern);
|
||||
if (_len > 0 && _pattern[_len - 1] == '*') {
|
||||
_type = _type == MATCH_EQUALS ? MATCH_STARTS_WITH : MATCH_CONTAINS;
|
||||
_pattern[--_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Matcher::~Matcher() {
|
||||
free(_pattern);
|
||||
}
|
||||
|
||||
Matcher::Matcher(const Matcher& m) {
|
||||
_type = m._type;
|
||||
_pattern = strdup(m._pattern);
|
||||
_len = m._len;
|
||||
}
|
||||
|
||||
Matcher& Matcher::operator=(const Matcher& m) {
|
||||
free(_pattern);
|
||||
|
||||
_type = m._type;
|
||||
_pattern = strdup(m._pattern);
|
||||
_len = m._len;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Matcher::matches(const char* s) {
|
||||
switch (_type) {
|
||||
case MATCH_EQUALS:
|
||||
return strcmp(s, _pattern) == 0;
|
||||
case MATCH_CONTAINS:
|
||||
return strstr(s, _pattern) != NULL;
|
||||
case MATCH_STARTS_WITH:
|
||||
return strncmp(s, _pattern, _len) == 0;
|
||||
case MATCH_ENDS_WITH:
|
||||
int slen = strlen(s);
|
||||
return slen >= _len && strcmp(s + slen - _len, _pattern) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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 (demangled != NULL) {
|
||||
strncpy(_buf, demangled, sizeof(_buf) - 1);
|
||||
free(demangled);
|
||||
return _buf;
|
||||
}
|
||||
void FrameName::buildFilter(std::vector<Matcher>& vector, const char* base, int offset) {
|
||||
while (offset != 0) {
|
||||
vector.push_back(base + offset);
|
||||
offset = ((int*)(base + offset))[-1];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
char* FrameName::javaMethodName(jmethodID method) {
|
||||
const char* FrameName::decodeNativeSymbol(const char* name) {
|
||||
const char* lib_name = (_style & STYLE_LIB_NAMES) ? Profiler::instance()->getLibraryName(name) : NULL;
|
||||
|
||||
if (name[0] == '_' && name[1] == 'Z') {
|
||||
char* demangled = Demangle::demangle(name);
|
||||
if (demangled != NULL) {
|
||||
if (lib_name != NULL) {
|
||||
_str.assign(lib_name).append("`").append(demangled);
|
||||
} else {
|
||||
_str.assign(demangled);
|
||||
}
|
||||
free(demangled);
|
||||
return _str.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
if (lib_name != NULL) {
|
||||
return _str.assign(lib_name).append("`").append(name).c_str();
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
jclass method_class;
|
||||
char* class_name = NULL;
|
||||
char* method_name = NULL;
|
||||
char* result;
|
||||
char* method_sig = NULL;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmtiError err;
|
||||
|
||||
if ((err = jvmti->GetMethodName(method, &method_name, NULL, NULL)) == 0 &&
|
||||
if ((err = jvmti->GetMethodName(method, &method_name, &method_sig, NULL)) == 0 &&
|
||||
(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, _simple, _dotted);
|
||||
strcat(result, ".");
|
||||
strcat(result, method_name);
|
||||
if (_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), "[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, bool simple, bool dotted) {
|
||||
char* result = _buf;
|
||||
|
||||
void FrameName::javaClassName(const char* symbol, size_t length, int style) {
|
||||
int array_dimension = 0;
|
||||
while (*symbol == '[') {
|
||||
array_dimension++;
|
||||
@@ -90,86 +204,120 @@ char* FrameName::javaClassName(const char* symbol, int length, bool simple, bool
|
||||
}
|
||||
|
||||
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 (simple) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') result = s + 1;
|
||||
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 (dotted) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') *s = '.';
|
||||
if (style & STYLE_DOTTED) {
|
||||
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) {
|
||||
const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
if (frame.method_id == NULL) {
|
||||
return "[unknown]";
|
||||
}
|
||||
|
||||
switch (frame.bci) {
|
||||
case BCI_NATIVE_FRAME:
|
||||
return cppDemangle((const char*)frame.method_id);
|
||||
return decodeNativeSymbol((const char*)frame.method_id);
|
||||
|
||||
case BCI_SYMBOL: {
|
||||
VMSymbol* symbol = (VMSymbol*)frame.method_id;
|
||||
char* class_name = javaClassName(symbol->body(), symbol->length(), _simple, true);
|
||||
return strcat(class_name, _dotted ? "" : "_[i]");
|
||||
}
|
||||
|
||||
case BCI_SYMBOL_OUTSIDE_TLAB: {
|
||||
VMSymbol* symbol = (VMSymbol*)((uintptr_t)frame.method_id ^ 1);
|
||||
char* class_name = javaClassName(symbol->body(), symbol->length(), _simple, true);
|
||||
return strcat(class_name, _dotted ? " (out)" : "_[k]");
|
||||
case BCI_ALLOC:
|
||||
case BCI_ALLOC_OUTSIDE_TLAB:
|
||||
case BCI_LOCK:
|
||||
case BCI_PARK: {
|
||||
const char* symbol = _class_names[(uintptr_t)frame.method_id];
|
||||
javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
|
||||
if (!for_matching && !(_style & STYLE_DOTTED)) {
|
||||
_str += frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]";
|
||||
}
|
||||
return _str.c_str();
|
||||
}
|
||||
|
||||
case BCI_THREAD_ID: {
|
||||
int tid = (int)(uintptr_t)frame.method_id;
|
||||
MutexLocker ml(_thread_names_lock);
|
||||
ThreadMap::iterator it = _thread_names.find(tid);
|
||||
if (it != _thread_names.end()) {
|
||||
snprintf(_buf, sizeof(_buf), "[%s tid=%d]", it->second.c_str(), tid);
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf), "[tid=%d]", tid);
|
||||
if (for_matching) {
|
||||
return it != _thread_names.end() ? it->second.c_str() : "";
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameName::include(const char* frame_name) {
|
||||
for (int i = 0; i < _include.size(); i++) {
|
||||
if (_include[i].matches(frame_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FrameName::exclude(const char* frame_name) {
|
||||
for (int i = 0; i < _exclude.size(); i++) {
|
||||
if (_exclude[i].matches(frame_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
#include <jvmti.h>
|
||||
#include <locale.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "arguments.h"
|
||||
#include "mutex.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
@@ -31,27 +33,66 @@
|
||||
|
||||
typedef std::map<jmethodID, std::string> JMethodCache;
|
||||
typedef std::map<int, std::string> ThreadMap;
|
||||
typedef std::map<unsigned int, const char*> ClassMap;
|
||||
|
||||
|
||||
enum MatchType {
|
||||
MATCH_EQUALS,
|
||||
MATCH_CONTAINS,
|
||||
MATCH_STARTS_WITH,
|
||||
MATCH_ENDS_WITH
|
||||
};
|
||||
|
||||
|
||||
class Matcher {
|
||||
private:
|
||||
MatchType _type;
|
||||
char* _pattern;
|
||||
int _len;
|
||||
|
||||
public:
|
||||
Matcher(const char* pattern);
|
||||
~Matcher();
|
||||
|
||||
Matcher(const Matcher& m);
|
||||
Matcher& operator=(const Matcher& m);
|
||||
|
||||
bool matches(const char* s);
|
||||
};
|
||||
|
||||
|
||||
class FrameName {
|
||||
private:
|
||||
JMethodCache _cache;
|
||||
char _buf[520];
|
||||
bool _simple;
|
||||
bool _annotate;
|
||||
bool _dotted;
|
||||
static JMethodCache _cache;
|
||||
|
||||
ClassMap _class_names;
|
||||
std::vector<Matcher> _include;
|
||||
std::vector<Matcher> _exclude;
|
||||
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;
|
||||
|
||||
const char* cppDemangle(const char* name);
|
||||
char* javaMethodName(jmethodID method);
|
||||
char* javaClassName(const char* symbol, int length, bool simple, bool dotted);
|
||||
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
|
||||
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(bool simple, bool annotate, bool dotted, 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);
|
||||
const char* name(ASGCT_CallFrame& frame, bool for_matching = false);
|
||||
|
||||
bool hasIncludeList() { return !_include.empty(); }
|
||||
bool hasExcludeList() { return !_exclude.empty(); }
|
||||
|
||||
bool include(const char* frame_name);
|
||||
bool exclude(const char* frame_name);
|
||||
};
|
||||
|
||||
#endif // _FRAMENAME_H
|
||||
|
||||
BIN
src/helper/one/profiler/Instrument.class
Normal file
BIN
src/helper/one/profiler/Instrument.class
Normal file
Binary file not shown.
28
src/helper/one/profiler/Instrument.java
Normal file
28
src/helper/one/profiler/Instrument.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021 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.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.
117
src/helper/one/profiler/JfrSync.java
Normal file
117
src/helper/one/profiler/JfrSync.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2021 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.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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
Configuration config;
|
||||
try {
|
||||
config = Configuration.getConfiguration(settings);
|
||||
} catch (NoSuchFileException e) {
|
||||
config = Configuration.create(Paths.get(settings));
|
||||
}
|
||||
|
||||
Recording recording = new Recording(config);
|
||||
masterRecording = recording;
|
||||
|
||||
disableBuiltinEvents(recording, eventMask);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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.
111
src/helper/one/profiler/Server.java
Normal file
111
src/helper/one/profiler/Server.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2022 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.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 {
|
||||
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 "version=full";
|
||||
}
|
||||
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;
|
||||
}
|
||||
43
src/incbin.h
Normal file
43
src/incbin.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
#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
|
||||
620
src/instrument.cpp
Normal file
620
src/instrument.cpp
Normal file
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
|
||||
INCBIN(INSTRUMENT_CLASS, "one/profiler/Instrument.class")
|
||||
|
||||
|
||||
enum ConstantTag {
|
||||
CONSTANT_Utf8 = 1,
|
||||
CONSTANT_Integer = 3,
|
||||
CONSTANT_Float = 4,
|
||||
CONSTANT_Long = 5,
|
||||
CONSTANT_Double = 6,
|
||||
CONSTANT_Class = 7,
|
||||
CONSTANT_String = 8,
|
||||
CONSTANT_Fieldref = 9,
|
||||
CONSTANT_Methodref = 10,
|
||||
CONSTANT_InterfaceMethodref = 11,
|
||||
CONSTANT_NameAndType = 12,
|
||||
CONSTANT_MethodHandle = 15,
|
||||
CONSTANT_MethodType = 16,
|
||||
CONSTANT_Dynamic = 17,
|
||||
CONSTANT_InvokeDynamic = 18,
|
||||
CONSTANT_Module = 19,
|
||||
CONSTANT_Package = 20
|
||||
};
|
||||
|
||||
class Constant {
|
||||
private:
|
||||
u8 _tag;
|
||||
u8 _info[2];
|
||||
|
||||
public:
|
||||
u8 tag() {
|
||||
return _tag;
|
||||
}
|
||||
|
||||
int slots() {
|
||||
return _tag == CONSTANT_Long || _tag == CONSTANT_Double ? 2 : 1;
|
||||
}
|
||||
|
||||
u16 info() {
|
||||
return (u16)_info[0] << 8 | (u16)_info[1];
|
||||
}
|
||||
|
||||
int length() {
|
||||
switch (_tag) {
|
||||
case CONSTANT_Utf8:
|
||||
return 2 + info();
|
||||
case CONSTANT_Integer:
|
||||
case CONSTANT_Float:
|
||||
case CONSTANT_Fieldref:
|
||||
case CONSTANT_Methodref:
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
case CONSTANT_NameAndType:
|
||||
case CONSTANT_Dynamic:
|
||||
case CONSTANT_InvokeDynamic:
|
||||
return 4;
|
||||
case CONSTANT_Long:
|
||||
case CONSTANT_Double:
|
||||
return 8;
|
||||
case CONSTANT_Class:
|
||||
case CONSTANT_String:
|
||||
case CONSTANT_MethodType:
|
||||
case CONSTANT_Module:
|
||||
case CONSTANT_Package:
|
||||
return 2;
|
||||
case CONSTANT_MethodHandle:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool equals(const char* value, u16 len) {
|
||||
return _tag == CONSTANT_Utf8 && info() == len && memcmp(_info + 2, value, len) == 0;
|
||||
}
|
||||
|
||||
bool matches(const char* value, u16 len) {
|
||||
if (len > 0 && value[len - 1] == '*') {
|
||||
return _tag == CONSTANT_Utf8 && info() >= len - 1 && memcmp(_info + 2, value, len - 1) == 0;
|
||||
}
|
||||
return equals(value, len);
|
||||
}
|
||||
};
|
||||
|
||||
enum Scope {
|
||||
SCOPE_CLASS,
|
||||
SCOPE_FIELD,
|
||||
SCOPE_METHOD,
|
||||
SCOPE_REWRITE_METHOD,
|
||||
SCOPE_REWRITE_CODE
|
||||
};
|
||||
|
||||
enum PatchConstants {
|
||||
EXTRA_CONSTANTS = 6,
|
||||
EXTRA_BYTECODES = 4,
|
||||
EXTRA_STACKMAPS = 1
|
||||
};
|
||||
|
||||
|
||||
class BytecodeRewriter {
|
||||
private:
|
||||
const u8* _src;
|
||||
const u8* _src_limit;
|
||||
|
||||
u8* _dst;
|
||||
int _dst_len;
|
||||
int _dst_capacity;
|
||||
|
||||
Constant** _cpool;
|
||||
u16 _cpool_len;
|
||||
|
||||
const char* _target_class;
|
||||
u16 _target_class_len;
|
||||
const char* _target_method;
|
||||
u16 _target_method_len;
|
||||
const char* _target_signature;
|
||||
u16 _target_signature_len;
|
||||
|
||||
// Reader
|
||||
|
||||
const u8* get(int bytes) {
|
||||
const u8* result = _src;
|
||||
_src += bytes;
|
||||
return _src <= _src_limit ? result : NULL;
|
||||
}
|
||||
|
||||
u8 get8() {
|
||||
return *get(1);
|
||||
}
|
||||
|
||||
u16 get16() {
|
||||
return ntohs(*(u16*)get(2));
|
||||
}
|
||||
|
||||
u32 get32() {
|
||||
return ntohl(*(u32*)get(4));
|
||||
}
|
||||
|
||||
u64 get64() {
|
||||
return OS::ntoh64(*(u64*)get(8));
|
||||
}
|
||||
|
||||
Constant* getConstant() {
|
||||
Constant* c = (Constant*)get(1);
|
||||
get(c->length());
|
||||
return c;
|
||||
}
|
||||
|
||||
// Writer
|
||||
|
||||
u8* alloc(int bytes) {
|
||||
if (_dst_len + bytes > _dst_capacity) {
|
||||
grow(_dst_len + bytes + 2000);
|
||||
}
|
||||
u8* result = _dst + _dst_len;
|
||||
_dst_len += bytes;
|
||||
return result;
|
||||
}
|
||||
|
||||
void grow(int new_capacity) {
|
||||
u8* new_dst = NULL;
|
||||
VM::jvmti()->Allocate(new_capacity, &new_dst);
|
||||
memcpy(new_dst, _dst, _dst_len);
|
||||
VM::jvmti()->Deallocate(_dst);
|
||||
|
||||
_dst = new_dst;
|
||||
_dst_capacity = new_capacity;
|
||||
}
|
||||
|
||||
void put(const u8* src, int bytes) {
|
||||
memcpy(alloc(bytes), src, bytes);
|
||||
}
|
||||
|
||||
void put8(u8 v) {
|
||||
*alloc(1) = v;
|
||||
}
|
||||
|
||||
void put16(u16 v) {
|
||||
*(u16*)alloc(2) = htons(v);
|
||||
}
|
||||
|
||||
void put32(u32 v) {
|
||||
*(u32*)alloc(4) = htonl(v);
|
||||
}
|
||||
|
||||
void put64(u64 v) {
|
||||
*(u64*)alloc(8) = OS::hton64(v);
|
||||
}
|
||||
|
||||
void putConstant(const char* value) {
|
||||
u16 len = strlen(value);
|
||||
put8(CONSTANT_Utf8);
|
||||
put16(len);
|
||||
put((const u8*)value, len);
|
||||
}
|
||||
|
||||
void putConstant(u8 tag, u16 ref) {
|
||||
put8(tag);
|
||||
put16(ref);
|
||||
}
|
||||
|
||||
void putConstant(u8 tag, u16 ref1, u16 ref2) {
|
||||
put8(tag);
|
||||
put16(ref1);
|
||||
put16(ref2);
|
||||
}
|
||||
|
||||
// BytecodeRewriter
|
||||
|
||||
void rewriteCode();
|
||||
void rewriteBytecodeTable(int data_len);
|
||||
void rewriteStackMapTable();
|
||||
void rewriteVerificationTypeInfo();
|
||||
void rewriteAttributes(Scope scope);
|
||||
void rewriteMembers(Scope scope);
|
||||
bool rewriteClass();
|
||||
|
||||
public:
|
||||
BytecodeRewriter(const u8* class_data, int class_data_len, const char* target_class) :
|
||||
_src(class_data),
|
||||
_src_limit(class_data + class_data_len),
|
||||
_dst(NULL),
|
||||
_dst_len(0),
|
||||
_dst_capacity(class_data_len + 400),
|
||||
_cpool(NULL) {
|
||||
|
||||
_target_class = target_class;
|
||||
_target_class_len = strlen(_target_class);
|
||||
|
||||
_target_method = _target_class + _target_class_len + 1;
|
||||
_target_signature = strchr(_target_method, '(');
|
||||
|
||||
if (_target_signature == NULL) {
|
||||
_target_method_len = strlen(_target_method);
|
||||
} else {
|
||||
_target_method_len = _target_signature - _target_method;
|
||||
_target_signature_len = strlen(_target_signature);
|
||||
}
|
||||
}
|
||||
|
||||
~BytecodeRewriter() {
|
||||
delete[] _cpool;
|
||||
}
|
||||
|
||||
void rewrite(u8** new_class_data, int* new_class_data_len) {
|
||||
if (VM::jvmti()->Allocate(_dst_capacity, &_dst) == 0) {
|
||||
if (rewriteClass()) {
|
||||
*new_class_data = _dst;
|
||||
*new_class_data_len = _dst_len;
|
||||
} else {
|
||||
VM::jvmti()->Deallocate(_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void BytecodeRewriter::rewriteCode() {
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length);
|
||||
|
||||
int code_begin = _dst_len;
|
||||
|
||||
u16 max_stack = get16();
|
||||
put16(max_stack);
|
||||
|
||||
u16 max_locals = get16();
|
||||
put16(max_locals);
|
||||
|
||||
u32 code_length = get32();
|
||||
put32(code_length + EXTRA_BYTECODES);
|
||||
|
||||
// invokestatic "one/profiler/Instrument.recordSample()V"
|
||||
// nop ensures that tableswitch/lookupswitch needs no realignment
|
||||
put8(0xb8);
|
||||
put16(_cpool_len);
|
||||
put8(0);
|
||||
// The rest of the code is unchanged
|
||||
put(get(code_length), code_length);
|
||||
|
||||
u16 exception_table_length = get16();
|
||||
put16(exception_table_length);
|
||||
|
||||
for (int i = 0; i < exception_table_length; i++) {
|
||||
u16 start_pc = get16();
|
||||
u16 end_pc = get16();
|
||||
u16 handler_pc = get16();
|
||||
u16 catch_type = get16();
|
||||
put16(EXTRA_BYTECODES + start_pc);
|
||||
put16(EXTRA_BYTECODES + end_pc);
|
||||
put16(EXTRA_BYTECODES + handler_pc);
|
||||
put16(catch_type);
|
||||
}
|
||||
|
||||
rewriteAttributes(SCOPE_REWRITE_CODE);
|
||||
|
||||
// Patch attribute length
|
||||
*(u32*)(_dst + code_begin - 4) = htonl(_dst_len - code_begin);
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteBytecodeTable(int data_len) {
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length);
|
||||
|
||||
u16 table_length = get16();
|
||||
put16(table_length);
|
||||
|
||||
for (int i = 0; i < table_length; i++) {
|
||||
u16 start_pc = get16();
|
||||
put16(EXTRA_BYTECODES + start_pc);
|
||||
|
||||
put(get(data_len), data_len);
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteStackMapTable() {
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length + EXTRA_STACKMAPS);
|
||||
|
||||
u16 number_of_entries = get16();
|
||||
put16(number_of_entries + EXTRA_STACKMAPS);
|
||||
|
||||
// Prepend same_frame
|
||||
put8(EXTRA_BYTECODES - 1);
|
||||
|
||||
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) {
|
||||
u16 attributes_count = get16();
|
||||
put16(attributes_count);
|
||||
|
||||
for (int i = 0; i < attributes_count; i++) {
|
||||
u16 attribute_name_index = get16();
|
||||
put16(attribute_name_index);
|
||||
|
||||
Constant* attribute_name = _cpool[attribute_name_index];
|
||||
if (scope == SCOPE_REWRITE_METHOD && attribute_name->equals("Code", 4)) {
|
||||
rewriteCode();
|
||||
continue;
|
||||
} else if (scope == SCOPE_REWRITE_CODE) {
|
||||
if (attribute_name->equals("LineNumberTable", 15)) {
|
||||
rewriteBytecodeTable(2);
|
||||
continue;
|
||||
} else if (attribute_name->equals("LocalVariableTable", 18) ||
|
||||
attribute_name->equals("LocalVariableTypeTable", 22)) {
|
||||
rewriteBytecodeTable(8);
|
||||
continue;
|
||||
} else if (attribute_name->equals("StackMapTable", 13)) {
|
||||
rewriteStackMapTable();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length);
|
||||
put(get(attribute_length), attribute_length);
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteMembers(Scope scope) {
|
||||
u16 members_count = get16();
|
||||
put16(members_count);
|
||||
|
||||
for (int i = 0; i < members_count; i++) {
|
||||
u16 access_flags = get16();
|
||||
put16(access_flags);
|
||||
|
||||
u16 name_index = get16();
|
||||
put16(name_index);
|
||||
|
||||
u16 descriptor_index = get16();
|
||||
put16(descriptor_index);
|
||||
|
||||
bool need_rewrite = scope == SCOPE_METHOD
|
||||
&& _cpool[name_index]->matches(_target_method, _target_method_len)
|
||||
&& (_target_signature == NULL || _cpool[descriptor_index]->matches(_target_signature, _target_signature_len));
|
||||
|
||||
rewriteAttributes(need_rewrite ? SCOPE_REWRITE_METHOD : SCOPE_METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
bool BytecodeRewriter::rewriteClass() {
|
||||
u32 magic = get32();
|
||||
put32(magic);
|
||||
|
||||
u32 version = get32();
|
||||
put32(version);
|
||||
|
||||
_cpool_len = get16();
|
||||
put16(_cpool_len + EXTRA_CONSTANTS);
|
||||
|
||||
const u8* cpool_start = _src;
|
||||
|
||||
_cpool = new Constant*[_cpool_len];
|
||||
for (int i = 1; i < _cpool_len; i += _cpool[i]->slots()) {
|
||||
_cpool[i] = getConstant();
|
||||
}
|
||||
|
||||
const u8* cpool_end = _src;
|
||||
put(cpool_start, cpool_end - cpool_start);
|
||||
|
||||
putConstant(CONSTANT_Methodref, _cpool_len + 1, _cpool_len + 2);
|
||||
putConstant(CONSTANT_Class, _cpool_len + 3);
|
||||
putConstant(CONSTANT_NameAndType, _cpool_len + 4, _cpool_len + 5);
|
||||
putConstant("one/profiler/Instrument");
|
||||
putConstant("recordSample");
|
||||
putConstant("()V");
|
||||
|
||||
u16 access_flags = get16();
|
||||
put16(access_flags);
|
||||
|
||||
u16 this_class = get16();
|
||||
put16(this_class);
|
||||
|
||||
u16 class_name_index = _cpool[this_class]->info();
|
||||
if (!_cpool[class_name_index]->equals(_target_class, _target_class_len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u16 super_class = get16();
|
||||
put16(super_class);
|
||||
|
||||
u16 interfaces_count = get16();
|
||||
put16(interfaces_count);
|
||||
put(get(interfaces_count * 2), interfaces_count * 2);
|
||||
|
||||
rewriteMembers(SCOPE_FIELD);
|
||||
rewriteMembers(SCOPE_METHOD);
|
||||
rewriteAttributes(SCOPE_CLASS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
char* Instrument::_target_class = NULL;
|
||||
bool Instrument::_instrument_class_loaded = false;
|
||||
u64 Instrument::_interval;
|
||||
volatile u64 Instrument::_calls;
|
||||
volatile bool Instrument::_running;
|
||||
|
||||
Error Instrument::check(Arguments& args) {
|
||||
if (!_instrument_class_loaded) {
|
||||
JNIEnv* jni = VM::jni();
|
||||
const JNINativeMethod native_method = {(char*)"recordSample", (char*)"()V", (void*)recordSample};
|
||||
|
||||
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->ExceptionDescribe();
|
||||
return Error("Could not load Instrument class");
|
||||
}
|
||||
|
||||
_instrument_class_loaded = true;
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error Instrument::start(Arguments& args) {
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (args._interval < 0) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
|
||||
setupTargetClassAndMethod(args._event);
|
||||
_interval = args._interval ? args._interval : 1;
|
||||
_calls = 0;
|
||||
_running = true;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
|
||||
retransformMatchedClasses(jvmti);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void Instrument::stop() {
|
||||
_running = false;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
retransformMatchedClasses(jvmti); // undo transformation
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
|
||||
}
|
||||
|
||||
void Instrument::setupTargetClassAndMethod(const char* event) {
|
||||
char* new_class = strdup(event);
|
||||
*strrchr(new_class, '.') = 0;
|
||||
|
||||
for (char* s = new_class; *s; s++) {
|
||||
if (*s == '.') *s = '/';
|
||||
}
|
||||
|
||||
char* old_class = _target_class;
|
||||
_target_class = new_class;
|
||||
free(old_class);
|
||||
}
|
||||
|
||||
void Instrument::retransformMatchedClasses(jvmtiEnv* jvmti) {
|
||||
jint class_count;
|
||||
jclass* classes;
|
||||
if (jvmti->GetLoadedClasses(&class_count, &classes) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
jint matched_count = 0;
|
||||
size_t len = strlen(_target_class);
|
||||
for (int i = 0; i < class_count; i++) {
|
||||
char* signature;
|
||||
if (jvmti->GetClassSignature(classes[i], &signature, NULL) == 0) {
|
||||
if (signature[0] == 'L' && strncmp(signature + 1, _target_class, len) == 0 && signature[len + 1] == ';') {
|
||||
classes[matched_count++] = classes[i];
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)signature);
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_count > 0) {
|
||||
jvmti->RetransformClasses(matched_count, classes);
|
||||
VM::jni()->ExceptionClear();
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)classes);
|
||||
}
|
||||
|
||||
void JNICALL Instrument::ClassFileLoadHook(jvmtiEnv* jvmti, JNIEnv* jni,
|
||||
jclass class_being_redefined, jobject loader,
|
||||
const char* name, jobject protection_domain,
|
||||
jint class_data_len, const u8* class_data,
|
||||
jint* new_class_data_len, u8** new_class_data) {
|
||||
// Do not retransform if the profiling has stopped
|
||||
if (!_running) return;
|
||||
|
||||
if (name == NULL || strcmp(name, _target_class) == 0) {
|
||||
BytecodeRewriter rewriter(class_data, class_data_len, _target_class);
|
||||
rewriter.rewrite(new_class_data, new_class_data_len);
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL Instrument::recordSample(JNIEnv* jni, jobject unused) {
|
||||
if (!_enabled) return;
|
||||
|
||||
if (_interval <= 1 || ((atomicInc(_calls) + 1) % _interval) == 0) {
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
|
||||
}
|
||||
}
|
||||
58
src/instrument.h
Normal file
58
src/instrument.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _INSTRUMENT_H
|
||||
#define _INSTRUMENT_H
|
||||
|
||||
#include <jvmti.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class Instrument : public Engine {
|
||||
private:
|
||||
static char* _target_class;
|
||||
static bool _instrument_class_loaded;
|
||||
static u64 _interval;
|
||||
static volatile u64 _calls;
|
||||
static volatile bool _running;
|
||||
|
||||
public:
|
||||
const char* title() {
|
||||
return "Java method profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "calls";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
void setupTargetClassAndMethod(const char* event);
|
||||
|
||||
void retransformMatchedClasses(jvmtiEnv* jvmti);
|
||||
|
||||
static void JNICALL ClassFileLoadHook(jvmtiEnv* jvmti, JNIEnv* jni,
|
||||
jclass class_being_redefined, jobject loader,
|
||||
const char* name, jobject protection_domain,
|
||||
jint class_data_len, const u8* class_data,
|
||||
jint* new_class_data_len, u8** new_class_data);
|
||||
|
||||
static void JNICALL recordSample(JNIEnv* jni, jobject unused);
|
||||
};
|
||||
|
||||
#endif // _INSTRUMENT_H
|
||||
@@ -16,15 +16,46 @@
|
||||
|
||||
#include <sys/time.h>
|
||||
#include "itimer.h"
|
||||
#include "j9StackTraces.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackWalker.h"
|
||||
|
||||
|
||||
long ITimer::_interval;
|
||||
CStack ITimer::_cstack;
|
||||
|
||||
|
||||
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, NULL);
|
||||
if (!_enabled) return;
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
void ITimer::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);
|
||||
}
|
||||
|
||||
Error ITimer::check(Arguments& args) {
|
||||
OS::installSignalHandler(SIGPROF, NULL, SIG_IGN);
|
||||
|
||||
struct itimerval tv_on = {{1, 0}, {1, 0}};
|
||||
if (setitimer(ITIMER_PROF, &tv_on, NULL) != 0) {
|
||||
return Error("ITIMER_PROF is not supported on this system");
|
||||
}
|
||||
|
||||
struct itimerval tv_off = {{0, 0}, {0, 0}};
|
||||
setitimer(ITIMER_PROF, &tv_off, NULL);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error ITimer::start(Arguments& args) {
|
||||
@@ -32,13 +63,26 @@ Error ITimer::start(Arguments& args) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
|
||||
_cstack = args._cstack;
|
||||
|
||||
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}};
|
||||
setitimer(ITIMER_PROF, &tv, NULL);
|
||||
|
||||
if (setitimer(ITIMER_PROF, &tv, NULL) != 0) {
|
||||
return Error("ITIMER_PROF is not supported on this system");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
@@ -46,4 +90,6 @@ Error ITimer::start(Arguments& args) {
|
||||
void ITimer::stop() {
|
||||
struct itimerval tv = {{0, 0}, {0, 0}};
|
||||
setitimer(ITIMER_PROF, &tv, NULL);
|
||||
|
||||
J9StackTraces::stop();
|
||||
}
|
||||
|
||||
@@ -24,18 +24,21 @@
|
||||
class ITimer : public Engine {
|
||||
private:
|
||||
static long _interval;
|
||||
static CStack _cstack;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "itimer";
|
||||
const char* title() {
|
||||
return "CPU profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
71
src/j9Ext.cpp
Normal file
71
src/j9Ext.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
91
src/j9Ext.h
Normal file
91
src/j9Ext.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
#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
|
||||
71
src/j9ObjectSampler.cpp
Normal file
71
src/j9ObjectSampler.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
#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, BCI_ALLOC, 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, BCI_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();
|
||||
}
|
||||
36
src/j9ObjectSampler.h
Normal file
36
src/j9ObjectSampler.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
#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
|
||||
186
src/j9StackTraces.cpp
Normal file
186
src/j9StackTraces.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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, 0, &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);
|
||||
}
|
||||
}
|
||||
60
src/j9StackTraces.h
Normal file
60
src/j9StackTraces.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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
|
||||
86
src/j9WallClock.cpp
Normal file
86
src/j9WallClock.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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, 0, &event, si->frame_count, frames);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)stack_infos);
|
||||
}
|
||||
|
||||
jni->PopLocalFrame(NULL);
|
||||
|
||||
OS::sleep(_interval);
|
||||
}
|
||||
|
||||
free(frames);
|
||||
|
||||
VM::detachThread();
|
||||
}
|
||||
52
src/j9WallClock.h
Normal file
52
src/j9WallClock.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 Andrei Pangin
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,384 +16,35 @@
|
||||
|
||||
#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 temp_path_storage[TMP_PATH] = {0};
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
const char* get_temp_path() {
|
||||
return temp_path_storage;
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
// 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, temp_path_storage, sizeof(temp_path_storage) - 10);
|
||||
strcpy(temp_path_storage + (path_size > 1 ? path_size : 0), "/tmp");
|
||||
|
||||
// Parse /proc/pid/status to find process credentials
|
||||
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 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>
|
||||
|
||||
// macOS has a secure per-user temporary directory
|
||||
const char* get_temp_path() {
|
||||
if (temp_path_storage[0] == 0) {
|
||||
int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, temp_path_storage, sizeof(temp_path_storage));
|
||||
if (path_size == 0 || path_size > sizeof(temp_path_storage)) {
|
||||
strcpy(temp_path_storage, "/tmp");
|
||||
}
|
||||
}
|
||||
|
||||
return temp_path_storage;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
||||
const char* get_temp_path() {
|
||||
return "/tmp";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
extern int is_openj9_process(int pid);
|
||||
extern int jattach_openj9(int pid, int nspid, int argc, char** argv);
|
||||
extern int jattach_hotspot(int pid, int nspid, int argc, char** argv);
|
||||
|
||||
|
||||
// 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", get_temp_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", get_temp_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", get_temp_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) {
|
||||
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");
|
||||
int mnt_changed = enter_ns(pid, "mnt");
|
||||
|
||||
// Make sure our /tmp and target /tmp is the same
|
||||
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)) {
|
||||
@@ -401,33 +52,41 @@ 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);
|
||||
} else {
|
||||
return jattach_hotspot(pid, nspid, argc, argv);
|
||||
}
|
||||
|
||||
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"
|
||||
"Copyright 2021 Andrei Pangin\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);
|
||||
}
|
||||
|
||||
#endif // JATTACH_VERSION
|
||||
|
||||
184
src/jattach/jattach_hotspot.c
Normal file
184
src/jattach/jattach_hotspot.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
|
||||
// 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", nspid, 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) {
|
||||
// Protocol version
|
||||
if (write(fd, "1", 2) <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
const char* arg = i < argc ? argv[i] : "";
|
||||
if (write(fd, arg, strlen(arg) + 1) <= 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd, int argc, char** argv) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
||||
444
src/jattach/jattach_openj9.c
Normal file
444
src/jattach/jattach_openj9.c
Normal file
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/sem.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
#define MAX_NOTIF_FILES 256
|
||||
static int notif_lock[MAX_NOTIF_FILES];
|
||||
|
||||
|
||||
// Translate HotSpot command to OpenJ9 equivalent
|
||||
static void translate_command(char* buf, size_t bufsize, int argc, char** argv) {
|
||||
const char* cmd = argv[0];
|
||||
|
||||
if (strcmp(cmd, "load") == 0 && argc >= 2) {
|
||||
if (argc > 2 && strcmp(argv[2], "true") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_LOADAGENTPATH(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
|
||||
} else {
|
||||
snprintf(buf, bufsize, "ATTACH_LOADAGENT(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
|
||||
}
|
||||
|
||||
} else if (strcmp(cmd, "jcmd") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : "");
|
||||
|
||||
} else if (strcmp(cmd, "threaddump") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "dumpheap") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "inspectheap") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "datadump") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "properties") == 0) {
|
||||
strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES");
|
||||
|
||||
} else if (strcmp(cmd, "agentProperties") == 0) {
|
||||
strcpy(buf, "ATTACH_GETAGENTPROPERTIES");
|
||||
|
||||
} else {
|
||||
snprintf(buf, bufsize, "%s", cmd);
|
||||
}
|
||||
|
||||
buf[bufsize - 1] = 0;
|
||||
}
|
||||
|
||||
// Unescape a string and print it on stdout
|
||||
static void print_unescaped(char* str) {
|
||||
char* p = strchr(str, '\n');
|
||||
if (p != NULL) {
|
||||
*p = 0;
|
||||
}
|
||||
|
||||
while ((p = strchr(str, '\\')) != NULL) {
|
||||
switch (p[1]) {
|
||||
case 0:
|
||||
break;
|
||||
case 'f':
|
||||
*p = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
*p = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
*p = '\r';
|
||||
break;
|
||||
case 't':
|
||||
*p = '\t';
|
||||
break;
|
||||
default:
|
||||
*p = p[1];
|
||||
}
|
||||
fwrite(str, 1, p - str + 1, stdout);
|
||||
str = p + 2;
|
||||
}
|
||||
|
||||
fwrite(str, 1, strlen(str), stdout);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Send command with arguments to socket
|
||||
static int write_command(int fd, const char* cmd) {
|
||||
size_t len = strlen(cmd) + 1;
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
ssize_t bytes = write(fd, cmd + off, len - off);
|
||||
if (bytes <= 0) {
|
||||
return -1;
|
||||
}
|
||||
off += bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd, const char* cmd) {
|
||||
size_t size = 8192;
|
||||
char* buf = malloc(size);
|
||||
|
||||
size_t off = 0;
|
||||
while (buf != NULL) {
|
||||
ssize_t bytes = read(fd, buf + off, size - off);
|
||||
if (bytes == 0) {
|
||||
fprintf(stderr, "Unexpected EOF reading response\n");
|
||||
return 1;
|
||||
} else if (bytes < 0) {
|
||||
perror("Error reading response");
|
||||
return 1;
|
||||
}
|
||||
|
||||
off += bytes;
|
||||
if (buf[off - 1] == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (off >= size) {
|
||||
buf = realloc(buf, size *= 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (buf == NULL) {
|
||||
fprintf(stderr, "Failed to allocate memory for response\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) {
|
||||
if (strncmp(buf, "ATTACH_ACK", 10) != 0) {
|
||||
// AgentOnLoad error code comes right after AgentInitializationException
|
||||
result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1;
|
||||
}
|
||||
} else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0) {
|
||||
char* p = strstr(buf, "openj9_diagnostics.string_result=");
|
||||
if (p != NULL) {
|
||||
// The result of a diagnostic command is encoded in Java Properties format
|
||||
print_unescaped(p + 33);
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
buf[off - 1] = '\n';
|
||||
fwrite(buf, 1, off, stdout);
|
||||
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void detach(int fd) {
|
||||
if (write_command(fd, "ATTACH_DETACHED") != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
ssize_t bytes;
|
||||
do {
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
} while (bytes > 0 && buf[bytes - 1] != 0);
|
||||
}
|
||||
|
||||
static void close_with_errno(int fd) {
|
||||
int saved_errno = errno;
|
||||
close(fd);
|
||||
errno = saved_errno;
|
||||
}
|
||||
|
||||
static int acquire_lock(const char* subdir, const char* filename) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename);
|
||||
|
||||
int lock_fd = open(path, O_WRONLY | O_CREAT, 0666);
|
||||
if (lock_fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (flock(lock_fd, LOCK_EX) < 0) {
|
||||
close_with_errno(lock_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return lock_fd;
|
||||
}
|
||||
|
||||
static void release_lock(int lock_fd) {
|
||||
flock(lock_fd, LOCK_UN);
|
||||
close(lock_fd);
|
||||
}
|
||||
|
||||
static int create_attach_socket(int* port) {
|
||||
// Try IPv6 socket first, then fall back to IPv4
|
||||
int s = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (s != -1) {
|
||||
struct sockaddr_in6 addr = {AF_INET6, 0};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
|
||||
&& getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
|
||||
*port = ntohs(addr.sin6_port);
|
||||
return s;
|
||||
}
|
||||
} else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
|
||||
struct sockaddr_in addr = {AF_INET, 0};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
|
||||
&& getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
|
||||
*port = ntohs(addr.sin_port);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
close_with_errno(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void close_attach_socket(int s, int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
|
||||
unlink(path);
|
||||
|
||||
close(s);
|
||||
}
|
||||
|
||||
static unsigned long long random_key() {
|
||||
unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL;
|
||||
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
ssize_t r = read(fd, &key, sizeof(key));
|
||||
(void)r;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static int write_reply_info(int pid, int port, unsigned long long key) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
|
||||
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port);
|
||||
ssize_t r = write(fd, path, chars);
|
||||
(void)r;
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int notify_semaphore(int value, int notif_count) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
|
||||
|
||||
key_t sem_key = ftok(path, 0xa1);
|
||||
int sem = semget(sem_key, 1, IPC_CREAT | 0666);
|
||||
if (sem < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
|
||||
while (notif_count-- > 0) {
|
||||
semop(sem, &op, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int accept_client(int s, unsigned long long key) {
|
||||
struct timeval tv = {5, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
int client = accept(s, NULL, NULL);
|
||||
if (client < 0) {
|
||||
perror("JVM did not respond");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buf[35];
|
||||
size_t off = 0;
|
||||
while (off < sizeof(buf)) {
|
||||
ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
|
||||
if (bytes <= 0) {
|
||||
fprintf(stderr, "The JVM connection was prematurely closed\n");
|
||||
close(client);
|
||||
return -1;
|
||||
}
|
||||
off += bytes;
|
||||
}
|
||||
|
||||
char expected[35];
|
||||
snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
|
||||
if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
|
||||
fprintf(stderr, "Unexpected JVM response\n");
|
||||
close(client);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reset the timeout, as the command execution may take arbitrary long time
|
||||
struct timeval tv0 = {0, 0};
|
||||
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv0, sizeof(tv0));
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
static int lock_notification_files() {
|
||||
int count = 0;
|
||||
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
|
||||
|
||||
DIR* dir = opendir(path);
|
||||
if (dir != NULL) {
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
|
||||
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
|
||||
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
|
||||
notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void unlock_notification_files(int count) {
|
||||
int i;
|
||||
for (i = 0; i < count; i++) {
|
||||
if (notif_lock[i] >= 0) {
|
||||
release_lock(notif_lock[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int is_openj9_process(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0;
|
||||
}
|
||||
|
||||
int jattach_openj9(int pid, int nspid, int argc, char** argv) {
|
||||
int attach_lock = acquire_lock("", "_attachlock");
|
||||
if (attach_lock < 0) {
|
||||
perror("Could not acquire attach lock");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int notif_count = 0;
|
||||
int port;
|
||||
int s = create_attach_socket(&port);
|
||||
if (s < 0) {
|
||||
perror("Failed to listen to attach socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned long long key = random_key();
|
||||
if (write_reply_info(nspid, port, key) != 0) {
|
||||
perror("Could not write replyInfo");
|
||||
goto error;
|
||||
}
|
||||
|
||||
notif_count = lock_notification_files();
|
||||
if (notify_semaphore(1, notif_count) != 0) {
|
||||
perror("Could not notify semaphore");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int fd = accept_client(s, key);
|
||||
if (fd < 0) {
|
||||
// The error message has been already printed
|
||||
goto error;
|
||||
}
|
||||
|
||||
close_attach_socket(s, nspid);
|
||||
unlock_notification_files(notif_count);
|
||||
notify_semaphore(-1, notif_count);
|
||||
release_lock(attach_lock);
|
||||
|
||||
printf("Connected to remote JVM\n");
|
||||
|
||||
char cmd[8192];
|
||||
translate_command(cmd, sizeof(cmd), argc, argv);
|
||||
|
||||
if (write_command(fd, cmd) != 0) {
|
||||
perror("Error writing to socket");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = read_response(fd, cmd);
|
||||
if (result != 1) {
|
||||
detach(fd);
|
||||
}
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
|
||||
error:
|
||||
if (s >= 0) {
|
||||
close_attach_socket(s, nspid);
|
||||
}
|
||||
if (notif_count > 0) {
|
||||
unlock_notification_files(notif_count);
|
||||
notify_semaphore(-1, notif_count);
|
||||
}
|
||||
release_lock(attach_lock);
|
||||
|
||||
return 1;
|
||||
}
|
||||
243
src/jattach/psutil.c
Normal file
243
src/jattach/psutil.c
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
// Less than MAX_PATH to leave some space for appending
|
||||
char tmp_path[MAX_PATH - 100];
|
||||
|
||||
// Called just once to fill in tmp_path buffer
|
||||
void get_tmp_path(int pid) {
|
||||
// Try user-provided alternative path first
|
||||
const char* jattach_path = getenv("JATTACH_PATH");
|
||||
if (jattach_path != NULL && strlen(jattach_path) < sizeof(tmp_path)) {
|
||||
strcpy(tmp_path, jattach_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_tmp_path_r(pid, tmp_path, sizeof(tmp_path)) != 0) {
|
||||
strcpy(tmp_path, "/tmp");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
// The first line of /proc/pid/sched looks like
|
||||
// java (1234, #threads: 12)
|
||||
// where 1234 is the host PID (before Linux 4.1)
|
||||
static 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.
|
||||
static int alt_lookup_nspid(int pid) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
pid = atoi(entry->d_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
|
||||
if (snprintf(buf, bufsize, "/proc/%d/root/tmp", pid) >= bufsize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if the remote /tmp can be accessed via /proc/[pid]/root
|
||||
struct stat stats;
|
||||
return stat(buf, &stats);
|
||||
}
|
||||
|
||||
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 -1;
|
||||
}
|
||||
|
||||
char* line = NULL;
|
||||
size_t size;
|
||||
int nspid_found = 0;
|
||||
|
||||
while (getline(&line, &size, status_file) != -1) {
|
||||
if (strncmp(line, "Uid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
|
||||
// Get the effective UID, which is the second value in the line
|
||||
*uid = (uid_t)atoi(strtok(NULL, "\t "));
|
||||
} else if (strncmp(line, "Gid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
|
||||
// Get the effective GID, which is the second value in the line
|
||||
*gid = (gid_t)atoi(strtok(NULL, "\t "));
|
||||
} else if (strncmp(line, "NStgid:", 7) == 0) {
|
||||
// PID namespaces can be nested; the last one is the innermost one
|
||||
char* s;
|
||||
for (s = strtok(line + 7, "\t "); s != NULL; s = strtok(NULL, "\t ")) {
|
||||
*nspid = atoi(s);
|
||||
}
|
||||
nspid_found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(status_file);
|
||||
|
||||
if (!nspid_found) {
|
||||
*nspid = alt_lookup_nspid(pid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int enter_ns(int pid, const char* type) {
|
||||
#ifdef __NR_setns
|
||||
char path[64], selfpath[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, type);
|
||||
snprintf(selfpath, sizeof(selfpath), "/proc/self/ns/%s", type);
|
||||
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat(selfpath, &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 -1;
|
||||
}
|
||||
|
||||
// Some ancient Linux distributions do not have setns() function
|
||||
int result = syscall(__NR_setns, newns, 0);
|
||||
close(newns);
|
||||
return result < 0 ? -1 : 1;
|
||||
}
|
||||
}
|
||||
#endif // __NR_setns
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
// macOS has a secure per-user temporary directory
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
|
||||
size_t path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, buf, bufsize);
|
||||
return path_size > 0 && path_size <= sizeof(tmp_path) ? 0 : -1;
|
||||
}
|
||||
|
||||
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 -1;
|
||||
}
|
||||
|
||||
*uid = info.kp_eproc.e_ucred.cr_uid;
|
||||
*gid = info.kp_eproc.e_ucred.cr_gid;
|
||||
*nspid = pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_ns(int pid, const char* type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else // __FreeBSD__
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
// Use default /tmp path on FreeBSD
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 -1;
|
||||
}
|
||||
|
||||
*uid = info.ki_uid;
|
||||
*gid = info.ki_groups[0];
|
||||
*nspid = pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_ns(int pid, const char* type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
46
src/jattach/psutil.h
Normal file
46
src/jattach/psutil.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef _PSUTIL_H
|
||||
#define _PSUTIL_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
#define MAX_PATH 1024
|
||||
extern char tmp_path[];
|
||||
|
||||
// Gets /tmp path of the specified process, as it can be accessed from the host.
|
||||
// The obtained path is stored in the global tmp_path buffer.
|
||||
void get_tmp_path(int pid);
|
||||
|
||||
// The reentrant version of get_tmp_path.
|
||||
// Stores the process-specific temporary path into the provided buffer.
|
||||
// Returns 0 on success, -1 on failure.
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize);
|
||||
|
||||
// Gets the owner uid/gid of the target process, and also its pid inside the container.
|
||||
// Returns 0 on success, -1 on failure.
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid);
|
||||
|
||||
// Tries to enter the namespace of the target process.
|
||||
// type of the namespace can be "mnt", "net", "pid", etc.
|
||||
// Returns 1, if the namespace has been successfully changed,
|
||||
// 0, if the target process is in the same namespace as the host,
|
||||
// -1, if the attempt failed.
|
||||
int enter_ns(int pid, const char* type);
|
||||
|
||||
#endif // _PSUTIL_H
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
/**
|
||||
* Java API for in-process profiling. Serves as a wrapper around
|
||||
* async-profiler native library. This class is a singleton.
|
||||
* The first call to {@link #getInstance()} initiates loading of
|
||||
* libasyncProfiler.so.
|
||||
*/
|
||||
public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
private static AsyncProfiler instance;
|
||||
|
||||
private final String version;
|
||||
|
||||
private AsyncProfiler() {
|
||||
this.version = version0();
|
||||
}
|
||||
|
||||
public static AsyncProfiler getInstance() {
|
||||
return getInstance(null);
|
||||
}
|
||||
|
||||
public static synchronized AsyncProfiler getInstance(String libPath) {
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
if (libPath == null) {
|
||||
System.loadLibrary("asyncProfiler");
|
||||
} else {
|
||||
System.load(libPath);
|
||||
}
|
||||
|
||||
instance = new AsyncProfiler();
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiling
|
||||
*
|
||||
* @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 {
|
||||
start0(event, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop profiling (without dumping results)
|
||||
*
|
||||
* @throws IllegalStateException If profiler is not running
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws IllegalStateException {
|
||||
stop0();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of samples collected during the profiling session
|
||||
*
|
||||
* @return Number of samples
|
||||
*/
|
||||
@Override
|
||||
public native long getSamples();
|
||||
|
||||
/**
|
||||
* Get profiler agent version, e.g. "1.0"
|
||||
*
|
||||
* @return Version string
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an agent-compatible profiling command -
|
||||
* the comma-separated list of arguments described in arguments.cpp
|
||||
*
|
||||
* @param command Profiling command
|
||||
* @return The command result
|
||||
* @throws IllegalArgumentException If failed to parse the command
|
||||
* @throws java.io.IOException If failed to create output file
|
||||
*/
|
||||
@Override
|
||||
public String execute(String command) throws IllegalArgumentException, java.io.IOException {
|
||||
return execute0(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump profile in 'collapsed stacktraces' format
|
||||
*
|
||||
* @param counter Which counter to display in the output
|
||||
* @return Textual representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public String dumpCollapsed(Counter counter) {
|
||||
return dumpCollapsed0(counter.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return dumpTraces0(maxTraces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump flat profile, i.e. the histogram of the hottest methods
|
||||
*
|
||||
* @param maxMethods Maximum number of methods to dump. 0 means no limit
|
||||
* @return Textual representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public String dumpFlat(int maxMethods) {
|
||||
return dumpFlat0(maxMethods);
|
||||
}
|
||||
|
||||
private native void start0(String event, long interval) throws IllegalStateException;
|
||||
private native void stop0() throws IllegalStateException;
|
||||
private native String execute0(String command) throws IllegalArgumentException, java.io.IOException;
|
||||
private native String dumpCollapsed0(int counter);
|
||||
private native String dumpTraces0(int maxTraces);
|
||||
private native String dumpFlat0(int maxMethods);
|
||||
private native String version0();
|
||||
}
|
||||
186
src/javaApi.cpp
186
src/javaApi.cpp
@@ -18,11 +18,17 @@
|
||||
#include <sstream>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "arguments.h"
|
||||
#include "incbin.h"
|
||||
#include "javaApi.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
static void throw_new(JNIEnv* env, const char* exception_class, const char* message) {
|
||||
INCBIN(SERVER_CLASS, "one/profiler/Server.class")
|
||||
|
||||
|
||||
static void throwNew(JNIEnv* env, const char* exception_class, const char* message) {
|
||||
jclass cls = env->FindClass(exception_class);
|
||||
if (cls != NULL) {
|
||||
env->ThrowNew(cls, message);
|
||||
@@ -30,34 +36,37 @@ static void throw_new(JNIEnv* env, const char* exception_class, const char* mess
|
||||
}
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval) {
|
||||
extern "C" DLLEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval, jboolean reset) {
|
||||
Arguments args;
|
||||
args._event = env->GetStringUTFChars(event, NULL);
|
||||
args._interval = interval;
|
||||
Error error = Profiler::_instance.start(args);
|
||||
env->ReleaseStringUTFChars(event, args._event);
|
||||
const char* event_str = env->GetStringUTFChars(event, NULL);
|
||||
if (strcmp(event_str, EVENT_ALLOC) == 0) {
|
||||
args._alloc = interval > 0 ? interval : 0;
|
||||
} else if (strcmp(event_str, EVENT_LOCK) == 0) {
|
||||
args._lock = interval > 0 ? interval : 0;
|
||||
} else {
|
||||
args._event = event_str;
|
||||
args._interval = interval;
|
||||
}
|
||||
|
||||
Error error = Profiler::instance()->start(args, reset);
|
||||
env->ReleaseStringUTFChars(event, event_str);
|
||||
|
||||
if (error) {
|
||||
throw_new(env, "java/lang/IllegalStateException", error.message());
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
extern "C" DLLEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_stop0(JNIEnv* env, jobject unused) {
|
||||
Error error = Profiler::_instance.stop();
|
||||
Error error = Profiler::instance()->stop();
|
||||
|
||||
if (error) {
|
||||
throw_new(env, "java/lang/IllegalStateException", error.message());
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
||||
return (jlong)Profiler::_instance.total_samples();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
extern "C" DLLEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring command) {
|
||||
Arguments args;
|
||||
const char* command_str = env->GetStringUTFChars(command, NULL);
|
||||
@@ -65,54 +74,123 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co
|
||||
env->ReleaseStringUTFChars(command, command_str);
|
||||
|
||||
if (error) {
|
||||
throw_new(env, "java/lang/IllegalArgumentException", error.message());
|
||||
throwNew(env, "java/lang/IllegalArgumentException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (args._file == NULL) {
|
||||
Log::open(args);
|
||||
|
||||
if (!args.hasOutputFile()) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.runInternal(args, out);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
if (!error) {
|
||||
if (out.tellp() >= 0x3fffffff) {
|
||||
throwNew(env, "java/lang/IllegalStateException", "Output exceeds string size limit");
|
||||
return NULL;
|
||||
}
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
}
|
||||
} else {
|
||||
std::ofstream out(args._file, std::ios::out | std::ios::trunc);
|
||||
if (out.is_open()) {
|
||||
Profiler::_instance.runInternal(args, out);
|
||||
out.close();
|
||||
return env->NewStringUTF("OK");
|
||||
} else {
|
||||
throw_new(env, "java/io/IOException", strerror(errno));
|
||||
std::ofstream out(args.file(), std::ios::out | std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
throwNew(env, "java/io/IOException", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
out.close();
|
||||
if (!error) {
|
||||
return env->NewStringUTF("OK");
|
||||
}
|
||||
}
|
||||
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT jlong JNICALL
|
||||
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
||||
return (jlong)Profiler::instance()->total_samples();
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_filterThread0(JNIEnv* env, jobject unused, jthread thread, jboolean enable) {
|
||||
int thread_id;
|
||||
if (thread == NULL) {
|
||||
thread_id = OS::threadId();
|
||||
} else if ((thread_id = VMThread::nativeThreadId(env, thread)) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadFilter* thread_filter = Profiler::instance()->threadFilter();
|
||||
if (enable) {
|
||||
thread_filter->add(thread_id);
|
||||
} else {
|
||||
thread_filter->remove(thread_id);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_dumpCollapsed0(JNIEnv* env, jobject unused, jint counter) {
|
||||
Arguments args;
|
||||
args._counter = counter == COUNTER_SAMPLES ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.dumpCollapsed(out, args);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
#define F(name, sig) {(char*)#name, (char*)sig, (void*)Java_one_profiler_AsyncProfiler_##name}
|
||||
|
||||
static const JNINativeMethod profiler_natives[] = {
|
||||
F(start0, "(Ljava/lang/String;JZ)V"),
|
||||
F(stop0, "()V"),
|
||||
F(execute0, "(Ljava/lang/String;)Ljava/lang/String;"),
|
||||
F(getSamples, "()J"),
|
||||
F(filterThread0, "(Ljava/lang/Thread;Z)V"),
|
||||
};
|
||||
|
||||
static const JNINativeMethod* execute0 = &profiler_natives[2];
|
||||
|
||||
#undef F
|
||||
|
||||
|
||||
// Since AsyncProfiler class can be renamed or moved to another package (shaded),
|
||||
// we look for the actual class in the stack trace.
|
||||
void JavaAPI::registerNatives(jvmtiEnv* jvmti, JNIEnv* jni) {
|
||||
jvmtiFrameInfo frame[10];
|
||||
jint frame_count;
|
||||
if (jvmti->GetStackTrace(NULL, 0, sizeof(frame) / sizeof(frame[0]), frame, &frame_count) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
jclass System = jni->FindClass("java/lang/System");
|
||||
jmethodID load = jni->GetStaticMethodID(System, "load", "(Ljava/lang/String;)V");
|
||||
jmethodID loadLibrary = jni->GetStaticMethodID(System, "loadLibrary", "(Ljava/lang/String;)V");
|
||||
|
||||
// Look for System.load() or System.loadLibrary() method in the stack trace.
|
||||
// The next frame will belong to AsyncProfiler class.
|
||||
for (int i = 0; i < frame_count - 1; i++) {
|
||||
if (frame[i].method == load || frame[i].method == loadLibrary) {
|
||||
jclass profiler_class;
|
||||
if (jvmti->GetMethodDeclaringClass(frame[i + 1].method, &profiler_class) == 0) {
|
||||
for (int j = 0; j < sizeof(profiler_natives) / sizeof(JNINativeMethod); j++) {
|
||||
jni->RegisterNatives(profiler_class, &profiler_natives[j], 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jni->ExceptionClear();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_dumpTraces0(JNIEnv* env, jobject unused, jint max_traces) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.dumpSummary(out);
|
||||
Profiler::_instance.dumpTraces(out, max_traces ? max_traces : MAX_CALLTRACES);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
}
|
||||
bool JavaAPI::startHttpServer(jvmtiEnv* jvmti, JNIEnv* jni, const char* address) {
|
||||
jclass handler = jni->FindClass("com/sun/net/httpserver/HttpHandler");
|
||||
jobject loader;
|
||||
if (handler != NULL && jvmti->GetClassLoader(handler, &loader) == 0) {
|
||||
jclass cls = jni->DefineClass(NULL, loader, (const jbyte*)SERVER_CLASS, INCBIN_SIZEOF(SERVER_CLASS));
|
||||
if (cls != NULL && jni->RegisterNatives(cls, execute0, 1) == 0) {
|
||||
jmethodID method = jni->GetStaticMethodID(cls, "start", "(Ljava/lang/String;)V");
|
||||
if (method != NULL) {
|
||||
jni->CallStaticVoidMethod(cls, method, jni->NewStringUTF(address));
|
||||
if (!jni->ExceptionCheck()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_dumpFlat0(JNIEnv* env, jobject unused, jint max_methods) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.dumpSummary(out);
|
||||
Profiler::_instance.dumpFlat(out, max_methods ? max_methods : MAX_CALLTRACES);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_version0(JNIEnv* env, jobject unused) {
|
||||
return env->NewStringUTF(PROFILER_VERSION);
|
||||
jni->ExceptionDescribe();
|
||||
return false;
|
||||
}
|
||||
|
||||
29
src/javaApi.h
Normal file
29
src/javaApi.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _JAVAAPI_H
|
||||
#define _JAVAAPI_H
|
||||
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
class JavaAPI {
|
||||
public:
|
||||
static void registerNatives(jvmtiEnv* jvmti, JNIEnv* jni);
|
||||
static bool startHttpServer(jvmtiEnv* jvmti, JNIEnv* jni, const char* address);
|
||||
};
|
||||
|
||||
#endif // _JAVAAPI_H
|
||||
241
src/jfrMetadata.cpp
Normal file
241
src/jfrMetadata.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "jfrMetadata.h"
|
||||
|
||||
|
||||
std::map<std::string, int> Element::_string_map;
|
||||
std::vector<std::string> Element::_strings;
|
||||
|
||||
JfrMetadata JfrMetadata::_root;
|
||||
|
||||
JfrMetadata::JfrMetadata() : Element("root") {
|
||||
*this
|
||||
<< (element("metadata")
|
||||
|
||||
<< type("boolean", T_BOOLEAN)
|
||||
<< type("char", T_CHAR)
|
||||
<< type("float", T_FLOAT)
|
||||
<< type("double", T_DOUBLE)
|
||||
<< type("byte", T_BYTE)
|
||||
<< type("short", T_SHORT)
|
||||
<< type("int", T_INT)
|
||||
<< type("long", T_LONG)
|
||||
|
||||
<< type("java.lang.String", T_STRING)
|
||||
|
||||
<< (type("java.lang.Class", T_CLASS, "Java Class")
|
||||
<< field("classLoader", T_CLASS_LOADER, "Class Loader", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL)
|
||||
<< field("package", T_PACKAGE, "Package", F_CPOOL)
|
||||
<< field("modifiers", T_INT, "Access Modifiers"))
|
||||
|
||||
<< (type("java.lang.Thread", T_THREAD, "Thread")
|
||||
<< field("osName", T_STRING, "OS Thread Name")
|
||||
<< field("osThreadId", T_LONG, "OS Thread Id")
|
||||
<< field("javaName", T_STRING, "Java Thread Name")
|
||||
<< field("javaThreadId", T_LONG, "Java Thread Id"))
|
||||
|
||||
<< (type("jdk.types.ClassLoader", T_CLASS_LOADER, "Java Class Loader")
|
||||
<< field("type", T_CLASS, "Type", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.FrameType", T_FRAME_TYPE, "Frame type", true)
|
||||
<< field("description", T_STRING, "Description"))
|
||||
|
||||
<< (type("jdk.types.ThreadState", T_THREAD_STATE, "Java Thread State", true)
|
||||
<< field("name", T_STRING, "Name"))
|
||||
|
||||
<< (type("jdk.types.StackTrace", T_STACK_TRACE, "Stacktrace")
|
||||
<< field("truncated", T_BOOLEAN, "Truncated")
|
||||
<< field("frames", T_STACK_FRAME, "Stack Frames", F_ARRAY))
|
||||
|
||||
<< (type("jdk.types.StackFrame", T_STACK_FRAME)
|
||||
<< field("method", T_METHOD, "Java Method", F_CPOOL)
|
||||
<< field("lineNumber", T_INT, "Line Number")
|
||||
<< field("bytecodeIndex", T_INT, "Bytecode Index")
|
||||
<< field("type", T_FRAME_TYPE, "Frame Type", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.Method", T_METHOD, "Java Method")
|
||||
<< field("type", T_CLASS, "Type", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL)
|
||||
<< field("descriptor", T_SYMBOL, "Descriptor", F_CPOOL)
|
||||
<< field("modifiers", T_INT, "Access Modifiers")
|
||||
<< field("hidden", T_BOOLEAN, "Hidden"))
|
||||
|
||||
<< (type("jdk.types.Package", T_PACKAGE, "Package")
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.Symbol", T_SYMBOL, "Symbol", true)
|
||||
<< field("string", T_STRING, "String"))
|
||||
|
||||
<< (type("profiler.types.LogLevel", T_LOG_LEVEL, "Log Level", true)
|
||||
<< field("name", T_STRING, "Name"))
|
||||
|
||||
<< (type("jdk.ExecutionSample", T_EXECUTION_SAMPLE, "Method Profiling Sample")
|
||||
<< category("Java Virtual Machine", "Profiling")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("sampledThread", T_THREAD, "Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("state", T_THREAD_STATE, "Thread State", F_CPOOL))
|
||||
|
||||
<< (type("jdk.ObjectAllocationInNewTLAB", T_ALLOC_IN_NEW_TLAB, "Allocation in new TLAB")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES)
|
||||
<< field("tlabSize", T_LONG, "TLAB Size", F_BYTES))
|
||||
|
||||
<< (type("jdk.ObjectAllocationOutsideTLAB", T_ALLOC_OUTSIDE_TLAB, "Allocation outside TLAB")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES))
|
||||
|
||||
<< (type("jdk.JavaMonitorEnter", T_MONITOR_ENTER, "Java Monitor Blocked")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("monitorClass", T_CLASS, "Monitor Class", F_CPOOL)
|
||||
<< field("previousOwner", T_THREAD, "Previous Monitor Owner", F_CPOOL)
|
||||
<< field("address", T_LONG, "Monitor Address", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.ThreadPark", T_THREAD_PARK, "Java Thread Park")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("parkedClass", T_CLASS, "Class Parked On", F_CPOOL)
|
||||
<< field("timeout", T_LONG, "Park Timeout", F_DURATION_NANOS)
|
||||
<< field("until", T_LONG, "Park Until", F_TIME_MILLIS)
|
||||
<< field("address", T_LONG, "Address of Object Parked", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.CPULoad", T_CPU_LOAD, "CPU Load")
|
||||
<< category("Operating System", "Processor")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("jvmUser", T_FLOAT, "JVM User", F_PERCENTAGE)
|
||||
<< field("jvmSystem", T_FLOAT, "JVM System", F_PERCENTAGE)
|
||||
<< field("machineTotal", T_FLOAT, "Machine Total", F_PERCENTAGE))
|
||||
|
||||
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Async-profiler Recording")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("id", T_LONG, "Id")
|
||||
<< field("name", T_STRING, "Name")
|
||||
<< field("destination", T_STRING, "Destination")
|
||||
<< field("maxAge", T_LONG, "Max Age", F_DURATION_MILLIS)
|
||||
<< field("maxSize", T_LONG, "Max Size", F_BYTES)
|
||||
<< field("recordingStart", T_LONG, "Start Time", F_TIME_MILLIS)
|
||||
<< field("recordingDuration", T_LONG, "Recording Duration", F_DURATION_MILLIS))
|
||||
|
||||
<< (type("jdk.ActiveSetting", T_ACTIVE_SETTING, "Async-profiler Setting")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("id", T_LONG, "Event Id")
|
||||
<< field("name", T_STRING, "Setting Name")
|
||||
<< field("value", T_STRING, "Setting Value"))
|
||||
|
||||
<< (type("jdk.OSInformation", T_OS_INFORMATION, "OS Information")
|
||||
<< category("Operating System")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("osVersion", T_STRING, "OS Version"))
|
||||
|
||||
<< (type("jdk.CPUInformation", T_CPU_INFORMATION, "CPU Information")
|
||||
<< category("Operating System", "Processor")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("cpu", T_STRING, "Type")
|
||||
<< field("description", T_STRING, "Description")
|
||||
<< field("sockets", T_INT, "Sockets", F_UNSIGNED)
|
||||
<< field("cores", T_INT, "Cores", F_UNSIGNED)
|
||||
<< field("hwThreads", T_INT, "Hardware Threads", F_UNSIGNED))
|
||||
|
||||
<< (type("jdk.JVMInformation", T_JVM_INFORMATION, "JVM Information")
|
||||
<< category("Java Virtual Machine")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("jvmName", T_STRING, "JVM Name")
|
||||
<< field("jvmVersion", T_STRING, "JVM Version")
|
||||
<< field("jvmArguments", T_STRING, "JVM Command Line Arguments")
|
||||
<< field("jvmFlags", T_STRING, "JVM Settings File Arguments")
|
||||
<< field("javaArguments", T_STRING, "Java Application Arguments")
|
||||
<< field("jvmStartTime", T_LONG, "JVM Start Time", F_TIME_MILLIS)
|
||||
<< field("pid", T_LONG, "Process Identifier"))
|
||||
|
||||
<< (type("jdk.InitialSystemProperty", T_INITIAL_SYSTEM_PROPERTY, "Initial System Property")
|
||||
<< category("Java Virtual Machine")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("key", T_STRING, "Key")
|
||||
<< field("value", T_STRING, "Value"))
|
||||
|
||||
<< (type("jdk.NativeLibrary", T_NATIVE_LIBRARY, "Native Library")
|
||||
<< category("Java Virtual Machine", "Runtime")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("name", T_STRING, "Name")
|
||||
<< field("baseAddress", T_LONG, "Base Address", F_ADDRESS)
|
||||
<< field("topAddress", T_LONG, "Top Address", F_ADDRESS))
|
||||
|
||||
<< (type("profiler.Log", T_LOG, "Log Message")
|
||||
<< category("Profiler")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("level", T_LOG_LEVEL, "Level", F_CPOOL)
|
||||
<< field("message", T_STRING, "Message"))
|
||||
|
||||
<< (type("profiler.LiveObject", T_LIVE_OBJECT, "Live Object")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES)
|
||||
<< field("allocationTime", T_LONG, "Allocation Time", F_TIME_TICKS))
|
||||
|
||||
<< (type("jdk.jfr.Label", T_LABEL, NULL)
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.Category", T_CATEGORY, NULL)
|
||||
<< field("value", T_STRING, NULL, F_ARRAY))
|
||||
|
||||
<< (type("jdk.jfr.Timestamp", T_TIMESTAMP, "Timestamp")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.Timespan", T_TIMESPAN, "Timespan")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.DataAmount", T_DATA_AMOUNT, "Data Amount")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< type("jdk.jfr.MemoryAddress", T_MEMORY_ADDRESS, "Memory Address")
|
||||
|
||||
<< type("jdk.jfr.Unsigned", T_UNSIGNED, "Unsigned Value")
|
||||
|
||||
<< type("jdk.jfr.Percentage", T_PERCENTAGE, "Percentage"))
|
||||
|
||||
<< element("region").attribute("locale", "en_US").attribute("gmtOffset", "0");
|
||||
|
||||
// The map is used only during construction
|
||||
_string_map.clear();
|
||||
}
|
||||
235
src/jfrMetadata.h
Normal file
235
src/jfrMetadata.h
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _JFRMETADATA_H
|
||||
#define _JFRMETADATA_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
enum JfrType {
|
||||
T_METADATA = 0,
|
||||
T_CPOOL = 1,
|
||||
|
||||
T_BOOLEAN = 4,
|
||||
T_CHAR = 5,
|
||||
T_FLOAT = 6,
|
||||
T_DOUBLE = 7,
|
||||
T_BYTE = 8,
|
||||
T_SHORT = 9,
|
||||
T_INT = 10,
|
||||
T_LONG = 11,
|
||||
|
||||
T_STRING = 20,
|
||||
T_CLASS = 21,
|
||||
T_THREAD = 22,
|
||||
T_CLASS_LOADER = 23,
|
||||
T_FRAME_TYPE = 24,
|
||||
T_THREAD_STATE = 25,
|
||||
T_STACK_TRACE = 26,
|
||||
T_STACK_FRAME = 27,
|
||||
T_METHOD = 28,
|
||||
T_PACKAGE = 29,
|
||||
T_SYMBOL = 30,
|
||||
T_LOG_LEVEL = 31,
|
||||
|
||||
T_EVENT = 100,
|
||||
T_EXECUTION_SAMPLE = 101,
|
||||
T_ALLOC_IN_NEW_TLAB = 102,
|
||||
T_ALLOC_OUTSIDE_TLAB = 103,
|
||||
T_MONITOR_ENTER = 104,
|
||||
T_THREAD_PARK = 105,
|
||||
T_CPU_LOAD = 106,
|
||||
T_ACTIVE_RECORDING = 107,
|
||||
T_ACTIVE_SETTING = 108,
|
||||
T_OS_INFORMATION = 109,
|
||||
T_CPU_INFORMATION = 110,
|
||||
T_JVM_INFORMATION = 111,
|
||||
T_INITIAL_SYSTEM_PROPERTY = 112,
|
||||
T_NATIVE_LIBRARY = 113,
|
||||
T_LOG = 114,
|
||||
T_LIVE_OBJECT = 115,
|
||||
|
||||
T_ANNOTATION = 200,
|
||||
T_LABEL = 201,
|
||||
T_CATEGORY = 202,
|
||||
T_TIMESTAMP = 203,
|
||||
T_TIMESPAN = 204,
|
||||
T_DATA_AMOUNT = 205,
|
||||
T_MEMORY_ADDRESS = 206,
|
||||
T_UNSIGNED = 207,
|
||||
T_PERCENTAGE = 208,
|
||||
};
|
||||
|
||||
|
||||
class Attribute {
|
||||
public:
|
||||
int _key;
|
||||
int _value;
|
||||
|
||||
Attribute(int key, int value) : _key(key), _value(value) {
|
||||
}
|
||||
};
|
||||
|
||||
class Element {
|
||||
protected:
|
||||
static std::map<std::string, int> _string_map;
|
||||
static std::vector<std::string> _strings;
|
||||
|
||||
static int getId(const char* s) {
|
||||
std::string str(s);
|
||||
int id = _string_map[str];
|
||||
if (id == 0) {
|
||||
id = _string_map[str] = _string_map.size();
|
||||
_strings.push_back(str);
|
||||
}
|
||||
return id - 1;
|
||||
}
|
||||
|
||||
public:
|
||||
const int _name;
|
||||
std::vector<Attribute> _attributes;
|
||||
std::vector<const Element*> _children;
|
||||
|
||||
Element(const char* name) : _name(getId(name)), _attributes(), _children() {
|
||||
}
|
||||
|
||||
Element& attribute(const char* key, const char* value) {
|
||||
_attributes.push_back(Attribute(getId(key), getId(value)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Element& attribute(const char* key, JfrType value) {
|
||||
char value_str[16];
|
||||
sprintf(value_str, "%d", value);
|
||||
return attribute(key, value_str);
|
||||
}
|
||||
|
||||
Element& operator<<(const Element& child) {
|
||||
_children.push_back(&child);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class JfrMetadata : Element {
|
||||
private:
|
||||
static JfrMetadata _root;
|
||||
|
||||
enum FieldFlags {
|
||||
F_CPOOL = 0x1,
|
||||
F_ARRAY = 0x2,
|
||||
F_UNSIGNED = 0x4,
|
||||
F_BYTES = 0x8,
|
||||
F_TIME_TICKS = 0x10,
|
||||
F_TIME_MILLIS = 0x20,
|
||||
F_DURATION_TICKS = 0x40,
|
||||
F_DURATION_NANOS = 0x80,
|
||||
F_DURATION_MILLIS = 0x100,
|
||||
F_ADDRESS = 0x200,
|
||||
F_PERCENTAGE = 0x400,
|
||||
};
|
||||
|
||||
static Element& element(const char* name) {
|
||||
return *new Element(name);
|
||||
}
|
||||
|
||||
static Element& type(const char* name, JfrType id, const char* label = NULL, bool simple = false) {
|
||||
Element& e = element("class");
|
||||
e.attribute("name", name);
|
||||
e.attribute("id", id);
|
||||
if (simple) {
|
||||
e.attribute("simpleType", "true");
|
||||
} else if (id > T_ANNOTATION) {
|
||||
e.attribute("superType", "java.lang.annotation.Annotation");
|
||||
} else if (id > T_EVENT) {
|
||||
e.attribute("superType", "jdk.jfr.Event");
|
||||
}
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& field(const char* name, JfrType type, const char* label = NULL, int flags = 0) {
|
||||
Element& e = element("field");
|
||||
e.attribute("name", name);
|
||||
e.attribute("class", type);
|
||||
if (flags & F_CPOOL) {
|
||||
e.attribute("constantPool", "true");
|
||||
}
|
||||
if (flags & F_ARRAY) {
|
||||
e.attribute("dimension", "1");
|
||||
}
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
if (flags & F_UNSIGNED) {
|
||||
e << annotation(T_UNSIGNED);
|
||||
} else if (flags & F_BYTES) {
|
||||
e << annotation(T_UNSIGNED) << annotation(T_DATA_AMOUNT, "BYTES");
|
||||
} else if (flags & F_TIME_TICKS) {
|
||||
e << annotation(T_TIMESTAMP, "TICKS");
|
||||
} else if (flags & F_TIME_MILLIS) {
|
||||
e << annotation(T_TIMESTAMP, "MILLISECONDS_SINCE_EPOCH");
|
||||
} else if (flags & F_DURATION_TICKS) {
|
||||
e << annotation(T_TIMESPAN, "TICKS");
|
||||
} else if (flags & F_DURATION_NANOS) {
|
||||
e << annotation(T_TIMESPAN, "NANOSECONDS");
|
||||
} else if (flags & F_DURATION_MILLIS) {
|
||||
e << annotation(T_TIMESPAN, "MILLISECONDS");
|
||||
} else if (flags & F_ADDRESS) {
|
||||
e << annotation(T_UNSIGNED) << annotation(T_MEMORY_ADDRESS);
|
||||
} else if (flags & F_PERCENTAGE) {
|
||||
e << annotation(T_PERCENTAGE);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& annotation(JfrType type, const char* value = NULL) {
|
||||
Element& e = element("annotation");
|
||||
e.attribute("class", type);
|
||||
if (value != NULL) {
|
||||
e.attribute("value", value);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& category(const char* value0, const char* value1 = NULL) {
|
||||
Element& e = annotation(T_CATEGORY);
|
||||
e.attribute("value-0", value0);
|
||||
if (value1 != NULL) {
|
||||
e.attribute("value-1", value1);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
public:
|
||||
JfrMetadata();
|
||||
|
||||
static Element* root() {
|
||||
return &_root;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& strings() {
|
||||
return _strings;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _JFRMETADATA_H
|
||||
126
src/linearAllocator.cpp
Normal file
126
src/linearAllocator.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "linearAllocator.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
LinearAllocator::LinearAllocator(size_t chunk_size) {
|
||||
_chunk_size = chunk_size;
|
||||
_reserve = _tail = allocateChunk(NULL);
|
||||
}
|
||||
|
||||
LinearAllocator::~LinearAllocator() {
|
||||
clear();
|
||||
freeChunk(_tail);
|
||||
}
|
||||
|
||||
void LinearAllocator::clear() {
|
||||
if (_reserve->prev == _tail) {
|
||||
freeChunk(_reserve);
|
||||
}
|
||||
while (_tail->prev != NULL) {
|
||||
Chunk* current = _tail;
|
||||
_tail = _tail->prev;
|
||||
freeChunk(current);
|
||||
}
|
||||
_reserve = _tail;
|
||||
_tail->offs = sizeof(Chunk);
|
||||
}
|
||||
|
||||
size_t LinearAllocator::usedMemory() {
|
||||
size_t bytes = _reserve->prev == _tail ? _chunk_size : 0;
|
||||
for (Chunk* chunk = _tail; chunk != NULL; chunk = chunk->prev) {
|
||||
bytes += _chunk_size;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::trim() {
|
||||
return __atomic_exchange_n(&_tail->prev, NULL, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
void LinearAllocator::freeChain(Chunk* chunk) {
|
||||
while (chunk != NULL) {
|
||||
Chunk* current = chunk;
|
||||
chunk = chunk->prev;
|
||||
freeChunk(current);
|
||||
}
|
||||
}
|
||||
|
||||
void* LinearAllocator::alloc(size_t size) {
|
||||
Chunk* chunk = _tail;
|
||||
|
||||
do {
|
||||
// Fast path: bump a pointer with CAS
|
||||
for (size_t offs = chunk->offs; offs + size <= _chunk_size; offs = chunk->offs) {
|
||||
if (__sync_bool_compare_and_swap(&chunk->offs, offs, offs + size)) {
|
||||
if (_chunk_size / 2 - offs < size) {
|
||||
// Stepped over a middle of the chunk - it's time to prepare a new one
|
||||
reserveChunk(chunk);
|
||||
}
|
||||
return (char*)chunk + offs;
|
||||
}
|
||||
}
|
||||
} while ((chunk = getNextChunk(chunk)) != NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::allocateChunk(Chunk* current) {
|
||||
Chunk* chunk = (Chunk*)OS::safeAlloc(_chunk_size);
|
||||
if (chunk != NULL) {
|
||||
chunk->prev = current;
|
||||
chunk->offs = sizeof(Chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void LinearAllocator::freeChunk(Chunk* current) {
|
||||
OS::safeFree(current, _chunk_size);
|
||||
}
|
||||
|
||||
void LinearAllocator::reserveChunk(Chunk* current) {
|
||||
Chunk* reserve = allocateChunk(current);
|
||||
if (reserve != NULL && !__sync_bool_compare_and_swap(&_reserve, current, reserve)) {
|
||||
// Unlikely case that we are too late
|
||||
freeChunk(reserve);
|
||||
}
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::getNextChunk(Chunk* current) {
|
||||
Chunk* reserve = _reserve;
|
||||
|
||||
if (reserve == current) {
|
||||
// Unlikely case: no reserve yet.
|
||||
// It's probably being allocated right now, so let's compete
|
||||
reserve = allocateChunk(current);
|
||||
if (reserve == NULL) {
|
||||
// Not enough memory
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Chunk* prev_reserve = __sync_val_compare_and_swap(&_reserve, current, reserve);
|
||||
if (prev_reserve != current) {
|
||||
freeChunk(reserve);
|
||||
reserve = prev_reserve;
|
||||
}
|
||||
}
|
||||
|
||||
// Expected case: a new chunk is already reserved
|
||||
Chunk* tail = __sync_val_compare_and_swap(&_tail, current, reserve);
|
||||
return tail == current ? reserve : tail;
|
||||
}
|
||||
53
src/linearAllocator.h
Normal file
53
src/linearAllocator.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _LINEARALLOCATOR_H
|
||||
#define _LINEARALLOCATOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
struct Chunk {
|
||||
Chunk* prev;
|
||||
volatile size_t offs;
|
||||
// To avoid false sharing
|
||||
char _padding[56];
|
||||
};
|
||||
|
||||
class LinearAllocator {
|
||||
private:
|
||||
size_t _chunk_size;
|
||||
Chunk* _tail;
|
||||
Chunk* _reserve;
|
||||
|
||||
Chunk* allocateChunk(Chunk* current);
|
||||
void freeChunk(Chunk* current);
|
||||
void reserveChunk(Chunk* current);
|
||||
Chunk* getNextChunk(Chunk* current);
|
||||
|
||||
public:
|
||||
LinearAllocator(size_t chunk_size);
|
||||
~LinearAllocator();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
Chunk* trim();
|
||||
void freeChain(Chunk* chunk);
|
||||
|
||||
void* alloc(size_t size);
|
||||
};
|
||||
|
||||
#endif // _LINEARALLOCATOR_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user