Compare commits

...

477 Commits

Author SHA1 Message Date
Andrei Pangin
5a1f7b8e4a Manually unwind method prologue 2023-03-18 02:01:28 +00:00
Andrei Pangin
8128696c0b Avoid unsafe stack walking in transient call_helper state 2023-03-08 23:49:15 +00:00
Andrei Pangin
dead47e0fb Use DWARF stack walking in BpfClient 2023-02-21 03:26:43 +00:00
Andrei Pangin
56730dc7b5 Merge branch 'master' into bpfclient 2023-02-21 02:16:26 +00:00
Andrei Pangin
9fc0495f86 Walk frames with missing DWARF info correctly 2023-02-21 02:02:17 +00:00
Andrei Pangin
db1ca37a2e Improve demangling of C++ symbols 2023-02-21 01:58:05 +00:00
Andrei Pangin
4ffdb05d3a Allow JfrReader to parse in-memory buffer 2023-02-17 15:59:25 +00:00
Andrei Pangin
26eab2e657 Fixed post-merge conflicts 2023-02-14 02:48:28 +00:00
Andrei Pangin
8fee26589d Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
#	src/arguments.cpp
#	src/arguments.h
#	src/flightRecorder.cpp
2023-02-14 02:23:33 +00:00
Andrei Pangin
cdb8704156 Demangle Rust symbols 2023-02-14 02:15:48 +00:00
Andrei Pangin
c722b3972a Avoid crash in fillFrameTypes() 2023-02-14 01:54:42 +00:00
Andrei Pangin
48703edee7 Do not dump redundant threads in a JFR chunk 2023-01-09 23:09:43 +00:00
Andrei Pangin
63799a6055 Better fix for mapped pseudofiles 2022-12-22 00:11:38 +00:00
Andrei Pangin
ebda293a42 Do not try to parse mapped pseudofiles 2022-12-20 22:57:35 +00:00
Andrei Pangin
69c0340a08 [README] Clarification on allocation profiling and debug symbols 2022-12-13 02:25:47 +00:00
Andrei Pangin
45074592cf Simplified pom.xml 2022-11-27 02:19:12 +00:00
Andrei Pangin
32601bccd9 Release 2.9 2022-11-26 22:38:37 +00:00
Andrei Pangin
8c4824be7f Reference to ap-loader 2022-11-26 22:30:35 +00:00
Andrei Pangin
b08bf2d574 #93: Profiler API with embedded agent as a Maven artifact 2022-11-26 22:20:09 +00:00
Andrei Pangin
b4b2218782 Java API: extract embedded libasyncProfiler.so 2022-11-21 00:16:12 +04:00
Andrei Pangin
e1f149f3ae #669: Avoid JVM crash when reading Program Headers 2022-11-16 02:47:59 +04:00
Andrei Pangin
80808a3f1b 2.9 Release Candidate 2022-11-14 04:30:25 +04:00
Andrei Pangin
7eaefdb18f LiveObject JFR event 2022-11-14 03:57:20 +04:00
Andrei Pangin
5e4a402c7e #673: Try adjusting SP when AGCT fails 2022-11-13 00:09:36 +04:00
Andrei Pangin
b0a44524ba #392, #675: Do not truncate signatures in collapsed format 2022-11-12 23:00:55 +04:00
Yonatan Goldschmidt
ed092da71b Update README with C1/interpreted methods suffix info (#671) 2022-11-02 10:11:19 +04:00
Andrei Pangin
58c62fe4e8 Support leak profiler on OpenJ9 2022-10-15 01:45:17 +04:00
Andrei Pangin
bdaefa9a3b #658: Restart send/recv calls in case of EINTR 2022-10-13 23:35:13 +04:00
Andrei Pangin
8168c7dc91 #657: jfrsync sometimes fails when filename contains %t 2022-10-13 01:30:01 +04:00
Andrei Pangin
98a2006386 Fixed broken link 2022-10-12 01:10:21 +04:00
Johannes Bechberger
389d6c5daa Allow to set Bytecode version for Java compile in makefile (#655) 2022-10-12 00:25:59 +04:00
Andrei Pangin
03e6fc5a17 #633: Java Heap leak profiler 2022-10-03 04:14:17 +04:00
Andrei Pangin
9653401b4b Merge remote-tracking branch 'origin/master' into bpfclient 2022-10-02 00:47:37 +04:00
Andrei Pangin
f1e2b96a2f Reset 'alloc' and 'lock' arguments 2022-10-02 00:47:19 +04:00
Andrei Pangin
4f6c675504 Use jump instead of trap for allocation hooks 2022-10-02 00:41:32 +04:00
Andrei Pangin
cced4fcb51 Fixed merge conflicts 2022-10-01 02:39:31 +04:00
Andrei Pangin
904da2ac6b Merge remote-tracking branch 'origin/master' into bpfclient
# Conflicts:
#	src/callTraceStorage.cpp
#	src/callTraceStorage.h
#	src/linearAllocator.cpp
#	src/linearAllocator.h
2022-10-01 02:06:16 +04:00
Andrei Pangin
b5634b9d88 #647: Patch rpath for Homebrew JDK 2022-09-17 02:07:17 +03:00
Andrei Pangin
32b5fd8e3c vaddr in ELF Program Header might not match the actual load address 2022-09-16 17:12:55 +03:00
Andrei Pangin
b7dfd74a63 meminfo fixes 2022-09-16 15:57:22 +03:00
Andrei Pangin
ed30401cc2 #643: `--include/--exclude options in the FlameGraph converter 2022-09-13 19:03:06 +03:00
Andrei Pangin
d4bee9647f Print used memory stats 2022-09-13 02:29:14 +03:00
Andrei Pangin
d93477f680 Handle fcntl() and ioctl() failures 2022-09-12 01:02:32 +03:00
Andrei Pangin
696087c2ab Fixed VM.log command for JDK 18 2022-09-10 00:26:02 +03:00
Andrei Pangin
1e8301e831 Fix GCC 11 warnings 2022-09-09 23:40:10 +03:00
Andrei Pangin
64d9f98a0f Sync with jattach repo 2022-09-08 09:05:53 +03:00
Andrei Pangin
2613894b85 #645: Fixed attaching to a container on Linux 3.x 2022-09-08 08:55:54 +03:00
Andrei Pangin
746166ccb4 Merge branch 'master' into bpfclient
# Conflicts:
#	src/fdtransfer/fdtransfer.h
2022-08-30 17:02:28 +03:00
Andrei Pangin
b8c8db45d7 Do not use ELF NODELETE flag because of glibc bug 2022-08-29 23:18:47 +03:00
Yonatan Goldschmidt
14f58ed2c7 Generate randomized fdtransfer path per async-profiler invocation (#635) 2022-08-24 11:56:46 +03:00
Andrei Pangin
7fa11e768b #632: Fixed allocation profiling on Zing 2022-08-17 01:14:32 +03:00
Andrei Pangin
74ecedc671 Broken 'jfr print' in jfrsync mode 2022-08-16 17:49:07 +03:00
Andrei Pangin
63f2539e5e #636: Cannot parse JFR files produced by JDK 20 EA 2022-08-16 17:12:02 +03:00
Andrei Pangin
31261ea7be #626: --simple and --dot options in jfr2flame converter 2022-08-02 03:17:43 +03:00
Andrei Pangin
9cec0765cd Safe parsing of kallsyms 2022-07-25 21:12:56 +03:00
Andrei Pangin
55da899511 #622: Display inlined frames under a runtime stub 2022-07-24 02:35:07 +03:00
Andrei Pangin
e9de87c0c3 Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
#	src/profiler.cpp
2022-07-18 01:44:05 +03:00
Andrei Pangin
56ae519224 Make CodeCache functions public 2022-07-17 00:38:14 +03:00
Andrei Pangin
733cf7c668 Release 2.8.3 2022-07-16 22:57:55 +03:00
Andrei Pangin
ee3ef243d3 Do not call System.loadLibrary, if libasyncProfiler is preloaded with -agentpath 2022-07-16 20:43:35 +03:00
Andrei Pangin
28357c2fb4 #618: Support virtualized ARM64 Mac 2022-07-16 18:34:07 +03:00
Andrei Pangin
2459d7eac4 #617: An option to generate certain JFR events by async-profiler in jfrsync mode 2022-07-16 03:10:03 +03:00
Andrei Pangin
cc1682d20a Handle different versions of Zing properly 2022-07-15 01:21:11 +03:00
Andrei Pangin
9ec48d9666 Could not recreate perf_event after the first failure 2022-07-14 18:04:13 +03:00
Andrei Pangin
e2abcd2238 Release 2.8.2 2022-07-14 01:59:33 +03:00
Andrei Pangin
9ae31b0e91 #611: Double click selects the name of the current frame 2022-07-10 04:07:27 +03:00
Andrei Pangin
a836ad6f89 #612, #616: Make possible to attach to Alpine container from a glibc host 2022-07-10 00:24:41 +03:00
Andrei Pangin
c6f11d2673 #615: Fixed JDK 7 crash in ConstMethod::id() 2022-07-06 20:00:18 +03:00
Andrei Pangin
989c3747e2 Fix CPU profiling on Zing JVM 2022-07-04 03:32:10 +03:00
Andrei Pangin
ce9ebc2b63 #610: Could not set dlopen hook on Arch Linux 2022-07-04 01:24:09 +03:00
Andrei Pangin
52cdc138d0 Properly cut C++ function arguments in JFR output 2022-07-03 23:26:11 +03:00
Andrei Pangin
d09be06029 Mark interpreted frames with _[0] in collapsed output 2022-07-03 21:53:22 +03:00
Andrei Pangin
859c36ef9c Fix FD leak when using fdtransfer client 2022-07-03 21:28:50 +03:00
Andrei Pangin
cd084b5a97 Disable Engine events when stopping profiler 2022-07-03 21:09:19 +03:00
Andrei Pangin
1505345ee9 Scan only alive nmethods 2022-07-03 20:26:13 +03:00
Andrei Pangin
a53a141dd2 Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
2022-06-21 15:18:59 +03:00
Andrei Pangin
8547d642ab Add timeout for the fdtransferClient connection 2022-06-21 15:17:47 +03:00
Andrei Pangin
fa989850b6 Release 2.8.1 2022-06-11 00:52:14 +03:00
Andrei Pangin
1e4738ba7b #607: INCBIN symbols resolved incorrectly in debug builds 2022-06-09 01:58:11 +03:00
Andrei Pangin
c6b5c877f1 Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
2022-06-08 18:54:44 +03:00
Andrei Pangin
80a3c66969 Release 2.8.1-ea 2022-06-08 16:12:56 +03:00
Andrei Pangin
e1d5df7ffd #601: '--lib' option to customize profiler .so path in the container 2022-06-08 04:26:12 +03:00
Andrei Pangin
6ca5ad2c93 #526, #530: JFR converter improvements: time range, collapsed output, pattern highlighting 2022-06-08 03:58:35 +03:00
Andrei Pangin
7af24609eb #549: '%n' pattern in the filenames 2022-06-05 04:25:24 +03:00
Andrei Pangin
80302eb43d #555: Workaround JFR bug in AttachCurrentThread 2022-06-05 02:11:06 +03:00
Andrei Pangin
0396bc7d1b Allow 'profiler.sh list' command without PID 2022-06-04 15:14:38 +03:00
Andrei Pangin
b29bfd3f3a 'mcache' option to retain jmethodID name cache between runs 2022-06-02 04:11:38 +03:00
Andrei Pangin
93b8171601 Remove always-false condition 2022-06-01 03:11:47 +03:00
Nikita Nazarov
721225393e Remove library name fetching for unknown and error frames (#599) 2022-06-01 01:57:02 +03:00
Andrey Pangin
58a3cb0d25 Minor cleanup/formatting 2022-05-24 14:52:10 +03:00
Artyom Drozdov
6754312e83 Resources deduplication between cpp and java (#591) 2022-05-24 13:39:23 +03:00
Andrei Pangin
daf844397e Warn about I/O issues when dumping profile 2022-05-22 21:12:46 +03:00
Andrei Pangin
5e0021a99f Do not reuse safemode=64 which meant different thing 2022-05-22 14:30:43 +03:00
Andrei Pangin
8d846a01e7 Merge branch 'master' into bpfclient
# Conflicts:
#	src/vmEntry.cpp
2022-05-22 03:28:44 +03:00
Andrei Pangin
37c56c44bb Workaround for JDK-8185348 2022-05-22 03:25:31 +03:00
Andrei Pangin
88dd2345ea Avoid race of writing to the same output file from two threads 2022-05-21 20:23:12 +03:00
Andrei Pangin
ded970fd50 Handle quirks of Alpine/musl dynamic linker 2022-05-19 03:47:50 +03:00
Andrei Pangin
a23400b85e Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
2022-05-17 18:33:58 +03:00
Andrei Pangin
796aff5555 Check zombie methods when marking frame types 2022-05-17 18:32:49 +03:00
Artyom Drozdov
d36e8e91f9 Bump java plugin source/target version (#590) 2022-05-17 15:56:13 +03:00
Andrei Pangin
323c02e0d7 Minor formatting 2022-05-16 02:16:42 +03:00
Tim Steinbach
53cfd2b8b1 Add pprof converter (#587) 2022-05-16 00:56:09 +03:00
Andrei Pangin
4431121760 #585: Fixed crash with repeated JFR dumps & lock profiling 2022-05-14 03:20:25 +03:00
Andrei Pangin
4a88ee445f Release 2.8 2022-05-10 04:17:09 +03:00
Andrei Pangin
ce0fc0a2d9 Fixed musl 2022-05-10 04:04:05 +03:00
Andrei Pangin
768b437593 Missing headers 2022-05-09 22:56:41 +03:00
Andrei Pangin
20fa8564c4 Do not merge source files when building fat binary on macOS 2022-05-09 20:21:42 +03:00
Andrei Pangin
aaf24effe8 ARM32 and x86 pop frame support 2022-05-09 20:00:44 +03:00
Andrei Pangin
3bbeed6267 ARM64 pop frame support 2022-05-09 14:57:51 +03:00
Andrei Pangin
cb51be8eb3 Merge different frame types of the same method on the Flame Graph 2022-05-09 02:44:21 +03:00
Andrei Pangin
7749b72e73 Tree view colors 2022-05-08 18:27:42 +03:00
Andrei Pangin
7112bc14e5 Stack recovery fixes 2022-05-08 17:45:38 +03:00
Andrey Pangin
df5fb83354 Fixed merge issues 2022-04-28 14:15:59 +03:00
Andrey Pangin
90b7c78643 Merge remote-tracking branch 'origin/master' into bpfclient
# Conflicts:
#	Makefile
#	src/profiler.cpp
2022-04-28 14:04:08 +03:00
Andrei Pangin
56ca88677b Release 2.8-ea 2022-04-26 08:55:33 +03:00
Andrei Pangin
8a258d31a5 Show C1 compiled frames. Merge different compilation types on a flame graph. 2022-04-26 08:43:56 +03:00
Andrei Pangin
e33a01e0c6 Re-implemented stack recovery. Unwind native stacks manually before AGCT. 2022-04-26 08:37:22 +03:00
Andrei Pangin
5d5138ea61 Allow profiler server only at JVM startup 2022-04-24 16:15:31 +03:00
Andrei Pangin
10b5ad0ee5 #512: Simple HTTP server for managing async-profiler 2022-04-24 03:10:49 +03:00
Andrey Pangin
80c3993b7e Merge remote-tracking branch 'origin/master' into bpfclient 2022-04-14 17:00:26 +03:00
Andrey Pangin
b81be14adf BpfClient: add thread scheduling policy 2022-04-14 17:00:17 +03:00
Andrei Pangin
b5258cca2c JVM TI based allocation profiling fine tuning 2022-04-14 03:02:26 +03:00
Andrei Pangin
0718a09e0e #169: JVM TI based allocation profiling for JDK >= 11 2022-04-14 01:46:48 +03:00
Andrei Pangin
f86d1a415b BpfClient fixes 2022-04-12 14:05:00 +03:00
Andrei Pangin
c00b6c25de Merge branch 'master' into bpfclient
# Conflicts:
#	src/callTraceStorage.cpp
2022-04-12 13:10:20 +03:00
Andrei Pangin
fe173c4101 #561, #579: Fixed concurrency issues when collecting samples / restarting profiler 2022-04-11 01:49:34 +03:00
Andrey Pangin
4713106ac9 Fix switchChunk 2022-04-01 18:43:25 +03:00
Andrey Pangin
c89fea8de5 Merge remote-tracking branch 'origin/master' into bpfclient
# Conflicts:
#	src/profiler.cpp
#	src/profiler.h
2022-03-31 19:53:25 +03:00
Andrei Pangin
8032daa49d --cpu converter option to extract CPU profile from the wall-clock output 2022-03-31 03:05:22 +03:00
Andrei Pangin
fa8b8f8072 #569: Distinguish runnable/sleeping threads in OpenJ9 wall-clock profiler 2022-03-31 01:34:48 +03:00
Andrei Pangin
85f3a68c56 #570: Display an error when using 'jfrsync' on JDK without Flight Recorder 2022-03-30 23:47:26 +03:00
Andrei Pangin
60ce15569a #572: Careful parsing of ELF Program Headers 2022-03-29 04:24:06 +03:00
Andrei Pangin
9838ddb693 #572: CodeCache refactoring + parseProgramHeaders() fixes 2022-03-28 04:44:15 +03:00
Andrei Pangin
2f341043ef #571, #572: Locate DWARF info and GOT/PLT from ELF Program Headers 2022-03-27 22:09:08 +03:00
Andrei Pangin
ee55fbe17b #557: Do not fail on unknown argument 2022-03-10 01:01:29 +03:00
Andrey Pangin
0309f3dfd3 JFR checkpoints fix 2022-02-22 19:18:41 +03:00
Andrey Pangin
5ec58fd873 Allow multiple profilers at the same time 2022-02-21 19:12:06 +03:00
Andrey Pangin
1fda752243 Merge remote-tracking branch 'origin/master' into inc2 2022-02-21 13:34:43 +03:00
Andrei Pangin
d7a2a4fc8b Mark top methods as interpreted/compiled/inlined (#553) 2022-02-21 04:58:19 +03:00
Andrei Pangin
e9b7747015 Do not mmap perf page in --all-user mode. cstack=fp forces manual stack walking. 2022-02-19 23:05:55 +03:00
Andrey Pangin
ff305e1d16 Checkpoints 2022-02-18 20:40:24 +03:00
Andrey Pangin
5cc4721e66 Checkpoints 2022-02-17 20:20:20 +03:00
Andrey Pangin
fee03218d1 Merge branch 'call-trace-storage' into bpfclient 2022-02-17 17:24:52 +03:00
Andrey Pangin
dade9b2283 Fixed size CallTraceStorage table 2022-02-17 16:31:27 +03:00
Andrei Pangin
b96e07b001 List loglevel argument 2022-02-16 03:17:34 +03:00
Ludovic Henry
ba00ca26c1 Add loglevel argument (#551) 2022-02-16 02:00:22 +03:00
Andrey Pangin
db9ec2e59e Resolved merge conflicts 2022-02-15 13:49:14 +03:00
Andrey Pangin
f0a7a36825 Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
#	src/profiler.cpp
#	src/vmEntry.cpp
2022-02-15 13:40:17 +03:00
Andrei Pangin
9ed175d73e Updated links to 2.7 binaries 2022-02-14 02:29:33 +03:00
Andrei Pangin
b287816559 Release 2.7 2022-02-14 00:57:18 +03:00
Andrei Pangin
9a979a712d OpenJ9-related fixes 2022-02-13 23:48:45 +03:00
Andrei Pangin
42442ed593 Fixed AllocTracer on ARM32 2022-02-13 21:59:59 +03:00
Andrei Pangin
432d622aa4 Follow-up to #537: added a few comments 2022-02-13 20:36:46 +03:00
Gunter Haug
f477f8d4c0 Look in plt sections for the global offset table too (#537)
Co-authored-by: Johannes Bechberger
2022-02-13 20:34:44 +03:00
Andrei Pangin
456ff57115 OpenJ9 fixes 2022-02-09 04:26:40 +03:00
Andrei Pangin
5ec28a86b2 #548: syntax error in profiler.sh 2022-02-08 20:49:47 +03:00
Andrei Pangin
e7cd6ee6fb OpenJ9 support 2022-02-01 16:04:48 +03:00
Andrei Pangin
8ba8fc748c Minor DWARF fix 2022-01-28 03:42:19 +03:00
Andrei Pangin
6f869d3eff BpfClient protocol update 2022-01-27 18:23:11 +03:00
Andrei Pangin
462988d8de Merge branch 'v2.x' into bpfclient 2022-01-27 16:46:44 +03:00
Andrei Pangin
5f37bf3ad6 DWARF unwinding used incorrect symbol base 2022-01-26 18:11:41 +03:00
Andrei Pangin
26e9c7aef2 DWARF stack walking fixes 2022-01-25 03:53:14 +03:00
Andrei Pangin
73b5e97c39 JfrReader backward compatibility
(cherry picked from commit 8efba10acc)
2022-01-25 02:52:49 +03:00
Andrei Pangin
8efba10acc JfrReader backward compatibility 2022-01-25 01:56:33 +03:00
Andrei Pangin
e894420119 #531: Rewrite StackMapTable correctly 2022-01-23 01:23:25 +03:00
Andrei Pangin
41121aa64f #531: Rewrite StackMapTable correctly 2022-01-23 01:22:44 +03:00
Andrei Pangin
2519bf4307 #416: Improve reliability of stack recovery from [not_walkable_not_Java]
(cherry picked from commit 2ddd4d230c)
2022-01-22 03:55:27 +03:00
Andrei Pangin
2ddd4d230c #416: Improve reliability of stack recovery from [not_walkable_not_Java] 2022-01-21 08:55:09 +03:00
Andrei Pangin
09bf0f025b PROFILER_VERSION 2022-01-20 08:35:03 +03:00
Andrei Pangin
1398e7ef75 #215: DWARF stack unwinding 2022-01-20 03:45:37 +03:00
Andrei Pangin
3456dd3d90 Patch Global Offset Table to hook libc functions 2022-01-19 02:48:36 +03:00
Andrei Pangin
12dfaba106 eBPF client 2022-01-13 05:03:26 +03:00
Andrei Pangin
c0d45fecec Updated links to version 2.6 2022-01-11 00:10:49 +03:00
Yonatan Goldschmidt
7e5f8a03f3 Fail arguments parsing upon unknown argument (#503) 2022-01-10 03:58:33 +03:00
Andrei Pangin
ce91abe6d9 Do not show 'GC_active' for compiler threads 2022-01-10 01:48:13 +03:00
Andrei Pangin
4ba7524d7c Release 2.6 2022-01-09 21:10:30 +03:00
Andrei Pangin
734ef03ebf Include shared library names in JFR output 2022-01-08 04:08:24 +03:00
Andrei Pangin
cf17e5efc3 #513: Protect native stack walking from crashes 2022-01-06 05:20:15 +03:00
Andrei Pangin
35b420a941 Compute FlameGraph depth when minwidth > 0 2022-01-06 01:35:27 +03:00
Andrei Pangin
ee4cd8e2b6 Revived workaround for slow JVM TI functions 2022-01-05 23:39:51 +03:00
Andrei Pangin
6f3134e99f Pass chunksize & jstackdepth options to the Flight Recorder in jfrsync mode 2022-01-05 22:21:44 +03:00
Andrei Pangin
c537b8298d Do not spoil VM.log decorators 2022-01-05 05:56:59 +03:00
Andrei Pangin
605550cf96 Off-by-one bug 2022-01-05 03:55:23 +03:00
Andrei Pangin
a57bbf3587 Optimize compilation with -fwhole-program 2022-01-04 02:17:30 +03:00
Andrei Pangin
9131344d61 Minor build system improvements 2022-01-01 21:54:39 +03:00
Andrei Pangin
2d0b9c9921 Avoid JVM crashes related to CompiledMethodLoad bugs and stack walking during GC 2022-01-01 21:34:01 +03:00
Andrei Pangin
30905fda4c Faster compilation, smaller binary 2021-12-26 04:35:04 +03:00
Andrei Pangin
5a11a71db9 Fixed races between flush/stop. Possibility to set timeout as hh:mm:ss 2021-12-19 21:47:11 +03:00
Andrei Pangin
d79a82935f Renamed 'duration' to 'timeout' 2021-12-15 03:41:39 +03:00
Andrei Pangin
995048c2fd #71: Continuous profiling 2021-12-15 03:28:24 +03:00
Andrei Pangin
7331e30ed5 JFR duration should be in nanos, not ticks 2021-12-12 03:06:20 +03:00
Andrei Pangin
1f5e4ca8aa Parse recording settings in JfrReader 2021-12-12 03:05:16 +03:00
Andrei Pangin
7ebed4e8e1 Fixed links to 2.5.1 binaries 2021-12-07 03:09:43 +03:00
Andrei Pangin
170451990b Release 2.5.1 2021-12-05 23:53:26 +03:00
Andrei Pangin
11a1d6d308 Read kernel symbols only for perf_events 2021-12-04 00:05:53 +03:00
Yonatan Goldschmidt
955413db8d Remove double close() call (#514) 2021-12-03 23:41:06 +03:00
Andrei Pangin
8a701b41e3 Fixed unsafe access to Recording fields 2021-12-03 04:09:14 +03:00
Andrei Pangin
dccd4c326a Mark native functions. Redo ZeroInterpreter support. 2021-11-29 04:17:36 +03:00
Andrei Pangin
8771888d28 Make sure async-profiler DSO cannot be unloaded 2021-11-28 22:07:00 +03:00
Andrei Pangin
7d25210d2c #506: Throw exception if output size exceeds string limit 2021-11-27 20:29:30 +03:00
Yonatan Goldschmidt
9087bc57d8 Mmap perf_events pages in fdtransfer (#475) 2021-11-15 23:47:03 +03:00
Andrei Pangin
3e1e1c614a Fixed RedefineClasses recursion when loading via JNI 2021-11-02 16:02:46 +03:00
Yonatan Goldschmidt
b80d163699 Escape backslashes in flamegraph frame names (#487) 2021-11-01 23:30:11 +03:00
Andrei Pangin
9705b66864 Minor rewording in README 2021-10-31 20:32:09 +03:00
Krzysztof Ślusarski
3c33c6aa47 Update README regarding the CodeCache flushing on Java method instrumentation (#483) 2021-10-31 20:15:29 +03:00
Andrei Pangin
4af6b65268 Merge event types (avoid duplicate categories) in jfrsync mode 2021-10-20 02:38:38 +03:00
Andrei Pangin
03c7b36bca Fixed deadlock between Profiler::stop() and flushJfr() 2021-10-20 01:36:17 +03:00
Aleksey Shipilëv
f8b15526b1 Handle OpenJDK C++ Interpreter (#474) 2021-10-11 22:38:27 +03:00
Andrey Pangin
e519fd84c4 Allow JMC to read incomplete JFR recordings 2021-10-01 22:46:12 +03:00
Andrey Pangin
79e1017088 Fixed link to macOS binaries 2021-10-01 05:28:21 +03:00
Andrey Pangin
edbb9e7c03 Release 2.5 2021-10-01 05:22:27 +03:00
Andrey Pangin
7eb15cfcf0 Canvas height should not be more than 32767px 2021-10-01 04:38:18 +03:00
Andrey Pangin
eafbbaea8b Avoid use of certain libstdc++ symbols 2021-10-01 02:42:33 +03:00
Andrey Pangin
d8228a1fec #462: An option to prepend library name to native symbols 2021-09-30 04:40:40 +03:00
Andrey Pangin
8a447481f8 #405: Resolve symlinks to profiler.sh 2021-09-30 02:54:18 +03:00
Andrey Pangin
791077354e Fixed wrong TSC frequency problem. Removed misleading CPUTimeStampCounter event. 2021-09-29 03:15:57 +03:00
Andrey Pangin
e071b9fa14 Use global CodeCache boundaries instead of _java_methods/_runtime_stubs where possible 2021-09-28 21:17:21 +03:00
Andrey Pangin
cc340923b9 #458: Wrong 'Matched %' when searching flame graphs with large values 2021-09-23 01:05:59 +03:00
Andrey Pangin
9c333219b5 Parse incomplete JFR files 2021-09-23 01:03:23 +03:00
Andrey Pangin
9acf8c1648 Ticks vs. nanos fixes 2021-09-23 00:24:34 +03:00
Andrey Pangin
5570afed9d Use RDTSC for JFR timestamps when possible 2021-09-21 05:33:22 +03:00
Andrey Pangin
975a506d83 Read JFR chunks lazily; support combined recordings 2021-09-20 01:42:42 +03:00
Andrey Pangin
09fb14bd87 Combine JFR recording with async-profiler samples in a single .jfr file 2021-09-19 22:45:01 +03:00
Andrey Pangin
3256d824de The workaround for #295 didn't work sometimes 2021-09-16 01:05:23 +03:00
Andrey Pangin
cc98710a6f [ppc64] Fixed native stack walking in wall-clock profiler 2021-09-12 02:44:58 +03:00
Gunter Haug
452f14c3d2 Port async-profiler 2.0 to Power Linux little endian (linuxppc64le) (#459) 2021-09-11 16:54:26 +03:00
Andrey Pangin
be8bba1900 Fixed application termination during JFR profiling 2021-09-10 01:05:11 +03:00
Andrey Pangin
65b5356ace Fixed sendfile() not working on 5.10+ kernels 2021-09-08 23:42:57 +03:00
Andrey Pangin
e91363c05a Fixed compiler warning 2021-09-08 21:19:58 +03:00
Andrey Pangin
3e47bf7551 #454: SCAN_STACK recovery results in impossible stack traces 2021-09-08 02:41:37 +03:00
Kirill Timofeev
9447068af3 DCEVM is also hotspot (#457) 2021-09-07 19:11:27 +03:00
Andrey Pangin
7e750825da Add lines/bci information in a Flame Graph 2021-09-06 03:57:48 +03:00
Andrey Pangin
eda5779552 Smallish naming and stylistic changes 2021-09-06 01:26:32 +03:00
Yonatan Goldschmidt
fc3b1ca84f Profile low-privileged processes with perf_events (#411) 2021-09-05 22:13:01 +03:00
Andrey Pangin
d13de48c0a #402: Changes in shared code required for ppc64le port 2021-09-05 21:15:46 +03:00
Andrey Pangin
552c699687 Fix package names for hidden/anonymous classes 2021-09-01 22:36:52 +03:00
Andrey Pangin
0fcc4d9bac #360: Chunked JFR. Support JFR files more than 2 GB. 2021-09-01 01:08:54 +03:00
Andrey Pangin
868bfec2a5 Dump results while profiling session is running. Switch JFR chunks. 2021-08-20 03:32:21 +03:00
Andrey Pangin
4a77d68bcb Sync with jattach 2.0: psutil interface, better container support, Open9 VMs 2021-08-11 23:41:54 +03:00
Andrey Pangin
a38a375dc6 Compatibility with debug builds of JDK 8 2021-08-01 02:59:33 +03:00
Andrey Pangin
6bcd23fcf0 An option to group threads by scheduling policy 2021-07-29 01:06:04 +03:00
Andrey Pangin
8d2847a032 Updated the list of supported platforms 2021-07-28 01:20:12 +03:00
Andrey Pangin
e0998af713 Rework VM shutdown issue: avoid calling Profiler destructor 2021-07-19 02:42:31 +03:00
Andrey Pangin
01b3e6c517 Use standard JVM TI stack walker where possible. Record BCI in alloc/lock events 2021-07-11 20:13:27 +03:00
Andrey Pangin
def6eb4b1c Profile concurrent locks without JVM symbols 2021-07-11 16:57:04 +03:00
Andrey Pangin
4032c56caf Extend CompiledMethodLoad bug workaround to JDK < 11 2021-07-07 02:12:06 +03:00
Andrey Pangin
9b789f6516 Workaround JDK bug related to posting CompiledMethodLoad event 2021-07-07 00:28:34 +03:00
Andrey Pangin
6ddaf9ab71 Log messages in JFR. List native libraries in JFR. 2021-07-01 03:12:49 +03:00
Andrey Pangin
11131499ab One more fix of the race condition during VM shutdown 2021-06-30 02:47:23 +03:00
Andrey Pangin
d2abac1c30 Fixed 'check' command for multi-event profiling 2021-06-14 23:50:17 +03:00
Andrey Pangin
b7e9079b52 Fixed possible access to freed memory caused by a race during VM shutdown 2021-06-14 00:11:44 +03:00
Andrey Pangin
ff49ccccb7 #390: Fixes for macOS/M1 build. Sign macOS package. 2021-06-13 15:05:05 +03:00
Andrey Pangin
7dd075cca6 #440: Fixed crash when RetransformClasses is called with invalid arguments 2021-06-09 19:30:11 +03:00
Simon Legner
bd8078bc11 Enable GitHub Actions (#435) 2021-06-06 23:59:01 +03:00
Simon Legner
1622fe5d72 Travis CI: use Ubuntu 18.04 (Bionic Beaver) (#434) 2021-06-06 22:46:07 +03:00
Andrey Pangin
44d7941728 #390: Run profiler natively on Apple M1 chip 2021-05-30 20:07:15 +03:00
Andrey Pangin
d23b40048b Removed stackFrame_aarch64 (to be re-implemented from scratch) 2021-05-30 20:03:10 +03:00
Andrey Pangin
3b2db709ff #433: Skip AArch64 mapping symbols 2021-05-30 02:26:14 +03:00
Andrey Pangin
096fc88c82 Attempt to read temporary output file through /proc/[pid]/root 2021-05-17 01:45:40 +03:00
Yonatan Goldschmidt
4b0303916d Read log file via /proc/pid/root on Linux (#413) 2021-05-17 01:33:17 +03:00
Andrey Pangin
9fb2ca800a #414: Warn about conflict with another agent 2021-05-12 23:50:57 +03:00
Andrey Pangin
d917cfdb63 #415: Fixed escaping of ' character 2021-05-12 20:38:18 +03:00
Uri Baghin
f2006f3da1 Use time types instead of long. (#428) 2021-05-09 23:09:17 +03:00
Andrey Pangin
c30b22f204 #419: Count the specified argument of a function. Added munmap to the list of known functions 2021-05-03 03:04:27 +03:00
Andrey Pangin
f48ebcc72b #418: Cap Flame Graph height to 32767px 2021-05-03 00:10:55 +03:00
Andrey Pangin
339aee5cfc #379: Generate Flame Graph title depending on the event and --total 2021-05-02 23:37:15 +03:00
Andrey Pangin
cde3fae978 #423: Use Java 6 target only when building 'release' 2021-04-29 04:14:19 +03:00
Andrey Pangin
91eab91634 #414: Fixed infinite CodeCache growth 2021-04-11 22:44:00 +03:00
Andrey Pangin
3df00e3439 #150: jfr2flame can produce Allocation and Lock flame graphs 2021-04-11 21:47:15 +03:00
Andrey Pangin
c66ac2cfd0 Removed unused function 2021-04-04 21:15:08 +03:00
Andrei Pangin
f236482228 Raw PMU events; kprobes & uprobes (#406) 2021-03-28 23:35:08 +03:00
Andrey Pangin
3ff315ea8f Release 2.0 2021-03-14 20:19:13 +03:00
Andrey Pangin
308074a9eb Print error if JVM does not support Tool Interface (e.g. minimal VM) 2021-03-14 16:49:11 +03:00
Andrey Pangin
685da8d84f Update native thread names on Zing 2021-03-14 16:29:26 +03:00
Andrey Pangin
bd7bf9726e Add [unknown_Java] frame in safe mode 2021-03-14 15:22:34 +03:00
Andrey Pangin
034677435d Fixed handling of some profiler.sh arguments 2021-03-14 14:49:17 +03:00
Andrey Pangin
89f7d34456 Added new safe mode for troubleshooting 2021-03-14 01:03:41 +03:00
Andrey Pangin
ec8a40431a An alias for time-to-safepoint profiling: --ttsp 2021-03-13 01:17:19 +03:00
Andrey Pangin
81bc1f2df2 Support appending JFR profiles on 'resume' command 2021-03-13 00:46:43 +03:00
Andrey Pangin
40ff09a14f Combine async-profiler recording with JDK Flight Recording 2021-03-13 00:14:52 +03:00
Andrey Pangin
d6de541799 Corrected README links 2021-03-04 00:44:33 +03:00
Andrey Pangin
b807987f1d 2.0 Release Candidate 2021-03-04 00:22:42 +03:00
Andrey Pangin
02875138f1 Updated README 2021-03-03 23:52:30 +03:00
Andrey Pangin
d1c19d1904 Updated CHANGELOG. New FlameGraph example 2021-03-03 04:14:51 +03:00
Andrey Pangin
cc2307b92c 'cstack' option now correctly works in multievent profiles 2021-03-03 00:39:47 +03:00
Andrey Pangin
b5d89fef29 --begin/--end compatibility with allocation profiling 2021-03-02 05:11:46 +03:00
Andrey Pangin
646a92e2a0 Improved CLI experience 2021-03-02 02:35:29 +03:00
Andrey Pangin
bcd2375f39 Merge branch 'master' into v2.0
# Conflicts:
#	CHANGELOG.md
#	Makefile
#	README.md
2021-03-01 04:49:53 +03:00
Andrey Pangin
3cbe6aec2f Multievent fixes; 'alloc=bytes' and 'lock=duration' options 2021-03-01 04:41:00 +03:00
Andrey Pangin
2d51c07b23 Output profile in text format: traces + flat 2021-02-28 19:51:24 +03:00
Andrey Pangin
32ead969c1 Graceful handling of storage overflow 2021-02-28 00:38:00 +03:00
Andrey Pangin
de55fadbba JFR writer fixes 2021-02-25 00:33:20 +03:00
Andrey Pangin
da0ac08c64 Correct PerfEvents stack traces when begin/end options are set 2021-02-24 08:02:13 +03:00
Andrey Pangin
5dd9e86a1d Release 1.8.4 2021-02-24 02:50:58 +03:00
Andrey Pangin
81583b9af3 Merge branch 'master' into v2.0
# Conflicts:
#	src/profiler.cpp
2021-02-24 02:34:48 +03:00
Andrey Pangin
7ec5c195e7 Better error handling 2021-02-24 02:31:48 +03:00
Andrey Pangin
cb0f1eb72d Fixed JDK 7 crash during wall-clock profiling 2021-02-20 03:41:37 +03:00
Andrey Pangin
051424890a Recover stack traces below C1 Runtime stubs 2021-02-03 02:08:59 +03:00
Andrey Pangin
34daf4f540 #386: Added a note about IntelliJ IDEA 2021-02-01 19:11:59 +03:00
Andrey Pangin
3a44bb6ba6 Merge branch 'master' into v2.0 2021-01-29 02:05:55 +03:00
Andrey Pangin
f73ac36c9c Fixed symbol resolution when return address points beyond the function 2021-01-29 02:05:26 +03:00
Andrey Pangin
c94b1685cf Merge branch 'master' into v2.0 2021-01-29 01:33:50 +03:00
Andrey Pangin
90d4420d3f Make all symbols private by default for better compiler optimization 2021-01-29 01:32:37 +03:00
Andrey Pangin
a96501a26a Enable native stacks for non-signal events, e.g. lock profiling 2021-01-29 00:09:15 +03:00
Andrey Pangin
39f84be219 Write profiler settings in JFR 2021-01-27 00:12:29 +03:00
Andrey Pangin
4af327e2c1 Write JVM info, system properties, and profiler version in JFR 2021-01-26 02:43:46 +03:00
Andrey Pangin
61919df2ff Make symbols private by default to improve gcc optimizations 2021-01-26 02:42:43 +03:00
Andrey Pangin
26880ecb22 #93: Basic POM for publishing async-profiler Java API to Maven Central 2021-01-14 05:05:10 +03:00
Ivan Zemlyanskiy
af02f6b0fb Migrate documentation from README.md to Wiki (#383) 2021-01-14 00:32:31 +03:00
Andrey Pangin
c11d4ca487 Add OS and CPU information in JFR output 2021-01-12 05:07:02 +03:00
Andrey Pangin
b2dfe9b5b0 Fixed compilation on JDK 7 2021-01-10 20:51:02 +03:00
Andrey Pangin
5585a77355 Merged master->v2.0 2021-01-10 20:46:59 +03:00
Andrey Pangin
b5a67c2b95 Release 1.8.3 2021-01-06 17:53:37 +03:00
Andrey Pangin
9aea04a56a New safemode=32 for sanity check of top Java frames 2021-01-06 17:44:34 +03:00
Andrey Pangin
a48f77b380 #377: Fix JvmtiEnv::GetStackTrace problem after RedefineClasses 2020-12-24 03:05:01 +03:00
Andrey Pangin
8c5f6c1357 Gracefully stop profiler when terminating JVM 2020-12-24 02:58:38 +03:00
Andrey Pangin
88730d4388 Fixed possible deadlock on non-HotSpot JVMs 2020-12-24 02:57:56 +03:00
Andrey Pangin
d132777a60 #378: Create libasyncProfiler.dylib symlink on macOS 2020-12-10 01:12:40 +03:00
Andrey Pangin
04dac10d41 JFRv2 parser. Added JFR->FlameGraph converter; fixed FlameScope converter 2020-12-06 21:20:41 +03:00
Andrey Pangin
5290b81190 Attempt to recover stack trace from String.indexOf intrinsic 2020-11-15 23:38:42 +03:00
Andrey Pangin
93e1f963ef Links to v2.0 Early access 2020-11-09 04:51:48 +03:00
Andrey Pangin
a18af69f8b Minor build fixes 2020-11-09 04:50:02 +03:00
Andrey Pangin
60cac04c24 2.0-b1 Early Access 2020-11-09 04:33:35 +03:00
Andrey Pangin
3d7e8efd3b Changelog 2020-11-09 04:15:22 +03:00
Andrey Pangin
d26d69e550 Returned tree output format 2020-11-09 04:14:15 +03:00
Andrey Pangin
8160e49c14 Dump flat profile in text format 2020-11-07 04:04:18 +03:00
Andrey Pangin
731ac31064 Bias JFR buffers to threads. Distinguish TLAB/outside allocations in Flame Graph 2020-11-07 00:21:30 +03:00
Andrey Pangin
013ceee55d Resurrected FlameGraph and collapsed output formats 2020-11-05 04:54:19 +03:00
Andrey Pangin
f7ef0e97b2 Clean-room FlameGraph implementation. Removed 3rd party copyrighted code. No more CDDL license 2020-11-04 20:47:43 +03:00
Andrey Pangin
c01fe588ce Merge branch 'master' into v2.0 2020-11-04 18:32:58 +03:00
Andrey Pangin
e498ad27d2 Improved HTML FlameGraph performance 2020-11-04 04:29:36 +03:00
Andrey Pangin
edb31a0f79 Merge branch 'master' into v2.0
# Conflicts:
#	CHANGELOG.md
#	src/allocTracer.cpp
#	src/allocTracer.h
2020-11-02 03:54:29 +03:00
Andrey Pangin
13394b7125 Release 1.8.2 2020-11-02 02:54:32 +03:00
Andrey Pangin
d227a83e42 Fixed warnings on JDK 15 and 16 2020-11-02 02:16:41 +03:00
Andrey Pangin
7e8ad02ccb Fixed allocation sizes on JDK 8u262+ 2020-11-02 00:40:47 +03:00
Andrey Pangin
450f251732 Support 32-bit systems 2020-11-01 04:40:25 +03:00
Andrey Pangin
53ca190457 Merge branch 'master' into v2.0 2020-11-01 02:21:16 +03:00
Andrey Pangin
683144a907 Release 1.8.2 2020-11-01 00:58:29 +03:00
Andrey Pangin
02b65627cd Merge branch 'master' into v2.0
# Conflicts:
#	src/flightRecorder.cpp
#	src/profiler.h
2020-10-31 23:09:24 +03:00
Andrey Pangin
48e4fd5035 #363: New native libraries are not tracked in JDK 15 2020-10-31 22:46:38 +03:00
Andrey Pangin
642a1ac7fb Timers for macOS and Linux; jdk.CPULoad event 2020-10-31 02:54:12 +03:00
Andrey Pangin
114e711fd6 jdk.ActiveRecording event 2020-10-30 03:59:41 +03:00
Andrey Pangin
f833f41b46 jdk.CPULoad event 2020-10-30 03:10:21 +03:00
Andrey Pangin
a82163b703 Line numbers in JFR output 2020-10-26 01:15:58 +03:00
Andrey Pangin
6b49cfa9be JFRv2 2020-10-21 03:41:12 +03:00
Andrey Pangin
6c26e5ae69 Added link to AArch64 build 2020-10-18 17:37:44 +03:00
Andrey Pangin
1634380a16 Added tag for aarch64 release 2020-10-18 04:04:52 +03:00
Andrey Pangin
1a6e582ad7 #356: 'resume' command continues writing JFR instead of creating a new file 2020-10-17 23:44:27 +03:00
Andrey Pangin
4b5a17b336 #350: More careful native stack walking in wall-clock mode 2020-09-22 03:05:27 +03:00
Andrey Pangin
8392e568f4 #351: Updated README instructions to check libjvm debug symbols 2020-09-22 00:43:01 +03:00
Andrey Pangin
d7d56c762b Merge branch 'master' into v2.0
# Conflicts:
#	src/instrument.cpp
#	src/profiler.h
2020-09-14 21:54:47 +03:00
Andrey Pangin
a4c6d42677 Release 1.8.1 2020-09-05 01:02:19 +03:00
Andrey Pangin
b7e907884b Fixed profiler check command after #347 2020-09-04 04:26:05 +03:00
Andrey Pangin
5b69492dba #347: Do not read /proc/kallsyms when --all-user is specified 2020-09-04 02:14:59 +03:00
James Yuzawa
5a789bda42 Clean up debug symbols instructions and troubleshooting (#345) 2020-09-03 00:31:05 +03:00
James Yuzawa
a010f387b3 Specify JVM process by 'jps' application name (#346) 2020-09-03 00:17:46 +03:00
Andrey Pangin
61d5cdcd68 Profile multiple events from the command line 2020-08-30 03:02:43 +03:00
Andrey Pangin
2b14ee69ef #340: UnsatisfiedLinkError when attaching method profiler 2020-08-12 23:02:26 +03:00
Andrey Pangin
048b54621d Redo fix for #328 2020-08-12 23:02:05 +03:00
Andrey Pangin
94d406c531 Revert README 2020-08-11 22:37:10 +03:00
Andrey Pangin
800580bb30 Release 1.8 2020-08-10 23:18:20 +03:00
Andrey Pangin
8cecd2df9b #339: Report invalid interval argument 2020-08-09 18:50:59 +03:00
Andrey Pangin
d86883043a #330: Release packages should be extracted into separate folder 2020-08-09 18:35:26 +03:00
Andrey Pangin
d0772ba62c Added collapsed->FlameGraph converter 2020-08-09 16:06:13 +03:00
Andrey Pangin
d6d4a3c2a3 FlameGraph: skip empty lines 2020-08-06 23:07:28 +03:00
Andrey Pangin
49f9050bf5 FlameGraph minor cosmetic changes 2020-08-06 01:55:47 +03:00
Andrey Pangin
67b77b9645 Improved FlameGraph converter 2020-08-05 02:04:19 +03:00
Andrey Pangin
971fc85d1c FlameGraph 2.0 on HTML5 canvas 2020-08-04 23:26:04 +03:00
Andrey Pangin
50b9fe4d85 Merged with master 2020-08-04 19:52:27 +03:00
Jason Zaugg
f9db1099f9 Register natives one-by-one to support partial Java API implementations (#337) 2020-08-04 19:05:08 +03:00
Andrey Pangin
adce201837 #335: Fixed unsafe thread local storage access 2020-07-29 23:26:39 +03:00
Andrey Pangin
a905d50e00 #328, #14: Fixed long attach time and slow class loading 2020-07-29 22:20:45 +03:00
Andrey Pangin
f006e00443 async-profiler 2.0: Record cpu+alloc+lock in a single JFR. No framebuffer/hashtable limits. 2020-07-27 01:35:07 +03:00
Andrey Pangin
5ef449c2ed #335: Do not restart poll() calls with finite timeout 2020-07-20 00:14:48 +03:00
Andrey Pangin
d9ca3e42a8 #329: Support both ARM and THUMB flavors of JDK binaries 2020-06-19 02:37:19 +03:00
Claus F. Strasburger
269bef2867 profiler.sh: work on minimal systems (#303) 2020-06-15 00:28:30 +03:00
Andrey Pangin
e62cb2cfd1 #327: Per-thread reverse flamegraph / call tree 2020-05-31 08:34:13 +03:00
Andrey Pangin
7135840f70 Compilation fix 2020-05-25 01:32:53 +03:00
Andrey Pangin
31ddc2f562 #248: Converter to the format supported by FlameScope 2020-05-25 00:33:42 +03:00
Sergei Egorov
a5beee66ff Update the Java Agent options link to the latest release (#322) 2020-05-17 23:35:36 +03:00
Andrey Pangin
c15439348f javadoc comment 2020-05-17 15:15:36 +03:00
Andrey Pangin
17fe36e43e Release 1.7.1 2020-05-14 03:53:03 +03:00
Andrey Pangin
5312a793ec 'safemode' option to disable stack recovery techniques 2020-05-14 02:34:30 +03:00
Kirill Timofeev
4d43db91e1 Ensure code blob exists properly (#316)
Check that code blob is not removed to avoid returning NULL
when another code blob loaded at similar address range
that was used by removed one

Co-authored-by: Simon Ogorodnik <Simon.Ogorodnik@jetbrains.com>
2020-05-13 21:49:29 +03:00
Andrey Pangin
0020af54a3 LBR call stack support (--cstack lbr) 2020-04-21 23:47:53 +03:00
Andrey Pangin
f67d392ad8 Synthesize symbol names for PLT entries 2020-04-21 22:39:13 +03:00
Andrei Pangin
ff70da1736 Added --filter <thread-ids> for wall-clock profiling mode (#315) 2020-04-15 00:27:56 +03:00
Andrey ``Bass'' Shcheglov
07438daa70 Clean up the Makefile (#314) 2020-04-15 00:26:01 +03:00
Andrey Pangin
e1e8aa068a #310: Fixed crash on Zing 2020-03-27 18:20:12 +03:00
Andrey Pangin
a2691f919e #309: README paragraph about missed output file 2020-03-19 18:25:08 +03:00
Andrey Pangin
f496a167fe Release 1.7 2020-03-17 23:29:44 +03:00
Andrey Pangin
d9a1252550 JDK 14 compatibility: late load of libjava.so 2020-03-17 22:58:59 +03:00
Andrey Pangin
f3ca611267 Flush profiler output on 'version' command 2020-03-16 00:33:09 +03:00
Andrey Pangin
119da0fcb2 Replace unsafe calls to JVMTI GetStackTrace with manually patched AsyncGetCallTrace 2020-02-29 01:54:51 +03:00
Andrey Pangin
6bb7f749c9 Fixed build 2020-02-24 21:41:54 +03:00
Andrey Pangin
9593745098 #187: Filter stack traces by the given name pattern 2020-02-24 19:54:49 +03:00
Felix Barnsteiner
e891ecd9da Add NOTICE with CDDL license header (#301) 2020-02-19 20:20:23 +03:00
Andrey Pangin
b8493976b6 Version 1.7-ea3 2020-02-17 00:07:11 +03:00
Andrey Pangin
54b85dc718 'check' command to test if the specified event is available without starting profiler 2020-02-16 23:26:51 +03:00
Andrey Pangin
675a28fdc2 #300: Method invocation profiling by pattern: '-e java.io.File.*' 2020-02-16 20:52:53 +03:00
Andrey Pangin
1a4437999b Use different signals for cpu/wall/alloc to allow profiling multiple events at the same time 2020-02-16 20:00:59 +03:00
Andrey Pangin
ee2438e25f #277: Removed AsyncProfiler.getNativeThreadId() 2020-02-16 19:00:34 +03:00
Andrey Pangin
cd062fead9 Allow mangled function names, e.g. -e VMThread::execute 2020-02-16 18:11:02 +03:00
Andrey Pangin
156389f11a #133: An option to exclude native stack frames 2020-02-16 04:13:23 +03:00
Andrey Pangin
8373224395 #293: Allow shading of AsyncProfiler API (automatic discovery of the class with native methods) 2020-02-16 02:16:23 +03:00
Andrey Pangin
fe6c4ddeda Version 1.7-ea2 2020-02-13 03:15:28 +03:00
Andrey Pangin
98ac0c58d6 #281: Note about aqua/brown frames in an allocation Flame Graph 2020-02-13 02:35:45 +03:00
Andrey Pangin
b57106b858 Reduce the number of native methods in Java API 2020-02-13 02:16:52 +03:00
Andrey Pangin
c204e28348 #277: Record Java Thread ID in JFR output 2020-02-12 04:43:27 +03:00
Andrey Pangin
fc17386ec0 Display native thread names 2020-02-12 03:40:01 +03:00
Andrey Pangin
49d86abc6c #252: Record thread names in JFR output 2020-02-12 02:35:55 +03:00
Andrey Pangin
9fc97fc681 #279, #287, #296: Wall clock profiler improvements:
- stable interval
 - thread states (runnable vs. sleeping)
 - Java API to update set of monitored threads
2020-02-11 03:26:19 +03:00
Andrey Pangin
9b24fdef99 #290: Remove problematic optimization 2020-01-31 03:53:12 +03:00
Andrey Pangin
e37059d409 Version 1.7-ea 2020-01-27 01:49:06 +03:00
Andrey Pangin
869058b56b #295: Workaround for JDK 11 bug in ServerSocket.accept() 2020-01-27 01:28:47 +03:00
Andrey Pangin
11b0f4598e Correct stack traces when executing VM runtime code 2020-01-24 03:26:26 +03:00
Andrey Pangin
cc9cee7bec Fixed instrumentation of anonymous classes 2020-01-23 03:01:02 +03:00
Andrey Pangin
5ae46d2312 #290: CodeCache for compiled methods and runtime stubs must be handled separately 2020-01-21 03:34:52 +03:00
Andrey Pangin
776b5597bf #286: Enable CPU profiling on WSL 2020-01-07 20:38:00 +03:00
Andrey Pangin
e282b76880 #286: Add an error message if ITIMER_PROF is not supported 2020-01-07 19:14:18 +03:00
Andrey Pangin
e47d7f408f #277: getNativeThreadId() now returns tid of the current Thread 2020-01-07 18:36:19 +03:00
KUBOTA Yuji
5044869ecd Make release version of javac configurable (#285) 2020-01-06 18:59:58 +03:00
Andrey Pangin
9516f54311 Better C stack in allocation profiling mode 2020-01-06 05:17:56 +03:00
Andrey Pangin
9bd414411f --cstack option to collect native stack traces for Java-level events 2020-01-06 04:36:35 +03:00
Andrei Pangin
0334c5900e Profile invocations of an arbitrary Java method
I.e. record all stack traces, where the methods is called.
2020-01-06 03:20:31 +03:00
Andrey Pangin
f502979135 Enable allocation profiling on Zing 2019-12-23 00:34:04 +03:00
Andrey Pangin
d89fc3fbdc Java thread <-> VMThread bridge 2019-12-22 23:51:23 +03:00
Andrey Pangin
9279531cf8 #277: Java API for getting native thread ID 2019-12-22 21:59:26 +03:00
Per Lundberg
9de1a63542 README.md: minor grammar fixes (#283) 2019-12-18 14:07:25 +03:00
Andrey Pangin
21af257716 #271: Reduce the amount of unknown_Java even more 2019-12-17 01:49:06 +03:00
Andrey Pangin
28ed6f490e Intercept Thread.setNativeName 2019-12-17 01:34:57 +03:00
Andrey Pangin
dc4f01dd14 Removed syncwalk argument (no longer needed) 2019-12-13 03:57:58 +03:00
Andrey Pangin
1e3a4b77ee #271: Further reduce the amount of unknown/not_walkable frames 2019-12-13 03:52:00 +03:00
Andrey Pangin
4cec7a3bb0 #271: Reduce the amount of unknown_Java frames 2019-12-12 02:45:51 +03:00
tomgoren
2557363892 Update README with elevated permissions syntax updates and extra Docker runtime flag (#270) 2019-11-28 01:36:10 +03:00
Andrey Pangin
78035134f4 #263: Replace non-printable characters in function names 2019-11-23 18:22:18 +03:00
Andrey Pangin
0ef1122a3b #266: Fix [unknown] frames due to kptr_restrict 2019-11-17 05:36:47 +03:00
RoySunnySean007
8bb57de1d1 #265: Update README.md about installing debuginfo package 2019-11-14 13:09:59 +03:00
Andrey Pangin
11d74b73af #262: Fixed NativeLibrary.load0 signature on JDK 9+ (NoSuchMethodError) 2019-10-28 02:02:55 +03:00
Andrei Pangin
a759960bb0 Removed the note about reducing TLAB size
Reducing TLAB size in production is discouraged.
2019-10-26 04:29:26 +03:00
Andrey Pangin
7edcd2660a #256: Fixed crash on Zing JVM 2019-10-17 04:51:37 +03:00
Andrey Pangin
a97a5cae13 #255: Truncate too long signatures 2019-10-15 02:47:13 +03:00
Dmitry Timofeev
d2e7e2718c Document the placeholders in the file name (#254) 2019-10-12 18:51:08 +03:00
Andrey Pangin
e1c3100c60 Fixed lock profiling on some macOS JDK builds 2019-10-04 19:29:47 +03:00
Andrey Pangin
93c63d50d5 --sync-walk option to use alternative stack walker in expert mode 2019-09-30 21:04:42 +03:00
Andrey Pangin
7e6db636d8 #250: Print error message when failed to parse symbols due to the OS bug 2019-09-29 22:04:57 +03:00
Andrey Pangin
78a83a31b2 #250: Fixed mmap bug when parsing symbols 2019-09-28 12:07:39 +03:00
Andrey Pangin
adcf89234b Release 1.6 2019-09-09 16:52:39 +03:00
Andrey Pangin
b7e9e6b955 #192: Pause/resume profiling 2019-09-07 13:05:39 +03:00
Andrey Pangin
19e16dc973 #211: The agent autodetects output format by the filename 2019-09-03 03:38:20 +03:00
Andrey Pangin
84602f8660 Added download links to 1.6-ea 2019-09-02 03:28:29 +03:00
Andrey Pangin
f5850e6f3b 1.6-ea release 2019-09-02 02:50:25 +03:00
Andrey Pangin
c14f9a9feb #146: Manage sampling rate of allocation profiling 2019-09-02 02:31:00 +03:00
Andrey Pangin
bbad5d835b #213: An option to print method signatures (-g) 2019-09-02 00:47:37 +03:00
Andrey Pangin
f03fdae8df Updated FlameGraph script 2019-09-01 21:50:17 +03:00
Andrey Pangin
2159c7fd33 #242: [macos] Must not use pthread_mach_thread_np inside signal handler 2019-09-01 15:56:05 +03:00
Andrey Pangin
b66f920422 Reset PerfEvents ring buffer, even when cannot collect the stack trace 2019-08-29 00:30:09 +03:00
Andrey Pangin
45e53b83f9 #208: Expand %p (pid) and %t (timestamp) in the output file names 2019-08-27 03:25:46 +03:00
Andrey Pangin
d265d142e6 Fixed use of stale pointer when copying arguments 2019-08-26 02:49:55 +03:00
Andrey Pangin
5f94f6ee50 #229: Treat -f filename relative to the current shell directory 2019-08-25 20:57:51 +03:00
Andrey Pangin
e7150f1b5e #232: Increase maximum Java stack depth with -j option 2019-08-25 19:50:26 +03:00
Andrey Pangin
e89d41de54 #233: Parse symbols of JNI libraries loaded in runtime.
Improve second start time.
2019-08-25 18:24:11 +03:00
Andrey Pangin
7049adb202 #227: JFR fix 2019-08-11 17:21:47 +03:00
Andrey Pangin
99926e5c74 Java API execute() method may spoil JFR output 2019-07-16 23:50:15 +03:00
Andrey Pangin
37777104fd #220: 'profiler.sh status' shows wrong event 2019-05-25 22:28:28 +03:00
Andrey Pangin
a518d93ec8 Updated README to reflect #212, #218 2019-05-20 17:15:01 +03:00
Andrey Pangin
936a9fea8d #197: Can't get cache-misses on AArch64 2019-05-20 03:18:03 +03:00
Andrey Pangin
47c576e552 Fixed corner cases with no JVM debug symbols 2019-05-20 03:17:42 +03:00
Andrey Pangin
df05305642 #219: Fix JDK 11 macOS crash when profiling native thread 2019-05-20 02:42:38 +03:00
Janusz Dziemidowicz
d7a7a04684 Add Java 12 memory profiling support (#217) 2019-05-08 00:21:08 +03:00
Andrey Pangin
9c04d76392 #123, #163, #202: Include all AsyncGetCallTrace failures in the profile 2019-02-18 01:20:37 +03:00
Andrey Pangin
9df5518cd7 #199: Return error when failed to allocate frame buffer 2019-02-17 01:47:48 +03:00
Andrey Pangin
f77f2d1afb #200: Added JATTACH_PATH env variable to override attach socket directory 2019-02-17 01:29:22 +03:00
Andrey Pangin
cef015d25f Fixed race condition when creating perf_events descriptor 2019-01-25 04:10:57 +03:00
Andrey Pangin
861598538a #196: thread-smoke-test should do some real work 2019-01-24 01:54:20 +03:00
Andrey Pangin
34da66f6fd Updated comment about OpenJDK debug symbols 2019-01-22 03:36:43 +03:00
Dmitriy Dumanskiy
cc4126911e Extended troubleshooting section (#194) 2019-01-22 03:16:04 +03:00
149 changed files with 20201 additions and 6473 deletions

21
.github/workflows/cpp.yml vendored Normal file
View 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
View File

@@ -1,5 +1,8 @@
/build/
/nbproject/
/out/
/target/
/.idea/
/test/*.class
.vscode
*.iml

View File

@@ -1,6 +1,6 @@
language: cpp
dist: precise
dist: bionic
sudo: required

View File

@@ -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
View File

@@ -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
View File

@@ -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: [![Build Status](https://travis-ci.org/jvm-profiling-tools/async-profiler.svg?branch=master)](https://travis-ci.org/jvm-profiling-tools/async-profiler)
Build status: [![Build Status](https://github.com/jvm-profiling-tools/async-profiler/actions/workflows/cpp.yml/badge.svg?branch=master)](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
```
![Example](https://github.com/jvm-profiling-tools/async-profiler/blob/master/demo/SwingSet2.svg)
[![Example](https://github.com/jvm-profiling-tools/async-profiler/blob/master/demo/flamegraph.png)](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.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 141 KiB

882
demo/flamegraph.html Normal file
View 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'>&#x1f53b;</button>&nbsp;&nbsp;<button id='search' title='Search'>&#x1f50d;</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'>&#x274c;</span></p>
<p id='status'>&nbsp;</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

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -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
View 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
View 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>

View File

@@ -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//&/&amp;}
TITLE=${TITLE//</&lt;}
TITLE=${TITLE//>/&gt;}
TITLE=${TITLE//,/&#44;}
TITLE="$(echo "$2" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/,/\&#44;/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

View File

@@ -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();
}
}

View File

@@ -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

View 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);
}

View File

@@ -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);

View File

@@ -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";
}

View File

@@ -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

View File

@@ -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(&timestamp, &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;
}

View File

@@ -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
View 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(&params);
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
View 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
View 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
View 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

View File

@@ -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;
}

View File

@@ -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

View 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();
}
}

View 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();
}
}
}

View 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
View 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");
}
}

View 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
View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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);
}
}

View 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) {
}
}

View 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;
}
}

View 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"));
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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() {
}

View File

@@ -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
View 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

View 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

View 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
View 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

View 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__

View File

@@ -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, '&', "&amp;");
replace(s, '<', "&lt;");
replace(s, '>', "&gt;");
}
};
@@ -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, '&', "&amp;", 5);
StringUtils::replace(name, '<', "&lt;", 4);
StringUtils::replace(name, '>', "&gt;", 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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

Binary file not shown.

View 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();
}

Binary file not shown.

View 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;
}
}

Binary file not shown.

View 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
View 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
View 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
View 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

View File

@@ -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, &notif);
}
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();
}

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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

View 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;
}

View 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
View 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
View 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

View File

@@ -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();
}

View File

@@ -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
View 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
View 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
View 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
View 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
View 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