Compare commits

...

434 Commits

Author SHA1 Message Date
Andrei Pangin
c0ded32547 Bypass dynamic agent loading restriction 2024-09-06 13:30:42 +01:00
Kerem Kat
b9a3737531 Avoid JVM crash by deleting JNI refs after calling GetMethodDeclaringClass (#981)
This is a partial fix, complementing JDK-8268364.
2024-09-04 18:00:12 +01:00
Soumadipta Roy
d92b893826 Add flamegraph to collapsed conversion in test framework (#976) 2024-08-23 14:38:15 +01:00
Alih789
b8025a2449 Make JfrClass field method public so that G1HeapSummary class has access to determine difference in fields based on jdk version. (#971) 2024-08-14 22:58:22 +01:00
Andrei Pangin
02670ccde5 Fixed out-of-bounds array access in getDebuginfodCache() 2024-08-14 19:13:35 +01:00
Andrei Pangin
76311145c8 #843: Fix race between parsing and concurrent unloading of shared libraries 2024-08-12 02:01:47 +01:00
Andrei Pangin
179b0b1285 Minor stylistic changes 2024-08-07 07:49:14 +01:00
Kerem Kat
40118962ad #929: Load symbols from debuginfod cache (#962) 2024-08-07 07:09:17 +01:00
Andrei Pangin
321a712ff8 Added Native API test 2024-07-26 14:41:40 +03:00
Andrei Pangin
78123a85a7 Fixed parsing non-PIC executables 2024-07-26 14:37:09 +03:00
Andrei Pangin
7f712bb4e9 Fixed recursion in pthread_create when using native profiling API 2024-07-26 14:35:38 +03:00
Andrei Pangin
09f0a8fef7 #959: JFR converter fails to filter thread states in jfrsync profiles 2024-07-18 17:49:11 +01:00
Andrei Pangin
6207d5dc86 Fix unsafe access to CodeCache 2024-07-16 21:59:18 +01:00
Andrei Pangin
c9cb0c1cb2 Minor cleanup 2024-07-16 21:58:47 +01:00
Long Yang
bd095f13ec #955: Add --libpath option to specify the path of libasyncProfier.so in the container 2024-07-15 15:22:21 +01:00
Andrei Pangin
174e295ffb Test framework cleanup and fixes 2024-07-14 23:44:46 +01:00
Soumadipta Roy
76c2024e83 Test framework and a set of new tests (#812, #951) 2024-07-09 15:59:26 +01:00
Andrei Pangin
e38cd32e70 #952: Docker image for building async-profiler release packages for x64 and arm64 2024-07-07 16:16:11 +01:00
Andrei Pangin
c25345454a #952: Solve musl and glibc compatibility issues 2024-07-07 16:08:52 +01:00
Andrei Pangin
4e9f0961c0 Simplify Makefile 2024-06-27 03:03:20 +01:00
Andrei Pangin
0d90f1c817 Do not package redundant .md files 2024-06-25 13:52:48 +01:00
Andrei Pangin
df4af83dc2 Added issue and pull request templates 2024-06-25 13:42:27 +01:00
Andrei Pangin
f4528dec7c Added CoC and Contributing Guidelines 2024-06-25 09:42:42 +01:00
Andrei Pangin
30f4f321b8 Cleanup dead code 2024-06-25 09:03:40 +01:00
Andrei Pangin
28fbb132c5 Handle truncated JFRs 2024-06-22 00:56:09 +01:00
Andrei Pangin
9660e15b1e Fix jfrconv build rules for macOS 2024-06-19 01:41:31 +01:00
Andrei Pangin
33a65c8dac #895: Rewrote jfrconv to make it a statically linked executable 2024-06-16 22:16:50 +01:00
Andrei Pangin
dbcaa4d81a JfrReader should jump over custom events 2024-06-11 23:08:50 +01:00
Andrei Pangin
475343646c JfrReader should read strings in a constant pool 2024-06-11 15:41:39 +01:00
Andrei Pangin
2f30dc3f9b #940: lseek64 is not declared when compiling on Alpine/aarch64 2024-06-01 23:05:45 +01:00
Andrei Pangin
34a9e1354a #923: cstack=vm support for JDK 23+ 2024-05-24 01:57:00 +01:00
Andrei Pangin
0c4cd5cb14 #934: Fix crash on Zing in a native thread 2024-05-23 02:29:52 +01:00
Andrei Pangin
048cafa4ef #923: Adjust VMStructs for JDK 23 2024-05-13 01:32:28 +01:00
Andrei Pangin
ed7d848061 #928: Do not record live object samples between JFR chunks 2024-05-12 00:59:05 +01:00
Andrei Pangin
b96d09a883 Minor refactoring 2024-05-12 00:45:20 +01:00
Andrei Pangin
9733a08e93 An option to accumulate JFR events in memory instead of flushing to a file (#925) 2024-05-07 23:58:55 +01:00
Andrei Pangin
07e6015d1d #921: Fix compilation failure on ARM32 2024-05-03 03:08:46 +03:00
Andrei Pangin
26a94a1839 Build jfrconv.exe for Windows 2024-05-02 02:49:21 +03:00
Andrei Pangin
2de2a187cc jfrconv: autodetect pb.gz output format 2024-05-02 02:47:57 +03:00
Andrei Pangin
ac514e57e9 #914: Do not set DebugNonSafepoints, if it is already set in the Command Line 2024-04-19 02:11:18 +01:00
Andrei Pangin
059cf1367f #917: Allow -e alloc,cpu,lock syntax 2024-04-18 23:37:41 +01:00
Andrei Pangin
005f7a3dc3 Workaround dependency on GLIBCXX_3.4.20 2024-04-10 16:30:55 +01:00
Andrei Pangin
978fe2ccde Pack stack traces in pprof format 2024-03-26 03:55:27 +00:00
Vsevolod Tolstopyatov
5da0a5f3f3 FlameGraph: search with Command+F key (⌘F) on mac (#906) 2024-03-22 22:52:34 +00:00
Andrei Pangin
241f5e3965 Build failure due to missing -ldl 2024-03-19 22:01:29 +00:00
Andrei Pangin
a75ebc7786 #905, #895: Converter enhancements 2024-03-16 02:50:01 +00:00
Andrei Pangin
397f450809 #759: Discover available profiling signal automatically 2024-03-16 00:02:41 +00:00
Andrei Pangin
1be8838964 Do not parse the same executable twice 2024-03-15 03:07:30 +00:00
Andrei Pangin
cbecc6f8b4 #893: Fix [no_Java_frame] on ARM64 2024-03-10 22:07:04 +00:00
Andrei Pangin
ddc280dfc4 #896: Flame Graph: alt+click to remove stacks 2024-02-28 19:30:56 +00:00
Andrei Pangin
7be97a0aa3 Fix DefineClass crash on OpenJ9 2024-02-23 02:43:47 +00:00
Andrei Pangin
9fe16b6eb5 #892: Resolve tracepoint id in asprof 2024-02-21 00:04:25 +00:00
Andrei Pangin
6248a879f9 Updated comment on -j option 2024-02-17 00:38:11 +00:00
Charm
3b2d969b49 Fix memory leak of keys from jvmti->GetSystemProperties (#891) 2024-02-14 12:55:25 +00:00
Chuan-kai Lin
ad05845b98 Fix Proto encoding of negative integers (#889) 2024-02-07 20:37:03 +00:00
Andrei Pangin
e751ea1b0d #886: Support cstack=vm for slowdebug JVM 2024-02-06 02:37:28 +00:00
Andrei Pangin
deec21814b #884: Record event timestamps early 2024-02-06 00:52:20 +00:00
Andrei Pangin
f657048f16 #885: Print error message if JVM fails to load libasyncProfiler.so 2024-02-05 01:35:34 +00:00
Andrei Pangin
03004b6505 #769: Workaround for JDK-8312065 on JDK 8 2024-01-28 18:06:55 +00:00
Andrei Pangin
7643623628 #872: Get rid of libstdc++ streams 2024-01-27 23:51:15 +00:00
Andrei Pangin
a17529378b #881: --to/from arguments did not work with jfrsync 2024-01-24 00:34:58 +00:00
Andrei Pangin
985738ec37 Replace source/target with release in pom.xml 2024-01-22 22:49:12 +00:00
Andrei Pangin
4e441b4024 Release 3.0 2024-01-20 23:17:15 +00:00
Andrei Pangin
76012dc568 Fix INCBIN macro usage 2024-01-20 22:59:13 +00:00
Andrei Pangin
a76d06f1f2 Update CHANGELOG and README 2024-01-20 18:53:42 +00:00
Andrei Pangin
81ad77eadd Delete travis config 2024-01-20 15:47:52 +00:00
Andrei Pangin
9fad48890c Use shorter variant of copyright headers 2024-01-20 15:34:53 +00:00
Andrei Pangin
d23121855a Update sample flamegraph 2024-01-20 14:59:38 +00:00
Andrei Pangin
fdb4bb4819 Update links to the github repo 2024-01-20 13:51:32 +00:00
Andrei Pangin
8fd1db0d8d Sync jattach sources with jattach repo 2024-01-20 13:39:19 +00:00
Tolya Korniltsev
77140be5fa jfr2pprof: Fix multichunk jfr symbols and location's line numbers (#879) 2024-01-17 02:24:00 +00:00
Andrei Pangin
7b50b1d834 #819: Extend AArch64 stack walking fix to cstack=dwarf 2024-01-16 23:49:01 +00:00
Andrei Pangin
b653d28eae #819: Set default DWARF unwind table for vDSO 2024-01-15 20:36:01 +00:00
Andrei Pangin
9e4f2a6af6 #819: Fix AArch64 stack unwinding with cstack=vm 2024-01-15 15:44:02 +00:00
Long Yang
8910a7a462 #873: the accuracy issue of ObjectSampler in --total mode (#876) 2024-01-05 18:44:29 +00:00
Andrei Pangin
915b09067c #726: Allow LD_PRELOAD of JVM TI agent 2024-01-03 02:31:56 +00:00
Andrei Pangin
1a5d74e23c #841: Fix LD_PRELOAD support 2024-01-01 20:52:29 +03:00
Andrei Pangin
809a19ce8f asprof help misses cstack=vm 2023-12-19 23:45:27 +00:00
Andrei Pangin
fcdb4aeec7 Automatically shutdown profiler upon reaching FD limit 2023-12-18 00:18:20 +00:00
Andrei Pangin
9e874cb94b #864: Reduce Flame Graph size 2023-12-16 03:09:54 +00:00
Andrei Pangin
f099abe619 Normalize Lambda names from original JFR recordings 2023-12-15 04:41:17 +00:00
Andrei Pangin
d3dde7e5e7 Fix stack unwinding from nmethod_entry_barriers on JDK 21 2023-12-15 03:53:36 +00:00
Andrei Pangin
9b5dc907a5 Fix detection of JIT compilation level on JDK 21 2023-12-15 01:10:45 +00:00
Andrei Pangin
5da01c0a44 #864: Deduplicate strings in a Flame Graph 2023-12-12 01:47:41 +00:00
Andrei Pangin
b8a60e66de #863: asprof to run jattach commands 2023-12-10 03:32:25 +00:00
Andrei Pangin
02c5934300 Fix ActiveSetting value for cstack=vm in JFR recording 2023-12-05 12:57:07 +00:00
Suresh
29b78f4670 Make converter Arguments class public (#859) 2023-12-04 21:24:31 +00:00
Andrei Pangin
ff0d86b8e7 #831: Workaround for JDK-8313816 2023-12-04 13:28:05 +00:00
Andrei Pangin
aa0305965f Updated supported platforms and change log 2023-12-04 00:10:11 +00:00
Andrei Pangin
4397b70894 #857: Support JFR recordings from JDK 22 2023-12-03 17:45:40 +00:00
Andrei Pangin
8e0b0953f6 #832: Normalize names of hidden classes / lambdas 2023-12-03 02:18:48 +00:00
Andrei Pangin
db9c4c4c56 #783: Shutdown asprof gracefully on SIGTERM 2023-12-03 01:52:38 +00:00
Andrei Pangin
3d28531e26 #832: Normalize names of hidden classes / lambdas 2023-12-03 00:58:34 +00:00
Andrei Pangin
bfdaa44260 #849: Parse concatenated multi-chunk JFRs 2023-12-03 00:39:40 +00:00
Andrei Pangin
ab6a7c9bd5 #853: Workaround for JDK-8321116 2023-12-01 21:07:47 +00:00
Leslie Zhai
104b7bda5a Initial loongarch port (#770) 2023-12-01 09:42:43 +00:00
Andrei Pangin
8d77e0909c #855: ctimer mode for accurate profiling without perf_events 2023-12-01 01:44:53 +00:00
Andrei Pangin
a1354b6a75 Restructure perfEvents sources; fix off-by-one bug 2023-11-28 01:49:54 +00:00
Andrei Pangin
0d0f0f0c67 #644: Fixed RISC-V build and tests 2023-11-26 03:25:04 +00:00
Aleksey Shipilëv
752b79ec4e Basic RISC-V support (#644) 2023-11-26 03:22:01 +00:00
Thomas Matthijs
48a97b64e7 #827: Handle both -jar and -cp when running converter.jar 2023-11-24 15:28:34 +00:00
Andrei Pangin
4e13b8138b #803: --loop cannot be combined with --fdtransfer 2023-11-24 01:27:44 +00:00
Andrei Pangin
b1b5b9898f #840, #637: loop and timeout options should work regardless of how the profiler was started 2023-11-24 00:43:33 +00:00
Andrei Pangin
6ab9f83aec #834: Protect VMStructs::initLogging from OutOfMemoryError 2023-11-03 02:16:26 +00:00
Andrei Pangin
49d08fd068 Fixed macOS build 2023-11-03 01:57:04 +00:00
Andrei Pangin
a237cad115 #833: Time-to-safepoint JFR event 2023-11-02 01:20:00 +00:00
Andrei Pangin
50e63b7ad6 Demangle Rust symbols correctly and shorten C++ signatures by default 2023-11-01 00:32:56 +00:00
Andrei Pangin
c2fed3b63a Improve DWARF parser on AArch64 2023-10-31 01:43:01 +00:00
Andrei Pangin
d29cb86bd0 Fixed build with MERGE=false 2023-09-20 23:32:30 +01:00
Andrei Pangin
326d797d5a Restart interrupted poll/epoll_wait manually 2023-09-20 23:21:08 +01:00
Andrei Pangin
cb95d37b84 Hardware breakpoints stuck in infinite loop on ARM64 2023-09-05 14:27:13 +01:00
Andrei Pangin
e799c2a665 fastThreadId fix 2023-09-05 14:26:23 +01:00
Andrei Pangin
56a50a8002 Handle AArch64-specific DWARF opcode 2023-09-05 01:44:49 +01:00
Andrei Pangin
f483c166ba Fixed AArch64 arraycopy stack walking on JDK 8 2023-09-04 17:47:02 +01:00
Andrei Pangin
3c26f5138a Resolved cyclic dependency in VMStructs initialization 2023-09-04 17:23:59 +01:00
Andrei Pangin
02e92eb45c Allow profiling startup allocations on JDK 11+ 2023-09-01 18:03:19 +01:00
Andrei Pangin
902d17d0b1 Workaround for JDK-8313796 2023-09-01 13:03:29 +01:00
Olga Stogova
a97cf59f5b Refresh design of tree view (#808) 2023-08-31 11:30:41 +01:00
Andrei Pangin
5fc3c975fe Unwind copy_longs stubs on AArch64 2023-08-31 03:07:32 +01:00
Andrei Pangin
e79c44eb38 Fixed attach to OpenJ9 on macOS 2023-08-30 02:45:45 +01:00
Andrei Pangin
77a308a7b9 Fixed DWARF stack walking on macOS/ARM64 2023-08-30 02:14:32 +01:00
Andrei Pangin
d1de831e6d #805: Lock profiling optimizations 2023-08-28 01:13:49 +01:00
Andrei Pangin
847504dfd4 Change profiler.sh to asprof in the documentation 2023-08-27 22:13:38 +01:00
Andrei Pangin
dcc3ffd083 NPE when closing JfrReader 2023-08-24 12:17:00 +01:00
Andrei Pangin
0a5ac270a0 #450: Close log file deleted by the launcher 2023-08-23 00:21:04 +01:00
Andrei Pangin
62b2387a80 #791, #800: Fix path translation for containers 2023-08-20 16:24:00 +01:00
Andrei Pangin
409a3e3b19 Build for Java 8 by default, but release for Java 7 2023-08-20 02:03:15 +01:00
Andrei Pangin
26954081d3 Workaround for JDK-8237858 on AArch64 2023-08-18 17:47:42 +01:00
Andrei Pangin
98ad173d24 Bump up major version 2023-08-17 16:41:51 +01:00
Andrei Pangin
6e014258e3 #795: Experimental option to obtain stack traces without AsyncGetCallTrace 2023-08-16 16:50:45 +01:00
Andrei Pangin
625dcd2615 Compiler warnings: sprintf -> snprintf 2023-08-11 14:00:07 +01:00
Andrei Pangin
a852bdd22e #747: Workaround for JDK-8307549 2023-08-02 16:38:30 +01:00
Andrei Pangin
eb2879939b Fixed clang compilation failure 2023-07-31 00:28:40 +01:00
Andrei Pangin
db457f3f8b #777: Show JIT compilation task (#785) 2023-07-30 20:27:37 +01:00
Andrei Pangin
25587664ab Fix for displaying vtable targets on JDK 21 2023-07-26 00:52:33 +01:00
Andrei Pangin
14c495dc53 #776: Annotate JFR unit types with @ContentType 2023-07-22 22:17:23 +01:00
Andrei Pangin
27e2d4cb4b Run tests with loglevel=error 2023-07-21 19:02:20 +01:00
Andrei Pangin
3c33c755be Fix for #761: relocate addresses in .dynamic section properly 2023-07-16 15:24:39 +01:00
Andrei Pangin
7a52d79b16 Support UseCompressedObjectHeaders aka Lilliput 2023-07-11 16:05:31 +01:00
Andrei Pangin
117594bb4d #759: Configure alternative profiling signal 2023-07-04 14:19:08 +01:00
Andrei Pangin
89bf3eabc0 #761: Parse dynamic linking structures 2023-07-03 17:28:04 +01:00
Andrei Pangin
f9138bd7f6 Avoid chain reaction when profiling context-switches 2023-06-16 15:06:37 +01:00
Andrei Pangin
557a15f47f Return error if profiling mode is not supported with non-Java processes 2023-06-12 16:32:48 +01:00
Andrei Pangin
d46d6c67c0 #751: Profile non-Java processes 2023-06-12 15:07:48 +01:00
Andrei Pangin
03dc1b9c98 jfr2flame converter adds redundant 'tid' to thread names 2023-06-02 02:45:45 +01:00
Andrei Pangin
5d24f85d1b Compatibility with instrumenting agents 2023-06-02 02:37:05 +01:00
Andrei Pangin
cb1c00013f Improve reader support for custom JFR events. Add CPULoad and ObjectCount events. 2023-05-21 01:05:32 +01:00
Andrei Pangin
e53eab62e4 Add support for custom events in JfrReader 2023-05-19 02:22:39 +01:00
Andrei Pangin
d8f1b9c15a #750: --jfrsync may specify a list of JFR events 2023-05-19 00:37:32 +01:00
Andrei Pangin
567b97d223 Fixed crash with combined cpu+wall profiling 2023-05-17 16:14:53 +01:00
Kirill Timofeev
c7dd73ca6e "check" action prints result to a file (#684) 2023-05-13 01:38:24 +01:00
Alkis Evlogimenos
bbc1b3270f Compile with -std=c++2a without errors (#721) 2023-05-13 01:34:41 +01:00
Andrei Pangin
3bdf9db876 #689: Calculate ELF base address correctly 2023-05-13 01:24:58 +01:00
Andrei Pangin
c6a43ae2ee Compilation fix 2023-05-04 01:13:03 +01:00
Andrei Pangin
6c9b71594c Support GCHeapSummary event in JfrReader 2023-05-04 01:09:28 +01:00
Andrei Pangin
1cb2c05161 Fixed flaky test 2023-05-04 00:24:07 +01:00
Andrei Pangin
6dfd2159fe Fixed allocation profiling on JDK 20.0.1 2023-05-03 23:56:38 +01:00
Andrei Pangin
bdceedda58 Draft Release 2.10 2023-04-25 08:46:30 +02:00
Johannes Bechberger
f57d3dcfda Fix build when the main folder contains flame.html or tree.html files (#743) 2023-04-20 12:51:14 +01:00
Andrei Pangin
39b0bdb5e4 Record GCHeapSummary events in JFR 2023-04-03 02:54:03 +01:00
Andrei Pangin
ac2eabfe96 #740: Profile CPU + Wall clock together 2023-04-02 01:25:10 +01:00
Andrei Pangin
f64eed870a #739: JFR overflow fix 2023-04-01 21:36:34 +01:00
Andrei Pangin
d51f445243 #708: Fix deadlock between Profiler::stop and timerLoop 2023-03-31 00:50:22 +01:00
Andrei Pangin
578e3a1162 #734: Raw PMU event descriptors 2023-03-28 04:31:42 +01:00
Andrei Pangin
6b35b46070 #736: Show targets of vtable/itable calls 2023-03-28 03:59:33 +01:00
Andrei Pangin
ac561f3ba7 #733: Make the same binary work with glibc and musl 2023-03-24 03:51:28 +00:00
Andrei Pangin
7f4b6536c7 #724: No more profiler.sh 2023-03-24 01:48:53 +00:00
Andrei Pangin
e3b7bfca22 Fixed link to build status 2023-03-14 03:32:31 +00:00
Andrei Pangin
c8de91df6b #723: --clock option to select JFR timestamp source: TSC or CLOCK_MONOTONIC 2023-03-14 03:31:06 +00:00
Andrei Pangin
91691ce039 #702: Make Flame Graph status line and search results always visible 2023-03-12 20:27:05 +00:00
Andrei Pangin
4a7ce8ce79 #724: Binary launcher 2023-03-12 04:17:32 +00:00
Andrei Pangin
a8f20ebc79 #719: Automatically classify execution samples into categories 2023-02-26 18:28:25 +00:00
Andrei Pangin
62c1a799ef An option to read JFR file by chunks 2023-02-23 16:40:31 +00:00
Andrei Pangin
d350738229 #714: Prefer ObjectSampler for allocation profiling when JVM has no debug symbols 2023-02-23 00:34:07 +00:00
Andrei Pangin
ada44ee12e #712: Update README regarding chunksize and chunktime 2023-02-23 00:16:52 +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
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
f1e2b96a2f Reset 'alloc' and 'lock' arguments 2022-10-02 00:47:19 +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
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
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
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
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
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
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
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
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
fe173c4101 #561, #579: Fixed concurrency issues when collecting samples / restarting profiler 2022-04-11 01:49:34 +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
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
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
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
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
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
2ddd4d230c #416: Improve reliability of stack recovery from [not_walkable_not_Java] 2022-01-21 08:55:09 +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
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
223 changed files with 21065 additions and 8218 deletions

52
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
---
name: "🐛 Bug Report"
description: Report a bug
title: "(short issue description)"
labels: [bug]
assignees: []
body:
- type: textarea
id: description
attributes:
label: Describe the bug
description: What is the problem? A clear and concise description of the bug.
validations:
required: true
- type: textarea
id: behavior
attributes:
label: Expected vs. actual behavior
description: |
What did you expect to happen? What happened instead?
validations:
required: false
- type: textarea
id: reproduction
attributes:
label: Reproduction Steps
description: |
Step-by-step instructions how to reproduce the issue. Attach a code sample if available.
validations:
required: false
- type: textarea
id: context
attributes:
label: Additional Information/Context
description: |
Anything else that might be relevant for troubleshooting this bug: profiles, screenshots, etc.
validations:
required: false
- type: input
id: version
attributes:
label: Async-profiler version
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment details
description: |
OS name and version, JDK version, CPU architecture. Is an application running in a container?
validations:
required: false

6
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
---
blank_issues_enabled: false
contact_links:
- name: 💬 General Question
url: https://github.com/async-profiler/async-profiler/discussions
about: Please ask and answer questions as a discussion thread

View File

@@ -0,0 +1,14 @@
---
name: "📕 Documentation Issue"
description: Report an issue in the profiler documentation
title: "(short issue description)"
labels: [documentation]
assignees: []
body:
- type: textarea
id: description
attributes:
label: Describe the issue
description: A clear and concise description of the issue.
validations:
required: true

View File

@@ -0,0 +1,39 @@
---
name: 🚀 Feature Request
description: Suggest an idea for this project
title: "(short issue description)"
labels: [enhancement]
assignees: []
body:
- type: textarea
id: description
attributes:
label: Describe the feature
description: A clear and concise description of the feature you are proposing.
validations:
required: true
- type: textarea
id: use-case
attributes:
label: Use Case
description: |
Why do you need this feature? For example: "I'm always frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: |
Suggest how to implement the addition or change. Provide references to alternative solutions, if any.
validations:
required: false
- type: checkboxes
id: ack
attributes:
label: Acknowledgements
options:
- label: I may be able to implement this feature request
required: false
- label: This feature might incur a breaking change
required: false

17
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,17 @@
### Description
### Related issues
### Motivation and context
### How has this been tested?
---
By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license].
[Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0

9
.github/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,9 @@
## Reporting Security Issues
We take all security reports seriously.
When we receive such reports,
we will investigate and subsequently address
any potential vulnerabilities as quickly as possible.
If you discover a potential security issue in this project,
please notify our Security Team via [email](mailto:security@profiler.tools).
Please do *not* create a public GitHub issue in this project.

23
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Continuous Integration
on:
- push
- pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '11'
- run: sudo sysctl kernel.perf_event_paranoid=1
- run: make -j`nproc`
- run: make test
- uses: actions/upload-artifact@v3
with:
path: |
build/bin/
build/lib/

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/build/
/nbproject/
/out/
/target/
/.idea/
/test/*.class
.vscode

View File

@@ -1,11 +0,0 @@
language: cpp
dist: precise
sudo: required
before_install:
- sudo apt-get install default-jdk
- sudo bash -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'
script: make && make test

View File

@@ -1,21 +1,222 @@
# Changelog
## [2.0-b1] - Early access
## [3.0] - 2024-01-20
### Features
- #724: Binary launcher `asprof`
- #751: Profile non-Java processes
- #795: AsyncGetCallTrace replacement
- #719: Classify execution samples into categories in JFR converter
- #855: `ctimer` mode for accurate profiling without perf_events
- #740: Profile CPU + Wall clock together
- #736: Show targets of vtable/itable calls
- #777: Show JIT compilation task
- #644: RISC-V port
- #770: LoongArch64 port
### Improvements
- #733: Make the same `libasyncProfiler` work with both glibc and musl
- #734: Support raw PMU event descriptors
- #759: Configure alternative profiling signal
- #761: Parse dynamic linking structures
- #723: `--clock` option to select JFR timestamp source
- #750: `--jfrsync` may specify a list of JFR events
- #849: Parse concatenated multi-chunk JFRs
- #833: Time-to-safepoint JFR event
- #832: Normalize names of hidden classes / lambdas
- #864: Reduce size of HTML Flame Graph
- #783: Shutdown asprof gracefully on SIGTERM
- Better demangling of C++ and Rust symbols
- DWARF unwinding for ARM64
- `JfrReader` can parse in-memory buffer
- Support custom events in `JfrReader`
- An option to read JFR file by chunks
- Record `GCHeapSummary` events in JFR
### Bug fixes
- Workaround macOS crashes in SafeFetch
- Fixed attach to OpenJ9 on macOS
- Support `UseCompressedObjectHeaders` aka Lilliput
- Fixed allocation profiling on JDK 20.0.x
- Fixed context-switches profiling
- Prefer ObjectSampler to TLAB hooks for allocation profiling
- Improved accuracy of ObjectSampler in `--total` mode
- Make Flame Graph status line and search results always visible
- `loop` and `timeout` options did not work in some modes
- Restart interrupted poll/epoll_wait syscalls
- Fixed stack unwinding issues on ARM64
- Workaround for stale jmethodIDs
- Calculate ELF base address correctly
- Do not dump redundant threads in a JFR chunk
- `check` action prints result to a file
- Annotate JFR unit types with `@ContentType`
## [2.9] - 2022-11-27
### Features
- Java Heap leak profiler
- `meminfo` command to print profiler's memory usage
- Profiler API with embedded agent as a Maven artifact
### Improvements
- `--include`/`--exclude` options in the FlameGraph converter
- `--simple` and `--dot` options in jfr2flame converter
- An option for agressive recovery of `[unknown_Java]` stack traces
- Do not truncate signatures in collapsed format
- Display inlined frames under a runtime stub
### Bug fixes
- Profiler did not work with Homebrew JDK
- Fixed allocation profiling on Zing
- Various `jfrsync` fixes
- Symbol parsing fixes
- Attaching to a container on Linux 3.x could fail
## [2.8.3] - 2022-07-16
### Improvements
- Support virtualized ARM64 macOS
- A switch to generate auxiliary events by async-profiler or FlightRecorder in jfrsync mode
### Bug fixes
- Could not recreate perf_events after the first failure
- Handle different versions of Zing properly
- Do not call System.loadLibrary, when libasyncProfiler is preloaded
## [2.8.2] - 2022-07-13
### Bug fixes
- The same .so works with glibc and musl
- dlopen hook did not work on Arch Linux
- Fixed JDK 7 crash
- Fixed CPU profiling on Zing
### Changes
- Mark interpreted frames with `_[0]` in collapsed output
- Double click selects a method name on a flame graph
## [2.8.1] - 2022-06-10
### Improvements
- JFR to pprof converter (contributed by @NeQuissimus)
- JFR converter improvements: time range, collapsed output, pattern highlighting
- `%n` pattern in file names; limit number of output files
- `--lib` to customize profiler library path in a container
- `profiler.sh list` command now works without PID
### Bug fixes
- Fixed crashes related to continuous profiling
- Fixed Alpine/musl compatibility issues
- Fixed incomplete collapsed output due to weird locale settings
- Workaround for JDK-8185348
## [2.8] - 2022-05-09
### Features
- Mark top methods as interpreted, compiled (C1/C2), or inlined
- JVM TI based allocation profiling for JDK 11+
- Embedded HTTP management server
### Improvements
- Re-implemented stack recovery for better reliability
- Add `loglevel` argument
- Do not mmap perf page in `--all-user` mode
- Distinguish runnable/sleeping threads in OpenJ9 wall-clock profiler
- `--cpu` converter option to extract CPU profile from the wall-clock output
## [2.7] - 2022-02-14
### Features
- Experimental support for OpenJ9 VM
- DWARF stack unwinding
### Improvements
- Better handling of VM threads (fixed missing JIT threads)
- More reliable recovery from `not_walkable` AGCT failures
- Do not accept unknown agent arguments
## [2.6] - 2022-01-09
### Features
- Continuous profiling; `loop` and `timeout` options
### Improvements
- Reliability improvements: avoid certain crashes and deadlocks
- Smaller and faster agent library
- Minor `jfr` and `jfrsync` enhancements (see the commit log)
## [2.5.1] - 2021-12-05
### Bug fixes
- Prevent early unloading of libasyncProfiler.so
- Read kernel symbols only for perf_events
- Escape backslashes in flame graphs
- Avoid duplicate categories in `jfrsync` mode
- Fixed stack overflow in RedefineClasses
- Fixed deadlock when flushing JFR
### Improvements
- Support OpenJDK C++ Interpreter (aka Zero)
- Allow reading incomplete JFR recordings
## [2.5] - 2021-10-01
### Features
- macOS/ARM64 (aka Apple M1) port
- PPC64LE port (contributed by @ghaug)
- Profile low-privileged processes with perf_events (contributed by @Jongy)
- Raw PMU events; kprobes & uprobes
- Dump results in the middle of profiling session
- Chunked JFR; support JFR files larger than 2 GB
- Integrate async-profiler events with JDK Flight Recordings
### Improvements
- Use RDTSC for JFR timestamps when possible
- Show line numbers and bci in Flame Graphs
- jfr2flame can produce Allocation and Lock flame graphs
- Flame Graph title depends on the event and `--total`
- Include profiler logs and native library list in JFR output
- Lock profiling no longer requires JVM symbols
- Better container support
- Native function profiler can count the specified argument
- An option to group threads by scheduling policy
- An option to prepend library name to native symbols
### Notes
- macOS build is provided as a fat binary that works both on x86-64 and ARM64
- 32-bit binaries are no longer shipped. It is still possible to build them from sources
- Dropped JDK 6 support (may still work though)
## [2.0] - 2021-03-14
### Features
- Profile multiple events together (cpu + alloc + lock)
- HTML 5 Flame Graphs: faster rendering, smaller size
- JFR v2 output format, compatible with FlightRecorder API
- JFR to Flame Graph converter
- Automatically turn profiling on/off at `--begin`/`--end` functions
- Time-to-safepoint profiling
- Time-to-safepoint profiling: `--ttsp`
### Improvements
- Unlimited frame buffer. Removed `-b` option and 64K stack traces limit
- Record CPU load in JFR format
- Additional JFR events: OS, CPU, and JVM information; CPU load
- Record bytecode indices / line numbers
- Native stack traces for Java events
- Improved CLI experience
- Better error handling; an option to log warnings/errors to a dedicated stream
- Reduced the amount of unknown stack traces
### Changes
- Removed non-ASL code. No more CDDL license
## [1.8.4] - 2021-02-24
### Improvements
- Smaller and faster agent library
### Bug fixes
- Fixed JDK 7 crash during wall-clock profiling
## [1.8.3] - 2021-01-06
### Improvements

4
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,4 @@
## Code of Conduct
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
opensource-codeofconduct@amazon.com with any additional questions or comments.

59
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,59 @@
# Contributing Guidelines
Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
documentation, we greatly value feedback and contributions from our community.
Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
information to effectively respond to your bug report or contribution.
## Security issue notifications
If you discover a potential security issue in this project we ask that you notify our [Security Team](mailto:security@profiler.tools). Please do **not** create a public GitHub issue.
## Reporting Bugs/Feature Requests
We welcome you to use the GitHub issue tracker to report bugs or suggest features.
When filing an issue, please check [existing open](https://github.com/async-profiler/async-profiler/issues), or [recently closed](https://github.com/async-profiler/async-profiler/issues?q=is%3Aissue+is%3Aclosed), issues to make sure somebody else hasn't already
reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
* A reproducible test case or series of steps
* The version of our code being used
* Any modifications you've made relevant to the bug
* Anything unusual about your environment or deployment
## Contributing via Pull Requests
Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
1. You are working against the latest source on the *master* branch.
2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
To send us a pull request, please:
1. Fork the repository.
2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
3. Ensure local tests pass.
4. Commit to your fork using clear commit messages.
5. Send us a pull request, answering any default questions in the pull request interface.
6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
## Finding contributions to work on
Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/async-profiler/async-profiler/labels/help%20wanted) issues is a great place to start.
## Code of Conduct
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
opensource-codeofconduct@amazon.com with any additional questions or comments.
## Licensing
See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.

34
Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
# Image for building async-profiler release packages for x64 and arm64
# Stage 0: download musl sources and build cross-toolchains for both architectures
FROM public.ecr.aws/lts/ubuntu:18.04
RUN apt-get update && apt-get install -y --no-install-recommends \
sudo patchelf make g++ g++-aarch64-linux-gnu openjdk-11-jdk-headless && \
rm -rf /var/cache/apt /var/lib/apt/lists/*
ARG musl_src=musl-1.2.5
ARG musl_sha256=a9a118bbe84d8764da0ea0d28b3ab3fae8477fc7e4085d90102b8596fc7c75e4
ADD https://musl.libc.org/releases/${musl_src}.tar.gz /
RUN echo ${musl_sha256} ${musl_src}.tar.gz | sha256sum -c
RUN ["/bin/bash", "-c", "\
tar xfz ${musl_src}.tar.gz && \
cd /${musl_src} && \
./configure --disable-shared --prefix=/usr/local/musl/x86_64 && \
make -j`nproc` && make install && make clean && \
./configure --disable-shared --prefix=/usr/local/musl/aarch64 --target=aarch64-linux-gnu && \
make -j`nproc` && make install && make clean && \
ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/{asm-generic,linux} /usr/local/musl/x86_64/include/ && \
ln -s /usr/aarch64-linux-gnu/include/{asm,asm-generic,linux} /usr/local/musl/aarch64/include/"]
# Stage 1: install build tools + copy musl toolchain from the previous step
FROM public.ecr.aws/lts/ubuntu:18.04
# This line should be exactly the same as at stage 0 to benefit from caching
RUN apt-get update && apt-get install -y --no-install-recommends \
sudo patchelf make g++ g++-aarch64-linux-gnu openjdk-11-jdk-headless && \
rm -rf /var/cache/apt /var/lib/apt/lists/*
COPY --from=0 /usr/local/musl /usr/local/musl

191
Makefile
View File

@@ -1,112 +1,189 @@
PROFILER_VERSION=2.0-b1
JATTACH_VERSION=1.5
JAVAC_RELEASE_VERSION=6
PROFILER_VERSION=3.0
ifeq ($(COMMIT_TAG),true)
PROFILER_VERSION := $(PROFILER_VERSION)-$(shell git rev-parse --short=8 HEAD)
endif
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
LIB_PROFILER=libasyncProfiler.$(SOEXT)
LIB_PROFILER_SO=libasyncProfiler.so
JATTACH=jattach
API_JAR=async-profiler.jar
CONVERTER_JAR=converter.jar
ASPROF=bin/asprof
JFRCONV=bin/jfrconv
LIB_PROFILER=lib/libasyncProfiler.$(SOEXT)
API_JAR=jar/async-profiler.jar
CONVERTER_JAR=jar/jfr-converter.jar
TEST_JAR=test.jar
CFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
CXXFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
INCLUDES=-I$(JAVA_HOME)/include
CC=$(CROSS_COMPILE)gcc
CXX=$(CROSS_COMPILE)g++
STRIP=$(CROSS_COMPILE)strip
CFLAGS=-O3 -fno-exceptions
CXXFLAGS=-O3 -fno-exceptions -fno-omit-frame-pointer -fvisibility=hidden
CPPFLAGS=
DEFS=-DPROFILER_VERSION=\"$(PROFILER_VERSION)\"
INCLUDES=-I$(JAVA_HOME)/include -Isrc/helper
LIBS=-ldl -lpthread
MERGE=true
JAVAC=$(JAVA_HOME)/bin/javac
JAR=$(JAVA_HOME)/bin/jar
JAVA=$(JAVA_HOME)/bin/java
JAVA_TARGET=8
JAVAC_OPTIONS=--release $(JAVA_TARGET) -Xlint:-options
LOG_DIR=build/test/logs
LOG_LEVEL=
SKIP=
TEST_FLAGS=-DlogDir=$(LOG_DIR) -DlogLevel=$(LOG_LEVEL) -Dskip=$(SKIP)
SOURCES := $(wildcard src/*.cpp)
HEADERS := $(wildcard src/*.h)
RESOURCES := $(wildcard src/res/*)
JAVA_HELPER_CLASSES := $(wildcard src/helper/one/profiler/*.class)
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
TEST_SOURCES := $(shell find test -name '*.java')
TESTS := $(notdir $(patsubst %/,%,$(wildcard test/test/*/)))
ifeq ($(JAVA_HOME),)
export JAVA_HOME:=$(shell java -cp . JavaHome)
endif
OS:=$(shell uname -s)
ifeq ($(OS), Darwin)
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
ifeq ($(OS),Darwin)
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/server
INCLUDES += -I$(JAVA_HOME)/include/darwin
SOEXT=dylib
PACKAGE_EXT=zip
OS_TAG=macos
ifeq ($(FAT_BINARY),true)
FAT_BINARY_FLAGS=-arch x86_64 -arch arm64 -mmacos-version-min=10.12
CFLAGS += $(FAT_BINARY_FLAGS)
CXXFLAGS += $(FAT_BINARY_FLAGS)
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)
MERGE=false
endif
else
CXXFLAGS += -U_FORTIFY_SOURCE -Wl,-z,defs -Wl,--exclude-libs,ALL -static-libstdc++ -static-libgcc -fdata-sections -ffunction-sections -Wl,--gc-sections
ifeq ($(MERGE),true)
CXXFLAGS += -fwhole-program
endif
LIBS += -lrt
INCLUDES += -I$(JAVA_HOME)/include/linux
SOEXT=so
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
OS_TAG=linux-musl
else
OS_TAG=linux
endif
PACKAGE_EXT=tar.gz
OS_TAG=linux
endif
ARCH:=$(shell uname -m)
ifeq ($(ARCH),x86_64)
ARCH_TAG=x64
else ifeq ($(ARCH),aarch64)
ARCH_TAG=arm64
else ifeq ($(ARCH),arm64)
ARCH_TAG=arm64
else ifeq ($(findstring arm,$(ARCH)),arm)
ARCH_TAG=arm32
else ifeq ($(ARCH),ppc64le)
ARCH_TAG=ppc64le
else ifeq ($(ARCH),riscv64)
ARCH_TAG=riscv64
else ifeq ($(ARCH),loongarch64)
ARCH_TAG=loongarch64
else
ifeq ($(findstring arm,$(ARCH)),arm)
ARCH_TAG=arm
else
ifeq ($(findstring aarch64,$(ARCH)),aarch64)
ARCH_TAG=aarch64
else
ARCH_TAG=x86
endif
endif
ARCH_TAG=x86
endif
STATIC_BINARY=$(findstring musl-gcc,$(CC))
ifneq (,$(STATIC_BINARY))
CFLAGS += -static -fdata-sections -ffunction-sections -Wl,--gc-sections
endif
ifneq (,$(findstring $(ARCH_TAG),x86 x64 arm64))
CXXFLAGS += -momit-leaf-frame-pointer
endif
.PHONY: all release test clean
.PHONY: all jar release build-test test native clean
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(API_JAR) build/$(CONVERTER_JAR)
all: build/bin build/lib build/$(LIB_PROFILER) build/$(ASPROF) jar build/$(JFRCONV)
release: build $(PACKAGE_NAME).tar.gz
jar: build/jar build/$(API_JAR) build/$(CONVERTER_JAR)
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
build/$(API_JAR) build/$(CONVERTER_JAR) \
profiler.sh LICENSE *.md
mkdir -p $(PACKAGE_DIR)
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
chmod -R 755 $(PACKAGE_DIR)
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
tar cvzf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
release: $(PACKAGE_NAME).$(PACKAGE_EXT)
$(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR)
patchelf --remove-needed ld-linux-x86-64.so.2 --remove-needed ld-linux-aarch64.so.1 $(PACKAGE_DIR)/$(LIB_PROFILER)
tar czf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
rm -r $(PACKAGE_DIR)
%.$(SOEXT): %.so
-ln -s $(<F) $@
$(PACKAGE_NAME).zip: $(PACKAGE_DIR)
truncate -cs -`stat -f "%z" build/$(CONVERTER_JAR)` $(PACKAGE_DIR)/$(JFRCONV)
codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/$(ASPROF) $(PACKAGE_DIR)/$(JFRCONV) $(PACKAGE_DIR)/$(LIB_PROFILER)
cat build/$(CONVERTER_JAR) >> $(PACKAGE_DIR)/$(JFRCONV)
ditto -c -k --keepParent $(PACKAGE_DIR) $@
rm -r $(PACKAGE_DIR)
build:
mkdir -p build
$(PACKAGE_DIR): all LICENSE README.md
mkdir -p $(PACKAGE_DIR)
cp -RP build/bin build/lib LICENSE README.md $(PACKAGE_DIR)/
chmod -R 755 $(PACKAGE_DIR)
chmod 644 $(PACKAGE_DIR)/lib/* $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/README.md
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS)
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
build/%:
mkdir -p $@
build/$(JATTACH): src/jattach/jattach.c
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
build/$(ASPROF): src/main/* src/jattach/* src/fdtransfer.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFS) -o $@ src/main/*.cpp src/jattach/*.c
$(STRIP) $@
build/$(JFRCONV): src/launcher/* build/$(CONVERTER_JAR)
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFS) -o $@ src/launcher/*.cpp
$(STRIP) $@
cat build/$(CONVERTER_JAR) >> $@
build/$(LIB_PROFILER): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES)
ifeq ($(MERGE),true)
for f in src/*.cpp; do echo '#include "'$$f'"'; done |\
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFS) $(INCLUDES) -fPIC -shared -o $@ -xc++ - $(LIBS)
else
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFS) $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
endif
build/$(API_JAR): $(API_SOURCES)
mkdir -p build/api
$(JAVAC) -source $(JAVAC_RELEASE_VERSION) -target $(JAVAC_RELEASE_VERSION) -d build/api $^
$(JAR) cvf $@ -C build/api .
$(JAVAC) $(JAVAC_OPTIONS) -d build/api $(API_SOURCES)
$(JAR) cf $@ -C build/api .
$(RM) -r build/api
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) src/converter/MANIFEST.MF
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) $(RESOURCES)
mkdir -p build/converter
$(JAVAC) -source 7 -target 7 -d build/converter $(CONVERTER_SOURCES)
$(JAR) cvfm $@ src/converter/MANIFEST.MF -C build/converter .
$(JAVAC) $(JAVAC_OPTIONS) -d build/converter $(CONVERTER_SOURCES)
$(JAR) cfe $@ Main -C build/converter . -C src/res .
$(RM) -r build/converter
test: all
test/smoke-test.sh
test/thread-smoke-test.sh
test/alloc-smoke-test.sh
test/load-library-test.sh
echo "All tests passed"
%.class: %.java
$(JAVAC) -source 7 -target 7 -Xlint:-options -g:none $^
build-test: all build/$(TEST_JAR)
echo "Successfully built all tests $(LIB_PROFILER)"
test: all build/$(TEST_JAR)
echo "Running tests against $(LIB_PROFILER)"
$(JAVA) $(TEST_FLAGS) -ea -cp "build/test.jar:build/jar/*:build/lib/*" one.profiler.test.Runner $(TESTS)
build/$(TEST_JAR): $(TEST_SOURCES) build/$(CONVERTER_JAR)
mkdir -p build/test
$(JAVAC) --release 8 -cp "build/jar/*:build/converter/*" -d build/test $(TEST_SOURCES)
$(JAR) cf $@ -C build/test .
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.dylib > native/macos/libasyncProfiler.dylib
clean:
$(RM) -r build

637
README.md
View File

@@ -1,53 +1,638 @@
# async-profiler
[![Build Status](https://travis-ci.org/jvm-profiling-tools/async-profiler.svg?branch=master)](https://travis-ci.org/jvm-profiling-tools/async-profiler)
This project is a low overhead sampling profiler for Java
that does not suffer from [Safepoint bias problem](http://psy-lob-saw.blogspot.ru/2016/02/why-most-sampling-java-profilers-are.html).
It features HotSpot-specific APIs to collect stack traces
and to track memory allocations. The profiler works with
OpenJDK, Oracle JDK and other Java runtimes based on the HotSpot JVM.
OpenJDK and other Java runtimes based on the HotSpot JVM.
async-profiler can trace the following kinds of events:
- CPU cycles
- Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
- Allocations in Java Heap
- Contented lock attempts, including both Java object monitors and ReentrantLocks
## Usage
See our [Wiki](https://github.com/jvm-profiling-tools/async-profiler/wiki) or [3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr) to learn about all set of features.
See our [Wiki](https://github.com/async-profiler/async-profiler/wiki) or
[3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr)
to learn about all features.
## Download
Latest release (1.8.3):
Current release (3.0):
- Linux x64 (glibc): [async-profiler-1.8.3-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-x64.tar.gz)
- Linux x86 (glibc): [async-profiler-1.8.3-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-x86.tar.gz)
- Linux x64 (musl): [async-profiler-1.8.3-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-musl-x64.tar.gz)
- Linux ARM: [async-profiler-1.8.3-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-arm.tar.gz)
- Linux AArch64: [async-profiler-1.8.3-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-linux-aarch64.tar.gz)
- macOS x64: [async-profiler-1.8.3-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.3/async-profiler-1.8.3-macos-x64.tar.gz)
- Linux x64: [async-profiler-3.0-linux-x64.tar.gz](https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz)
- Linux arm64: [async-profiler-3.0-linux-arm64.tar.gz](https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-arm64.tar.gz)
- macOS x64/arm64: [async-profiler-3.0-macos.zip](https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-macos.zip)
- Converters between profile formats: [converter.jar](https://github.com/async-profiler/async-profiler/releases/download/v3.0/converter.jar)
(JFR to Flame Graph, JFR to pprof, collapsed stacks to Flame Graph)
[Early access](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.0-b1) (2.0-b1):
[Previous releases](https://github.com/async-profiler/async-profiler/releases)
- Linux x64 (glibc): [async-profiler-2.0-b1-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.0-b1/async-profiler-2.0-b1-linux-x64.tar.gz)
- macOS x64: [async-profiler-2.0-b1-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.0-b1/async-profiler-2.0-b1-macos-x64.tar.gz)
[Previous releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
async-profiler also comes bundled with IntelliJ IDEA Ultimate 2018.3 and later.
For more information refer to [IntelliJ IDEA documentation](https://www.jetbrains.com/help/idea/cpu-and-allocation-profiling-basic-concepts.html).
## Supported platforms
- **Linux** / x64 / x86 / ARM / AArch64
- **macOS** / x64
| | Officially maintained builds | Other available ports |
|-----------|------------------------------|-------------------------------------------|
| **Linux** | x64, arm64 | x86, arm32, ppc64le, riscv64, loongarch64 |
| **macOS** | x64, arm64 | |
Note: macOS profiling is limited to user space code only.
## CPU profiling
In this mode profiler collects stack trace samples that include **Java** methods,
**native** calls, **JVM** code and **kernel** functions.
The general approach is receiving call stacks generated by `perf_events`
and matching them up with call stacks generated by `AsyncGetCallTrace`,
in order to produce an accurate profile of both Java and native code.
Additionally, async-profiler provides a workaround to recover stack traces
in some [corner cases](https://bugs.openjdk.java.net/browse/JDK-8178287)
where `AsyncGetCallTrace` fails.
This approach has the following advantages compared to using `perf_events`
directly with a Java agent that translates addresses to Java method names:
* Does not require `-XX:+PreserveFramePointer`, which introduces
performance overhead that can be sometimes as high as 10%.
* Does not require generating a map file for translating Java code addresses
to method names.
* Displays interpreter frames.
* Does not produce large intermediate files (perf.data) for further processing in
user space scripts.
If you wish to resolve frames within `libjvm`, the [debug symbols](#installing-debug-symbols) are required.
## ALLOCATION profiling
The profiler can be configured to collect call sites where the largest amount
of heap memory is allocated.
async-profiler does not use intrusive techniques like bytecode instrumentation
or expensive DTrace probes which have significant performance impact.
It also does not affect Escape Analysis or prevent from JIT optimizations
like allocation elimination. Only actual heap allocations are measured.
The profiler features TLAB-driven sampling. It relies on HotSpot-specific
callbacks to receive two kinds of notifications:
- when an object is allocated in a newly created TLAB;
- when an object is allocated on a slow path outside TLAB.
Sampling interval can be adjusted with `--alloc` option.
For example, `--alloc 500k` will take one sample after 500 KB of allocated
space on average. Prior to JDK 11, intervals less than TLAB size will not take effect.
### Installing Debug Symbols
Prior to JDK 11, the allocation profiler required HotSpot debug symbols.
Some OpenJDK distributions (Amazon Corretto, Liberica JDK, Azul Zulu)
already have them embedded in `libjvm.so`, other OpenJDK builds typically
provide debug symbols in a separate package. For example, to install
OpenJDK debug symbols on Debian / Ubuntu, run:
```
# apt install openjdk-17-dbg
```
(replace `17` with the desired version of JDK).
On CentOS, RHEL and some other RPM-based distributions, this could be done with
[debuginfo-install](http://man7.org/linux/man-pages/man1/debuginfo-install.1.html) utility:
```
# debuginfo-install java-1.8.0-openjdk
```
On Gentoo the `icedtea` OpenJDK package can be built with the per-package setting
`FEATURES="nostrip"` to retain symbols.
The `gdb` tool can be used to verify if debug symbols are properly installed for the `libjvm` library.
For example, on Linux:
```
$ gdb $JAVA_HOME/lib/server/libjvm.so -ex 'info address UseG1GC'
```
This command's output will either contain `Symbol "UseG1GC" is at 0xxxxx`
or `No symbol "UseG1GC" in current context`.
## Wall-clock profiling
`-e wall` option tells async-profiler to sample all threads equally every given
period of time regardless of thread status: Running, Sleeping or Blocked.
For instance, this can be helpful when profiling application start-up time.
Wall-clock profiler is most useful in per-thread mode: `-t`.
Example: `asprof -e wall -t -i 5ms -f result.html 8983`
## Java method profiling
`-e ClassName.methodName` option instruments the given Java method
in order to record all invocations of this method with the stack traces.
Example: `-e java.util.Properties.getProperty` will profile all places
where `getProperty` method is called from.
Only non-native Java methods are supported. To profile a native method,
use hardware breakpoint event instead, e.g. `-e Java_java_lang_Throwable_fillInStackTrace`
**Be aware** that if you attach async-profiler at runtime, the first instrumentation
of a non-native Java method may cause the [deoptimization](https://github.com/openjdk/jdk/blob/bf2e9ee9d321ed289466b2410f12ad10504d01a2/src/hotspot/share/prims/jvmtiRedefineClasses.cpp#L4092-L4096)
of all compiled methods. The subsequent instrumentation flushes only the _dependent code_.
The massive CodeCache flush doesn't occur if attaching async-profiler as an agent.
Here are some useful native methods that you may want to profile:
* ```G1CollectedHeap::humongous_obj_allocate``` - trace _humongous allocations_ of the G1 GC,
* ```JVM_StartThread``` - trace creation of new Java threads,
* ```Java_java_lang_ClassLoader_defineClass1``` - trace class loading.
## Building
Make sure the `JAVA_HOME` environment variable points to your JDK installation,
and then run `make`. GCC is required. After building, the profiler agent binary
will be in the `build` subdirectory. Additionally, a small application `jattach`
that can load the agent into the target process will also be compiled to the
`build` subdirectory.
Build status: [![Build Status](https://github.com/async-profiler/async-profiler/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/async-profiler/async-profiler/actions/workflows/ci.yml)
Make sure the `JAVA_HOME` environment variable points to your JDK installation,
and then run `make`. GCC or Clang is required. After building, the profiler binaries
will be in the `build` subdirectory.
## Basic Usage
As of Linux 4.6, capturing kernel call stacks using `perf_events` from a non-root
process requires setting two runtime variables. You can set them using
sysctl or as follows:
```
# sysctl kernel.perf_event_paranoid=1
# sysctl kernel.kptr_restrict=0
```
async-profiler works in the context of the target Java application,
i.e. it runs as an agent in the process being profiled.
`asprof` is a tool to attach and control the agent.
A typical workflow would be to launch your Java application, attach
the agent and start profiling, exercise your performance scenario, and
then stop profiling. The agent's output, including the profiling results, will
be displayed on the console where you've started `asprof`.
Example:
```
$ jps
9234 Jps
8983 Computey
$ asprof start 8983
$ asprof stop 8983
```
The following may be used in lieu of the `pid` (8983):
- The keyword `jps`, which will use the most recently launched Java process.
- The application name as it appears in the `jps` output: e.g. `Computey`
Alternatively, you may specify `-d` (duration) argument to profile
the application for a fixed period of time with a single command.
```
$ asprof -d 30 8983
```
By default, the profiling frequency is 100Hz (every 10ms of CPU time).
Here is a sample of the output printed to the Java application's terminal:
```
--- Execution profile ---
Total samples: 687
Unknown (native): 1 (0.15%)
--- 6790000000 (98.84%) ns, 679 samples
[ 0] Primes.isPrime
[ 1] Primes.primesThread
[ 2] Primes.access$000
[ 3] Primes$1.run
[ 4] java.lang.Thread.run
... a lot of output omitted for brevity ...
ns percent samples top
---------- ------- ------- ---
6790000000 98.84% 679 Primes.isPrime
40000000 0.58% 4 __do_softirq
... more output omitted ...
```
This indicates that the hottest method was `Primes.isPrime`, and the hottest
call stack leading to it comes from `Primes.primesThread`.
## Launching as an Agent
If you need to profile some code as soon as the JVM starts up, instead of using the `asprof`,
it is possible to attach async-profiler as an agent on the command line. For example:
```
$ java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ...
```
Agent library is configured through the JVMTI argument interface.
The format of the arguments string is described
[in the source code](https://github.com/async-profiler/async-profiler/blob/v3.0/src/arguments.cpp#L44).
`asprof` actually converts command line arguments to that format.
For instance, `-e wall` is converted to `event=wall`, `-f profile.html`
is converted to `file=profile.html`, and so on. However, some arguments are processed
directly by `asprof`. E.g. `-d 5` results in 3 actions:
attaching profiler agent with start command, sleeping for 5 seconds,
and then attaching the agent again with stop command.
## Multiple events
It is possible to profile CPU, allocations, and locks at the same time.
Instead of CPU, you may choose any other execution event: wall-clock,
perf event, tracepoint, Java method, etc.
The only output format that supports multiple events together is JFR.
The recording will contain the following event types:
- `jdk.ExecutionSample`
- `jdk.ObjectAllocationInNewTLAB` (alloc)
- `jdk.ObjectAllocationOutsideTLAB` (alloc)
- `jdk.JavaMonitorEnter` (lock)
- `jdk.ThreadPark` (lock)
To start profiling cpu + allocations + locks together, specify
```
asprof -e cpu,alloc,lock -f profile.jfr ...
```
or use `--alloc` and `--lock` parameters with the desired threshold:
```
asprof -e cpu --alloc 2m --lock 10ms -f profile.jfr ...
```
The same, when starting profiler as an agent:
```
-agentpath:/path/to/libasyncProfiler.so=start,event=cpu,alloc=2m,lock=10ms,file=profile.jfr
```
## Flame Graph visualization
async-profiler provides out-of-the-box [Flame Graph](https://github.com/BrendanGregg/FlameGraph) support.
Specify `-o flamegraph` argument to dump profiling results as an interactive HTML Flame Graph.
Also, Flame Graph output format will be chosen automatically if the target filename ends with `.html`.
```
$ jps
9234 Jps
8983 Computey
$ asprof -d 30 -f /tmp/flamegraph.html 8983
```
[![Example](https://github.com/async-profiler/async-profiler/blob/master/demo/flamegraph.png)](https://htmlpreview.github.io/?https://github.com/async-profiler/async-profiler/blob/master/demo/flamegraph.html)
## Profiler Options
`asprof` command-line options.
* `start` - starts profiling in semi-automatic mode, i.e. profiler will run
until `stop` command is explicitly called.
* `resume` - starts or resumes earlier profiling session that has been stopped.
All the collected data remains valid. The profiling options are not preserved
between sessions, and should be specified again.
* `stop` - stops profiling and prints the report.
* `dump` - dump collected data without stopping profiling session.
* `check` - check if the specified profiling event is available.
* `status` - prints profiling status: whether profiler is active and
for how long.
* `meminfo` - prints used memory statistics.
* `list` - show the list of profiling events available for the target process
(if PID is specified) or for the default JVM.
* `-d N` - the profiling duration, in seconds. If no `start`, `resume`, `stop`
or `status` option is given, the profiler will run for the specified period
of time and then automatically stop.
Example: `asprof -d 30 8983`
* `-e event` - the profiling event: `cpu`, `alloc`, `lock`, `cache-misses` etc.
Use `list` to see the complete list of available events.
In allocation profiling mode the top frame of every call trace is the class
of the allocated object, and the counter is the heap pressure (the total size
of allocated TLABs or objects outside TLAB).
In lock profiling mode the top frame is the class of lock/monitor, and
the counter is number of nanoseconds it took to enter this lock/monitor.
Two special event types are supported on Linux: hardware breakpoints
and kernel tracepoints:
- `-e mem:<func>[:rwx]` sets read/write/exec breakpoint at function
`<func>`. The format of `mem` event is the same as in `perf-record`.
Execution breakpoints can be also specified by the function name,
e.g. `-e malloc` will trace all calls of native `malloc` function.
- `-e trace:<id>` sets a kernel tracepoint. It is possible to specify
tracepoint symbolic name, e.g. `-e syscalls:sys_enter_open` will trace
all `open` syscalls.
* `-i N` - sets the profiling interval in nanoseconds or in other units,
if N is followed by `ms` (for milliseconds), `us` (for microseconds),
or `s` (for seconds). Only CPU active time is counted. No samples
are collected while CPU is idle. The default is 10000000 (10ms).
Example: `asprof -i 500us 8983`
* `--alloc N` - allocation profiling interval in bytes or in other units,
if N is followed by `k` (kilobytes), `m` (megabytes), or `g` (gigabytes).
* `--live` - retain allocation samples with live objects only
(object that have not been collected by the end of profiling session).
Useful for finding Java heap memory leaks.
* `--lock N` - lock profiling threshold in nanoseconds (or other units).
In lock profiling mode, record contended locks that the JVM has waited for
longer than the specified duration.
* `-j N` - sets the maximum stack depth. The default is 2048.
Example: `asprof -j 30 8983`
* `-t` - profile threads separately. Each stack trace will end with a frame
that denotes a single thread.
Example: `asprof -t 8983`
* `-s` - print simple class names instead of FQN.
* `-n` - normalize names of hidden classes / lambdas.
* `-g` - print method signatures.
* `-a` - annotate JIT compiled methods with `_[j]`, inlined methods with `_[i]`, interpreted methods with `_[0]` and C1 compiled methods with `_[1]`.
* `-l` - prepend library names to symbols, e.g. ``libjvm.so`JVM_DefineClassWithSource``.
* `-o fmt` - specifies what information to dump when profiling ends.
`fmt` can be one of the following options:
- `traces[=N]` - dump call traces (at most N samples);
- `flat[=N]` - dump flat profile (top N hot methods);
can be combined with `traces`, e.g. `traces=200,flat=200`
- `jfr` - dump events in Java Flight Recorder format readable by Java Mission Control.
This *does not* require JDK commercial features to be enabled.
- `collapsed` - dump collapsed call traces in the format used by
[FlameGraph](https://github.com/brendangregg/FlameGraph) script. This is
a collection of call stacks, where each line is a semicolon separated list
of frames followed by a counter.
- `flamegraph` - produce Flame Graph in HTML format.
- `tree` - produce Call Tree in HTML format.
`--reverse` option will generate backtrace view.
* `--total` - count the total value of the collected metric instead of the number of samples,
e.g. total allocation size.
* `--chunksize N`, `--chunktime N` - approximate size and time limits for a single JFR chunk.
A new chunk will be started whenever either limit is reached.
The default `chunksize` is 100MB, and the default `chunktime` is 1 hour.
Example: `asprof -f profile.jfr --chunksize 100m --chunktime 1h 8983`
* `-I include`, `-X exclude` - filter stack traces by the given pattern(s).
`-I` defines the name pattern that *must* be present in the stack traces,
while `-X` is the pattern that *must not* occur in any of stack traces in the output.
`-I` and `-X` options can be specified multiple times. A pattern may begin or end with
a star `*` that denotes any (possibly empty) sequence of characters.
Example: `asprof -I 'Primes.*' -I 'java/*' -X '*Unsafe.park*' 8983`
* `-L level` - log level: `debug`, `info`, `warn`, `error` or `none`.
* `-F features` - comma separated list of HotSpot-specific features
to include in stack traces. Supported features are:
- `vtable` - display targets of megamorphic virtual calls as an extra frame
on top of `vtable stub` or `itable stub`.
- `comptask` - display current compilation task (a Java method being compiled)
in a JIT compiler stack trace.
* `--title TITLE`, `--minwidth PERCENT`, `--reverse` - FlameGraph parameters.
Example: `asprof -f profile.html --title "Sample CPU profile" --minwidth 0.5 8983`
* `-f FILENAME` - the file name to dump the profile information to.
`%p` in the file name is expanded to the PID of the target JVM;
`%t` - to the timestamp;
`%n{MAX}` - to the sequence number;
`%{ENV}` - to the value of the given environment variable.
Example: `asprof -o collapsed -f /tmp/traces-%t.txt 8983`
* `--loop TIME` - run profiler in a loop (continuous profiling).
The argument is either a clock time (`hh:mm:ss`) or
a loop duration in `s`econds, `m`inutes, `h`ours, or `d`ays.
Make sure the filename includes a timestamp pattern, or the output
will be overwritten on each iteration.
Example: `asprof --loop 1h -f /var/log/profile-%t.jfr 8983`
* `--all-user` - include only user-mode events. This option is helpful when kernel profiling
is restricted by `perf_event_paranoid` settings.
* `--sched` - group threads by Linux-specific scheduling policy: BATCH/IDLE/OTHER.
* `--cstack MODE` - how to walk native frames (C stack). Possible modes are
`fp` (Frame Pointer), `dwarf` (DWARF unwind info),
`lbr` (Last Branch Record, available on Haswell since Linux 4.1),
`vm` (HotSpot VM Structs) and `no` (do not collect C stack).
By default, C stack is shown in cpu, ctimer, wall-clock and perf-events profiles.
Java-level events like `alloc` and `lock` collect only Java stack.
* `--signal NUM` - use alternative signal for cpu or wall clock profiling.
To change both signals, specify two numbers separated by a slash: `--signal SIGCPU/SIGWALL`.
* `--clock SOURCE` - clock source for JFR timestamps: `tsc` (default)
or `monotonic` (equivalent for `CLOCK_MONOTONIC`).
* `--begin function`, `--end function` - automatically start/stop profiling
when the specified native function is executed.
* `--ttsp` - time-to-safepoint profiling. An alias for
`--begin SafepointSynchronize::begin --end RuntimeService::record_safepoint_synchronized`
It is not a separate event type, but rather a constraint. Whatever event type
you choose (e.g. `cpu` or `wall`), the profiler will work as usual, except that
only events between the safepoint request and the start of the VM operation
will be recorded.
* `--jfropts OPTIONS` - comma separated list of JFR recording options.
Currently, the only available option is `mem` supported on Linux 3.17+.
`mem` enables accumulating events in memory instead of flushing
synchronously to a file.
* `--jfrsync CONFIG` - start Java Flight Recording with the given configuration
synchronously with the profiler. The output .jfr file will include all regular
JFR events, except that execution samples will be obtained from async-profiler.
This option implies `-o jfr`.
- `CONFIG` is a predefined JFR profile or a JFR configuration file (.jfc)
or a list of JFR events started with `+`
Example: `asprof -e cpu --jfrsync profile -f combined.jfr 8983`
* `--fdtransfer` - runs a background process that provides access to perf_events
to an unprivileged process. `--fdtransfer` is useful for profiling a process
in a container (which lacks access to perf_events) from the host.
See [Profiling Java in a container](#profiling-java-in-a-container).
* `-v`, `--version` - prints the version of profiler library. If PID is specified,
gets the version of the library loaded into the given process.
## Profiling Java in a container
It is possible to profile Java processes running in a Docker or LXC container
both from within a container and from the host system.
When profiling from the host, `pid` should be the Java process ID in the host
namespace. Use `ps aux | grep java` or `docker top <container>` to find
the process ID.
async-profiler should be run from the host by a privileged user - it will
automatically switch to the proper pid/mount namespace and change
user credentials to match the target process. Also make sure that
the target container can access `libasyncProfiler.so` by the same
absolute path as on the host.
By default, Docker container restricts the access to `perf_event_open`
syscall. There are 3 alternatives to allow profiling in a container:
1. You can modify the [seccomp profile](https://docs.docker.com/engine/security/seccomp/)
or disable it altogether with `--security-opt seccomp=unconfined` option. In
addition, `--cap-add SYS_ADMIN` may be required.
2. You can use "fdtransfer": see the help for `--fdtransfer`.
3. Last, you may fall back to `-e ctimer` profiling mode, see [Troubleshooting](#troubleshooting).
## Restrictions/Limitations
* macOS profiling is limited to user space code only.
* On most Linux systems, `perf_events` captures call stacks with a maximum depth
of 127 frames. On recent Linux kernels, this can be configured using
`sysctl kernel.perf_event_max_stack` or by writing to the
`/proc/sys/kernel/perf_event_max_stack` file.
* Profiler allocates 8kB perf_event buffer for each thread of the target process.
Make sure `/proc/sys/kernel/perf_event_mlock_kb` value is large enough
(more than `8 * threads`) when running under unprivileged user.
Otherwise the message _"perf_event mmap failed: Operation not permitted"_
will be printed, and no native stack traces will be collected.
* You will not see the non-Java frames _preceding_ the Java frames on the
stack, unless `--cstack vm` is specified.
For example, if `start_thread` called `JavaMain` and then your Java
code started running, you will not see the first two frames in the resulting
stack. On the other hand, you _will_ see non-Java frames (user and kernel)
invoked by your Java code.
* No Java stacks will be collected if `-XX:MaxJavaStackTraceDepth` is zero
or negative. The exception is `--cstack vm` mode, which does not take
`MaxJavaStackTraceDepth` into account.
* Too short profiling interval may cause continuous interruption of heavy
system calls like `clone()`, so that it will never complete;
see [#97](https://github.com/async-profiler/async-profiler/issues/97).
The workaround is simply to increase the interval.
* When agent is not loaded at JVM startup (by using -agentpath option) it is
highly recommended to use `-XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints` JVM flags.
Without those flags the profiler will still work correctly but results might be
less accurate. For example, without `-XX:+DebugNonSafepoints` there is a high chance
that simple inlined methods will not appear in the profile. When the agent is attached at runtime,
`CompiledMethodLoad` JVMTI event enables debug info, but only for methods compiled after attaching.
## Troubleshooting
```
Failed to change credentials to match the target process: Operation not permitted
```
Due to limitation of HotSpot Dynamic Attach mechanism, the profiler must be run
by exactly the same user (and group) as the owner of target JVM process.
If profiler is run by a different user, it will try to automatically change
current user and group. This will likely succeed for `root`, but not for
other users, resulting in the above error.
```
Could not start attach mechanism: No such file or directory
```
The profiler cannot establish communication with the target JVM through UNIX domain socket.
Usually this happens in one of the following cases:
1. Attach socket `/tmp/.java_pidNNN` has been deleted. It is a common
practice to clean `/tmp` automatically with some scheduled script.
Configure the cleanup software to exclude `.java_pid*` files from deletion.
How to check: run `lsof -p PID | grep java_pid`
If it lists a socket file, but the file does not exist, then this is exactly
the described problem.
2. JVM is started with `-XX:+DisableAttachMechanism` option.
3. `/tmp` directory of Java process is not physically the same directory
as `/tmp` of your shell, because Java is running in a container or in
`chroot` environment. `jattach` attempts to solve this automatically,
but it might lack the required permissions to do so.
Check `strace build/jattach PID properties`
4. JVM is busy and cannot reach a safepoint. For instance,
JVM is in the middle of long-running garbage collection.
How to check: run `kill -3 PID`. Healthy JVM process should print
a thread dump and heap info in its console.
```
Target JVM failed to load libasyncProfiler.so
```
The connection with the target JVM has been established, but JVM is unable to load profiler shared library.
Make sure the user of JVM process has permissions to access `libasyncProfiler.so` by exactly the same absolute path.
For more information see [#78](https://github.com/async-profiler/async-profiler/issues/78).
```
No access to perf events. Try --fdtransfer or --all-user option or 'sysctl kernel.perf_event_paranoid=1'
```
or
```
Perf events unavailable
```
`perf_event_open()` syscall has failed.
Typical reasons include:
1. `/proc/sys/kernel/perf_event_paranoid` is set to restricted mode (>=2).
2. seccomp disables `perf_event_open` API in a container.
3. OS runs under a hypervisor that does not virtualize performance counters.
4. perf_event_open API is not supported on this system, e.g. WSL.
For permissions-related reasons (such as 1 and 2), using `--fdtransfer` while running the profiler
as a privileged user may solve the issue.
If changing the configuration is not possible, you may fall back to
`-e ctimer` profiling mode. It is similar to `cpu` mode, but does not
require perf_events support. As a drawback, there will be no kernel
stack traces.
```
No AllocTracer symbols found. Are JDK debug symbols installed?
```
The OpenJDK debug symbols are required for allocation profiling.
See [Installing Debug Symbols](#installing-debug-symbols) for more details.
If the error message persists after a successful installation of the debug symbols,
it is possible that the JDK was upgraded when installing the debug symbols.
In this case, profiling any Java process which had started prior to the installation
will continue to display this message, since the process had loaded
the older version of the JDK which lacked debug symbols.
Restarting the affected Java processes should resolve the issue.
```
VMStructs unavailable. Unsupported JVM?
```
JVM shared library does not export `gHotSpotVMStructs*` symbols -
apparently this is not a HotSpot JVM. Sometimes the same message
can be also caused by an incorrectly built JDK
(see [#218](https://github.com/async-profiler/async-profiler/issues/218)).
In these cases installing JDK debug symbols may solve the problem.
```
Could not parse symbols from <libname.so>
```
Async-profiler was unable to parse non-Java function names because of
the corrupted contents in `/proc/[pid]/maps`. The problem is known to
occur in a container when running Ubuntu with Linux kernel 5.x.
This is the OS bug, see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018.
```
Could not open output file
```
Output file is written by the target JVM process, not by the profiler script.
Make sure the path specified in `-f` option is correct and is accessible by the JVM.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 141 KiB

743
demo/flamegraph.html Normal file
View File

@@ -0,0 +1,743 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style>
body {margin: 0; padding: 10px 10px 22px 10px; background-color: #ffffff}
h1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}
header {margin: -24px 0 5px 0; line-height: 24px}
button {font: 12px sans-serif; cursor: pointer}
p {position: fixed; bottom: 0; margin: 0; padding: 2px 3px 2px 3px; outline: 1px solid #ffc000; display: none; overflow: hidden; white-space: nowrap; background-color: #ffffe0}
a {color: #0366d6}
#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}
#hl span {padding: 0 3px 0 3px}
#status {left: 0}
#match {right: 0}
#reset {cursor: pointer}
#canvas {width: 100%; height: 576px}
</style>
</head>
<body style='font: 12px Verdana, sans-serif'>
<h1>CPU profile</h1>
<header style='text-align: left'><button id='reverse' title='Reverse'>&#x1f53b;</button>&nbsp;&nbsp;<button id='search' title='Search'>&#x1f50d;</button></header>
<header style='text-align: right'>Produced by <a href='https://github.com/async-profiler/async-profiler'>async-profiler</a></header>
<canvas id='canvas'></canvas>
<div id='hl'><span></span></div>
<p id='status'></p>
<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>&#x274c;</span></p>
<script>
// Copyright The async-profiler authors
// SPDX-License-Identifier: Apache-2.0
'use strict';
let root, rootLevel, px, pattern;
let level0 = 0, left0 = 0, width0 = 0;
let reverse = false;
const levels = Array(36);
for (let h = 0; h < levels.length; h++) {
levels[h] = [];
}
const canvas = document.getElementById('canvas');
const c = canvas.getContext('2d');
const hl = document.getElementById('hl');
const status = document.getElementById('status');
const canvasWidth = canvas.offsetWidth;
const canvasHeight = canvas.offsetHeight;
canvas.style.width = canvasWidth + 'px';
canvas.width = canvasWidth * (devicePixelRatio || 1);
canvas.height = canvasHeight * (devicePixelRatio || 1);
if (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);
c.font = document.body.style.font;
const palette = [
[0xb2e1b2, 20, 20, 20],
[0x50e150, 30, 30, 30],
[0x50cccc, 30, 30, 30],
[0xe15a5a, 30, 40, 40],
[0xc8c83c, 30, 30, 10],
[0xe17d00, 30, 30, 0],
[0xcce880, 20, 20, 20],
];
function getColor(p) {
const v = Math.random();
return '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);
}
function f(key, level, left, width, inln, c1, int) {
levels[level0 = level].push({left: left0 += left, width: width0 = width || width0,
color: getColor(palette[key & 7]), title: cpool[key >>> 3],
details: (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '')
});
}
function u(key, width, inln, c1, int) {
f(key, level0 + 1, 0, width, inln, c1, int)
}
function n(key, width, inln, c1, int) {
f(key, level0, width0, width, inln, c1, int)
}
function samples(n) {
return n === 1 ? '1 sample' : n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' samples';
}
function pct(a, b) {
return a >= b ? '100' : (100 * a / b).toFixed(2);
}
function findFrame(frames, x) {
let left = 0;
let right = frames.length - 1;
while (left <= right) {
const mid = (left + right) >>> 1;
const f = frames[mid];
if (f.left > x) {
right = mid - 1;
} else if (f.left + f.width <= x) {
left = mid + 1;
} else {
return f;
}
}
if (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];
if (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];
return null;
}
function removeStack(left, width) {
for (let h = 0; h < levels.length; h++) {
const frames = levels[h], newFrames = [];
for (let i = 0; i < frames.length; i++) {
const f = frames[i];
if (f.left >= left + width) {
f.left -= width;
} else if (f.left + f.width > left) {
if ((f.width -= width) <= 0 && h) continue;
}
newFrames.push(f);
}
levels[h] = newFrames;
}
}
function search(r) {
if (r === true && (r = prompt('Enter regexp to search:', '')) === null) {
return;
}
pattern = r ? RegExp(r) : undefined;
const matched = render(root, rootLevel);
document.getElementById('matchval').textContent = pct(matched, root.width) + '%';
document.getElementById('match').style.display = r ? 'inline-block' : 'none';
}
function render(newRoot, newLevel) {
if (root) {
c.fillStyle = '#ffffff';
c.fillRect(0, 0, canvasWidth, canvasHeight);
}
root = newRoot || levels[0][0];
rootLevel = newLevel || 0;
px = canvasWidth / root.width;
const x0 = root.left;
const x1 = x0 + root.width;
const marked = [];
function mark(f) {
return marked[f.left] >= f.width || (marked[f.left] = f.width);
}
function totalMarked() {
let total = 0;
let left = 0;
Object.keys(marked).sort(function(a, b) { return a - b; }).forEach(function(x) {
if (+x >= left) {
total += marked[x];
left = +x + marked[x];
}
});
return total;
}
function drawFrame(f, y, alpha) {
if (f.left < x1 && f.left + f.width > x0) {
c.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
if (f.width * px >= 21) {
const chars = Math.floor(f.width * px / 7);
const title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';
c.fillStyle = '#000000';
c.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);
}
if (alpha) {
c.fillStyle = 'rgba(255, 255, 255, 0.5)';
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
}
}
}
for (let h = 0; h < levels.length; h++) {
const y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;
const frames = levels[h];
for (let i = 0; i < frames.length; i++) {
drawFrame(frames[i], y, h < rootLevel);
}
}
return totalMarked();
}
function unpack(cpool) {
for (let i = 1; i < cpool.length; i++) {
cpool[i] = cpool[i - 1].substring(0, cpool[i].charCodeAt(0) - 32) + cpool[i].substring(1);
}
}
canvas.onmousemove = function() {
const h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);
if (h >= 0 && h < levels.length) {
const f = findFrame(levels[h], event.offsetX / px + root.left);
if (f) {
if (f !== root) getSelection().removeAllRanges();
hl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';
hl.style.width = (Math.min(f.width, root.width) * px) + 'px';
hl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';
hl.firstChild.textContent = f.title;
hl.style.display = 'block';
canvas.title = f.title + '\n(' + samples(f.width) + f.details + ', ' + pct(f.width, levels[0][0].width) + '%)';
canvas.style.cursor = 'pointer';
canvas.onclick = function() {
if (event.altKey && h >= rootLevel) {
removeStack(f.left, f.width);
root.width > f.width ? render(root, rootLevel) : render();
} else if (f !== root) {
render(f, h);
}
canvas.onmousemove();
};
status.textContent = 'Function: ' + canvas.title;
status.style.display = 'inline-block';
return;
}
}
canvas.onmouseout();
}
canvas.onmouseout = function() {
hl.style.display = 'none';
status.style.display = 'none';
canvas.title = '';
canvas.style.cursor = '';
canvas.onclick = null;
}
canvas.ondblclick = function() {
getSelection().selectAllChildren(hl);
}
document.getElementById('reverse').onclick = function() {
reverse = !reverse;
render();
}
document.getElementById('search').onclick = function() {
search(true);
}
document.getElementById('reset').onclick = function() {
search(false);
}
window.onkeydown = function() {
if ((event.ctrlKey || event.metaKey) && event.keyCode === 70) {
event.preventDefault();
search(true);
} else if (event.keyCode === 27) {
search(false);
}
}
const cpool = [
'all',
' C2Compiler::compile_method',
'!ompilation::Compilation',
'-compile_java_method',
'5method',
'-emit_code_body',
'&e::Code_Gen',
'+mpile',
')Optimize',
'\'Broker::compiler_thread_loop',
'/invoke_compiler_on_method',
'\'r::compile_method',
'"ntiguousSpace::allocate',
' DefNewGeneration::FastEvacuateFollowersClosure::do_void',
'2collect',
'4py_to_survivor_space',
' GenCollectedHeap::collect_generation',
'2do_collection',
'2satisfy_failed_allocation',
'#eration::promote',
' InstanceKlass::allocate_objArray',
'"terpreterRuntime::anewarray',
' JVM_ArrayCopy',
'!avaThread::run',
'$_sun_nio_ch_FileDispatcherImpl_read0',
' Matcher::match',
'!emAllocator::allocate',
' ObjArrayAllocator::initialize',
'!ffsetTableContigSpace::allocate',
' Parse::Parse',
'\'do_all_blocks',
'*call',
'*one_block',
'/ytecode',
'%Generator::generate',
'!haseCFG::do_global_code_motion',
'*global_code_motion',
'*schedule_late',
'4ocal',
'&haitin::Register_Allocate',
'.Split',
'.build_ifg_physical',
'.elide_copy',
'.interfere_with_live',
'.merge_multidefs',
'.post_allocate_copy_removal',
'%IdealLoop::Dominators',
'0build_and_optimize',
'6loop_early',
';late',
';tree',
'0optimize',
'0remix_address_expressions',
'0split_if_with_blocks',
'D_post',
'Fre',
'&terGVN::optimize',
'.subsume_node',
'.transform_old',
'%Live::add_liveout',
'+compute',
'%MacroExpand::expand_macro_nodes',
'!redictedCallGenerator::generate',
' TenuredGeneration::allocate',
'!hread::call_run',
' VMThread::evaluate_operation',
'*inner_execute',
'*run',
'"_GenCollectForAllocation::doit',
'#Operation::evaluate',
' __GI_read',
'"handle_mm_fault',
'"memcpy_sse2_unaligned_erms',
'%set_avx2_unaligned_erms',
' aci_CopyRight',
'!sm_exc_page_fault',
' clear_huge_page',
'&page_erms',
'&subpage',
'"one3',
'!opy_page_to_iter',
'%user_enhanced_fast_string',
' demo8/FileConverter$$Lambda$3.0x00007ffab9001000.apply',
'<4.0x00007ffab9001240.applyAsInt',
'4Entry.<init>',
':equals',
':hashCode',
'3.convertFile',
';List',
'4main',
'4readInput',
'4saveResult',
'!o_huge_pmd_anonymous_page',
'#syscall_64',
'#user_addr_fault',
' entry_SYSCALL_64_after_hwframe',
'!xc_page_fault',
' filemap_read',
' handle_mm_fault',
' java/io/BufferedReader.fill',
'7readLine',
')yteArrayOutputStream.ensureCapacity',
'>toByteArray',
'>write',
'(DataOutputStream.write',
'>Int',
'>UTF',
'(InputStreamReader.read',
'%lang/Integer.parseInt',
'*String.<init>',
'1decodeASCII',
'1hashCode',
'1length',
'1substring',
'0Latin1.hashCode',
'7newString',
'0UTF16.compress',
'+ystem$2.decodeASCII',
'0.arraycopy',
'*ThreadLocal.get',
'%nio/charset/CharsetDecoder.decode',
')file/Files.readAllLines',
'%util/ArrayList$ArrayListSpliterator.tryAdvance',
'3.add',
'4grow',
'4sort',
'/s.copyOf',
'7Range',
'1sort',
'*Comparator$$Lambda$5.0x00007ffab90494b0.compare',
'4.lambda$comparingInt$7b0bb60$1',
'*HashMap$Node.<init>',
'1.hash',
'2newNode',
'2put',
'5Val',
'2resize',
'.Set.add',
'*TimSort.binarySort',
'2mergeAt',
'7Collapse',
'7ForceCollapse',
'7Hi',
'7Lo',
'2sort',
'*stream/AbstractPipeline.copyInto',
'JWithCancel',
'Bevaluate',
'BwrapAndCopyInto',
'1Collectors$$Lambda$7.0x00007ffab904a268.accept',
'1DistinctOps$1$2.accept',
'Aend',
'1ReduceOps$3ReducingSink.accept',
';ReduceOp.evaluateSequential',
'3ferencePipeline$3$1.accept',
'B.collect',
'CforEachWithCancel',
'1Sink$ChainedReference.end',
'2liceOps$1$1.accept',
'2ortedOps$RefSortingSink.accept',
'Jend',
'!long_disjoint_arraycopy',
' ksys_read',
' new_sync_read',
' oop_arraycopy',
' start_thread',
'!un/nio/ch/ChannelInputStream.read',
'+FileChannelImpl.read',
'/DispatcherImpl.read',
'B0',
'+IOUtil.read',
'6IntoNativeBuffer',
'+Util.getTemporaryDirectBuffer',
')s/StreamDecoder.implRead',
'9read',
'=Bytes',
'+UTF_8$Decoder.decodeArrayLoop',
'?Loop',
' thread_native_entry',
' vfs_read',
'!oid ContiguousSpace::oop_since_save_marks_iterate<DefNewScanClosure>',
'%OopOopIterateDispatch<DefNewScanClosure>::Table::oop_oop_iterate<InstanceKlass, narrowOop>',
'fObjArrayKlass, narrowOop>',
'AYoungerGenClosure>::Table::oop_oop_iterate<InstanceKlass, narrowOop>'
];
unpack(cpool);
n(3,584)
f(635,1,1,178)
u(1323)
u(1428)
u(516)
u(188,70)
u(76)
f(84,7,2,68)
f(12,8,2,63)
u(60)
u(52,36)
f(204,11,3,2)
n(284,7)
u(292)
f(300,13,1,2)
n(308,4)
f(316,11,4,24)
f(324,12,6,2)
n(332,9)
f(348,13,5,4)
f(356,12,4,2)
n(364)
u(340)
f(484,12,2,3)
u(476)
f(68,10,3,23)
f(412,11,2,16)
u(380,15)
f(372,13,1,3)
n(388,2)
n(396)
n(404)
n(428,5)
f(436,14,1,2)
n(444)
u(420)
f(452,11,3,3)
u(468)
f(460,13,1,2)
f(492,11,2)
u(452)
u(468)
f(276,10,2,4)
u(236)
u(244)
u(260)
u(268)
u(252)
f(500,16,1,3)
f(500,17,1,2)
u(276)
u(236)
u(244)
u(260)
u(268)
u(252)
f(92,8,2,3)
u(20)
u(36)
u(28)
f(44,12,1,2)
f(540,5,2,108)
u(532)
u(524)
u(556)
u(548)
u(148)
u(140)
u(132)
u(116)
u(108)
f(1444,15,12,50)
f(1452,16,1,22)
f(124,17,2,20)
f(156,18,9,8)
f(228,19,2,2)
n(508)
n(605)
u(773)
u(757)
u(789)
u(573)
u(741)
u(613)
u(629)
u(621)
f(579,18,2)
f(1460,16,3,27)
f(124,17,11,16)
f(156,18,13,3)
u(605)
u(773)
u(757)
u(789)
u(573)
u(741)
u(613)
u(629)
u(621)
f(1468,15,3,46)
f(124,16,19,27)
f(100,17,10,4)
n(156,13)
f(228,18,1,2)
n(605,10)
u(773)
u(757)
u(789)
u(573)
u(741)
u(613)
u(629)
f(621,26,1,9)
f(713,1,9,405)
u(697)
u(705,229)
f(1241,4,1,228)
u(1177)
u(1225)
u(1185)
u(1161)
u(1169)
u(1249,107)
f(977,11,2,105)
f(1233,12,2,103,2,0,0)
u(657,13)
f(674,14,4,9,8,0,0)
f(866,15,1,3)
u(866)
f(906,15,3,5,4,0,0)
u(906,3,2,0,0)
u(922)
f(1018,18,1,2)
f(922,16,2)
f(1201,13,2,90,2,0,0)
u(1098,87,57,0,0)
u(1074,87,59,0,0)
u(1057,4)
u(689)
f(890,18,1,3)
f(914,19,1,2)
f(1082,16,2,83,59,1,0)
f(682,17,41,2)
n(1066,17)
u(1050)
f(1089,17,17,23,0,0,2)
f(1265,14,23,3)
u(1273)
u(985)
u(985)
u(993)
u(993)
u(1009)
u(1008)
u(172)
u(164)
u(212)
u(220)
u(587)
u(605)
u(773)
u(757)
u(789)
u(573)
u(741)
u(613)
u(629)
u(621)
f(1257,10,3,121)
u(1209)
u(1257)
u(1281)
f(1001,14,1,117)
u(1025)
u(1153)
u(1105,21,0,1,0)
f(1034,18,13,4)
u(1042)
f(666,20,1,3)
f(1315,18,3,4)
f(1121,17,4,73)
u(1113)
u(1137,16)
f(1034,20,14,2,1,0,0)
u(1042)
f(1145,19,2,57,0,2,1)
f(1033,20,54,3)
u(1042)
u(666)
f(1129,17,3,23)
u(1113)
u(1137)
f(1033,20,18,5,1,0,0)
u(1042)
u(666)
f(1218,14,5,3,1,0,0)
u(1194,3,1,0,0)
u(986,3,1,0,0)
f(985,17,1,2)
u(993)
u(993)
u(1009)
u(1008)
u(172)
u(164)
u(212)
u(220)
f(721,3,2,107)
u(969)
u(969)
f(801,6,4,97)
u(801,97,0,0,1)
f(793,8,77,14)
u(857,14,1,0,0)
u(1393,14,1,0,0)
f(1385,11,1,13)
u(961,3)
u(1417)
u(1409)
u(938)
u(882)
f(1401,12,3,10,2,0,0)
u(1329,10,3,0,0)
u(1329,10,3,0,0)
u(1329,10,3,0,0)
u(1337,10,3,1,0)
f(1361,17,2,8,2,0,0)
u(1361,8,2,0,0)
u(1369,6)
u(1345)
u(1353)
u(195)
u(563)
u(765)
u(749)
f(1301,26,1,5)
u(1437)
f(1309,28,1,4)
u(781)
f(645,30,1,3)
u(653)
f(1378,19,3,2)
u(954)
u(1291)
f(874,8,2,6,5,0,0)
u(874,6,5,0,0)
u(930,6,5,0,0)
f(985,6,6)
u(985)
u(993)
u(993)
u(1009)
u(1008,6,0,1,4)
f(172,12,2,4)
u(164)
u(212)
u(220)
u(587)
f(605,17,1,3)
u(773)
u(757)
u(789)
u(573)
u(741)
u(613)
u(629)
u(621)
f(729,3,3,69,0,2,1)
f(817,4,21,2)
u(1009)
u(945)
u(179)
u(595)
f(842,4,2,8,5,0,0)
f(826,5,1,7,5,0,0)
f(809,6,5,2)
u(1008,2,0,0,1)
f(849,4,2,38)
u(849,38,0,0,2)
f(834,6,12,23,21,0,0)
f(826,7,6,17,15,1,0)
f(898,6,17,3)
search();
</script></body></html>

BIN
demo/flamegraph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

118
pom-converter.xml Normal file
View File

@@ -0,0 +1,118 @@
<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>jfr-converter</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<name>async-profiler</name>
<url>https://profiler.tools</url>
<description>Low overhead sampling profiler for Java</description>
<licenses>
<license>
<name>Apache License Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>https://github.com/async-profiler/async-profiler</url>
<connection>scm:git:git@github.com:async-profiler/async-profiler.git</connection>
<developerConnection>scm:git:git@github.com:async-profiler/async-profiler.git</developerConnection>
</scm>
<developers>
<developer>
<id>apangin</id>
<name>Andrei Pangin</name>
<email>noreply@pangin.pro</email>
</developer>
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>src/converter</sourceDirectory>
<resources>
<resource>
<directory>src/res</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
</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>

44
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tools.profiler</groupId>
<artifactId>async-profiler</artifactId>
<version>1.8.3</version>
<version>3.0</version>
<packaging>jar</packaging>
<name>async-profiler</name>
@@ -18,9 +18,9 @@
</license>
</licenses>
<scm>
<url>https://github.com/jvm-profiling-tools/async-profiler</url>
<connection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</connection>
<developerConnection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</developerConnection>
<url>https://github.com/async-profiler/async-profiler</url>
<connection>scm:git:git@github.com:async-profiler/async-profiler.git</connection>
<developerConnection>scm:git:git@github.com:async-profiler/async-profiler.git</developerConnection>
</scm>
<developers>
<developer>
@@ -36,20 +36,50 @@
<build>
<sourceDirectory>src/api</sourceDirectory>
<resources>
<resource>
<directory>native</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<release>8</release>
</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>
@@ -99,4 +129,4 @@
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
</repository>
</distributionManagement>
</project>
</project>

View File

@@ -1,276 +0,0 @@
#!/bin/sh
set -eu
usage() {
echo "Usage: $0 [action] [options] <pid>"
echo "Actions:"
echo " start start profiling and return immediately"
echo " resume resume profiling without resetting collected data"
echo " stop stop profiling"
echo " check check if the specified profiling event is available"
echo " status print profiling status"
echo " list list profiling events supported by the target JVM"
echo " collect collect profile for the specified period of time"
echo " and then stop (default action)"
echo "Options:"
echo " -e event profiling event: cpu|alloc|lock|cache-misses etc."
echo " -d duration run profiling for <duration> seconds"
echo " -f filename dump output to <filename>"
echo " -i interval sampling interval in nanoseconds"
echo " -j jstackdepth maximum Java stack depth"
echo " -t profile different threads separately"
echo " -s simple class names instead of FQN"
echo " -g print method signatures"
echo " -a annotate Java method names"
echo " -o fmt output format: flat|collapsed|html|tree|jfr"
echo " -I include output only stack traces containing the specified pattern"
echo " -X exclude exclude stack traces with the specified pattern"
echo " -v, --version display version string"
echo ""
echo " --title string FlameGraph title"
echo " --minwidth pct skip frames smaller than pct%"
echo " --reverse generate stack-reversed FlameGraph / Call tree"
echo ""
echo " --all-kernel only include kernel-mode events"
echo " --all-user only include user-mode events"
echo " --cstack mode how to traverse C stack: fp|lbr|no"
echo " --begin function begin profiling when function is executed"
echo " --end function end profiling when function is executed"
echo ""
echo "<pid> is a numeric process ID of the target JVM"
echo " or 'jps' keyword to find running JVM automatically"
echo " or the application's name as it would appear in the jps tool"
echo ""
echo "Example: $0 -d 30 -f profile.svg 3456"
echo " $0 start -i 999000 jps"
echo " $0 stop -o flat jps"
echo " $0 -d 5 -e alloc MyAppName"
exit 1
}
mirror_output() {
# Mirror output from temporary file to local terminal
if [ "$USE_TMP" = true ]; then
if [ -f "$FILE" ]; then
cat "$FILE"
rm "$FILE"
fi
fi
}
check_if_terminated() {
if ! kill -0 "$PID" 2> /dev/null; then
mirror_output
exit 0
fi
}
jattach() {
set +e
"$JATTACH" "$PID" load "$PROFILER" true "$1" > /dev/null
RET=$?
set -e
# Check if jattach failed
if [ $RET -ne 0 ]; then
if [ $RET -eq 255 ]; then
echo "Failed to inject profiler into $PID"
if [ "$(uname -s)" = "Darwin" ]; then
otool -L "$PROFILER"
else
ldd "$PROFILER"
fi
fi
exit $RET
fi
mirror_output
}
OPTIND=1
SCRIPT_DIR="$(cd "$(dirname "$0")" > /dev/null 2>&1; pwd -P)"
JATTACH=$SCRIPT_DIR/build/jattach
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
ACTION="collect"
EVENT="cpu"
DURATION="60"
FILE=""
USE_TMP="true"
OUTPUT=""
FORMAT=""
PARAMS=""
PID=""
while [ $# -gt 0 ]; do
case $1 in
-h|"-?")
usage
;;
start|resume|stop|check|status|list|collect)
ACTION="$1"
;;
-v|--version)
ACTION="version"
;;
-e)
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
shift
;;
-d)
DURATION="$2"
shift
;;
-f)
FILE="$2"
USE_TMP=false
shift
;;
-i)
PARAMS="$PARAMS,interval=$2"
shift
;;
-j)
PARAMS="$PARAMS,jstackdepth=$2"
shift
;;
-t)
PARAMS="$PARAMS,threads"
;;
-s)
FORMAT="$FORMAT,simple"
;;
-g)
FORMAT="$FORMAT,sig"
;;
-a)
FORMAT="$FORMAT,ann"
;;
-o)
OUTPUT="$2"
shift
;;
-I|--include)
FORMAT="$FORMAT,include=$2"
shift
;;
-X|--exclude)
FORMAT="$FORMAT,exclude=$2"
shift
;;
--filter)
FILTER="$(echo "$2" | sed 's/,/;/g')"
FORMAT="$FORMAT,filter=$FILTER"
shift
;;
--title)
# escape XML special characters and comma
TITLE="$(echo "$2" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/,/\&#44;/g')"
FORMAT="$FORMAT,title=$TITLE"
shift
;;
--width|--height|--minwidth)
FORMAT="$FORMAT,${1#--}=$2"
shift
;;
--reverse)
FORMAT="$FORMAT,reverse"
;;
--all-kernel)
PARAMS="$PARAMS,allkernel"
;;
--all-user)
PARAMS="$PARAMS,alluser"
;;
--cstack|--call-graph)
PARAMS="$PARAMS,cstack=$2"
shift
;;
--begin|--end)
PARAMS="$PARAMS,${1#--}=$2"
shift
;;
--safe-mode)
PARAMS="$PARAMS,safemode=$2"
shift
;;
[0-9]*)
PID="$1"
;;
jps)
# A shortcut for getting PID of a running Java application
# -XX:+PerfDisableSharedMem prevents jps from appearing in its own list
PID=$(pgrep -n java || jps -q -J-XX:+PerfDisableSharedMem)
if [ "$PID" = "" ]; then
echo "No Java process could be found!"
fi
;;
-*)
echo "Unrecognized option: $1"
usage
;;
*)
if [ $# -eq 1 ]; then
# the last argument is the application name as it would appear in the jps tool
PID=$(jps -J-XX:+PerfDisableSharedMem | grep " $1$" | head -n 1 | cut -d ' ' -f 1)
if [ "$PID" = "" ]; then
echo "No Java process '$1' could be found!"
fi
else
echo "Unrecognized option: $1"
usage
fi
;;
esac
shift
done
if [ "$PID" = "" ] && [ "$ACTION" != "version" ]; then
usage
fi
# If no -f argument is given, use temporary file to transfer output to caller terminal.
# Let the target process create the file in case this script is run by superuser.
if [ "$USE_TMP" = true ]; then
FILE=/tmp/async-profiler.$$.$PID
else
case "$FILE" in
/*)
# Path is absolute
;;
*)
# Output file is written by the target process. Make the path absolute to avoid confusion.
FILE=$PWD/$FILE
;;
esac
fi
case $ACTION in
start|resume|check)
jattach "$ACTION,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
;;
stop)
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
;;
status)
jattach "status,file=$FILE"
;;
list)
jattach "list,file=$FILE"
;;
collect)
jattach "start,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
while [ "$DURATION" -gt 0 ]; do
DURATION=$(( DURATION-1 ))
check_if_terminated
sleep 1
done
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
;;
version)
if [ "$PID" = "" ]; then
java "-agentpath:$PROFILER=version=full" -version 2> /dev/null
else
jattach "version=full,file=$FILE"
fi
;;
esac

View File

@@ -1,38 +1,27 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "allocTracer.h"
#include "os.h"
#include "profiler.h"
#include "stackFrame.h"
#include "tsc.h"
#include "vmStructs.h"
int AllocTracer::_trap_kind;
Trap AllocTracer::_in_new_tlab;
Trap AllocTracer::_outside_tlab;
Trap AllocTracer::_in_new_tlab(0);
Trap AllocTracer::_outside_tlab(1);
u64 AllocTracer::_interval;
volatile u64 AllocTracer::_allocated_bytes;
// Called whenever our breakpoint trap is hit
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
StackFrame frame(ucontext);
int event_type;
EventType event_type;
uintptr_t total_size;
uintptr_t instance_size;
@@ -40,17 +29,18 @@ void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
if (_in_new_tlab.covers(frame.pc())) {
// send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread)
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
event_type = BCI_ALLOC;
event_type = ALLOC_SAMPLE;
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
instance_size = _trap_kind == 1 ? frame.arg3() : frame.arg2();
} else if (_outside_tlab.covers(frame.pc())) {
// send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread)
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
event_type = BCI_ALLOC_OUTSIDE_TLAB;
event_type = ALLOC_OUTSIDE_TLAB;
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
instance_size = 0;
} else {
// Not our trap
Profiler::instance()->trapHandler(signo, siginfo, ucontext);
return;
}
@@ -58,50 +48,37 @@ void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
uintptr_t klass = frame.arg0();
frame.ret();
if (_enabled) {
// TODO: _enabled also uses traps
if (_enabled && updateCounter(_allocated_bytes, total_size, _interval)) {
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
}
}
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
void AllocTracer::recordAllocation(void* ucontext, EventType event_type, uintptr_t rklass,
uintptr_t total_size, uintptr_t instance_size) {
if (_interval) {
// Do not record allocation unless allocated at least _interval bytes
while (true) {
u64 prev = _allocated_bytes;
u64 next = prev + total_size;
if (next < _interval) {
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next)) {
return;
}
} else {
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next % _interval)) {
break;
}
}
}
}
AllocEvent event;
event._start_time = TSC::ticks();
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());
event._class_id = Profiler::instance()->classMap()->lookup(symbol->body(), symbol->length());
}
Profiler::_instance.recordSample(ucontext, total_size, event_type, &event);
Profiler::instance()->recordSample(ucontext, total_size, event_type, &event);
}
Error AllocTracer::check(Arguments& args) {
if (args._live) {
return Error("'live' option is supported on OpenJDK 11+");
}
if (_in_new_tlab.entry() != 0 && _outside_tlab.entry() != 0) {
return Error::OK;
}
NativeCodeCache* libjvm = VMStructs::libjvm();
CodeCache* libjvm = VMStructs::libjvm();
const void* ne;
const void* oe;
@@ -118,9 +95,9 @@ Error AllocTracer::check(Arguments& args) {
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
}
if (!_in_new_tlab.assign(ne) || !_outside_tlab.assign(oe)) {
return Error("Unable to install allocation trap");
}
_in_new_tlab.assign(ne);
_outside_tlab.assign(oe);
_in_new_tlab.pair(_outside_tlab);
return Error::OK;
}
@@ -131,13 +108,12 @@ Error AllocTracer::start(Arguments& args) {
return error;
}
_interval = args._interval;
_interval = args._alloc > 0 ? args._alloc : 0;
_allocated_bytes = 0;
OS::installSignalHandler(SIGTRAP, signalHandler);
_in_new_tlab.install();
_outside_tlab.install();
if (!_in_new_tlab.install() || !_outside_tlab.install()) {
return Error("Cannot install allocation breakpoints");
}
return Error::OK;
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _ALLOCTRACER_H
@@ -20,6 +9,7 @@
#include <signal.h>
#include <stdint.h>
#include "engine.h"
#include "event.h"
#include "trap.h"
@@ -32,27 +22,23 @@ class AllocTracer : public Engine {
static u64 _interval;
static volatile u64 _allocated_bytes;
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
static void recordAllocation(void* ucontext, EventType event_type, uintptr_t rklass,
uintptr_t total_size, uintptr_t instance_size);
public:
const char* name() {
return "alloc";
const char* title() {
return "Allocation profile";
}
const char* units() {
return "bytes";
}
CStack cstack() {
return CSTACK_NO;
}
Error check(Arguments& args);
Error start(Arguments& args);
void stop();
static void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
};
#endif // _ALLOCTRACER_H

View File

@@ -1,22 +1,14 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Java API for in-process profiling. Serves as a wrapper around
@@ -39,25 +31,93 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
return instance;
}
if (libPath == null) {
System.loadLibrary("asyncProfiler");
} else {
AsyncProfiler profiler = new AsyncProfiler();
if (libPath != null) {
System.load(libPath);
} else {
try {
// No need to load library, if it has been preloaded with -agentpath
profiler.getVersion();
} catch (UnsatisfiedLinkError e) {
File file = extractEmbeddedLib();
if (file != null) {
try {
System.load(file.getPath());
} finally {
file.delete();
}
} else {
System.loadLibrary("asyncProfiler");
}
}
}
instance = new AsyncProfiler();
return instance;
instance = profiler;
return profiler;
}
private static File extractEmbeddedLib() {
String resourceName = "/" + getPlatformTag() + "/libasyncProfiler.so";
InputStream in = AsyncProfiler.class.getResourceAsStream(resourceName);
if (in == null) {
return null;
}
try {
String extractPath = System.getProperty("one.profiler.extractPath");
File file = File.createTempFile("libasyncProfiler-", ".so",
extractPath == null || extractPath.isEmpty() ? null : new File(extractPath));
try (FileOutputStream out = new FileOutputStream(file)) {
byte[] buf = new byte[32000];
for (int bytes; (bytes = in.read(buf)) >= 0; ) {
out.write(buf, 0, bytes);
}
}
return file;
} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
}
private static String getPlatformTag() {
String os = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch").toLowerCase();
if (os.contains("linux")) {
if (arch.equals("amd64") || arch.equals("x86_64") || arch.contains("x64")) {
return "linux-x64";
} else if (arch.equals("aarch64") || arch.contains("arm64")) {
return "linux-arm64";
} else if (arch.equals("aarch32") || arch.contains("arm")) {
return "linux-arm32";
} else if (arch.contains("86")) {
return "linux-x86";
} else if (arch.contains("ppc64")) {
return "linux-ppc64le";
}
} else if (os.contains("mac")) {
return "macos";
}
throw new UnsupportedOperationException("Unsupported platform: " + os + "-" + arch);
}
/**
* Start profiling
*
* @param event Profiling event, see {@link Events}
* @param event Profiling event, see {@link Events}
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
* @throws IllegalStateException If profiler is already running
*/
@Override
public void start(String event, long interval) throws IllegalStateException {
if (event == null) {
throw new NullPointerException();
}
start0(event, interval, true);
}
@@ -65,12 +125,15 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
* Start or resume profiling without resetting collected data.
* Note that event and interval may change since the previous profiling session.
*
* @param event Profiling event, see {@link Events}
* @param event Profiling event, see {@link Events}
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
* @throws IllegalStateException If profiler is already running
*/
@Override
public void resume(String event, long interval) throws IllegalStateException {
if (event == null) {
throw new NullPointerException();
}
start0(event, interval, false);
}
@@ -113,10 +176,13 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
* @param command Profiling command
* @return The command result
* @throws IllegalArgumentException If failed to parse the command
* @throws IOException If failed to create output file
* @throws IOException If failed to create output file
*/
@Override
public String execute(String command) throws IllegalArgumentException, IOException {
public String execute(String command) throws IllegalArgumentException, IllegalStateException, IOException {
if (command == null) {
throw new NullPointerException();
}
return execute0(command);
}
@@ -129,7 +195,22 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
@Override
public String dumpCollapsed(Counter counter) {
try {
return execute0("collapsed,counter=" + counter.name().toLowerCase());
return execute0("collapsed," + counter.name().toLowerCase());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
/**
* Dump collected stack traces
*
* @param maxTraces Maximum number of stack traces to dump. 0 means no limit
* @return Textual representation of the profile
*/
@Override
public String dumpTraces(int maxTraces) {
try {
return execute0(maxTraces == 0 ? "traces" : "traces=" + maxTraces);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -144,7 +225,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
@Override
public String dumpFlat(int maxMethods) {
try {
return execute0("flat=" + maxMethods);
return execute0(maxMethods == 0 ? "flat" : "flat=" + maxMethods);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -171,7 +252,7 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
}
private void filterThread(Thread thread, boolean enable) {
if (thread == null) {
if (thread == null || thread == Thread.currentThread()) {
filterThread0(null, enable);
} else {
// Need to take lock to avoid race condition with a thread state change
@@ -185,7 +266,10 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
}
private native void start0(String event, long interval, boolean reset) throws IllegalStateException;
private native void stop0() throws IllegalStateException;
private native String execute0(String command) throws IllegalArgumentException, IOException;
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
private native void filterThread0(Thread thread, boolean enable);
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;
@@ -35,8 +24,9 @@ public interface AsyncProfilerMXBean {
long getSamples();
String getVersion();
String execute(String command) throws IllegalArgumentException, java.io.IOException;
String execute(String command) throws IllegalArgumentException, IllegalStateException, java.io.IOException;
String dumpCollapsed(Counter counter);
String dumpTraces(int maxTraces);
String dumpFlat(int maxMethods);
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;
@@ -24,5 +13,6 @@ public class Events {
public static final String ALLOC = "alloc";
public static final String LOCK = "lock";
public static final String WALL = "wall";
public static final String CTIMER = "ctimer";
public static final String ITIMER = "itimer";
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _ARCH_H
@@ -31,28 +20,42 @@ static inline int atomicInc(volatile int& var, int increment = 1) {
return __sync_fetch_and_add(&var, increment);
}
static inline u64 loadAcquire(u64& var) {
return __atomic_load_n(&var, __ATOMIC_ACQUIRE);
}
static inline void storeRelease(u64& var, u64 value) {
return __atomic_store_n(&var, value, __ATOMIC_RELEASE);
}
#if defined(__x86_64__) || defined(__i386__)
typedef unsigned char instruction_t;
const instruction_t BREAKPOINT = 0xcc;
const int BREAKPOINT_OFFSET = 0;
const int SYSCALL_SIZE = 2;
const int FRAME_PC_SLOT = 1;
const int PROBE_SP_LIMIT = 4;
const int PLT_HEADER_SIZE = 16;
const int PLT_ENTRY_SIZE = 16;
const int PERF_REG_PC = 8; // PERF_REG_X86_IP
#define spinPause() asm volatile("pause")
#define rmb() asm volatile("lfence" : : : "memory")
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r"(addr) : "memory")
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r" (addr) : "memory")
#elif defined(__arm__) || defined(__thumb__)
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0xe7f001f0;
const instruction_t BREAKPOINT_THUMB = 0xde01de01;
const int BREAKPOINT_OFFSET = 0;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 1;
const int PROBE_SP_LIMIT = 0;
const int PLT_HEADER_SIZE = 20;
const int PLT_ENTRY_SIZE = 12;
const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
@@ -65,16 +68,76 @@ const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0xd4200000;
const int BREAKPOINT_OFFSET = 0;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 1;
const int PROBE_SP_LIMIT = 0;
const int PLT_HEADER_SIZE = 32;
const int PLT_ENTRY_SIZE = 16;
const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
#define spinPause() asm volatile("yield")
#define spinPause() asm volatile("isb")
#define rmb() asm volatile("dmb ish" : : : "memory")
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
#elif defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0x7fe00008;
// We place the break point in the third instruction slot on PPCLE as the first two are skipped if
// the call comes from within the same compilation unit according to the LE ABI.
const int BREAKPOINT_OFFSET = 8;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 2;
const int PROBE_SP_LIMIT = 0;
const int PLT_HEADER_SIZE = 24;
const int PLT_ENTRY_SIZE = 24;
const int PERF_REG_PC = 32; // PERF_REG_POWERPC_NIP
#define spinPause() asm volatile("yield") // does nothing, but using or 1,1,1 would lead to other problems
#define rmb() asm volatile ("sync" : : : "memory") // lwsync would do but better safe than sorry
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
#elif defined(__riscv) && (__riscv_xlen == 64)
typedef unsigned int instruction_t;
#if defined(__riscv_compressed)
const instruction_t BREAKPOINT = 0x9002; // EBREAK (compressed form)
#else
const instruction_t BREAKPOINT = 0x00100073; // EBREAK
#endif
const int BREAKPOINT_OFFSET = 0;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 1; // return address is at -1 from FP
const int PROBE_SP_LIMIT = 0;
const int PLT_HEADER_SIZE = 24; // Best guess from examining readelf
const int PLT_ENTRY_SIZE = 24; // ...same...
const int PERF_REG_PC = 0; // PERF_REG_RISCV_PC
#define spinPause() // No architecture support
#define rmb() asm volatile ("fence" : : : "memory")
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
#elif defined(__loongarch_lp64)
typedef unsigned int instruction_t;
const instruction_t BREAKPOINT = 0x002a0005; // EBREAK
const int BREAKPOINT_OFFSET = 0;
const int SYSCALL_SIZE = sizeof(instruction_t);
const int FRAME_PC_SLOT = 1;
const int PROBE_SP_LIMIT = 0;
const int PLT_HEADER_SIZE = 32;
const int PLT_ENTRY_SIZE = 16;
const int PERF_REG_PC = 0; // PERF_REG_LOONGARCH_PC
#define spinPause() asm volatile("ibar 0x0")
#define rmb() asm volatile("dbar 0x0" : : : "memory")
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
#else
#error "Compiling on unsupported arch"
@@ -82,4 +145,22 @@ const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
#endif
// Return address signing support.
// Apple M1 has 47 bit virtual addresses.
#if defined(__aarch64__) && defined(__APPLE__)
# define ADDRESS_BITS 47
# define WX_MEMORY true
#else
# define WX_MEMORY false
#endif
#ifdef ADDRESS_BITS
static inline const void* stripPointer(const void* p) {
return (const void*) ((unsigned long)p & ((1UL << ADDRESS_BITS) - 1));
}
#else
# define stripPointer(p) (p)
#endif
#endif // _ARCH_H

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <limits.h>
@@ -24,66 +13,98 @@
#include "arguments.h"
// Arguments of the last start/resume command; reused for shutdown and restart
Arguments _global_args;
// Predefined value that denotes successful operation
const Error Error::OK(NULL);
// Extra buffer space for expanding file pattern
const size_t EXTRA_BUF_SIZE = 512;
// Statically compute hash code of a string containing up to 12 [a-z] letters
#define HASH(s) (HASH12(s " "))
static const Multiplier NANOS[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'s', 1000000000}, {0, 0}};
static const Multiplier BYTES[] = {{'b', 1}, {'k', 1024}, {'m', 1048576}, {'g', 1073741824}, {0, 0}};
static const Multiplier SECONDS[] = {{'s', 1}, {'m', 60}, {'h', 3600}, {'d', 86400}, {0, 0}};
static const Multiplier UNIVERSAL[] = {{'n', 1}, {'u', 1000}, {'m', 1000000}, {'s', 1000000000}, {'b', 1}, {'k', 1024}, {'g', 1073741824}, {0, 0}};
#define HASH12(s) (s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55
// Statically compute hash code of a string containing up to 12 [a-z] letters
#define HASH(s) ((s[0] & 31LL) | (s[1] & 31LL) << 5 | (s[2] & 31LL) << 10 | (s[3] & 31LL) << 15 | \
(s[4] & 31LL) << 20 | (s[5] & 31LL) << 25 | (s[6] & 31LL) << 30 | (s[7] & 31LL) << 35 | \
(s[8] & 31LL) << 40 | (s[9] & 31LL) << 45 | (s[10] & 31LL) << 50 | (s[11] & 31LL) << 55)
// Simulate switch statement over string hashes
#define SWITCH(arg) long long arg_hash = hash(arg); if (0)
#define CASE(s) } else if (arg_hash == HASH(s)) {
#define CASE(s) } else if (arg_hash == HASH(s " ")) {
#define CASE2(s1, s2) } else if (arg_hash == HASH(s1) || arg_hash == HASH(s2)) {
#define DEFAULT() } else {
// Parses agent arguments.
// The format of the string is:
// arg[,arg...]
// where arg is one of the following options:
// start - start profiling
// resume - start or resume profiling without resetting collected data
// stop - stop profiling
// check - check if the specified profiling event is available
// status - print profiling status (inactive / running for X seconds)
// list - show the list of available profiling events
// version[=full] - display the agent version
// event=EVENT - which event to trace (cpu, alloc, lock, cache-misses etc.)
// collapsed[=C] - dump collapsed stacks (the format used by FlameGraph script)
// html[=C] - produce Flame Graph in HTML format
// tree[=C] - produce call tree in HTML format
// C is counter type: 'samples' or 'total'
// jfr - dump events in Java Flight Recorder format
// flat[=N] - dump top N methods (aka flat profile)
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
// jstackdepth=N - maximum Java stack depth (default: 2048)
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
// file=FILENAME - output file name for dumping
// filter=FILTER - thread filter
// threads - profile different threads separately
// cstack=MODE - how to collect C stack frames in addition to Java stack
// MODE is 'fp' (Frame Pointer), 'lbr' (Last Branch Record) or 'no'
// allkernel - include only kernel-mode events
// alluser - include only user-mode events
// simple - simple class names instead of FQN
// dot - dotted class names
// sig - print method signatures
// ann - annotate Java method names
// include=PATTERN - include stack traces containing PATTERN
// exclude=PATTERN - exclude stack traces containing PATTERN
// begin=FUNCTION - begin profiling when FUNCTION is executed
// end=FUNCTION - end profiling when FUNCTION is executed
// title=TITLE - FlameGraph title
// minwidth=PCT - FlameGraph minimum frame width in percent
// reverse - generate stack-reversed FlameGraph / Call tree
// start - start profiling
// resume - start or resume profiling without resetting collected data
// stop - stop profiling
// dump - dump collected data without stopping profiling session
// check - check if the specified profiling event is available
// status - print profiling status (inactive / running for X seconds)
// meminfo - print profiler memory stats
// list - show the list of available profiling events
// version - display the agent version
// event=EVENT - which event to trace (cpu, wall, cache-misses, etc.)
// alloc[=BYTES] - profile allocations with BYTES interval
// live - build allocation profile from live objects only
// lock[=DURATION] - profile contended locks longer than DURATION ns
// wall[=NS] - run wall clock profiling together with CPU profiling
// collapsed - dump collapsed stacks (the format used by FlameGraph script)
// flamegraph - produce Flame Graph in HTML format
// tree - produce call tree in HTML format
// jfr - dump events in Java Flight Recorder format
// jfropts=OPTIONS - JFR recording options: numeric bitmask or 'mem'
// jfrsync[=CONFIG] - start Java Flight Recording with the given config along with the profiler
// traces[=N] - dump top N call traces
// flat[=N] - dump top N methods (aka flat profile)
// samples - count the number of samples (default)
// total - count the total value (time, bytes, etc.) instead of samples
// chunksize=N - approximate size of JFR chunk in bytes (default: 100 MB)
// chunktime=N - duration of JFR chunk in seconds (default: 1 hour)
// timeout=TIME - automatically stop profiler at TIME (absolute or relative)
// loop=TIME - run profiler in a loop (continuous profiling)
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
// jstackdepth=N - maximum Java stack depth (default: 2048)
// signal=N - use alternative signal for cpu or wall clock profiling
// features=LIST - advanced stack trace features (vtable, comptask)"
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
// file=FILENAME - output file name for dumping
// log=FILENAME - log warnings and errors to the given dedicated stream
// loglevel=LEVEL - logging level: TRACE, DEBUG, INFO, WARN, ERROR, or NONE
// server=ADDRESS - start insecure HTTP server at ADDRESS/PORT
// filter=FILTER - thread filter
// threads - profile different threads separately
// sched - group threads by scheduling policy
// cstack=MODE - how to collect C stack frames in addition to Java stack
// MODE is 'fp', 'dwarf', 'lbr', 'vm' or 'no'
// clock=SOURCE - clock source for JFR timestamps: 'tsc' or 'monotonic'
// allkernel - include only kernel-mode events
// alluser - include only user-mode events
// fdtransfer - use fdtransfer to pass fds to the profiler
// simple - simple class names instead of FQN
// dot - dotted class names
// norm - normalize names of hidden classes / lambdas
// sig - print method signatures
// ann - annotate Java methods
// lib - prepend library names
// mcache - max age of jmethodID cache (default: 0 = disabled)
// include=PATTERN - include stack traces containing PATTERN
// exclude=PATTERN - exclude stack traces containing PATTERN
// begin=FUNCTION - begin profiling when FUNCTION is executed
// end=FUNCTION - end profiling when FUNCTION is executed
// title=TITLE - FlameGraph title
// minwidth=PCT - FlameGraph minimum frame width in percent
// reverse - generate stack-reversed FlameGraph / Call tree
//
// It is possible to specify multiple dump options at the same time
@@ -94,13 +115,15 @@ Error Arguments::parse(const char* args) {
size_t len = strlen(args);
free(_buf);
_buf = (char*)malloc(len + EXTRA_BUF_SIZE);
_buf = (char*)malloc(len + EXTRA_BUF_SIZE + 1);
if (_buf == NULL) {
return Error("Not enough memory to parse arguments");
}
strcpy(_buf, args);
char* args_copy = strcpy(_buf + EXTRA_BUF_SIZE, args);
for (char* arg = strtok(_buf, ","); arg != NULL; arg = strtok(NULL, ",")) {
const char* msg = NULL;
for (char* arg = strtok(args_copy, ","); arg != NULL; arg = strtok(NULL, ",")) {
char* value = strchr(arg, '=');
if (value != NULL) *value++ = 0;
@@ -115,80 +138,202 @@ Error Arguments::parse(const char* args) {
CASE("stop")
_action = ACTION_STOP;
CASE("dump")
_action = ACTION_DUMP;
CASE("check")
_action = ACTION_CHECK;
CASE("status")
_action = ACTION_STATUS;
CASE("meminfo")
_action = ACTION_MEMINFO;
CASE("list")
_action = ACTION_LIST;
CASE("version")
_action = value == NULL ? ACTION_VERSION : ACTION_FULL_VERSION;
_action = ACTION_VERSION;
// Output formats
CASE2("collapsed", "folded")
CASE("collapsed")
_output = OUTPUT_COLLAPSED;
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
CASE2("flamegraph", "html")
CASE("flamegraph")
_output = OUTPUT_FLAMEGRAPH;
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
CASE("tree")
_output = OUTPUT_TREE;
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
CASE("jfr")
_output = OUTPUT_JFR;
CASE("jfropts")
_output = OUTPUT_JFR;
if (value == NULL) {
msg = "Invalid jfropts";
} else if (value[0] >= '0' && value[0] <= '9') {
_jfr_options = (int)strtol(value, NULL, 0);
} else if (strstr(value, "mem")) {
_jfr_options |= IN_MEMORY;
}
CASE("jfrsync")
_output = OUTPUT_JFR;
_jfr_options |= JFR_SYNC_OPTS;
_jfr_sync = value == NULL ? "default" : value;
CASE("traces")
_output = OUTPUT_TEXT;
_dump_traces = value == NULL ? INT_MAX : atoi(value);
CASE("flat")
_output = OUTPUT_FLAT;
_output = OUTPUT_TEXT;
_dump_flat = value == NULL ? INT_MAX : atoi(value);
CASE("samples")
_counter = COUNTER_SAMPLES;
CASE("total")
_counter = COUNTER_TOTAL;
CASE("chunksize")
if (value == NULL || (_chunk_size = parseUnits(value, BYTES)) < 0) {
msg = "Invalid chunksize";
}
CASE("chunktime")
if (value == NULL || (_chunk_time = parseUnits(value, SECONDS)) < 0) {
msg = "Invalid chunktime";
}
// Basic options
CASE("event")
if (value == NULL || value[0] == 0) {
return Error("event must not be empty");
msg = "event must not be empty";
} else if (strcmp(value, EVENT_ALLOC) == 0) {
if (_alloc < 0) _alloc = 0;
} else if (strcmp(value, EVENT_LOCK) == 0) {
if (_lock < 0) _lock = 0;
} else if (_event != NULL) {
msg = "Duplicate event argument";
} else {
_event = value;
}
if (!addEvent(value)) {
return Error("multiple incompatible events");
CASE("timeout")
if (value == NULL || (_timeout = parseTimeout(value)) == -1) {
msg = "Invalid timeout";
}
CASE("loop")
_loop = true;
if (value == NULL || (_timeout = parseTimeout(value)) == -1) {
msg = "Invalid loop duration";
}
CASE("alloc")
_alloc = value == NULL ? 0 : parseUnits(value, BYTES);
CASE("lock")
_lock = value == NULL ? 0 : parseUnits(value, NANOS);
CASE("wall")
_wall = value == NULL ? 0 : parseUnits(value, NANOS);
CASE("cpu")
if (_event != NULL) {
msg = "Duplicate event argument";
} else {
_event = EVENT_CPU;
}
CASE("interval")
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
return Error("Invalid interval");
if (value == NULL || (_interval = parseUnits(value, UNIVERSAL)) <= 0) {
msg = "Invalid interval";
}
CASE("jstackdepth")
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
return Error("jstackdepth must be > 0");
msg = "jstackdepth must be > 0";
}
CASE("safemode")
_safe_mode = value == NULL ? INT_MAX : atoi(value);
CASE("signal")
if (value == NULL || (_signal = atoi(value)) <= 0) {
msg = "signal must be > 0";
} else if ((value = strchr(value, '/')) != NULL) {
// Two signals were specified: one for CPU profiling, another for wall clock
_signal |= atoi(value + 1) << 8;
}
CASE("features")
if (value != NULL) {
if (strstr(value, "probesp")) _features.probe_sp = 1;
if (strstr(value, "vtable")) _features.vtable_target = 1;
if (strstr(value, "comptask")) _features.comp_task = 1;
}
CASE("safemode") {
// Left for compatibility purpose; will be eventually migrated to 'features'
int bits = value == NULL ? INT_MAX : (int)strtol(value, NULL, 0);
_features.unknown_java = (bits & 1) ? 0 : 1;
_features.unwind_stub = (bits & 2) ? 0 : 1;
_features.unwind_comp = (bits & 4) ? 0 : 1;
_features.unwind_native = (bits & 8) ? 0 : 1;
_features.java_anchor = (bits & 16) ? 0 : 1;
_features.gc_traces = (bits & 32) ? 0 : 1;
}
CASE("file")
if (value == NULL || value[0] == 0) {
return Error("file must not be empty");
msg = "file must not be empty";
}
_file = value;
CASE("log")
_log = value == NULL || value[0] == 0 ? NULL : value;
CASE("loglevel")
if (value == NULL || value[0] == 0) {
msg = "loglevel must not be empty";
}
_loglevel = value;
CASE("server")
if (value == NULL || value[0] == 0) {
msg = "server address must not be empty";
}
_server = value;
CASE("fdtransfer")
_fdtransfer = true;
if (value == NULL || value[0] == 0) {
msg = "fdtransfer path must not be empty";
}
_fdtransfer_path = value;
// Filters
CASE("filter")
_filter = value == NULL ? "" : value;
CASE("include")
if (value != NULL) appendToEmbeddedList(_include, value);
// Workaround -Wstringop-overflow warning
if (value == arg + 8) appendToEmbeddedList(_include, arg + 8);
CASE("exclude")
if (value != NULL) appendToEmbeddedList(_exclude, value);
// Workaround -Wstringop-overflow warning
if (value == arg + 8) appendToEmbeddedList(_exclude, arg + 8);
CASE("threads")
_threads = true;
CASE("sched")
_sched = true;
CASE("live")
_live = true;
CASE("allkernel")
_ring = RING_KERNEL;
@@ -197,12 +342,21 @@ Error Arguments::parse(const char* args) {
CASE("cstack")
if (value != NULL) {
if (value[0] == 'n') {
_cstack = CSTACK_NO;
} else if (value[0] == 'l') {
_cstack = CSTACK_LBR;
} else {
_cstack = CSTACK_FP;
switch (value[0]) {
case 'n': _cstack = CSTACK_NO; break;
case 'd': _cstack = CSTACK_DWARF; break;
case 'l': _cstack = CSTACK_LBR; break;
case 'v': _cstack = CSTACK_VM; break;
default: _cstack = CSTACK_FP;
}
}
CASE("clock")
if (value != NULL) {
if (value[0] == 't') {
_clock = CLK_TSC;
} else if (value[0] == 'm') {
_clock = CLK_MONOTONIC;
}
}
@@ -213,12 +367,21 @@ Error Arguments::parse(const char* args) {
CASE("dot")
_style |= STYLE_DOTTED;
CASE("norm")
_style |= STYLE_NORMALIZE;
CASE("sig")
_style |= STYLE_SIGNATURES;
CASE("ann")
_style |= STYLE_ANNOTATE;
CASE("lib")
_style |= STYLE_LIB_NAMES;
CASE("mcache")
_mcache = value == NULL ? 1 : (unsigned char)strtol(value, NULL, 0);
CASE("begin")
_begin = value;
@@ -227,45 +390,54 @@ Error Arguments::parse(const char* args) {
// FlameGraph options
CASE("title")
if (value != NULL) _title = value;
_title = value;
CASE("minwidth")
if (value != NULL) _minwidth = atof(value);
CASE("reverse")
_reverse = true;
DEFAULT()
if (_unknown_arg == NULL) _unknown_arg = arg;
}
}
if (_file != NULL && strchr(_file, '%') != NULL) {
_file = expandFilePattern(_buf + len + 1, EXTRA_BUF_SIZE - 1, _file);
// Return error only after parsing all arguments, when 'log' is already set
if (msg != NULL) {
return Error(msg);
}
if (_event == NULL && _alloc < 0 && _lock < 0 && _wall < 0) {
_event = EVENT_CPU;
}
if (_file != NULL && _output == OUTPUT_NONE) {
_output = detectOutputFormat(_file);
if (_output == OUTPUT_SVG) {
return Error("SVG format is obsolete, use .html for FlameGraph");
}
_dump_traces = 100;
_dump_flat = 200;
}
if (_output != OUTPUT_NONE && (_action == ACTION_NONE || _action == ACTION_STOP)) {
if (_action == ACTION_NONE && _output != OUTPUT_NONE) {
_action = ACTION_DUMP;
}
return Error::OK;
}
bool Arguments::addEvent(const char* event) {
if (strcmp(event, EVENT_ALLOC) == 0) {
_events |= EK_ALLOC;
} else if (strcmp(event, EVENT_LOCK) == 0) {
_events |= EK_LOCK;
} else {
if (_events & EK_CPU) {
return false;
}
_events |= EK_CPU;
_event_desc = event;
const char* Arguments::file() {
if (_file != NULL && strchr(_file, '%') != NULL) {
return expandFilePattern(_file);
}
return true;
return _file;
}
// Returns true if the log file is a temporary file of asprof launcher
bool Arguments::hasTemporaryLog() const {
return _log != NULL && strncmp(_log, "/tmp/asprof-log.", 16) == 0;
}
// The linked list of string offsets is embedded right into _buf array
@@ -283,11 +455,14 @@ long long Arguments::hash(const char* arg) {
return h;
}
// Expands %p to the process id
// %t to the timestamp
const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char* pattern) {
char* ptr = dest;
char* end = dest + max_size - 1;
// Expands the following patterns:
// %p process id
// %t timestamp (yyyyMMdd-hhmmss)
// %n{MAX} sequence number
// %{ENV} environment variable
const char* Arguments::expandFilePattern(const char* pattern) {
char* ptr = _buf;
char* end = _buf + EXTRA_BUF_SIZE - 1;
while (ptr < end && *pattern != 0) {
char c = *pattern++;
@@ -306,13 +481,35 @@ const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
continue;
} else if (c == 'n') {
unsigned int max_files = 0;
const char* p;
if (*pattern == '{' && (p = strchr(pattern, '}')) != NULL) {
max_files = atoi(pattern + 1);
pattern = p + 1;
}
ptr += snprintf(ptr, end - ptr, "%u", max_files > 0 ? _file_num % max_files : _file_num);
continue;
} else if (c == '{') {
char env_key[128];
const char* p = strchr(pattern, '}');
if (p != NULL && p - pattern < sizeof(env_key)) {
memcpy(env_key, pattern, p - pattern);
env_key[p - pattern] = 0;
const char* env_value = getenv(env_key);
if (env_value != NULL) {
ptr += snprintf(ptr, end - ptr, "%s", env_value);
pattern = p + 1;
continue;
}
}
}
}
*ptr++ = c;
}
*ptr = 0;
return dest;
*(ptr < end ? ptr : end) = 0;
return _buf;
}
Output Arguments::detectOutputFormat(const char* file) {
@@ -324,37 +521,57 @@ Output Arguments::detectOutputFormat(const char* file) {
return OUTPUT_JFR;
} else if (strcmp(ext, ".collapsed") == 0 || strcmp(ext, ".folded") == 0) {
return OUTPUT_COLLAPSED;
} else if (strcmp(ext, ".svg") == 0) {
return OUTPUT_SVG;
}
}
return OUTPUT_FLAT;
return OUTPUT_TEXT;
}
long Arguments::parseUnits(const char* str) {
long Arguments::parseUnits(const char* str, const Multiplier* multipliers) {
char* end;
long result = strtol(str, &end, 0);
if (end == str) {
return -1;
}
switch (*end) {
case 0:
return result;
case 'K': case 'k':
case 'U': case 'u': // microseconds
return result * 1000;
case 'M': case 'm': // million, megabytes or milliseconds
return result * 1000000;
case 'G': case 'g':
case 'S': case 's': // seconds
return result * 1000000000;
char c = *end;
if (c == 0) {
return result;
}
if (c >= 'A' && c <= 'Z') {
c += 'a' - 'A';
}
for (const Multiplier* m = multipliers; m->symbol; m++) {
if (c == m->symbol) {
return result * m->multiplier;
}
}
return -1;
}
int Arguments::parseTimeout(const char* str) {
const char* p = strchr(str, ':');
if (p == NULL) {
return parseUnits(str, SECONDS);
}
int hh = str[0] >= '0' && str[0] <= '2' ? atoi(str) : 0xff;
int mm = p[1] >= '0' && p[1] <= '5' ? atoi(p + 1) : 0xff;
int ss = (p = strchr(p + 1, ':')) != NULL && p[1] >= '0' && p[1] <= '5' ? atoi(p + 1) : 0xff;
return 0xff000000 | hh << 16 | mm << 8 | ss;
}
Arguments::~Arguments() {
if (!_shared) free(_buf);
}
void Arguments::save(Arguments& other) {
if (!_shared) free(_buf);
*this = other;
other._shared = true;
void Arguments::save() {
if (this != &_global_args) {
free(_global_args._buf);
_global_args = *this;
_shared = true;
}
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _ARGUMENTS_H
@@ -20,68 +9,117 @@
#include <stddef.h>
const long DEFAULT_INTERVAL = 10000000; // 10 ms
const long DEFAULT_INTERVAL = 10000000; // 10 ms
const long DEFAULT_ALLOC_INTERVAL = 524287; // 512 KiB
const int DEFAULT_JSTACKDEPTH = 2048;
const char* const EVENT_CPU = "cpu";
const char* const EVENT_ALLOC = "alloc";
const char* const EVENT_LOCK = "lock";
const char* const EVENT_WALL = "wall";
const char* const EVENT_CTIMER = "ctimer";
const char* const EVENT_ITIMER = "itimer";
enum Action {
#define SHORT_ENUM __attribute__((__packed__))
enum SHORT_ENUM Action {
ACTION_NONE,
ACTION_START,
ACTION_RESUME,
ACTION_STOP,
ACTION_DUMP,
ACTION_CHECK,
ACTION_STATUS,
ACTION_MEMINFO,
ACTION_LIST,
ACTION_VERSION,
ACTION_FULL_VERSION,
ACTION_DUMP
ACTION_VERSION
};
enum Counter {
enum SHORT_ENUM Counter {
COUNTER_SAMPLES,
COUNTER_TOTAL
};
enum Ring {
enum SHORT_ENUM Ring {
RING_ANY,
RING_KERNEL,
RING_USER
};
enum EventKind {
EK_CPU = 1,
EK_ALLOC = 2,
EK_LOCK = 4
};
enum Style {
STYLE_SIMPLE = 1,
STYLE_DOTTED = 2,
STYLE_SIGNATURES = 4,
STYLE_ANNOTATE = 8
STYLE_SIMPLE = 0x1,
STYLE_DOTTED = 0x2,
STYLE_NORMALIZE = 0x4,
STYLE_SIGNATURES = 0x8,
STYLE_ANNOTATE = 0x10,
STYLE_LIB_NAMES = 0x20,
STYLE_NO_SEMICOLON = 0x40
};
enum CStack {
// Whenever enum changes, update SETTING_CSTACK in FlightRecorder
enum SHORT_ENUM CStack {
CSTACK_DEFAULT,
CSTACK_NO,
CSTACK_FP,
CSTACK_LBR
CSTACK_DWARF,
CSTACK_LBR,
CSTACK_VM
};
enum Output {
enum SHORT_ENUM Clock {
CLK_DEFAULT,
CLK_TSC,
CLK_MONOTONIC
};
enum SHORT_ENUM Output {
OUTPUT_NONE,
OUTPUT_FLAT,
OUTPUT_TEXT,
OUTPUT_SVG, // obsolete
OUTPUT_COLLAPSED,
OUTPUT_FLAMEGRAPH,
OUTPUT_TREE,
OUTPUT_JFR
};
enum JfrOption {
NO_SYSTEM_INFO = 0x1,
NO_SYSTEM_PROPS = 0x2,
NO_NATIVE_LIBS = 0x4,
NO_CPU_LOAD = 0x8,
NO_HEAP_SUMMARY = 0x10,
IN_MEMORY = 0x100,
JFR_SYNC_OPTS = NO_SYSTEM_INFO | NO_SYSTEM_PROPS | NO_NATIVE_LIBS | NO_CPU_LOAD | NO_HEAP_SUMMARY
};
struct StackWalkFeatures {
// Stack recovery techniques used to workaround AsyncGetCallTrace flaws
unsigned short unknown_java : 1;
unsigned short unwind_stub : 1;
unsigned short unwind_comp : 1;
unsigned short unwind_native : 1;
unsigned short java_anchor : 1;
unsigned short gc_traces : 1;
// Additional HotSpot-specific features
unsigned short probe_sp : 1;
unsigned short vtable_target : 1;
unsigned short comp_task : 1;
unsigned short _reserved : 7;
StackWalkFeatures() : unknown_java(1), unwind_stub(1), unwind_comp(1), unwind_native(1), java_anchor(1), gc_traces(1),
probe_sp(0), vtable_target(0), comp_task(0), _reserved(0) {
}
};
struct Multiplier {
char symbol;
long multiplier;
};
class Error {
private:
@@ -109,30 +147,53 @@ class Arguments {
bool _shared;
void appendToEmbeddedList(int& list, char* value);
const char* expandFilePattern(const char* pattern);
static long long hash(const char* arg);
static const char* expandFilePattern(char* dest, size_t max_size, const char* pattern);
static Output detectOutputFormat(const char* file);
static long parseUnits(const char* str);
static long parseUnits(const char* str, const Multiplier* multipliers);
static int parseTimeout(const char* str);
public:
Action _action;
Counter _counter;
Ring _ring;
int _events;
const char* _event_desc;
const char* _event;
int _timeout;
long _interval;
int _jstackdepth;
int _safe_mode;
long _alloc;
long _lock;
long _wall;
int _jstackdepth;
int _signal;
const char* _file;
const char* _log;
const char* _loglevel;
const char* _unknown_arg;
const char* _server;
const char* _filter;
int _include;
int _exclude;
unsigned char _mcache;
bool _loop;
bool _preloaded;
bool _threads;
bool _sched;
bool _live;
bool _fdtransfer;
const char* _fdtransfer_path;
int _style;
StackWalkFeatures _features;
CStack _cstack;
Clock _clock;
Output _output;
long _chunk_size;
long _chunk_time;
const char* _jfr_sync;
int _jfr_options;
int _dump_traces;
int _dump_flat;
unsigned int _file_num;
const char* _begin;
const char* _end;
// FlameGraph parameters
@@ -146,36 +207,72 @@ class Arguments {
_action(ACTION_NONE),
_counter(COUNTER_SAMPLES),
_ring(RING_ANY),
_events(0),
_event_desc(NULL),
_event(NULL),
_timeout(0),
_interval(0),
_alloc(-1),
_lock(-1),
_wall(-1),
_jstackdepth(DEFAULT_JSTACKDEPTH),
_safe_mode(0),
_signal(0),
_file(NULL),
_log(NULL),
_loglevel(NULL),
_unknown_arg(NULL),
_server(NULL),
_filter(NULL),
_include(0),
_exclude(0),
_mcache(0),
_loop(false),
_preloaded(false),
_threads(false),
_sched(false),
_live(false),
_fdtransfer(false),
_fdtransfer_path(NULL),
_style(0),
_features(),
_cstack(CSTACK_DEFAULT),
_clock(CLK_DEFAULT),
_output(OUTPUT_NONE),
_chunk_size(100 * 1024 * 1024),
_chunk_time(3600),
_jfr_sync(NULL),
_jfr_options(0),
_dump_traces(0),
_dump_flat(0),
_file_num(0),
_begin(NULL),
_end(NULL),
_title("Flame Graph"),
_title(NULL),
_minwidth(0),
_reverse(false) {
}
~Arguments();
void save(Arguments& other);
void save();
Error parse(const char* args);
bool addEvent(const char* event);
const char* file();
bool hasTemporaryLog() const;
bool hasOutputFile() const {
return _file != NULL &&
(_action == ACTION_STOP || _action == ACTION_DUMP ? _output != OUTPUT_JFR : _action >= ACTION_CHECK);
}
bool hasOption(JfrOption option) const {
return (_jfr_options & option) != 0;
}
friend class FrameName;
friend class Recording;
};
extern Arguments _global_args;
#endif // _ARGUMENTS_H

51
src/asprof.cpp Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "asprof.h"
#include "hooks.h"
#include "profiler.h"
static asprof_error_t asprof_error(const char* msg) {
return (asprof_error_t)msg;
}
DLLEXPORT void asprof_init() {
Hooks::init(true);
}
DLLEXPORT const char* asprof_error_str(asprof_error_t err) {
return err;
}
DLLEXPORT asprof_error_t asprof_execute(const char* command, asprof_writer_t output_callback) {
Arguments args;
Error error = args.parse(command);
if (error) {
return asprof_error(error.message());
}
Log::open(args);
if (!args.hasOutputFile()) {
CallbackWriter out(output_callback);
error = Profiler::instance()->runInternal(args, out);
if (!error) {
return NULL;
}
} else {
FileWriter out(args.file());
if (!out.is_open()) {
return asprof_error("Could not open output file");
}
error = Profiler::instance()->runInternal(args, out);
if (!error) {
return NULL;
}
}
return asprof_error(error.message());
}

43
src/asprof.h Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _ASPROF_H
#define _ASPROF_H
#include <stddef.h>
#ifdef __clang__
# define DLLEXPORT __attribute__((visibility("default")))
#else
# define DLLEXPORT __attribute__((visibility("default"),externally_visible))
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef const char* asprof_error_t;
typedef void (*asprof_writer_t)(const char* buf, size_t size);
// Should be called once prior to any other API functions
DLLEXPORT void asprof_init();
typedef void (*asprof_init_t)();
// Returns an error message for the given error code or NULL if there is no error
DLLEXPORT const char* asprof_error_str(asprof_error_t err);
typedef const char* (*asprof_error_str_t)(asprof_error_t err);
// Executes async-profiler command using output_callback as an optional sink
// for the profiler output. Returning an error code or NULL on success.
DLLEXPORT asprof_error_t asprof_execute(const char* command, asprof_writer_t output_callback);
typedef asprof_error_t (*asprof_execute_t)(const char* command, asprof_writer_t output_callback);
#ifdef __cplusplus
}
#endif
#endif // _ASPROF_H

View File

@@ -1,28 +1,16 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <unistd.h>
#include "callTraceStorage.h"
#include "os.h"
static const u32 INITIAL_CAPACITY = 65536;
static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024;
static const size_t PAGE_ALIGNMENT = sysconf(_SC_PAGESIZE) - 1;
static const u32 OVERFLOW_TRACE_ID = 0x7fffffff;
class LongHashTable {
@@ -36,7 +24,7 @@ class LongHashTable {
static size_t getSize(u32 capacity) {
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
return (size + OS::page_mask) & ~OS::page_mask;
}
public:
@@ -56,6 +44,10 @@ class LongHashTable {
return prev;
}
size_t usedMemory() {
return getSize(_capacity);
}
LongHashTable* prev() {
return _prev;
}
@@ -87,8 +79,11 @@ class LongHashTable {
};
CallTrace CallTraceStorage::_overflow_trace = {1, {BCI_ERROR, (jmethodID)"storage_overflow"}};
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
_overflow = 0;
}
CallTraceStorage::~CallTraceStorage() {
@@ -103,6 +98,15 @@ void CallTraceStorage::clear() {
}
_current_table->clear();
_allocator.clear();
_overflow = 0;
}
size_t CallTraceStorage::usedMemory() {
size_t bytes = _allocator.usedMemory();
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
bytes += table->usedMemory();
}
return bytes;
}
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
@@ -112,11 +116,20 @@ void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
u32 capacity = table->capacity();
for (u32 slot = 0; slot < capacity; slot++) {
if (keys[slot] != 0) {
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
if (keys[slot] != 0 && loadAcquire(values[slot].samples) != 0) {
// Reset samples to avoid duplication of call traces between JFR chunks
values[slot].samples = 0;
CallTrace* trace = values[slot].acquireTrace();
if (trace != NULL) {
map[capacity - (INITIAL_CAPACITY - 1) + slot] = trace;
}
}
}
}
if (_overflow > 0) {
map[OVERFLOW_TRACE_ID] = &_overflow_trace;
}
}
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
@@ -133,6 +146,20 @@ void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
}
}
void CallTraceStorage::collectSamples(std::map<u64, CallTraceSample>& map) {
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
u64* keys = table->keys();
CallTraceSample* values = table->values();
u32 capacity = table->capacity();
for (u32 slot = 0; slot < capacity; slot++) {
if (keys[slot] != 0 && values[slot].acquireTrace() != NULL) {
map[keys[slot]] += values[slot];
}
}
}
}
// Adaptation of MurmurHash64A by Austin Appleby
u64 CallTraceStorage::calcHash(int num_frames, ASGCT_CallFrame* frames) {
const u64 M = 0xc6a4a7935bd1e995ULL;
@@ -225,22 +252,40 @@ u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter)
if (trace == NULL) {
trace = storeCallTrace(num_frames, frames);
}
table->values()[slot].trace = trace;
table->values()[slot].setTrace(trace);
break;
}
if (++step >= capacity) {
// Very unlikely case of a table overflow
return 0;
atomicInc(_overflow);
return OVERFLOW_TRACE_ID;
}
// Improved version of linear probing
slot = (slot + step) & (capacity - 1);
}
// TODO: check overhead
CallTraceSample& s = table->values()[slot];
atomicInc(s.samples);
atomicInc(s.counter, counter);
if (counter != 0) {
CallTraceSample& s = table->values()[slot];
atomicInc(s.samples);
atomicInc(s.counter, counter);
}
return capacity - (INITIAL_CAPACITY - 1) + slot;
}
void CallTraceStorage::add(u32 call_trace_id, u64 counter) {
if (call_trace_id == OVERFLOW_TRACE_ID) {
return;
}
call_trace_id += (INITIAL_CAPACITY - 1);
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
if (call_trace_id >= table->capacity()) {
CallTraceSample& s = table->values()[call_trace_id - table->capacity()];
atomicInc(s.samples);
atomicInc(s.counter, counter);
break;
}
}
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _CALLTRACESTORAGE_H
@@ -35,12 +24,34 @@ struct CallTraceSample {
CallTrace* trace;
u64 samples;
u64 counter;
CallTrace* acquireTrace() {
return __atomic_load_n(&trace, __ATOMIC_ACQUIRE);
}
void setTrace(CallTrace* value) {
return __atomic_store_n(&trace, value, __ATOMIC_RELEASE);
}
CallTraceSample& operator+=(const CallTraceSample& s) {
trace = s.trace;
samples += s.samples;
counter += s.counter;
return *this;
}
bool operator<(const CallTraceSample& other) const {
return counter > other.counter;
}
};
class CallTraceStorage {
private:
static CallTrace _overflow_trace;
LinearAllocator _allocator;
LongHashTable* _current_table;
u64 _overflow;
u64 calcHash(int num_frames, ASGCT_CallFrame* frames);
CallTrace* storeCallTrace(int num_frames, ASGCT_CallFrame* frames);
@@ -51,10 +62,14 @@ class CallTraceStorage {
~CallTraceStorage();
void clear();
size_t usedMemory();
void collectTraces(std::map<u32, CallTrace*>& map);
void collectSamples(std::vector<CallTraceSample*>& samples);
void collectSamples(std::map<u64, CallTraceSample>& map);
u32 put(int num_frames, ASGCT_CallFrame* frames, u64 counter);
void add(u32 call_trace_id, u64 counter);
};
#endif // _CALLTRACESTORAGE

32
src/chk.cpp Normal file
View File

@@ -0,0 +1,32 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __clang__
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
// libgcc refers to __sprintf_chk, but there is no such symbol in musl libc.
// Export a weak symbol in order to make profiler library work both with glibc and musl.
#define WEAK_EXPORT __attribute__((visibility("default"),externally_visible,weak))
extern "C" WEAK_EXPORT
int __sprintf_chk(char* s, int flag, size_t slen, const char* format, ...) {
va_list args;
va_start(args, format);
int ret = vsnprintf(s, slen, format, args);
va_end(args);
if (ret >= slen) {
fprintf(stderr, "__sprintf_chk failed\n");
abort();
}
return ret;
}
#endif // __clang__

View File

@@ -1,42 +1,83 @@
/*
* Copyright 2016 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include "codeCache.h"
#include "dwarf.h"
#include "os.h"
char* NativeFunc::create(const char* name, short lib_index) {
NativeFunc* f = (NativeFunc*)malloc(sizeof(NativeFunc) + 1 + strlen(name));
f->_lib_index = lib_index;
f->_mark = 0;
return strcpy(f->_name, name);
}
void NativeFunc::destroy(char* name) {
free(from(name));
}
size_t NativeFunc::usedMemory(const char* name) {
return sizeof(NativeFunc) + 1 + strlen(from(name)->_name);
}
CodeCache::CodeCache(const char* name, short lib_index, bool imports_patchable,
const void* min_address, const void* max_address) {
_name = NativeFunc::create(name, -1);
_lib_index = lib_index;
_min_address = min_address;
_max_address = max_address;
_text_base = NULL;
_plt_offset = 0;
_plt_size = 0;
memset(_imports, 0, sizeof(_imports));
_imports_patchable = imports_patchable;
_debug_symbols = false;
_dwarf_table = NULL;
_dwarf_table_length = 0;
_capacity = INITIAL_CODE_CACHE_CAPACITY;
_count = 0;
_blobs = new CodeBlob[_capacity];
}
CodeCache::~CodeCache() {
for (int i = 0; i < _count; i++) {
NativeFunc::destroy(_blobs[i]._name);
}
NativeFunc::destroy(_name);
delete[] _blobs;
free(_dwarf_table);
}
void CodeCache::expand() {
CodeBlob* old_blobs = _blobs;
CodeBlob* new_blobs = new CodeBlob[_capacity * 2];
int live = 0;
for (int i = 0; i < _count; i++) {
if (_blobs[i]._method != NULL) {
new_blobs[live++] = _blobs[i];
}
}
memcpy(new_blobs, old_blobs, _count * sizeof(CodeBlob));
_count = live;
_capacity *= 2;
_blobs = new_blobs;
delete[] old_blobs;
}
void CodeCache::add(const void* start, int length, jmethodID method, bool update_bounds) {
void CodeCache::add(const void* start, int length, const char* name, bool update_bounds) {
char* name_copy = NativeFunc::create(name, _lib_index);
// Replace non-printable characters
for (char* s = name_copy; *s != 0; s++) {
if (*s < ' ') *s = '?';
}
if (_count >= _capacity) {
expand();
}
@@ -44,58 +85,20 @@ void CodeCache::add(const void* start, int length, jmethodID method, bool update
const void* end = (const char*)start + length;
_blobs[_count]._start = start;
_blobs[_count]._end = end;
_blobs[_count]._method = method;
_blobs[_count]._name = name_copy;
_count++;
if (update_bounds) {
if (start < _min_address) _min_address = start;
if (end > _max_address) _max_address = end;
updateBounds(start, end);
}
}
void CodeCache::remove(const void* start, jmethodID method) {
for (int i = 0; i < _count; i++) {
if (_blobs[i]._start == start && _blobs[i]._method == method) {
_blobs[i]._method = NULL;
return;
}
}
void CodeCache::updateBounds(const void* start, const void* end) {
if (start < _min_address) _min_address = start;
if (end > _max_address) _max_address = end;
}
jmethodID CodeCache::find(const void* address) {
for (int i = 0; i < _count; i++) {
CodeBlob* cb = _blobs + i;
if (address >= cb->_start && address < cb->_end && cb->_method != NULL) {
return _blobs[i]._method;
}
}
return NULL;
}
NativeCodeCache::NativeCodeCache(const char* name, const void* min_address, const void* max_address) {
_name = strdup(name);
_min_address = min_address;
_max_address = max_address;
}
NativeCodeCache::~NativeCodeCache() {
for (int i = 0; i < _count; i++) {
free(_blobs[i]._method);
}
free(_name);
}
void NativeCodeCache::add(const void* start, int length, const char* name, bool update_bounds) {
char* name_copy = strdup(name);
// Replace non-printable characters
for (char* s = name_copy; *s != 0; s++) {
if (*s < ' ') *s = '?';
}
CodeCache::add(start, length, (jmethodID)name_copy, update_bounds);
}
void NativeCodeCache::sort() {
void CodeCache::sort() {
if (_count == 0) return;
qsort(_blobs, _count, sizeof(CodeBlob), CodeBlob::comparator);
@@ -104,7 +107,35 @@ void NativeCodeCache::sort() {
if (_max_address == NO_MAX_ADDRESS) _max_address = _blobs[_count - 1]._end;
}
const char* NativeCodeCache::binarySearch(const void* address) {
void CodeCache::mark(NamePredicate predicate, char value) {
for (int i = 0; i < _count; i++) {
const char* blob_name = _blobs[i]._name;
if (blob_name != NULL && predicate(blob_name)) {
NativeFunc::mark(blob_name, value);
}
}
}
CodeBlob* CodeCache::findBlob(const char* name) {
for (int i = 0; i < _count; i++) {
const char* blob_name = _blobs[i]._name;
if (blob_name != NULL && strcmp(blob_name, name) == 0) {
return &_blobs[i];
}
}
return NULL;
}
CodeBlob* CodeCache::findBlobByAddress(const void* address) {
for (int i = 0; i < _count; i++) {
if (address >= _blobs[i]._start && address < _blobs[i]._end) {
return &_blobs[i];
}
}
return NULL;
}
const char* CodeCache::binarySearch(const void* address) {
int low = 0;
int high = _count - 1;
@@ -115,38 +146,124 @@ const char* NativeCodeCache::binarySearch(const void* address) {
} else if (_blobs[mid]._start > address) {
high = mid - 1;
} else {
return (const char*)_blobs[mid]._method;
return _blobs[mid]._name;
}
}
// Symbols with zero size can be valid functions: e.g. ASM entry points or kernel code.
// Also, in some cases (endless loop) the return address may point beyond the function.
if (low > 0 && (_blobs[low - 1]._start == _blobs[low - 1]._end || _blobs[low - 1]._end == address)) {
return (const char*)_blobs[low - 1]._method;
return _blobs[low - 1]._name;
}
return _name;
}
const void* NativeCodeCache::findSymbol(const char* name) {
for (int i = 0; i < _count; i++) {
const char* blob_name = (const char*)_blobs[i]._method;
if (blob_name != NULL && strcmp(blob_name, name) == 0) {
return _blobs[i]._start;
}
}
return NULL;
const void* CodeCache::findSymbol(const char* name) {
CodeBlob* blob = findBlob(name);
return blob == NULL ? NULL : blob->_start;
}
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix) {
const void* CodeCache::findSymbolByPrefix(const char* prefix) {
return findSymbolByPrefix(prefix, strlen(prefix));
}
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
const void* CodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
for (int i = 0; i < _count; i++) {
const char* blob_name = (const char*)_blobs[i]._method;
const char* blob_name = _blobs[i]._name;
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
return _blobs[i]._start;
}
}
return NULL;
}
void CodeCache::addImport(void** entry, const char* name) {
switch (name[0]) {
case 'd':
if (strcmp(name, "dlopen") == 0) {
_imports[im_dlopen] = entry;
}
break;
case 'p':
if (strcmp(name, "pthread_create") == 0) {
_imports[im_pthread_create] = entry;
} else if (strcmp(name, "pthread_exit") == 0) {
_imports[im_pthread_exit] = entry;
} else if (strcmp(name, "pthread_setspecific") == 0) {
_imports[im_pthread_setspecific] = entry;
} else if (strcmp(name, "poll") == 0) {
_imports[im_poll] = entry;
}
break;
}
}
void** CodeCache::findImport(ImportId id) {
if (!_imports_patchable) {
makeImportsPatchable();
_imports_patchable = true;
}
return _imports[id];
}
void CodeCache::patchImport(ImportId id, void* hook_func) {
void** entry = findImport(id);
if (entry != NULL) {
*entry = hook_func;
}
}
void CodeCache::makeImportsPatchable() {
void** min_import = (void**)-1;
void** max_import = NULL;
for (int i = 0; i < NUM_IMPORTS; i++) {
if (_imports[i] != NULL && _imports[i] < min_import) min_import = _imports[i];
if (_imports[i] != NULL && _imports[i] > max_import) max_import = _imports[i];
}
if (max_import != NULL) {
uintptr_t patch_start = (uintptr_t)min_import & ~OS::page_mask;
uintptr_t patch_end = (uintptr_t)max_import & ~OS::page_mask;
mprotect((void*)patch_start, patch_end - patch_start + OS::page_size, PROT_READ | PROT_WRITE);
}
}
void CodeCache::setDwarfTable(FrameDesc* table, int length) {
_dwarf_table = table;
_dwarf_table_length = length;
}
FrameDesc* CodeCache::findFrameDesc(const void* pc) {
u32 target_loc = (const char*)pc - _text_base;
int low = 0;
int high = _dwarf_table_length - 1;
while (low <= high) {
int mid = (unsigned int)(low + high) >> 1;
if (_dwarf_table[mid].loc < target_loc) {
low = mid + 1;
} else if (_dwarf_table[mid].loc > target_loc) {
high = mid - 1;
} else {
return &_dwarf_table[mid];
}
}
if (low > 0) {
return &_dwarf_table[low - 1];
} else if (target_loc - _plt_offset < _plt_size) {
return &FrameDesc::empty_frame;
} else {
return &FrameDesc::default_frame;
}
}
size_t CodeCache::usedMemory() {
size_t bytes = _capacity * sizeof(CodeBlob);
bytes += _dwarf_table_length * sizeof(FrameDesc);
bytes += NativeFunc::usedMemory(_name);
for (int i = 0; i < _count; i++) {
bytes += NativeFunc::usedMemory(_blobs[i]._name);
}
return bytes;
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _CODECACHE_H
@@ -23,14 +12,63 @@
#define NO_MIN_ADDRESS ((const void*)-1)
#define NO_MAX_ADDRESS ((const void*)0)
typedef bool (*NamePredicate)(const char* name);
const int INITIAL_CODE_CACHE_CAPACITY = 1000;
const int MAX_NATIVE_LIBS = 2048;
enum ImportId {
im_dlopen,
im_pthread_create,
im_pthread_exit,
im_pthread_setspecific,
im_poll,
NUM_IMPORTS
};
enum Mark {
MARK_INTERPRETER = 1,
MARK_COMPILER_ENTRY = 2
};
class NativeFunc {
private:
short _lib_index;
char _mark;
char _reserved;
char _name[0];
static NativeFunc* from(const char* name) {
return (NativeFunc*)(name - sizeof(NativeFunc));
}
public:
static char* create(const char* name, short lib_index);
static void destroy(char* name);
static size_t usedMemory(const char* name);
static short libIndex(const char* name) {
return from(name)->_lib_index;
}
static char mark(const char* name) {
return from(name)->_mark;
}
static void mark(const char* name, char value) {
from(name)->_mark = value;
}
};
class CodeBlob {
public:
const void* _start;
const void* _end;
jmethodID _method;
char* _name;
static int comparator(const void* c1, const void* c2) {
CodeBlob* cb1 = (CodeBlob*)c1;
@@ -48,60 +86,120 @@ class CodeBlob {
};
class FrameDesc;
class CodeCache {
protected:
private:
char* _name;
short _lib_index;
const void* _min_address;
const void* _max_address;
const char* _text_base;
unsigned int _plt_offset;
unsigned int _plt_size;
void** _imports[NUM_IMPORTS];
bool _imports_patchable;
bool _debug_symbols;
FrameDesc* _dwarf_table;
int _dwarf_table_length;
int _capacity;
int _count;
CodeBlob* _blobs;
const void* _min_address;
const void* _max_address;
void expand();
void makeImportsPatchable();
public:
CodeCache() {
_capacity = INITIAL_CODE_CACHE_CAPACITY;
_count = 0;
_blobs = new CodeBlob[_capacity];
_min_address = NO_MIN_ADDRESS;
_max_address = NO_MAX_ADDRESS;
}
CodeCache(const char* name,
short lib_index = -1,
bool imports_patchable = false,
const void* min_address = NO_MIN_ADDRESS,
const void* max_address = NO_MAX_ADDRESS);
~CodeCache() {
delete[] _blobs;
}
~CodeCache();
bool contains(const void* address) {
return address >= _min_address && address < _max_address;
}
void add(const void* start, int length, jmethodID method, bool update_bounds = false);
void remove(const void* start, jmethodID method);
jmethodID find(const void* address);
};
class NativeCodeCache : public CodeCache {
private:
char* _name;
public:
NativeCodeCache(const char* name,
const void* min_address = NO_MIN_ADDRESS,
const void* max_address = NO_MAX_ADDRESS);
~NativeCodeCache();
const char* name() {
const char* name() const {
return _name;
}
const void* minAddress() const {
return _min_address;
}
const void* maxAddress() const {
return _max_address;
}
bool contains(const void* address) const {
return address >= _min_address && address < _max_address;
}
void setTextBase(const char* text_base) {
_text_base = text_base;
}
void setPlt(unsigned int plt_offset, unsigned int plt_size) {
_plt_offset = plt_offset;
_plt_size = plt_size;
}
bool hasDebugSymbols() const {
return _debug_symbols;
}
void setDebugSymbols(bool debug_symbols) {
_debug_symbols = debug_symbols;
}
void add(const void* start, int length, const char* name, bool update_bounds = false);
void updateBounds(const void* start, const void* end);
void sort();
void mark(NamePredicate predicate, char value);
void addImport(void** entry, const char* name);
void** findImport(ImportId id);
void patchImport(ImportId, void* hook_func);
CodeBlob* findBlob(const char* name);
CodeBlob* findBlobByAddress(const void* address);
const char* binarySearch(const void* address);
const void* findSymbol(const char* name);
const void* findSymbolByPrefix(const char* prefix);
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
void setDwarfTable(FrameDesc* table, int length);
FrameDesc* findFrameDesc(const void* pc);
size_t usedMemory();
};
class CodeCacheArray {
private:
CodeCache* _libs[MAX_NATIVE_LIBS];
int _count;
public:
CodeCacheArray() : _count(0) {
}
CodeCache* operator[](int index) {
return _libs[index];
}
int count() {
return __atomic_load_n(&_count, __ATOMIC_ACQUIRE);
}
void add(CodeCache* lib) {
int index = __atomic_load_n(&_count, __ATOMIC_ACQUIRE);
_libs[index] = lib;
__atomic_store_n(&_count, index + 1, __ATOMIC_RELEASE);
}
};
#endif // _CODECACHE_H

View File

@@ -1,442 +0,0 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
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;
public class FlameGraph {
public String title = "Flame Graph";
public boolean reverse;
public double minwidth;
public int skip;
public String input;
public String output;
private final Frame root = new Frame();
private int depth;
private long mintotal;
public FlameGraph(String... args) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (!arg.startsWith("--") && !arg.isEmpty()) {
if (input == null) {
input = arg;
} else {
output = arg;
}
} else if (arg.equals("--title")) {
title = args[++i];
} else if (arg.equals("--reverse")) {
reverse = true;
} else if (arg.equals("--minwidth")) {
minwidth = Double.parseDouble(args[++i]);
} else if (arg.equals("--skip")) {
skip = Integer.parseInt(args[++i]);
}
}
}
public void parse() throws IOException {
parse(new InputStreamReader(new FileInputStream(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) {
Frame frame = root;
if (reverse) {
for (int i = trace.length; --i >= skip; ) {
frame.total += ticks;
frame = frame.child(trace[i]);
}
} else {
for (int i = skip; i < trace.length; i++) {
frame.total += ticks;
frame = frame.child(trace[i]);
}
}
frame.total += ticks;
frame.self += ticks;
depth = Math.max(depth, trace.length);
}
public void dump() throws IOException {
if (output == null) {
dump(System.out);
} else {
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
dump(out);
}
}
}
public void dump(PrintStream out) {
out.print(applyReplacements(HEADER,
"{title}", title,
"{height}", (depth + 1) * 16,
"{depth}", depth + 1,
"{reverse}", reverse));
mintotal = (long) (root.total * minwidth / 100);
printFrame(out, "all", root, 0, 0);
out.print(FOOTER);
}
// Replace ${variables} in the given string with field values
private String applyReplacements(String s, Object... params) {
StringBuilder result = new StringBuilder(s.length() + 256);
int p = 0;
for (int q; (q = s.indexOf('$', p)) >= 0; ) {
result.append(s, p, q);
p = s.indexOf('}', q + 2) + 1;
String var = s.substring(q + 1, p);
for (int i = 0; i < params.length; i += 2) {
if (var.equals(params[i])) {
result.append(params[i + 1]);
break;
}
}
}
result.append(s, p, s.length());
return result.toString();
}
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
int type = frameType(title);
title = stripSuffix(title);
if (title.indexOf('\'') >= 0) {
title = title.replace("'", "\\'");
}
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + title + "')");
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 String stripSuffix(String title) {
int len = title.length();
if (len >= 4 && title.charAt(len - 1) == ']' && title.regionMatches(len - 4, "_[", 0, 2)) {
return title.substring(0, len - 4);
}
return title;
}
private int frameType(String title) {
if (title.endsWith("_[j]")) {
return 0;
} else if (title.endsWith("_[i]")) {
return 1;
} else if (title.endsWith("_[k]")) {
return 2;
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
return 3;
} else if (title.indexOf('/') > 0 || title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
return 0;
} else {
return 4;
}
}
public static void main(String[] args) throws IOException {
FlameGraph fg = new FlameGraph(args);
if (fg.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.exit(1);
}
fg.parse();
fg.dump();
}
static class Frame extends TreeMap<String, Frame> {
long total;
long self;
Frame child(String title) {
Frame child = get(title);
if (child == null) {
put(title, child = new Frame());
}
return child;
}
}
private static final String HEADER = "<!DOCTYPE html>\n" +
"<html lang='en'>\n" +
"<head>\n" +
"<meta charset='utf-8'>\n" +
"<style>\n" +
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n" +
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n" +
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n" +
"\tbutton {font: 12px sans-serif; cursor: pointer}\n" +
"\tp {margin: 5px 0 5px 0}\n" +
"\ta {color: #0366d6}\n" +
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n" +
"\t#hl span {padding: 0 3px 0 3px}\n" +
"\t#status {overflow: hidden; white-space: nowrap}\n" +
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n" +
"\t#reset {cursor: pointer}\n" +
"</style>\n" +
"</head>\n" +
"<body style='font: 12px Verdana, sans-serif'>\n" +
"<h1>${title}</h1>\n" +
"<header style='text-align: left'><button id='reverse' title='Reverse'>&#x1f53b;</button>&nbsp;&nbsp;<button id='search' title='Search'>&#x1f50d;</button></header>\n" +
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n" +
"<canvas id='canvas' style='width: 100%; height: ${height}px'></canvas>\n" +
"<div id='hl'><span></span></div>\n" +
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>&#x274c;</span></p>\n" +
"<p id='status'>&nbsp;</p>\n" +
"<script>\n" +
"\t// Copyright 2020 Andrei Pangin\n" +
"\t// Licensed under the Apache License, Version 2.0.\n" +
"\t'use strict';\n" +
"\tvar root, rootLevel, px, pattern;\n" +
"\tvar reverse = ${reverse};\n" +
"\tconst levels = Array(${depth});\n" +
"\tfor (let h = 0; h < levels.length; h++) {\n" +
"\t\tlevels[h] = [];\n" +
"\t}\n" +
"\n" +
"\tconst canvas = document.getElementById('canvas');\n" +
"\tconst c = canvas.getContext('2d');\n" +
"\tconst hl = document.getElementById('hl');\n" +
"\tconst status = document.getElementById('status');\n" +
"\n" +
"\tconst canvasWidth = canvas.offsetWidth;\n" +
"\tconst canvasHeight = canvas.offsetHeight;\n" +
"\tcanvas.style.width = canvasWidth + 'px';\n" +
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n" +
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n" +
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n" +
"\tc.font = document.body.style.font;\n" +
"\n" +
"\tconst palette = [\n" +
"\t\t[0x50e150, 30, 30, 30],\n" +
"\t\t[0x50bebe, 30, 30, 30],\n" +
"\t\t[0xe17d00, 30, 30, 0],\n" +
"\t\t[0xc8c83c, 30, 30, 10],\n" +
"\t\t[0xe15a5a, 30, 40, 40],\n" +
"\t];\n" +
"\n" +
"\tfunction getColor(p) {\n" +
"\t\tconst v = Math.random();\n" +
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n" +
"\t}\n" +
"\n" +
"\tfunction f(level, left, width, type, title) {\n" +
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n" +
"\t}\n" +
"\n" +
"\tfunction samples(n) {\n" +
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n" +
"\t}\n" +
"\n" +
"\tfunction pct(a, b) {\n" +
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n" +
"\t}\n" +
"\n" +
"\tfunction findFrame(frames, x) {\n" +
"\t\tlet left = 0;\n" +
"\t\tlet right = frames.length - 1;\n" +
"\n" +
"\t\twhile (left <= right) {\n" +
"\t\t\tconst mid = (left + right) >>> 1;\n" +
"\t\t\tconst f = frames[mid];\n" +
"\n" +
"\t\t\tif (f.left > x) {\n" +
"\t\t\t\tright = mid - 1;\n" +
"\t\t\t} else if (f.left + f.width <= x) {\n" +
"\t\t\t\tleft = mid + 1;\n" +
"\t\t\t} else {\n" +
"\t\t\t\treturn f;\n" +
"\t\t\t}\n" +
"\t\t}\n" +
"\n" +
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n" +
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n" +
"\n" +
"\t\treturn null;\n" +
"\t}\n" +
"\n" +
"\tfunction search(r) {\n" +
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n" +
"\t\t\treturn;\n" +
"\t\t}\n" +
"\n" +
"\t\tpattern = r ? RegExp(r) : undefined;\n" +
"\t\tconst matched = render(root, rootLevel);\n" +
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%';\n" +
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n" +
"\t}\n" +
"\n" +
"\tfunction render(newRoot, newLevel) {\n" +
"\t\tif (root) {\n" +
"\t\t\tc.fillStyle = '#ffffff';\n" +
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n" +
"\t\t}\n" +
"\n" +
"\t\troot = newRoot || levels[0][0];\n" +
"\t\trootLevel = newLevel || 0;\n" +
"\t\tpx = canvasWidth / root.width;\n" +
"\n" +
"\t\tconst x0 = root.left;\n" +
"\t\tconst x1 = x0 + root.width;\n" +
"\t\tconst marked = [];\n" +
"\n" +
"\t\tfunction mark(f) {\n" +
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n" +
"\t\t}\n" +
"\n" +
"\t\tfunction totalMarked() {\n" +
"\t\t\tlet total = 0;\n" +
"\t\t\tlet left = 0;\n" +
"\t\t\tfor (let x in marked) {\n" +
"\t\t\t\tif (+x >= left) {\n" +
"\t\t\t\t\ttotal += marked[x];\n" +
"\t\t\t\t\tleft = +x + marked[x];\n" +
"\t\t\t\t}\n" +
"\t\t\t}\n" +
"\t\t\treturn total;\n" +
"\t\t}\n" +
"\n" +
"\t\tfunction drawFrame(f, y, alpha) {\n" +
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n" +
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n" +
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
"\n" +
"\t\t\t\tif (f.width * px >= 21) {\n" +
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n" +
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n" +
"\t\t\t\t\tc.fillStyle = '#000000';\n" +
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n" +
"\t\t\t\t}\n" +
"\n" +
"\t\t\t\tif (alpha) {\n" +
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n" +
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
"\t\t\t\t}\n" +
"\t\t\t}\n" +
"\t\t}\n" +
"\n" +
"\t\tfor (let h = 0; h < levels.length; h++) {\n" +
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n" +
"\t\t\tconst frames = levels[h];\n" +
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n" +
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n" +
"\t\t\t}\n" +
"\t\t}\n" +
"\n" +
"\t\treturn totalMarked();\n" +
"\t}\n" +
"\n" +
"\tcanvas.onmousemove = function() {\n" +
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n" +
"\t\tif (h >= 0 && h < levels.length) {\n" +
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n" +
"\t\t\tif (f) {\n" +
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n" +
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n" +
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n" +
"\t\t\t\thl.firstChild.textContent = f.title;\n" +
"\t\t\t\thl.style.display = 'block';\n" +
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%)';\n" +
"\t\t\t\tcanvas.style.cursor = 'pointer';\n" +
"\t\t\t\tcanvas.onclick = function() {\n" +
"\t\t\t\t\tif (f != root) {\n" +
"\t\t\t\t\t\trender(f, h);\n" +
"\t\t\t\t\t\tcanvas.onmousemove();\n" +
"\t\t\t\t\t}\n" +
"\t\t\t\t};\n" +
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n" +
"\t\t\t\treturn;\n" +
"\t\t\t}\n" +
"\t\t}\n" +
"\t\tcanvas.onmouseout();\n" +
"\t}\n" +
"\n" +
"\tcanvas.onmouseout = function() {\n" +
"\t\thl.style.display = 'none';\n" +
"\t\tstatus.textContent = '\\xa0';\n" +
"\t\tcanvas.title = '';\n" +
"\t\tcanvas.style.cursor = '';\n" +
"\t\tcanvas.onclick = '';\n" +
"\t}\n" +
"\n" +
"\tdocument.getElementById('reverse').onclick = function() {\n" +
"\t\treverse = !reverse;\n" +
"\t\trender();\n" +
"\t}\n" +
"\n" +
"\tdocument.getElementById('search').onclick = function() {\n" +
"\t\tsearch(true);\n" +
"\t}\n" +
"\n" +
"\tdocument.getElementById('reset').onclick = function() {\n" +
"\t\tsearch(false);\n" +
"\t}\n" +
"\n" +
"\twindow.onkeydown = function() {\n" +
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n" +
"\t\t\tevent.preventDefault();\n" +
"\t\t\tsearch(true);\n" +
"\t\t} else if (event.keyCode === 27) {\n" +
"\t\t\tsearch(false);\n" +
"\t\t}\n" +
"\t}\n";
private static final String FOOTER = "render();\n" +
"</script></body></html>\n";
}

View File

@@ -1 +0,0 @@
Main-Class: Main

View File

@@ -1,31 +1,122 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Main entry point of jar.
* Lists available converters.
*/
import one.convert.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
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");
public static void main(String[] argv) throws Exception {
Arguments args = new Arguments(argv);
if (args.help || args.files.isEmpty()) {
usage();
return;
}
if (args.files.size() == 1) {
args.files.add(".");
}
int fileCount = args.files.size() - 1;
String lastFile = args.files.get(fileCount);
boolean isDirectory = new File(lastFile).isDirectory();
if (args.output == null) {
int ext;
if (!isDirectory && (ext = lastFile.lastIndexOf('.')) > 0) {
args.output = lastFile.substring(ext + 1);
} else {
args.output = "html";
}
}
for (int i = 0; i < fileCount; i++) {
String input = args.files.get(i);
String output = isDirectory ? new File(lastFile, replaceExt(input, args.output)).getPath() : lastFile;
System.out.print("Converting " + getFileName(input) + " -> " + getFileName(output) + " ");
System.out.flush();
long startTime = System.nanoTime();
convert(input, output, args);
long endTime = System.nanoTime();
System.out.print("# " + (endTime - startTime) / 1000000 / 1000.0 + " s\n");
}
}
public static void convert(String input, String output, Arguments args) throws IOException {
if (isJfr(input)) {
if ("html".equals(args.output)) {
JfrToFlame.convert(input, output, args);
} else if ("pprof".equals(args.output) || "pb".equals(args.output) || args.output.endsWith("gz")) {
JfrToPprof.convert(input, output, args);
} else {
throw new IllegalArgumentException("Unrecognized output format: " + args.output);
}
} else {
FlameGraph.convert(input, output, args);
}
}
private static String getFileName(String fileName) {
return fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1);
}
private static String replaceExt(String fileName, String ext) {
int slash = fileName.lastIndexOf(File.separatorChar);
int dot = fileName.lastIndexOf('.');
return dot > slash ? fileName.substring(slash + 1, dot + 1) + ext : fileName.substring(slash + 1) + '.' + ext;
}
private static boolean isJfr(String fileName) throws IOException {
if (fileName.endsWith(".jfr")) {
return true;
} else if (fileName.endsWith(".collapsed") || fileName.endsWith(".txt") || fileName.endsWith(".csv")) {
return false;
}
byte[] buf = new byte[4];
try (FileInputStream fis = new FileInputStream(fileName)) {
return fis.read(buf) == 4 && buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'R' && buf[3] == 0;
}
}
private static void usage() {
System.out.print("Usage: jfrconv [options] <input> [<input>...] <output>\n" +
"\n" +
"Conversion options:\n" +
" -o --output FORMAT Output format: html, collapsed, pprof, pb.gz\n" +
"\n" +
"JFR options:\n" +
" --cpu CPU profile\n" +
" --wall Wall clock profile\n" +
" --alloc Allocation profile\n" +
" --live Live object profile\n" +
" --lock Lock contention profile\n" +
" -t --threads Split stack traces by threads\n" +
" -s --state LIST Filter thread states: runnable, sleeping\n" +
" --classify Classify samples into predefined categories\n" +
" --total Accumulate total value (time, bytes, etc.)\n" +
" --lines Show line numbers\n" +
" --bci Show bytecode indices\n" +
" --simple Simple class names instead of FQN\n" +
" --norm Normalize names of hidden classes / lambdas\n" +
" --dot Dotted class names\n" +
" --from TIME Start time in ms (absolute or relative)\n" +
" --to TIME End time in ms (absolute or relative)\n" +
"\n" +
"Flame Graph options:\n" +
" --title STRING Flame Graph title\n" +
" --minwidth X Skip frames smaller than X%\n" +
" --skip N Skip N bottom frames\n" +
" -r --reverse Reverse stack traces (icicle graph)\n" +
" -I --include REGEX Include only stacks with the specified frames\n" +
" -X --exclude REGEX Exclude stacks with the specified frames\n" +
" --highlight REGEX Highlight frames matching the given pattern\n");
}
}

View File

@@ -1,92 +0,0 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import one.jfr.ClassRef;
import one.jfr.Dictionary;
import one.jfr.JfrReader;
import one.jfr.MethodRef;
import one.jfr.StackTrace;
import java.nio.charset.StandardCharsets;
/**
* Converts .jfr output produced by async-profiler to HTML Flame Graph.
*/
public class jfr2flame {
private static final int FRAME_KERNEL = 5;
private final JfrReader jfr;
private final Dictionary<String> methodNames = new Dictionary<>();
public jfr2flame(JfrReader jfr) {
this.jfr = jfr;
}
public void convert(final FlameGraph fg) {
// Don't use lambda for faster startup
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
@Override
public void visit(long id, StackTrace stackTrace) {
long[] methods = stackTrace.methods;
byte[] types = stackTrace.types;
String[] trace = new String[methods.length];
for (int i = 0; i < methods.length; i++) {
trace[trace.length - 1 - i] = getMethodName(methods[i], types[i]);
}
fg.addSample(trace, stackTrace.samples);
}
});
}
private String getMethodName(long methodId, int type) {
String result = methodNames.get(methodId);
if (result != null) {
return result;
}
MethodRef method = jfr.methods.get(methodId);
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
if (className == null || className.length == 0) {
String methodStr = new String(methodName, StandardCharsets.UTF_8);
result = type == FRAME_KERNEL ? methodStr + "_[k]" : methodStr;
} else {
String classStr = new String(className, StandardCharsets.UTF_8);
String methodStr = new String(methodName, StandardCharsets.UTF_8);
result = classStr + '.' + methodStr + "_[j]";
}
methodNames.put(methodId, result);
return result;
}
public static void main(String[] args) throws Exception {
FlameGraph fg = new FlameGraph(args);
if (fg.input == null) {
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
System.exit(1);
}
try (JfrReader jfr = new JfrReader(fg.input)) {
new jfr2flame(jfr).convert(fg);
}
fg.dump();
}
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import one.jfr.ClassRef;
import one.jfr.Dictionary;
import one.jfr.JfrReader;
import one.jfr.MethodRef;
import one.jfr.Sample;
import one.jfr.StackTrace;
import one.proto.Proto;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
/**
* 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"};
private static final byte[] NO_STACK = "[no_stack]".getBytes();
private final JfrReader jfr;
public jfr2nflx(JfrReader jfr) {
this.jfr = jfr;
}
public void dump(OutputStream out) throws IOException {
long startTime = System.nanoTime();
int samples = jfr.samples.size();
long durationTicks = samples == 0 ? 0 : jfr.samples.get(samples - 1).time - jfr.startTicks + 1;
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);
// Don't use lambda for faster startup
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
@Override
public void visit(long id, StackTrace stackTrace) {
profile.field(5, nodes
.field(1, (int) id)
.field(2, packNode(node, stackTrace)));
nodes.reset();
node.reset();
}
});
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]) : NO_STACK);
node.field(2, 1);
node.field(4, top >= 0 ? FRAME_TYPE[types[top]] : "user");
for (Proto frame = new Proto(100); --top >= 0; frame.reset()) {
node.field(10, frame
.field(1, getMethodName(methods[top]))
.field(2, FRAME_TYPE[types[top]]));
}
return node;
}
private Proto packSamples() {
Proto proto = new Proto(10000);
for (Sample sample : jfr.samples) {
proto.writeInt(sample.stackTraceId);
}
return proto;
}
private Proto packDeltas() {
Proto proto = new Proto(10000);
double ticksPerSec = jfr.ticksPerSec;
long prevTime = jfr.startTicks;
for (Sample sample : jfr.samples) {
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
prevTime = sample.time;
}
return proto;
}
private Proto packTids() {
Proto proto = new Proto(10000);
for (Sample sample : jfr.samples) {
proto.writeInt(sample.tid);
}
return proto;
}
private byte[] getMethodName(long methodId) {
MethodRef method = jfr.methods.get(methodId);
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
if (className == null || className.length == 0) {
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,122 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Pattern;
public class Arguments {
public String title = "Flame Graph";
public String highlight;
public String output;
public String state;
public Pattern include;
public Pattern exclude;
public double minwidth;
public int skip;
public boolean help;
public boolean reverse;
public boolean cpu;
public boolean wall;
public boolean alloc;
public boolean live;
public boolean lock;
public boolean threads;
public boolean classify;
public boolean total;
public boolean lines;
public boolean bci;
public boolean simple;
public boolean norm;
public boolean dot;
public long from;
public long to;
public final List<String> files = new ArrayList<>();
public Arguments(String... args) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
String fieldName;
if (arg.startsWith("--")) {
fieldName = arg.substring(2);
} else if (arg.startsWith("-") && arg.length() == 2) {
fieldName = alias(arg.charAt(1));
} else {
files.add(arg);
continue;
}
try {
Field f = Arguments.class.getDeclaredField(fieldName);
if ((f.getModifiers() & (Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL)) != 0) {
throw new IllegalArgumentException(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);
}
}
}
private static String alias(char c) {
switch (c) {
case 'h':
return "help";
case 'o':
return "output";
case 'r':
return "reverse";
case 'I':
return "include";
case 'X':
return "exclude";
case 't':
return "threads";
case 's':
return "state";
default:
return String.valueOf(c);
}
}
// Milliseconds or HH:mm:ss.S or yyyy-MM-dd'T'HH:mm:ss.S
private static 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,32 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import java.util.Arrays;
public class CallStack {
String[] names = new String[16];
byte[] types = new byte[16];
int size;
public void push(String name, byte type) {
if (size >= names.length) {
names = Arrays.copyOf(names, size * 2);
types = Arrays.copyOf(types, size * 2);
}
names[size] = name;
types[size] = type;
size++;
}
public void pop() {
size--;
}
public void clear() {
size = 0;
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import one.jfr.StackTrace;
import static one.convert.Frame.*;
abstract class Classifier {
enum Category {
GC("[gc]", TYPE_CPP),
JIT("[jit]", TYPE_CPP),
VM("[vm]", TYPE_CPP),
VTABLE_STUBS("[vtable_stubs]", TYPE_NATIVE),
NATIVE("[native]", TYPE_NATIVE),
INTERPRETER("[Interpreter]", TYPE_NATIVE),
C1_COMP("[c1_comp]", TYPE_C1_COMPILED),
C2_COMP("[c2_comp]", TYPE_INLINED),
ADAPTER("[c2i_adapter]", TYPE_INLINED),
CLASS_INIT("[class_init]", TYPE_CPP),
CLASS_LOAD("[class_load]", TYPE_CPP),
CLASS_RESOLVE("[class_resolve]", TYPE_CPP),
CLASS_VERIFY("[class_verify]", TYPE_CPP),
LAMBDA_INIT("[lambda_init]", TYPE_CPP);
final String title;
final byte type;
Category(String title, byte type) {
this.title = title;
this.type = type;
}
}
public Category getCategory(StackTrace stackTrace) {
long[] methods = stackTrace.methods;
byte[] types = stackTrace.types;
Category category;
if ((category = detectGcJit(methods, types)) == null &&
(category = detectClassLoading(methods, types)) == null) {
category = detectOther(methods, types);
}
return category;
}
private Category detectGcJit(long[] methods, byte[] types) {
boolean vmThread = false;
for (int i = types.length; --i >= 0; ) {
if (types[i] == TYPE_CPP) {
switch (getMethodName(methods[i], types[i])) {
case "CompileBroker::compiler_thread_loop":
return Category.JIT;
case "GCTaskThread::run":
case "WorkerThread::run":
return Category.GC;
case "java_start":
case "thread_native_entry":
vmThread = true;
break;
}
} else if (types[i] != TYPE_NATIVE) {
break;
}
}
return vmThread ? Category.VM : null;
}
private Category detectClassLoading(long[] methods, byte[] types) {
for (int i = 0; i < methods.length; i++) {
String methodName = getMethodName(methods[i], types[i]);
if (methodName.equals("Verifier::verify")) {
return Category.CLASS_VERIFY;
} else if (methodName.startsWith("InstanceKlass::initialize")) {
return Category.CLASS_INIT;
} else if (methodName.startsWith("LinkResolver::") ||
methodName.startsWith("InterpreterRuntime::resolve") ||
methodName.startsWith("SystemDictionary::resolve")) {
return Category.CLASS_RESOLVE;
} else if (methodName.endsWith("ClassLoader.loadClass")) {
return Category.CLASS_LOAD;
} else if (methodName.endsWith("LambdaMetafactory.metafactory") ||
methodName.endsWith("LambdaMetafactory.altMetafactory")) {
return Category.LAMBDA_INIT;
} else if (methodName.endsWith("table stub")) {
return Category.VTABLE_STUBS;
} else if (methodName.equals("Interpreter")) {
return Category.INTERPRETER;
} else if (methodName.startsWith("I2C/C2I")) {
return i + 1 < types.length && types[i + 1] == TYPE_INTERPRETED ? Category.INTERPRETER : Category.ADAPTER;
}
}
return null;
}
private Category detectOther(long[] methods, byte[] types) {
boolean inJava = true;
for (int i = 0; i < types.length; i++) {
switch (types[i]) {
case TYPE_INTERPRETED:
return inJava ? Category.INTERPRETER : Category.NATIVE;
case TYPE_JIT_COMPILED:
return inJava ? Category.C2_COMP : Category.NATIVE;
case TYPE_INLINED:
inJava = true;
break;
case TYPE_NATIVE: {
String methodName = getMethodName(methods[i], types[i]);
if (methodName.startsWith("JVM_") || methodName.startsWith("Unsafe_") ||
methodName.startsWith("MHN_") || methodName.startsWith("jni_")) {
return Category.VM;
}
switch (methodName) {
case "call_stub":
case "deoptimization":
case "unknown_Java":
case "not_walkable_Java":
case "InlineCacheBuffer":
return Category.VM;
}
if (methodName.endsWith("_arraycopy") || methodName.contains("pthread_cond")) {
break;
}
inJava = false;
break;
}
case TYPE_CPP: {
String methodName = getMethodName(methods[i], types[i]);
if (methodName.startsWith("Runtime1::")) {
return Category.C1_COMP;
}
break;
}
case TYPE_C1_COMPILED:
return inJava ? Category.C1_COMP : Category.NATIVE;
}
}
return Category.NATIVE;
}
protected abstract String getMethodName(long method, byte type);
}

View File

@@ -0,0 +1,415 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import static one.convert.Frame.*;
public class FlameGraph implements Comparator<Frame> {
private static final Frame[] EMPTY_FRAME_ARRAY = {};
private static final String[] FRAME_SUFFIX = {"_[0]", "_[j]", "_[i]", "", "", "_[k]", "_[1]"};
private static final byte HAS_SUFFIX = (byte) 0x80;
private static final int FLUSH_THRESHOLD = 15000;
private final Arguments args;
private final Index<String> cpool = new Index<>(String.class, "");
private final Frame root = new Frame(0, TYPE_NATIVE);
private final StringBuilder outbuf = new StringBuilder(FLUSH_THRESHOLD + 1000);
private int[] order;
private int depth;
private int lastLevel;
private long lastX;
private long lastTotal;
private long mintotal;
public FlameGraph(Arguments args) {
this.args = args;
}
public void parseCollapsed(Reader in) throws IOException {
CallStack stack = new CallStack();
try (BufferedReader br = new BufferedReader(in)) {
for (String line; (line = br.readLine()) != null; ) {
int space = line.lastIndexOf(' ');
if (space <= 0) continue;
long ticks = Long.parseLong(line.substring(space + 1));
for (int from = 0, to; from < space; from = to + 1) {
if ((to = line.indexOf(';', from)) < 0) to = space;
String name = line.substring(from, to);
byte type = detectType(name);
if ((type & HAS_SUFFIX) != 0) {
name = name.substring(0, name.length() - 4);
type ^= HAS_SUFFIX;
}
stack.push(name, type);
}
addSample(stack, ticks);
stack.clear();
}
}
}
public void parseHtml(Reader in) throws IOException {
Frame[] levels = new Frame[128];
int level = 0;
long total = 0;
boolean needRebuild = args.reverse || args.include != null || args.exclude != null;
try (BufferedReader br = new BufferedReader(in)) {
while (!br.readLine().startsWith("const cpool")) ;
br.readLine();
String s = "";
for (String line; (line = br.readLine()).startsWith("'"); ) {
String packed = unescape(line.substring(1, line.lastIndexOf('\'')));
s = s.substring(0, packed.charAt(0) - ' ').concat(packed.substring(1));
cpool.put(s, cpool.size());
}
while (!br.readLine().isEmpty()) ;
for (String line; !(line = br.readLine()).isEmpty(); ) {
StringTokenizer st = new StringTokenizer(line.substring(2, line.length() - 1), ",");
int nameAndType = Integer.parseInt(st.nextToken());
char func = line.charAt(0);
if (func == 'f') {
level = Integer.parseInt(st.nextToken());
st.nextToken();
} else if (func == 'u') {
level++;
} else if (func != 'n') {
throw new IllegalStateException("Unexpected line: " + line);
}
if (st.hasMoreTokens()) {
total = Long.parseLong(st.nextToken());
}
int titleIndex = nameAndType >>> 3;
byte type = (byte) (nameAndType & 7);
if (st.hasMoreTokens() && (type <= TYPE_INLINED || type >= TYPE_C1_COMPILED)) {
type = TYPE_JIT_COMPILED;
}
Frame f = level > 0 || needRebuild ? new Frame(titleIndex, type) : root;
f.self = f.total = total;
if (st.hasMoreTokens()) f.inlined = Long.parseLong(st.nextToken());
if (st.hasMoreTokens()) f.c1 = Long.parseLong(st.nextToken());
if (st.hasMoreTokens()) f.interpreted = Long.parseLong(st.nextToken());
if (level > 0) {
Frame parent = levels[level - 1];
parent.put(f.key, f);
parent.self -= total;
depth = Math.max(depth, level);
}
if (level >= levels.length) {
levels = Arrays.copyOf(levels, level * 2);
}
levels[level] = f;
}
}
if (needRebuild) {
rebuild(levels[0], new CallStack(), cpool.keys());
}
}
private void rebuild(Frame frame, CallStack stack, String[] strings) {
if (frame.self > 0) {
addSample(stack, frame.self);
}
if (!frame.isEmpty()) {
for (Frame child : frame.values()) {
stack.push(strings[child.getTitleIndex()], child.getType());
rebuild(child, stack, strings);
stack.pop();
}
}
}
public void addSample(CallStack stack, long ticks) {
if (excludeStack(stack)) {
return;
}
Frame frame = root;
if (args.reverse) {
for (int i = stack.size; --i >= args.skip; ) {
frame = addChild(frame, stack.names[i], stack.types[i], ticks);
}
} else {
for (int i = args.skip; i < stack.size; i++) {
frame = addChild(frame, stack.names[i], stack.types[i], ticks);
}
}
frame.total += ticks;
frame.self += ticks;
depth = Math.max(depth, stack.size);
}
public void dump(PrintStream out) {
mintotal = (long) (root.total * args.minwidth / 100);
if ("collapsed".equals(args.output)) {
printFrameCollapsed(out, root, cpool.keys());
return;
}
String tail = getResource("/flame.html");
tail = printTill(out, tail, "/*height:*/300");
int depth = mintotal > 1 ? root.depth(mintotal) : this.depth + 1;
out.print(Math.min(depth * 16, 32767));
tail = printTill(out, tail, "/*title:*/");
out.print(args.title);
tail = printTill(out, tail, "/*reverse:*/false");
out.print(args.reverse);
tail = printTill(out, tail, "/*depth:*/0");
out.print(depth);
tail = printTill(out, tail, "/*cpool:*/");
printCpool(out);
tail = printTill(out, tail, "/*frames:*/");
printFrame(out, root, 0, 0);
out.print(outbuf);
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 printCpool(PrintStream out) {
String[] strings = cpool.keys();
Arrays.sort(strings);
out.print("'all'");
order = new int[strings.length];
String s = "";
for (int i = 1; i < strings.length; i++) {
int prefixLen = Math.min(getCommonPrefix(s, s = strings[i]), 95);
out.print(",\n'" + escape((char) (prefixLen + ' ') + s.substring(prefixLen)) + "'");
order[cpool.get(s)] = i;
}
// cpool is not used beyond this point
cpool.clear();
}
private void printFrame(PrintStream out, Frame frame, int level, long x) {
int nameAndType = order[frame.getTitleIndex()] << 3 | frame.getType();
boolean hasExtraTypes = (frame.inlined | frame.c1 | frame.interpreted) != 0 &&
frame.inlined < frame.total && frame.interpreted < frame.total;
char func = 'f';
if (level == lastLevel + 1 && x == lastX) {
func = 'u';
} else if (level == lastLevel && x == lastX + lastTotal) {
func = 'n';
}
StringBuilder sb = outbuf.append(func).append('(').append(nameAndType);
if (func == 'f') {
sb.append(',').append(level).append(',').append(x - lastX);
}
if (frame.total != lastTotal || hasExtraTypes) {
sb.append(',').append(frame.total);
if (hasExtraTypes) {
sb.append(',').append(frame.inlined).append(',').append(frame.c1).append(',').append(frame.interpreted);
}
}
sb.append(")\n");
if (sb.length() > FLUSH_THRESHOLD) {
out.print(sb);
sb.setLength(0);
}
lastLevel = level;
lastX = x;
lastTotal = frame.total;
Frame[] children = frame.values().toArray(EMPTY_FRAME_ARRAY);
Arrays.sort(children, this);
x += frame.self;
for (Frame child : children) {
if (child.total >= mintotal) {
printFrame(out, child, level + 1, x);
}
x += child.total;
}
}
private void printFrameCollapsed(PrintStream out, Frame frame, String[] strings) {
StringBuilder sb = outbuf;
int prevLength = sb.length();
if (frame != root) {
sb.append(strings[frame.getTitleIndex()]).append(FRAME_SUFFIX[frame.getType()]);
if (frame.self > 0) {
int tmpLength = sb.length();
out.print(sb.append(' ').append(frame.self).append('\n'));
sb.setLength(tmpLength);
}
sb.append(';');
}
if (!frame.isEmpty()) {
for (Frame child : frame.values()) {
if (child.total >= mintotal) {
printFrameCollapsed(out, child, strings);
}
}
}
sb.setLength(prevLength);
}
private boolean excludeStack(CallStack stack) {
Pattern include = args.include;
Pattern exclude = args.exclude;
if (include == null && exclude == null) {
return false;
}
for (int i = 0; i < stack.size; i++) {
if (exclude != null && exclude.matcher(stack.names[i]).matches()) {
return true;
}
if (include != null && include.matcher(stack.names[i]).matches()) {
if (exclude == null) return false;
include = null;
}
}
return include != null;
}
private Frame addChild(Frame frame, String title, byte type, long ticks) {
frame.total += ticks;
int titleIndex = cpool.index(title);
Frame child;
switch (type) {
case TYPE_INTERPRETED:
(child = frame.getChild(titleIndex, TYPE_JIT_COMPILED)).interpreted += ticks;
break;
case TYPE_INLINED:
(child = frame.getChild(titleIndex, TYPE_JIT_COMPILED)).inlined += ticks;
break;
case TYPE_C1_COMPILED:
(child = frame.getChild(titleIndex, TYPE_JIT_COMPILED)).c1 += ticks;
break;
default:
child = frame.getChild(titleIndex, type);
}
return child;
}
private static byte detectType(String title) {
if (title.endsWith("_[j]")) {
return TYPE_JIT_COMPILED | HAS_SUFFIX;
} else if (title.endsWith("_[i]")) {
return TYPE_INLINED | HAS_SUFFIX;
} else if (title.endsWith("_[k]")) {
return TYPE_KERNEL | HAS_SUFFIX;
} else if (title.endsWith("_[0]")) {
return TYPE_INTERPRETED | HAS_SUFFIX;
} else if (title.endsWith("_[1]")) {
return TYPE_C1_COMPILED | HAS_SUFFIX;
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
return TYPE_CPP;
} else if (title.indexOf('/') > 0 && title.charAt(0) != '['
|| title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
return TYPE_JIT_COMPILED;
} else {
return TYPE_NATIVE;
}
}
private static int getCommonPrefix(String a, String b) {
int length = Math.min(a.length(), b.length());
for (int i = 0; i < length; i++) {
if (a.charAt(i) != b.charAt(i) || a.charAt(i) > 127) {
return i;
}
}
return length;
}
private static String escape(String s) {
if (s.indexOf('\\') >= 0) s = s.replace("\\", "\\\\");
if (s.indexOf('\'') >= 0) s = s.replace("'", "\\'");
return s;
}
private static String unescape(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[32768];
for (int length; (length = stream.read(buffer)) != -1; ) {
result.write(buffer, 0, length);
}
return result.toString("UTF-8");
} catch (IOException e) {
throw new IllegalStateException("Can't load resource with name " + name);
}
}
@Override
public int compare(Frame f1, Frame f2) {
return order[f1.getTitleIndex()] - order[f2.getTitleIndex()];
}
public static void convert(String input, String output, Arguments args) throws IOException {
FlameGraph fg = new FlameGraph(args);
try (InputStreamReader in = new InputStreamReader(new FileInputStream(input), StandardCharsets.UTF_8)) {
if (input.endsWith(".html")) {
fg.parseHtml(in);
} else {
fg.parseCollapsed(in);
}
}
try (PrintStream out = new PrintStream(output, "UTF-8")) {
fg.dump(out);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import java.util.HashMap;
public class Frame extends HashMap<Integer, Frame> {
public static final byte TYPE_INTERPRETED = 0;
public static final byte TYPE_JIT_COMPILED = 1;
public static final byte TYPE_INLINED = 2;
public static final byte TYPE_NATIVE = 3;
public static final byte TYPE_CPP = 4;
public static final byte TYPE_KERNEL = 5;
public static final byte TYPE_C1_COMPILED = 6;
private static final int TYPE_SHIFT = 28;
final int key;
long total;
long self;
long inlined, c1, interpreted;
private Frame(int key) {
this.key = key;
}
Frame(int titleIndex, byte type) {
this(titleIndex | type << TYPE_SHIFT);
}
Frame getChild(int titleIndex, byte type) {
return super.computeIfAbsent(titleIndex | type << TYPE_SHIFT, Frame::new);
}
int getTitleIndex() {
return key & ((1 << TYPE_SHIFT) - 1);
}
byte getType() {
if (inlined * 3 >= total) {
return TYPE_INLINED;
} else if (c1 * 2 >= total) {
return TYPE_C1_COMPILED;
} else if (interpreted * 2 >= total) {
return TYPE_INTERPRETED;
} else {
return (byte) (key >>> TYPE_SHIFT);
}
}
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;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import java.lang.reflect.Array;
import java.util.HashMap;
public class Index<T> extends HashMap<T, Integer> {
private final Class<T> cls;
public Index(Class<T> cls, T empty) {
this.cls = cls;
super.put(empty, 0);
}
public int index(T key) {
Integer index = super.get(key);
if (index != null) {
return index;
} else {
int newIndex = super.size();
super.put(key, newIndex);
return newIndex;
}
}
@SuppressWarnings("unchecked")
public T[] keys() {
T[] result = (T[]) Array.newInstance(cls, size());
for (Entry<T, Integer> entry : entrySet()) {
result[entry.getValue()] = entry.getKey();
}
return result;
}
}

View File

@@ -0,0 +1,228 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import one.jfr.ClassRef;
import one.jfr.Dictionary;
import one.jfr.JfrReader;
import one.jfr.MethodRef;
import one.jfr.event.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import java.util.Map;
import static one.convert.Frame.*;
public abstract class JfrConverter extends Classifier {
protected final JfrReader jfr;
protected final Arguments args;
protected Dictionary<String> methodNames;
public JfrConverter(JfrReader jfr, Arguments args) {
this.jfr = jfr;
this.args = args;
}
public void convert() throws IOException {
jfr.stopAtNewChunk = true;
while (jfr.hasMoreChunks()) {
// Reset method dictionary, since new chunk may have different IDs
methodNames = new Dictionary<>();
convertChunk();
}
}
protected abstract void convertChunk() throws IOException;
protected EventAggregator collectEvents() 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;
BitSet threadStates = null;
if (args.state != null) {
threadStates = new BitSet();
for (String state : args.state.toUpperCase().split(",")) {
threadStates.set(toThreadState(state));
}
} else if (args.cpu) {
threadStates = getThreadStates(true);
} else if (args.wall) {
threadStates = getThreadStates(false);
}
long startTicks = args.from != 0 ? toTicks(args.from) : Long.MIN_VALUE;
long endTicks = args.to != 0 ? toTicks(args.to) : Long.MAX_VALUE;
for (Event event; (event = jfr.readEvent(eventClass)) != null; ) {
if (event.time >= startTicks && event.time <= endTicks) {
if (threadStates == null || threadStates.get(((ExecutionSample) event).threadState)) {
agg.collect(event);
}
}
}
return agg;
}
protected int toThreadState(String name) {
Map<Integer, String> threadStates = jfr.enums.get("jdk.types.ThreadState");
if (threadStates != null) {
for (Map.Entry<Integer, String> entry : threadStates.entrySet()) {
if (entry.getValue().startsWith(name, 6)) {
return entry.getKey();
}
}
}
throw new IllegalArgumentException("Unknown thread state: " + name);
}
protected BitSet getThreadStates(boolean cpu) {
BitSet set = new BitSet();
Map<Integer, String> threadStates = jfr.enums.get("jdk.types.ThreadState");
if (threadStates != null) {
for (Map.Entry<Integer, String> entry : threadStates.entrySet()) {
set.set(entry.getKey(), "STATE_DEFAULT".equals(entry.getValue()) == cpu);
}
}
return set;
}
// millis can be an absolute timestamp or an offset from the beginning/end of the recording
protected long toTicks(long millis) {
long nanos = millis * 1_000_000;
if (millis < 0) {
nanos += jfr.endNanos;
} else if (millis < 1500000000000L) {
nanos += jfr.startNanos;
}
return (long) ((nanos - jfr.chunkStartNanos) * (jfr.ticksPerSec / 1e9)) + jfr.chunkStartTicks;
}
@Override
protected String getMethodName(long methodId, byte methodType) {
String result = methodNames.get(methodId);
if (result == null) {
methodNames.put(methodId, result = resolveMethodName(methodId, methodType));
}
return result;
}
private String resolveMethodName(long methodId, byte methodType) {
MethodRef method = jfr.methods.get(methodId);
if (method == null) {
return "unknown";
}
ClassRef cls = jfr.classes.get(method.cls);
byte[] className = jfr.symbols.get(cls.name);
byte[] methodName = jfr.symbols.get(method.name);
if (className == null || className.length == 0 || isNativeFrame(methodType)) {
return new String(methodName, StandardCharsets.UTF_8);
} else {
String classStr = toJavaClassName(className, 0, args.dot);
if (methodName == null || methodName.length == 0) {
return classStr;
}
String methodStr = new String(methodName, StandardCharsets.UTF_8);
return classStr + '.' + methodStr;
}
}
protected String getClassName(long classId) {
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++;
}
String name = toJavaClassName(className, arrayDepth, true);
while (arrayDepth-- > 0) {
name = name.concat("[]");
}
return name;
}
protected String getThreadName(int tid) {
String threadName = jfr.threads.get(tid);
return threadName == null ? "[tid=" + tid + ']' :
threadName.startsWith("[tid=") ? threadName : '[' + threadName + " tid=" + tid + ']';
}
protected String toJavaClassName(byte[] symbol, int start, boolean dotted) {
int end = symbol.length;
if (start > 0) {
switch (symbol[start]) {
case 'B':
return "byte";
case 'C':
return "char";
case 'S':
return "short";
case 'I':
return "int";
case 'J':
return "long";
case 'Z':
return "boolean";
case 'F':
return "float";
case 'D':
return "double";
case 'L':
start++;
end--;
}
}
if (args.norm) {
for (int i = end - 2; i > start; i--) {
if (symbol[i] == '/' || symbol[i] == '.') {
if (symbol[i + 1] >= '0' && symbol[i + 1] <= '9') {
end = i;
if (i > start + 19 && symbol[i - 19] == '+' && symbol[i - 18] == '0') {
// Original JFR transforms lambda names to something like
// pkg.ClassName$$Lambda+0x00007f8177090218/543846639
end = i - 19;
}
}
break;
}
}
}
if (args.simple) {
for (int i = end - 2; i >= start; i--) {
if (symbol[i] == '/' && (symbol[i + 1] < '0' || symbol[i + 1] > '9')) {
start = i + 1;
break;
}
}
}
String s = new String(symbol, start, end - start, StandardCharsets.UTF_8);
return dotted ? s.replace('/', '.') : s;
}
protected boolean isNativeFrame(byte methodType) {
// In JDK Flight Recorder, TYPE_NATIVE denotes Java native methods,
// while in async-profiler, TYPE_NATIVE is for C methods
return methodType == TYPE_NATIVE && jfr.getEnumValue("jdk.types.FrameType", TYPE_KERNEL) != null ||
methodType == TYPE_CPP ||
methodType == TYPE_KERNEL;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import one.jfr.JfrReader;
import one.jfr.StackTrace;
import one.jfr.event.AllocationSample;
import one.jfr.event.Event;
import one.jfr.event.EventAggregator;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import static one.convert.Frame.*;
/**
* Converts .jfr output to HTML Flame Graph.
*/
public class JfrToFlame extends JfrConverter {
private final FlameGraph fg;
public JfrToFlame(JfrReader jfr, Arguments args) {
super(jfr, args);
this.fg = new FlameGraph(args);
}
@Override
protected void convertChunk() throws IOException {
collectEvents().forEach(new EventAggregator.Visitor() {
final CallStack stack = new CallStack();
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
final boolean scale = args.total && args.lock && ticksToNanos != 1.0;
@Override
public void visit(Event event, long value) {
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
if (stackTrace != null) {
Arguments args = JfrToFlame.this.args;
long[] methods = stackTrace.methods;
byte[] types = stackTrace.types;
int[] locations = stackTrace.locations;
if (args.threads) {
stack.push(getThreadName(event.tid), TYPE_NATIVE);
}
if (args.classify) {
Classifier.Category category = getCategory(stackTrace);
stack.push(category.title, category.type);
}
for (int i = methods.length; --i >= 0; ) {
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;
}
stack.push(methodName, types[i]);
}
long classId = event.classId();
if (classId != 0) {
stack.push(getClassName(classId), (event instanceof AllocationSample)
&& ((AllocationSample) event).tlabSize == 0 ? TYPE_KERNEL : TYPE_INLINED);
}
fg.addSample(stack, scale ? (long) (value * ticksToNanos) : value);
stack.clear();
}
}
});
}
public void dump(OutputStream out) throws IOException {
try (PrintStream ps = new PrintStream(out, false, "UTF-8")) {
fg.dump(ps);
}
}
public static void convert(String input, String output, Arguments args) throws IOException {
JfrToFlame converter;
try (JfrReader jfr = new JfrReader(input)) {
converter = new JfrToFlame(jfr, args);
converter.convert();
}
try (FileOutputStream out = new FileOutputStream(output)) {
converter.dump(out);
}
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.convert;
import one.jfr.JfrReader;
import one.jfr.StackTrace;
import one.jfr.event.Event;
import one.jfr.event.EventAggregator;
import one.proto.Proto;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
/**
* Converts .jfr output to <a href="https://github.com/google/pprof">pprof</a>.
*/
public class JfrToPprof extends JfrConverter {
private final Proto profile = new Proto(100000);
private final Index<String> strings = new Index<>(String.class, "");
private final Index<String> functions = new Index<>(String.class, "");
private final Index<Long> locations = new Index<>(Long.class, 0L);
public JfrToPprof(JfrReader jfr, Arguments args) {
super(jfr, args);
Proto sampleType;
if (args.alloc || args.live) {
sampleType = valueType("allocations", args.total ? "bytes" : "count");
} else if (args.lock) {
sampleType = valueType("locks", args.total ? "nanoseconds" : "count");
} else {
sampleType = valueType("cpu", args.total ? "nanoseconds" : "count");
}
profile.field(1, sampleType)
.field(13, strings.index("Produced by async-profiler"));
}
@Override
protected void convertChunk() throws IOException {
collectEvents().forEach(new EventAggregator.Visitor() {
final Proto s = new Proto(100);
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
final boolean scale = args.total && args.lock && ticksToNanos != 1.0;
@Override
public void visit(Event event, long value) {
profile.field(2, sample(s, event, scale ? (long) (value * ticksToNanos) : value));
s.reset();
}
});
}
public void dump(OutputStream out) throws IOException {
profile.field(3, mapping(1, 0, Long.MAX_VALUE, "async-profiler"));
Long[] locations = this.locations.keys();
for (int i = 1; i < locations.length; i++) {
profile.field(4, location(i, locations[i]));
}
String[] functions = this.functions.keys();
for (int i = 1; i < functions.length; i++) {
profile.field(5, function(i, functions[i]));
}
String[] strings = this.strings.keys();
for (String string : strings) {
profile.field(6, string);
}
profile.field(9, jfr.startNanos)
.field(10, jfr.durationNanos());
out.write(profile.buffer(), 0, profile.size());
}
private Proto sample(Proto s, Event event, long value) {
int packedLocations = s.startField(1);
long classId = event.classId();
if (classId != 0) {
int function = functions.index(getClassName(classId));
s.writeInt(locations.index((long) function << 16));
}
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
if (stackTrace != null) {
long[] methods = stackTrace.methods;
byte[] types = stackTrace.types;
int[] lines = stackTrace.locations;
for (int i = 0; i < methods.length; i++) {
String methodName = getMethodName(methods[i], types[i]);
int function = functions.index(methodName);
s.writeInt(locations.index((long) function << 16 | lines[i] >>> 16));
}
}
s.commitField(packedLocations);
s.field(2, value);
if (args.threads && event.tid != 0) {
s.field(3, label("thread", getThreadName(event.tid)));
}
if (args.classify && stackTrace != null) {
s.field(3, label("category", getCategory(stackTrace).title));
}
return s;
}
private Proto valueType(String type, String unit) {
return new Proto(16)
.field(1, strings.index(type))
.field(2, strings.index(unit));
}
private Proto label(String key, String str) {
return new Proto(16)
.field(1, strings.index(key))
.field(2, strings.index(str));
}
private Proto mapping(int id, long start, long limit, String fileName) {
return new Proto(16)
.field(1, id)
.field(2, start)
.field(3, limit)
.field(5, strings.index(fileName));
}
private Proto location(int id, long location) {
return new Proto(16)
.field(1, id)
.field(4, line((int) (location >>> 16), (int) location & 0xffff));
}
private Proto line(int functionId, int line) {
return new Proto(16)
.field(1, functionId)
.field(2, line);
}
private Proto function(int id, String name) {
return new Proto(16)
.field(1, id)
.field(2, strings.index(name));
}
public static void convert(String input, String output, Arguments args) throws IOException {
JfrToPprof converter;
try (JfrReader jfr = new JfrReader(input)) {
converter = new JfrToPprof(jfr, args);
converter.convert();
}
try (FileOutputStream fos = new FileOutputStream(output);
OutputStream out = args.output.endsWith("gz") ? new GZIPOutputStream(fos, 4096) : fos) {
converter.dump(out);
}
}
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;
@@ -31,22 +20,32 @@ public class Dictionary<T> {
this.values = new Object[INITIAL_CAPACITY];
}
public void clear() {
keys = new long[INITIAL_CAPACITY];
values = new Object[INITIAL_CAPACITY];
size = 0;
}
public void put(long key, T value) {
if (key == 0) {
throw new IllegalArgumentException("Zero key not allowed");
}
if (++size * 2 > keys.length) {
resize(keys.length * 2);
}
int mask = keys.length - 1;
int i = hashCode(key) & mask;
while (keys[i] != 0 && keys[i] != key) {
while (keys[i] != 0) {
if (keys[i] == key) {
values[i] = value;
return;
}
i = (i + 1) & mask;
}
keys[i] = key;
values[i] = value;
if (++size * 2 > keys.length) {
resize(keys.length * 2);
}
}
@SuppressWarnings("unchecked")
@@ -69,9 +68,8 @@ public class Dictionary<T> {
}
public int preallocate(int count) {
int newSize = size + count;
if (newSize * 2 > keys.length) {
resize(Integer.highestOneBit(newSize * 4 - 1));
if (count * 2 > keys.length) {
resize(Integer.highestOneBit(count * 4 - 1));
}
return count;
}
@@ -98,6 +96,7 @@ public class Dictionary<T> {
}
private static int hashCode(long key) {
key *= 0xc6a4a7935bd1e995L;
return (int) (key ^ (key >>> 32));
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;
@@ -20,13 +9,15 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
class JfrClass extends Element {
public class JfrClass extends Element {
final int id;
final boolean simpleType;
final String name;
final List<JfrField> fields;
JfrClass(Map<String, String> attributes) {
this.id = Integer.parseInt(attributes.get("id"));
this.simpleType = "true".equals(attributes.get("simpleType"));
this.name = attributes.get("name");
this.fields = new ArrayList<>(2);
}
@@ -38,7 +29,7 @@ class JfrClass extends Element {
}
}
JfrField field(String name) {
public JfrField field(String name) {
for (JfrField field : fields) {
if (field.name.equals(name)) {
return field;

View File

@@ -1,24 +1,13 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;
import java.util.Map;
class JfrField extends Element {
public class JfrField extends Element {
final String name;
final int type;
final boolean constantPool;

View File

@@ -1,24 +1,17 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;
import one.jfr.event.*;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
@@ -33,60 +26,282 @@ import java.util.Map;
* Parses JFR output produced by async-profiler.
*/
public class JfrReader implements Closeable {
private static final int BUFFER_SIZE = 2 * 1024 * 1024;
private static final int CHUNK_HEADER_SIZE = 68;
private static final int CPOOL_OFFSET = 16;
private static final int META_OFFSET = 24;
private static final int CHUNK_SIGNATURE = 0x464c5200;
private static final byte STATE_NEW_CHUNK = 0;
private static final byte STATE_READING = 1;
private static final byte STATE_EOF = 2;
private static final byte STATE_INCOMPLETE = 3;
private final FileChannel ch;
private final ByteBuffer buf;
private ByteBuffer buf;
private final long fileSize;
private long filePosition;
private byte state;
public final long startNanos;
public final long durationNanos;
public final long startTicks;
public final long ticksPerSec;
public long startNanos = Long.MAX_VALUE;
public long endNanos = Long.MIN_VALUE;
public long startTicks = Long.MAX_VALUE;
public long chunkStartNanos;
public long chunkEndNanos;
public long chunkStartTicks;
public long ticksPerSec;
public boolean stopAtNewChunk;
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<String> strings = 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 List<Sample> samples = new ArrayList<>();
public final Map<String, String> settings = new HashMap<>();
public final Map<String, Map<Integer, String>> enums = new HashMap<>();
private final Dictionary<Constructor<? extends Event>> customEvents = new Dictionary<>();
private int executionSample;
private int nativeMethodSample;
private int allocationInNewTLAB;
private int allocationOutsideTLAB;
private int allocationSample;
private int liveObject;
private int monitorEnter;
private int threadPark;
private int activeSetting;
public JfrReader(String fileName) throws IOException {
this.ch = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
this.buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
this.buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
this.fileSize = ch.size();
if (buf.getInt(0) != 0x464c5200) {
throw new IOException("Not a valid JFR file");
buf.flip();
ensureBytes(CHUNK_HEADER_SIZE);
if (!readChunk(0)) {
throw new IOException("Incomplete JFR file");
}
}
int version = buf.getInt(4);
if (version < 0x20000 || version > 0x2ffff) {
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
public JfrReader(ByteBuffer buf) throws IOException {
this.ch = null;
this.buf = buf;
this.fileSize = buf.limit();
buf.order(ByteOrder.BIG_ENDIAN);
if (!readChunk(0)) {
throw new IOException("Incomplete JFR file");
}
this.startNanos = buf.getLong(32);
this.durationNanos = buf.getLong(40);
this.startTicks = buf.getLong(48);
this.ticksPerSec = buf.getLong(56);
readMeta();
readConstantPool();
readEvents();
}
@Override
public void close() throws IOException {
ch.close();
if (ch != null) {
ch.close();
}
}
private void readMeta() {
buf.position(buf.getInt(META_OFFSET + 4));
getVarint();
public boolean eof() {
return state >= STATE_EOF;
}
public boolean incomplete() {
return state == STATE_INCOMPLETE;
}
public long durationNanos() {
return endNanos - startNanos;
}
public <E extends Event> void registerEvent(String name, Class<E> eventClass) {
JfrClass type = typesByName.get(name);
if (type != null) {
try {
customEvents.put(type.id, eventClass.getConstructor(JfrReader.class));
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("No suitable constructor found");
}
}
}
// Similar to eof(), but parses the next chunk header
public boolean hasMoreChunks() throws IOException {
return state == STATE_NEW_CHUNK ? readChunk(buf.position()) : state == STATE_READING;
}
public List<Event> readAllEvents() throws IOException {
return readAllEvents(null);
}
public <E extends Event> List<E> readAllEvents(Class<E> cls) throws IOException {
ArrayList<E> events = new ArrayList<>();
for (E event; (event = readEvent(cls)) != null; ) {
events.add(event);
}
Collections.sort(events);
return events;
}
public Event readEvent() throws IOException {
return readEvent(null);
}
@SuppressWarnings("unchecked")
public <E extends Event> E readEvent(Class<E> cls) throws IOException {
while (ensureBytes(CHUNK_HEADER_SIZE)) {
int pos = buf.position();
int size = getVarint();
int type = getVarint();
if (type == 'L' && buf.getInt(pos) == CHUNK_SIGNATURE) {
if (state != STATE_NEW_CHUNK && stopAtNewChunk) {
buf.position(pos);
state = STATE_NEW_CHUNK;
} else if (readChunk(pos)) {
continue;
}
return null;
}
if (type == executionSample || type == nativeMethodSample) {
if (cls == null || cls == ExecutionSample.class) return (E) readExecutionSample();
} else if (type == allocationInNewTLAB) {
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(true);
} else if (type == allocationOutsideTLAB || type == allocationSample) {
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(false);
} else if (type == liveObject) {
if (cls == null || cls == LiveObject.class) return (E) readLiveObject();
} else if (type == monitorEnter) {
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(false);
} else if (type == threadPark) {
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(true);
} else if (type == activeSetting) {
readActiveSetting();
} else {
Constructor<? extends Event> customEvent = customEvents.get(type);
if (customEvent != null && (cls == null || cls == customEvent.getDeclaringClass())) {
try {
return (E) customEvent.newInstance(this);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
} finally {
seek(filePosition + pos + size);
}
}
}
seek(filePosition + pos + size);
}
state = STATE_EOF;
return null;
}
private ExecutionSample readExecutionSample() {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int threadState = getVarint();
return new ExecutionSample(time, tid, stackTraceId, threadState);
}
private AllocationSample readAllocationSample(boolean tlab) {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int classId = getVarint();
long allocationSize = getVarlong();
long tlabSize = tlab ? getVarlong() : 0;
return new AllocationSample(time, tid, stackTraceId, classId, allocationSize, tlabSize);
}
private LiveObject readLiveObject() {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int classId = getVarint();
long allocationSize = getVarlong();
long allocatimeTime = getVarlong();
return new LiveObject(time, tid, stackTraceId, classId, allocationSize, allocatimeTime);
}
private ContendedLock readContendedLock(boolean hasTimeout) {
long time = getVarlong();
long duration = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int classId = getVarint();
if (hasTimeout) getVarlong();
long until = getVarlong();
long address = getVarlong();
return new ContendedLock(time, tid, stackTraceId, duration, classId);
}
private void readActiveSetting() {
for (JfrField field : typesByName.get("jdk.ActiveSetting").fields) {
getVarlong();
if ("id".equals(field.name)) {
break;
}
}
String name = getString();
String value = getString();
settings.put(name, value);
}
private boolean readChunk(int pos) throws IOException {
if (pos + CHUNK_HEADER_SIZE > buf.limit() || buf.getInt(pos) != CHUNK_SIGNATURE) {
throw new IOException("Not a valid JFR file");
}
int version = buf.getInt(pos + 4);
if (version < 0x20000 || version > 0x2ffff) {
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
}
long chunkStart = filePosition + pos;
long chunkSize = buf.getLong(pos + 8);
if (chunkStart + chunkSize > fileSize) {
state = STATE_INCOMPLETE;
return false;
}
long cpOffset = buf.getLong(pos + 16);
long metaOffset = buf.getLong(pos + 24);
if (cpOffset == 0 || metaOffset == 0) {
state = STATE_INCOMPLETE;
return false;
}
chunkStartNanos = buf.getLong(pos + 32);
chunkEndNanos = buf.getLong(pos + 32) + buf.getLong(pos + 40);
chunkStartTicks = buf.getLong(pos + 48);
ticksPerSec = buf.getLong(pos + 56);
startNanos = Math.min(startNanos, chunkStartNanos);
endNanos = Math.max(endNanos, chunkEndNanos);
startTicks = Math.min(startTicks, chunkStartTicks);
types.clear();
typesByName.clear();
readMeta(chunkStart + metaOffset);
readConstantPool(chunkStart + cpOffset);
cacheEventTypes();
seek(chunkStart + CHUNK_HEADER_SIZE);
state = STATE_READING;
return true;
}
private void readMeta(long metaOffset) throws IOException {
seek(metaOffset);
ensureBytes(5);
int posBeforeSize = buf.position();
ensureBytes(getVarint() - (buf.position() - posBeforeSize));
getVarint();
getVarlong();
getVarlong();
@@ -133,15 +348,18 @@ public class JfrReader implements Closeable {
}
}
private void readConstantPool() {
int offset = buf.getInt(CPOOL_OFFSET + 4);
while (true) {
buf.position(offset);
getVarint();
private void readConstantPool(long cpOffset) throws IOException {
long delta;
do {
seek(cpOffset);
ensureBytes(5);
int posBeforeSize = buf.position();
ensureBytes(getVarint() - (buf.position() - posBeforeSize));
getVarint();
getVarlong();
getVarlong();
long delta = getVarlong();
delta = getVarlong();
getVarint();
int poolCount = getVarint();
@@ -149,12 +367,7 @@ public class JfrReader implements Closeable {
int type = getVarint();
readConstants(types.get(type));
}
if (delta == 0) {
break;
}
offset += delta;
}
} while (delta != 0 && (cpOffset += delta) > 0);
}
private void readConstants(JfrClass type) {
@@ -163,10 +376,13 @@ public class JfrReader implements Closeable {
buf.position(buf.position() + (CHUNK_HEADER_SIZE + 3));
break;
case "java.lang.Thread":
readThreads(type.field("group") != null);
readThreads(type.fields.size());
break;
case "java.lang.Class":
readClasses(type.field("hidden") != null);
readClasses(type.fields.size());
break;
case "java.lang.String":
readStrings();
break;
case "jdk.types.Symbol":
readSymbols();
@@ -177,18 +393,16 @@ public class JfrReader implements Closeable {
case "jdk.types.StackTrace":
readStackTraces();
break;
case "jdk.types.FrameType":
readMap(frameTypes);
break;
case "jdk.types.ThreadState":
readMap(threadStates);
break;
default:
readOtherConstants(type.fields);
if (type.simpleType && type.fields.size() == 1) {
readEnumValues(type.name);
} else {
readOtherConstants(type.fields);
}
}
}
private void readThreads(boolean hasGroup) {
private void readThreads(int fieldCount) {
int count = threads.preallocate(getVarint());
for (int i = 0; i < count; i++) {
long id = getVarlong();
@@ -196,12 +410,12 @@ public class JfrReader implements Closeable {
int osThreadId = getVarint();
String javaName = getString();
long javaThreadId = getVarlong();
if (hasGroup) getVarlong();
readFields(fieldCount - 4);
threads.put(id, javaName != null ? javaName : osName);
}
}
private void readClasses(boolean hasHidden) {
private void readClasses(int fieldCount) {
int count = classes.preallocate(getVarint());
for (int i = 0; i < count; i++) {
long id = getVarlong();
@@ -209,7 +423,7 @@ public class JfrReader implements Closeable {
long name = getVarlong();
long pkg = getVarlong();
int modifiers = getVarint();
if (hasHidden) getVarint();
readFields(fieldCount - 4);
classes.put(id, new ClassRef(name));
}
}
@@ -241,13 +455,22 @@ public class JfrReader implements Closeable {
int depth = getVarint();
long[] methods = new long[depth];
byte[] types = new byte[depth];
int[] locations = new int[depth];
for (int i = 0; i < depth; i++) {
methods[i] = getVarlong();
int line = getVarint();
int bci = getVarint();
locations[i] = line << 16 | (bci & 0xffff);
types[i] = buf.get();
}
return new StackTrace(methods, types);
return new StackTrace(methods, types, locations);
}
private void readStrings() {
int count = strings.preallocate(getVarint());
for (int i = 0; i < count; i++) {
strings.put(getVarlong(), getString());
}
}
private void readSymbols() {
@@ -261,11 +484,13 @@ public class JfrReader implements Closeable {
}
}
private void readMap(Map<Integer, String> map) {
private void readEnumValues(String typeName) {
HashMap<Integer, String> map = new HashMap<>();
int count = getVarint();
for (int i = 0; i < count; i++) {
map.put(getVarint(), getString());
map.put((int) getVarlong(), getString());
}
enums.put(typeName, map);
}
private void readOtherConstants(List<JfrField> fields) {
@@ -294,36 +519,27 @@ public class JfrReader implements Closeable {
}
}
private void readEvents() {
int executionSample = getTypeId("jdk.ExecutionSample");
int nativeMethodSample = getTypeId("jdk.NativeMethodSample");
buf.position(CHUNK_HEADER_SIZE);
while (buf.hasRemaining()) {
int position = buf.position();
int size = getVarint();
int type = getVarint();
if (type == executionSample || type == nativeMethodSample) {
readExecutionSample();
} else {
buf.position(position + size);
}
private void readFields(int count) {
while (count-- > 0) {
getVarlong();
}
Collections.sort(samples);
}
private void readExecutionSample() {
long time = getVarlong();
int tid = getVarint();
int stackTraceId = getVarint();
int threadState = getVarint();
samples.add(new Sample(time, tid, stackTraceId, threadState));
private void cacheEventTypes() {
executionSample = getTypeId("jdk.ExecutionSample");
nativeMethodSample = getTypeId("jdk.NativeMethodSample");
allocationInNewTLAB = getTypeId("jdk.ObjectAllocationInNewTLAB");
allocationOutsideTLAB = getTypeId("jdk.ObjectAllocationOutsideTLAB");
allocationSample = getTypeId("jdk.ObjectAllocationSample");
liveObject = getTypeId("profiler.LiveObject");
monitorEnter = getTypeId("jdk.JavaMonitorEnter");
threadPark = getTypeId("jdk.ThreadPark");
activeSetting = getTypeId("jdk.ActiveSetting");
StackTrace stackTrace = stackTraces.get(stackTraceId);
if (stackTrace != null) {
stackTrace.samples++;
}
registerEvent("jdk.CPULoad", CPULoad.class);
registerEvent("jdk.GCHeapSummary", GCHeapSummary.class);
registerEvent("jdk.ObjectCount", ObjectCount.class);
registerEvent("jdk.ObjectCountAfterGC", ObjectCount.class);
}
private int getTypeId(String typeName) {
@@ -331,7 +547,23 @@ public class JfrReader implements Closeable {
return type != null ? type.id : -1;
}
private int getVarint() {
public int getEnumKey(String typeName, String value) {
Map<Integer, String> enumValues = enums.get(typeName);
if (enumValues != null) {
for (Map.Entry<Integer, String> entry : enumValues.entrySet()) {
if (value.equals(entry.getValue())) {
return entry.getKey();
}
}
}
return -1;
}
public String getEnumValue(String typeName, int key) {
return enums.get(typeName).get(key);
}
public int getVarint() {
int result = 0;
for (int shift = 0; ; shift += 7) {
byte b = buf.get();
@@ -342,7 +574,7 @@ public class JfrReader implements Closeable {
}
}
private long getVarlong() {
public long getVarlong() {
long result = 0;
for (int shift = 0; shift < 56; shift += 7) {
byte b = buf.get();
@@ -354,12 +586,22 @@ public class JfrReader implements Closeable {
return result | (buf.get() & 0xffL) << 56;
}
private String getString() {
public float getFloat() {
return buf.getFloat();
}
public double getDouble() {
return buf.getDouble();
}
public String getString() {
switch (buf.get()) {
case 0:
return null;
case 1:
return "";
case 2:
return strings.get(getVarlong());
case 3:
return new String(getBytes(), StandardCharsets.UTF_8);
case 4: {
@@ -376,9 +618,46 @@ public class JfrReader implements Closeable {
}
}
private byte[] getBytes() {
public byte[] getBytes() {
byte[] bytes = new byte[getVarint()];
buf.get(bytes);
return bytes;
}
private void seek(long pos) throws IOException {
long bufPosition = pos - filePosition;
if (bufPosition >= 0 && bufPosition <= buf.limit()) {
buf.position((int) bufPosition);
} else {
filePosition = pos;
ch.position(pos);
buf.rewind().flip();
}
}
private boolean ensureBytes(int needed) throws IOException {
if (buf.remaining() >= needed) {
return true;
}
if (ch == null) {
return false;
}
filePosition += buf.position();
if (buf.capacity() < needed) {
ByteBuffer newBuf = ByteBuffer.allocateDirect(needed);
newBuf.put(buf);
buf = newBuf;
} else {
buf.compact();
}
while (ch.read(buf) > 0 && buf.position() < needed) {
// keep reading
}
buf.flip();
return buf.limit() > 0;
}
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.jfr;
public class Sample implements Comparable<Sample> {
public final long time;
public final int tid;
public final int stackTraceId;
public final int threadState;
public Sample(long time, int tid, int stackTraceId, int threadState) {
this.time = time;
this.tid = tid;
this.stackTraceId = stackTraceId;
this.threadState = threadState;
}
@Override
public int compareTo(Sample o) {
return Long.compare(time, o.time);
}
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr;
@@ -19,10 +8,11 @@ package one.jfr;
public class StackTrace {
public final long[] methods;
public final byte[] types;
public long samples;
public final int[] locations;
public StackTrace(long[] methods, byte[] types) {
public StackTrace(long[] methods, byte[] types, int[] locations) {
this.methods = methods;
this.types = types;
this.locations = locations;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
public class AllocationSample extends Event {
public final int classId;
public final long allocationSize;
public final long tlabSize;
public AllocationSample(long time, int tid, int stackTraceId, int classId, long allocationSize, long tlabSize) {
super(time, tid, stackTraceId);
this.classId = classId;
this.allocationSize = allocationSize;
this.tlabSize = tlabSize;
}
@Override
public int hashCode() {
return classId * 127 + stackTraceId + (tlabSize == 0 ? 17 : 0);
}
@Override
public boolean sameGroup(Event o) {
if (o instanceof AllocationSample) {
AllocationSample a = (AllocationSample) o;
return classId == a.classId && (tlabSize == 0) == (a.tlabSize == 0);
}
return false;
}
@Override
public long classId() {
return classId;
}
@Override
public long value() {
return tlabSize != 0 ? tlabSize : allocationSize;
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
import one.jfr.JfrReader;
public class CPULoad extends Event {
public final float jvmUser;
public final float jvmSystem;
public final float machineTotal;
public CPULoad(JfrReader jfr) {
super(jfr.getVarlong(), 0, 0);
this.jvmUser = jfr.getFloat();
this.jvmSystem = jfr.getFloat();
this.machineTotal = jfr.getFloat();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
public class ContendedLock extends Event {
public final long duration;
public final int classId;
public ContendedLock(long time, int tid, int stackTraceId, long duration, int classId) {
super(time, tid, stackTraceId);
this.duration = duration;
this.classId = classId;
}
@Override
public int hashCode() {
return classId * 127 + stackTraceId;
}
@Override
public boolean sameGroup(Event o) {
if (o instanceof ContendedLock) {
ContendedLock c = (ContendedLock) o;
return classId == c.classId;
}
return false;
}
@Override
public long classId() {
return classId;
}
@Override
public long value() {
return duration;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
import java.lang.reflect.Field;
public abstract class Event implements Comparable<Event> {
public final long time;
public final int tid;
public final int stackTraceId;
protected Event(long time, int tid, int stackTraceId) {
this.time = time;
this.tid = tid;
this.stackTraceId = stackTraceId;
}
@Override
public int compareTo(Event o) {
return Long.compare(time, o.time);
}
@Override
public int hashCode() {
return stackTraceId;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getSimpleName())
.append("{time=").append(time)
.append(",tid=").append(tid)
.append(",stackTraceId=").append(stackTraceId);
for (Field f : getClass().getDeclaredFields()) {
try {
sb.append(',').append(f.getName()).append('=').append(f.get(this));
} catch (ReflectiveOperationException e) {
break;
}
}
return sb.append('}').toString();
}
public boolean sameGroup(Event o) {
return getClass() == o.getClass();
}
public long classId() {
return 0;
}
public long value() {
return 1;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
public class EventAggregator {
private static final int INITIAL_CAPACITY = 1024;
private final boolean threads;
private final boolean total;
private Event[] keys;
private long[] values;
private int size;
public EventAggregator(boolean threads, boolean total) {
this.threads = threads;
this.total = total;
this.keys = new Event[INITIAL_CAPACITY];
this.values = new long[INITIAL_CAPACITY];
}
public void collect(Event e) {
int mask = keys.length - 1;
int i = hashCode(e) & mask;
while (keys[i] != null) {
if (sameGroup(keys[i], e)) {
values[i] += total ? e.value() : 1;
return;
}
i = (i + 1) & mask;
}
keys[i] = e;
values[i] = total ? e.value() : 1;
if (++size * 2 > keys.length) {
resize(keys.length * 2);
}
}
public long getValue(Event e) {
int mask = keys.length - 1;
int i = hashCode(e) & mask;
while (keys[i] != null && !sameGroup(keys[i], e)) {
i = (i + 1) & mask;
}
return values[i];
}
public void forEach(Visitor visitor) {
for (int i = 0; i < keys.length; i++) {
if (keys[i] != null) {
visitor.visit(keys[i], values[i]);
}
}
}
private int hashCode(Event e) {
return e.hashCode() + (threads ? e.tid * 31 : 0);
}
private boolean sameGroup(Event e1, Event e2) {
return e1.stackTraceId == e2.stackTraceId && (!threads || e1.tid == e2.tid) && e1.sameGroup(e2);
}
private void resize(int newCapacity) {
Event[] newKeys = new Event[newCapacity];
long[] newValues = new long[newCapacity];
int mask = newKeys.length - 1;
for (int i = 0; i < keys.length; i++) {
if (keys[i] != null) {
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
if (newKeys[j] == null) {
newKeys[j] = keys[i];
newValues[j] = values[i];
break;
}
}
}
}
keys = newKeys;
values = newValues;
}
public interface Visitor {
void visit(Event event, long value);
}
}

View File

@@ -0,0 +1,15 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
public class ExecutionSample extends Event {
public final int threadState;
public ExecutionSample(long time, int tid, int stackTraceId, int threadState) {
super(time, tid, stackTraceId);
this.threadState = threadState;
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
import one.jfr.JfrReader;
public class GCHeapSummary extends Event {
public final int gcId;
public final boolean afterGC;
public final long committed;
public final long reserved;
public final long used;
public GCHeapSummary(JfrReader jfr) {
super(jfr.getVarlong(), 0, 0);
this.gcId = jfr.getVarint();
this.afterGC = jfr.getVarint() > 0;
long start = jfr.getVarlong();
long committedEnd = jfr.getVarlong();
this.committed = jfr.getVarlong();
long reservedEnd = jfr.getVarlong();
this.reserved = jfr.getVarlong();
this.used = jfr.getVarlong();
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
public class LiveObject extends Event {
public final int classId;
public final long allocationSize;
public final long allocationTime;
public LiveObject(long time, int tid, int stackTraceId, int classId, long allocationSize, long allocationTime) {
super(time, tid, stackTraceId);
this.classId = classId;
this.allocationSize = allocationSize;
this.allocationTime = allocationTime;
}
@Override
public int hashCode() {
return classId * 127 + stackTraceId;
}
@Override
public boolean sameGroup(Event o) {
if (o instanceof LiveObject) {
LiveObject a = (LiveObject) o;
return classId == a.classId;
}
return false;
}
@Override
public long classId() {
return classId;
}
@Override
public long value() {
return allocationSize;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.jfr.event;
import one.jfr.JfrReader;
public class ObjectCount extends Event {
public final int gcId;
public final int classId;
public final long count;
public final long totalSize;
public ObjectCount(JfrReader jfr) {
super(jfr.getVarlong(), 0, 0);
this.gcId = jfr.getVarint();
this.classId = jfr.getVarint();
this.count = jfr.getVarlong();
this.totalSize = jfr.getVarlong();
}
}

View File

@@ -1,21 +1,11 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.proto;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
@@ -48,6 +38,12 @@ public class Proto {
return this;
}
public Proto field(int index, long n) {
tag(index, 0);
writeLong(n);
return this;
}
public Proto field(int index, double d) {
tag(index, 1);
writeDouble(d);
@@ -56,7 +52,8 @@ public class Proto {
public Proto field(int index, String s) {
tag(index, 2);
writeString(s);
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
writeBytes(bytes, 0, bytes.length);
return this;
}
@@ -72,11 +69,39 @@ public class Proto {
return this;
}
public int startField(int index) {
tag(index, 2);
ensureCapacity(3);
return pos += 3;
}
public void commitField(int mark) {
int length = pos - mark;
if (length >= 1 << (7 * 3)) {
throw new IllegalArgumentException("Field too large");
}
buf[mark - 3] = (byte) (0x80 | (length & 0x7f));
buf[mark - 2] = (byte) (0x80 | ((length >>> 7) & 0x7f));
buf[mark - 1] = (byte) (length >>> 14);
}
public void writeInt(int n) {
int length = n == 0 ? 1 : (38 - Integer.numberOfLeadingZeros(n)) / 7;
ensureCapacity(length);
while (n > 0x7f) {
while ((n >>> 7) != 0) {
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 >>> 7) != 0) {
buf[pos++] = (byte) (0x80 | (n & 0x7f));
n >>>= 7;
}
@@ -97,16 +122,6 @@ public class Proto {
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);
@@ -121,7 +136,8 @@ public class Proto {
private void ensureCapacity(int length) {
if (pos + length > buf.length) {
buf = Arrays.copyOf(buf, Math.max(pos + length, buf.length * 2));
int newLength = buf.length * 2;
buf = Arrays.copyOf(buf, newLength < 0 ? 0x7ffffff0 : Math.max(newLength, pos + length));
}
}
}

128
src/cpuEngine.cpp Normal file
View File

@@ -0,0 +1,128 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <pthread.h>
#include "cpuEngine.h"
#include "j9StackTraces.h"
#include "profiler.h"
#include "stackWalker.h"
#include "tsc.h"
#include "vmStructs.h"
void** CpuEngine::_pthread_entry = NULL;
CpuEngine* CpuEngine::_current = NULL;
long CpuEngine::_interval;
CStack CpuEngine::_cstack;
int CpuEngine::_signal;
// Intercept thread creation/termination by patching libjvm's GOT entry for pthread_setspecific().
// HotSpot puts VMThread into TLS on thread start, and resets on thread end.
static int pthread_setspecific_hook(pthread_key_t key, const void* value) {
if (key != VMThread::key()) {
return pthread_setspecific(key, value);
}
if (pthread_getspecific(key) == value) {
return 0;
}
if (value != NULL) {
int result = pthread_setspecific(key, value);
CpuEngine::onThreadStart();
return result;
} else {
CpuEngine::onThreadEnd();
return pthread_setspecific(key, value);
}
}
void CpuEngine::onThreadStart() {
CpuEngine* current = __atomic_load_n(&_current, __ATOMIC_ACQUIRE);
if (current != NULL) {
current->createForThread(OS::threadId());
}
}
void CpuEngine::onThreadEnd() {
CpuEngine* current = __atomic_load_n(&_current, __ATOMIC_ACQUIRE);
if (current != NULL) {
current->destroyForThread(OS::threadId());
}
}
bool CpuEngine::setupThreadHook() {
if (_pthread_entry != NULL) {
return true;
}
if (!VM::loaded()) {
static void* dummy_pthread_entry;
_pthread_entry = &dummy_pthread_entry;
return true;
}
// Depending on Zing version, pthread_setspecific is called either from libazsys.so or from libjvm.so
if (VM::isZing()) {
CodeCache* libazsys = Profiler::instance()->findLibraryByName("libazsys");
if (libazsys != NULL && (_pthread_entry = libazsys->findImport(im_pthread_setspecific)) != NULL) {
return true;
}
}
CodeCache* lib = Profiler::instance()->findJvmLibrary("libj9thr");
return lib != NULL && (_pthread_entry = lib->findImport(im_pthread_setspecific)) != NULL;
}
void CpuEngine::enableThreadHook() {
*_pthread_entry = (void*)pthread_setspecific_hook;
__atomic_store_n(&_current, this, __ATOMIC_RELEASE);
}
void CpuEngine::disableThreadHook() {
*_pthread_entry = (void*)pthread_setspecific;
__atomic_store_n(&_current, NULL, __ATOMIC_RELEASE);
}
bool CpuEngine::isResourceLimit(int err) {
return err == EMFILE || err == ENOMEM;
}
int CpuEngine::createForAllThreads() {
int result = EPERM;
ThreadList* thread_list = OS::listThreads();
for (int tid; (tid = thread_list->next()) != -1; ) {
int err = createForThread(tid);
if (isResourceLimit(err)) {
result = err;
break;
} else if (result != 0) {
result = err;
}
}
delete thread_list;
return result;
}
void CpuEngine::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
if (!_enabled) return;
ExecutionEvent event(TSC::ticks());
Profiler::instance()->recordSample(ucontext, _interval, EXECUTION_SAMPLE, &event);
}
void CpuEngine::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
if (!_enabled) return;
J9StackTraceNotification notif;
StackContext java_ctx;
notif.num_frames = _cstack == CSTACK_NO ? 0 : _cstack == CSTACK_DWARF
? StackWalker::walkDwarf(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx)
: StackWalker::walkFP(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx);
J9StackTraces::checkpoint(_interval, &notif);
}

51
src/cpuEngine.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _CPUENGINE_H
#define _CPUENGINE_H
#include <signal.h>
#include "engine.h"
// Base class for CPU sampling engines: PerfEvents, CTimer, ITimer
class CpuEngine : public Engine {
protected:
static void** _pthread_entry;
static CpuEngine* _current;
static long _interval;
static CStack _cstack;
static int _signal;
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
static void signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext);
static bool setupThreadHook();
void enableThreadHook();
void disableThreadHook();
bool isResourceLimit(int err);
int createForAllThreads();
virtual int createForThread(int tid) { return -1; }
virtual void destroyForThread(int tid) {}
public:
const char* title() {
return "CPU profile";
}
const char* units() {
return "ns";
}
static void onThreadStart();
static void onThreadEnd();
};
#endif // _CPUENGINE_H

50
src/ctimer.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _CTIMER_H
#define _CTIMER_H
#include "cpuEngine.h"
#ifdef __linux__
class CTimer : public CpuEngine {
private:
static int _max_timers;
static int* _timers;
int createForThread(int tid);
void destroyForThread(int tid);
public:
Error check(Arguments& args);
Error start(Arguments& args);
void stop();
static bool supported() {
return true;
}
};
#else
class CTimer : public CpuEngine {
public:
Error check(Arguments& args) {
return Error("CTimer is not supported on this platform");
}
Error start(Arguments& args) {
return Error("CTimer is not supported on this platform");
}
static bool supported() {
return false;
}
};
#endif // __linux__
#endif // _CTIMER_H

141
src/ctimer_linux.cpp Normal file
View File

@@ -0,0 +1,141 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __linux__
#include <stdlib.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#include "ctimer.h"
#include "j9StackTraces.h"
#include "profiler.h"
#include "stackWalker.h"
#ifndef SIGEV_THREAD_ID
#define SIGEV_THREAD_ID 4
#endif
static inline clockid_t thread_cpu_clock(unsigned int tid) {
return ((~tid) << 3) | 6; // CPUCLOCK_SCHED | CPUCLOCK_PERTHREAD_MASK
}
int CTimer::_max_timers = 0;
int* CTimer::_timers = NULL;
int CTimer::createForThread(int tid) {
if (tid >= _max_timers) {
Log::warn("tid[%d] > pid_max[%d]. Restart profiler after changing pid_max", tid, _max_timers);
return -1;
}
struct sigevent sev;
sev.sigev_value.sival_ptr = NULL;
sev.sigev_signo = _signal;
sev.sigev_notify = SIGEV_THREAD_ID;
((int*)&sev.sigev_notify)[1] = tid;
// Use raw syscalls, since libc wrapper allows only predefined clocks
clockid_t clock = thread_cpu_clock(tid);
int timer;
if (syscall(__NR_timer_create, clock, &sev, &timer) < 0) {
return -1;
}
// Kernel timer ID may start with zero, but we use zero as an empty slot
if (!__sync_bool_compare_and_swap(&_timers[tid], 0, timer + 1)) {
// Lost race
syscall(__NR_timer_delete, timer);
return -1;
}
struct itimerspec ts;
ts.it_interval.tv_sec = (time_t)(_interval / 1000000000);
ts.it_interval.tv_nsec = _interval % 1000000000;
ts.it_value = ts.it_interval;
syscall(__NR_timer_settime, timer, 0, &ts, NULL);
return 0;
}
void CTimer::destroyForThread(int tid) {
if (tid >= _max_timers) {
return;
}
int timer = _timers[tid];
if (timer != 0 && __sync_bool_compare_and_swap(&_timers[tid], timer--, 0)) {
syscall(__NR_timer_delete, timer);
}
}
Error CTimer::check(Arguments& args) {
if (!setupThreadHook()) {
return Error("Could not set pthread hook");
}
timer_t timer;
if (timer_create(CLOCK_THREAD_CPUTIME_ID, NULL, &timer) < 0) {
return Error("Failed to create CPU timer");
}
timer_delete(timer);
return Error::OK;
}
Error CTimer::start(Arguments& args) {
if (!setupThreadHook()) {
return Error("Could not set pthread hook");
}
if (args._interval < 0) {
return Error("interval must be positive");
}
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
_cstack = args._cstack;
_signal = args._signal == 0 ? OS::getProfilingSignal(0) : args._signal & 0xff;
int max_timers = OS::getMaxThreadId();
if (max_timers != _max_timers) {
free(_timers);
_timers = (int*)calloc(max_timers, sizeof(int));
_max_timers = max_timers;
}
if (VM::isOpenJ9()) {
if (_cstack == CSTACK_DEFAULT) _cstack = CSTACK_DWARF;
OS::installSignalHandler(_signal, signalHandlerJ9);
Error error = J9StackTraces::start(args);
if (error) {
return error;
}
} else {
OS::installSignalHandler(_signal, signalHandler);
}
// Enable pthread hook before traversing currently running threads
enableThreadHook();
// Create timers for all existing threads
int err = createForAllThreads();
if (err) {
disableThreadHook();
J9StackTraces::stop();
return Error("Failed to create CPU timer");
}
return Error::OK;
}
void CTimer::stop() {
disableThreadHook();
for (int i = 0; i < _max_timers; i++) {
destroyForThread(i);
}
J9StackTraces::stop();
}
#endif // __linux__

135
src/demangle.cpp Normal file
View File

@@ -0,0 +1,135 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <cxxabi.h>
#include <stdlib.h>
#include <string.h>
#include "demangle.h"
char* Demangle::demangleCpp(const char* s) {
int status;
char* result = abi::__cxa_demangle(s, NULL, NULL, &status);
if (result == NULL && status == -2) {
// Strip compiler-specific suffix (e.g. ".part.123") and retry demangling
char buf[512];
const char* p = strchr(s, '.');
if (p != NULL && p - s < sizeof(buf)) {
memcpy(buf, s, p - s);
buf[p - s] = 0;
result = abi::__cxa_demangle(buf, NULL, NULL, &status);
}
}
return result;
}
char* Demangle::demangleRust(const char* s, const char* e) {
// Demangled symbol can be 1.5x longer than original, e.g. 1A1B1C -> A::B::C
char* result = (char*)malloc((e - s) * 3 / 2 + 1);
if (result == NULL) {
return NULL;
}
char* r = result;
char* tmp;
while (s < e) {
unsigned long len = strtoul(s, &tmp, 10);
const char* next = tmp + len;
if (len == 0 || next > e) {
break;
}
s = tmp;
if (s[0] == '_' && s[1] == '$') s++;
if (r > result) {
*r++ = ':';
*r++ = ':';
}
while (s < next) {
if (s[0] == '$') {
if (s[1] == 'L' && s[2] == 'T' && s[3] == '$') {
*r++ = '<';
s += 4;
} else if (s[1] == 'G' && s[2] == 'T' && s[3] == '$') {
*r++ = '>';
s += 4;
} else if (s[1] == 'L' && s[2] == 'P' && s[3] == '$') {
*r++ = '(';
s += 4;
} else if (s[1] == 'R' && s[2] == 'P' && s[3] == '$') {
*r++ = ')';
s += 4;
} else if (s[1] == 'S' && s[2] == 'P' && s[3] == '$') {
*r++ = '@';
s += 4;
} else if (s[1] == 'B' && s[2] == 'P' && s[3] == '$') {
*r++ = '*';
s += 4;
} else if (s[1] == 'R' && s[2] == 'F' && s[3] == '$') {
*r++ = '&';
s += 4;
} else if (s[1] == 'C' && s[2] == '$') {
*r++ = ',';
s += 3;
} else if (s[1] == 'u') {
*r++ = (char)strtoul(s + 2, &tmp, 16);
s = tmp + 1;
} else {
*r++ = '$';
s++;
}
} else if (s[0] == '.' && s[1] == '.') {
*r++ = ':';
*r++ = ':';
s += 2;
} else {
*r++ = *s++;
}
}
if (s > next) {
break;
}
}
*r = 0;
return result;
}
void Demangle::cutArguments(char* s) {
char* p = strrchr(s, ')');
if (p == NULL) return;
int balance = 1;
while (--p > s) {
if (*p == '(' && --balance == 0) {
*p = 0;
return;
} else if (*p == ')') {
balance++;
}
}
}
char* Demangle::demangle(const char* s, bool full_signature) {
// Check if the mangled symbol ends with a Rust hash "17h<hex>E"
const char* e = strrchr(s, 'E');
if (e != NULL && e - s > 22 && e[-19] == '1' && e[-18] == '7' && e[-17] == 'h') {
const char* h = e - 16;
while ((*h >= '0' && *h <= '9') || (*h >= 'a' && *h <= 'f')) h++;
if (h == e) {
return demangleRust(s + 3, e - 19);
}
}
char* result = demangleCpp(s);
if (result != NULL && !full_signature) {
cutArguments(result);
}
return result;
}

20
src/demangle.h Normal file
View File

@@ -0,0 +1,20 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _DEMANGLE_H
#define _DEMANGLE_H
class Demangle {
private:
static char* demangleCpp(const char* s);
static char* demangleRust(const char* s, const char* e);
static void cutArguments(char* s);
public:
static char* demangle(const char* s, bool full_signature);
};
#endif // _DEMANGLE_H

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
@@ -34,7 +23,7 @@ static inline bool keyEquals(const char* candidate, const char* key, size_t leng
Dictionary::Dictionary() {
_table = (DictTable*)calloc(1, sizeof(DictTable));
_table->base_index = _base_index = 1;
_base_index = _table->base_index = 1;
}
Dictionary::~Dictionary() {
@@ -45,7 +34,7 @@ Dictionary::~Dictionary() {
void Dictionary::clear() {
clear(_table);
memset(_table, 0, sizeof(DictTable));
_table->base_index = _base_index = 1;
_base_index = _table->base_index = 1;
}
void Dictionary::clear(DictTable* table) {
@@ -61,6 +50,21 @@ void Dictionary::clear(DictTable* table) {
}
}
size_t Dictionary::usedMemory() {
return _table != NULL ? usedMemory(_table) : 0;
}
size_t Dictionary::usedMemory(DictTable* table) {
size_t bytes = sizeof(DictTable);
for (int i = 0; i < ROWS; i++) {
DictRow* row = &table->rows[i];
if (row->next != NULL) {
bytes += usedMemory(row->next);
}
}
return bytes;
}
// Many popular symbols are quite short, e.g. "[B", "()V" etc.
// FNV-1a is reasonably fast and sufficiently random.
unsigned int Dictionary::hash(const char* key, size_t length) {

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _DICTIONARY_H
@@ -50,6 +39,7 @@ class Dictionary {
volatile unsigned int _base_index;
static void clear(DictTable* table);
static size_t usedMemory(DictTable* table);
static unsigned int hash(const char* key, size_t length);
@@ -60,6 +50,7 @@ class Dictionary {
~Dictionary();
void clear();
size_t usedMemory();
unsigned int lookup(const char* key);
unsigned int lookup(const char* key, size_t length);

355
src/dwarf.cpp Normal file
View File

@@ -0,0 +1,355 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include "dwarf.h"
#include "log.h"
enum {
DW_CFA_nop = 0x0,
DW_CFA_set_loc = 0x1,
DW_CFA_advance_loc1 = 0x2,
DW_CFA_advance_loc2 = 0x3,
DW_CFA_advance_loc4 = 0x4,
DW_CFA_offset_extended = 0x5,
DW_CFA_restore_extended = 0x6,
DW_CFA_undefined = 0x7,
DW_CFA_same_value = 0x8,
DW_CFA_register = 0x9,
DW_CFA_remember_state = 0xa,
DW_CFA_restore_state = 0xb,
DW_CFA_def_cfa = 0xc,
DW_CFA_def_cfa_register = 0xd,
DW_CFA_def_cfa_offset = 0xe,
DW_CFA_def_cfa_expression = 0xf,
DW_CFA_expression = 0x10,
DW_CFA_offset_extended_sf = 0x11,
DW_CFA_def_cfa_sf = 0x12,
DW_CFA_def_cfa_offset_sf = 0x13,
DW_CFA_val_offset = 0x14,
DW_CFA_val_offset_sf = 0x15,
DW_CFA_val_expression = 0x16,
DW_CFA_AARCH64_negate_ra_state = 0x2d,
DW_CFA_GNU_args_size = 0x2e,
DW_CFA_advance_loc = 0x1,
DW_CFA_offset = 0x2,
DW_CFA_restore = 0x3,
};
enum {
DW_OP_breg_pc = 0x70 + DW_REG_PC,
DW_OP_const1u = 0x08,
DW_OP_const1s = 0x09,
DW_OP_const2u = 0x0a,
DW_OP_const2s = 0x0b,
DW_OP_const4u = 0x0c,
DW_OP_const4s = 0x0d,
DW_OP_constu = 0x10,
DW_OP_consts = 0x11,
DW_OP_minus = 0x1c,
DW_OP_plus = 0x22,
};
FrameDesc FrameDesc::empty_frame = {0, DW_REG_SP | EMPTY_FRAME_SIZE << 8, DW_SAME_FP, -EMPTY_FRAME_SIZE};
FrameDesc FrameDesc::default_frame = {0, DW_REG_FP | LINKED_FRAME_SIZE << 8, -LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE + DW_STACK_SLOT};
DwarfParser::DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr) {
_name = name;
_image_base = image_base;
_capacity = 128;
_count = 0;
_table = (FrameDesc*)malloc(_capacity * sizeof(FrameDesc));
_prev = NULL;
_code_align = sizeof(instruction_t);
_data_align = -(int)sizeof(void*);
parse(eh_frame_hdr);
}
void DwarfParser::parse(const char* eh_frame_hdr) {
u8 version = eh_frame_hdr[0];
u8 eh_frame_ptr_enc = eh_frame_hdr[1];
u8 fde_count_enc = eh_frame_hdr[2];
u8 table_enc = eh_frame_hdr[3];
if (version != 1 || (eh_frame_ptr_enc & 0x7) != 0x3 || (fde_count_enc & 0x7) != 0x3 || (table_enc & 0xf7) != 0x33) {
Log::warn("Unsupported .eh_frame_hdr [%02x%02x%02x%02x] in %s",
version, eh_frame_ptr_enc, fde_count_enc, table_enc, _name);
return;
}
int fde_count = *(int*)(eh_frame_hdr + 8);
int* table = (int*)(eh_frame_hdr + 16);
for (int i = 0; i < fde_count; i++) {
_ptr = eh_frame_hdr + table[i * 2];
parseFde();
}
}
void DwarfParser::parseCie() {
u32 cie_len = get32();
if (cie_len == 0 || cie_len == 0xffffffff) {
return;
}
const char* cie_start = _ptr;
_ptr += 5;
while (*_ptr++) {}
_code_align = getLeb();
_data_align = getSLeb();
_ptr = cie_start + cie_len;
}
void DwarfParser::parseFde() {
u32 fde_len = get32();
if (fde_len == 0 || fde_len == 0xffffffff) {
return;
}
const char* fde_start = _ptr;
u32 cie_offset = get32();
if (_count == 0) {
_ptr = fde_start - cie_offset;
parseCie();
_ptr = fde_start + 4;
}
u32 range_start = getPtr() - _image_base;
u32 range_len = get32();
_ptr += getLeb();
parseInstructions(range_start, fde_start + fde_len);
addRecord(range_start + range_len, DW_REG_FP, LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE, -LINKED_FRAME_SIZE + DW_STACK_SLOT);
}
void DwarfParser::parseInstructions(u32 loc, const char* end) {
const u32 code_align = _code_align;
const int data_align = _data_align;
u32 cfa_reg = DW_REG_SP;
int cfa_off = EMPTY_FRAME_SIZE;
int fp_off = DW_SAME_FP;
int pc_off = -EMPTY_FRAME_SIZE;
u32 rem_cfa_reg;
int rem_cfa_off;
int rem_fp_off;
int rem_pc_off;
while (_ptr < end) {
u8 op = get8();
switch (op >> 6) {
case 0:
switch (op) {
case DW_CFA_nop:
case DW_CFA_set_loc:
_ptr = end;
break;
case DW_CFA_advance_loc1:
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
loc += get8() * code_align;
break;
case DW_CFA_advance_loc2:
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
loc += get16() * code_align;
break;
case DW_CFA_advance_loc4:
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
loc += get32() * code_align;
break;
case DW_CFA_offset_extended:
switch (getLeb()) {
case DW_REG_FP: fp_off = getLeb() * data_align; break;
case DW_REG_PC: pc_off = getLeb() * data_align; break;
default: skipLeb();
}
break;
case DW_CFA_restore_extended:
case DW_CFA_undefined:
case DW_CFA_same_value:
if (getLeb() == DW_REG_FP) {
fp_off = DW_SAME_FP;
}
break;
case DW_CFA_register:
skipLeb();
skipLeb();
break;
case DW_CFA_remember_state:
rem_cfa_reg = cfa_reg;
rem_cfa_off = cfa_off;
rem_fp_off = fp_off;
rem_pc_off = pc_off;
break;
case DW_CFA_restore_state:
cfa_reg = rem_cfa_reg;
cfa_off = rem_cfa_off;
fp_off = rem_fp_off;
pc_off = rem_pc_off;
break;
case DW_CFA_def_cfa:
cfa_reg = getLeb();
cfa_off = getLeb();
break;
case DW_CFA_def_cfa_register:
cfa_reg = getLeb();
break;
case DW_CFA_def_cfa_offset:
cfa_off = getLeb();
break;
case DW_CFA_def_cfa_expression: {
u32 len = getLeb();
cfa_reg = len == 11 ? DW_REG_PLT : DW_REG_INVALID;
cfa_off = DW_STACK_SLOT;
_ptr += len;
break;
}
case DW_CFA_expression:
skipLeb();
_ptr += getLeb();
break;
case DW_CFA_offset_extended_sf:
switch (getLeb()) {
case DW_REG_FP: fp_off = getSLeb() * data_align; break;
case DW_REG_PC: pc_off = getSLeb() * data_align; break;
default: skipLeb();
}
break;
case DW_CFA_def_cfa_sf:
cfa_reg = getLeb();
cfa_off = getSLeb() * data_align;
break;
case DW_CFA_def_cfa_offset_sf:
cfa_off = getSLeb() * data_align;
break;
case DW_CFA_val_offset:
case DW_CFA_val_offset_sf:
skipLeb();
skipLeb();
break;
case DW_CFA_val_expression:
if (getLeb() == DW_REG_PC) {
int pc_off = parseExpression();
if (pc_off != 0) {
fp_off = DW_PC_OFFSET | (pc_off << 1);
}
} else {
_ptr += getLeb();
}
break;
#ifdef __aarch64__
case DW_CFA_AARCH64_negate_ra_state:
break;
#endif
case DW_CFA_GNU_args_size:
skipLeb();
break;
default:
Log::warn("Unknown DWARF instruction 0x%x in %s", op, _name);
return;
}
break;
case DW_CFA_advance_loc:
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
loc += (op & 0x3f) * code_align;
break;
case DW_CFA_offset:
switch (op & 0x3f) {
case DW_REG_FP: fp_off = getLeb() * data_align; break;
case DW_REG_PC: pc_off = getLeb() * data_align; break;
default: skipLeb();
}
break;
case DW_CFA_restore:
if ((op & 0x3f) == DW_REG_FP) {
fp_off = DW_SAME_FP;
}
break;
}
}
addRecord(loc, cfa_reg, cfa_off, fp_off, pc_off);
}
// Parse a limited subset of DWARF expressions, which is used in DW_CFA_val_expression
// to point to the previous PC relative to the current PC.
// Returns the offset of the previous PC from the current PC.
int DwarfParser::parseExpression() {
int pc_off = 0;
int tos = 0;
u32 len = getLeb();
const char* end = _ptr + len;
while (_ptr < end) {
u8 op = get8();
switch (op) {
case DW_OP_breg_pc:
pc_off = getSLeb();
break;
case DW_OP_const1u:
tos = get8();
break;
case DW_OP_const1s:
tos = (signed char)get8();
break;
case DW_OP_const2u:
tos = get16();
break;
case DW_OP_const2s:
tos = (short)get16();
break;
case DW_OP_const4u:
case DW_OP_const4s:
tos = get32();
break;
case DW_OP_constu:
tos = getLeb();
break;
case DW_OP_consts:
tos = getSLeb();
break;
case DW_OP_minus:
pc_off -= tos;
break;
case DW_OP_plus:
pc_off += tos;
break;
default:
Log::warn("Unknown DWARF opcode 0x%x in %s", op, _name);
_ptr = end;
return 0;
}
}
return pc_off;
}
void DwarfParser::addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off, int pc_off) {
int cfa = cfa_reg | cfa_off << 8;
if (_prev == NULL || (_prev->loc == loc && --_count >= 0) ||
_prev->cfa != cfa || _prev->fp_off != fp_off || _prev->pc_off != pc_off) {
_prev = addRecordRaw(loc, cfa, fp_off, pc_off);
}
}
FrameDesc* DwarfParser::addRecordRaw(u32 loc, int cfa, int fp_off, int pc_off) {
if (_count >= _capacity) {
_capacity *= 2;
_table = (FrameDesc*)realloc(_table, _capacity * sizeof(FrameDesc));
}
FrameDesc* f = &_table[_count++];
f->loc = loc;
f->cfa = cfa;
f->fp_off = fp_off;
f->pc_off = pc_off;
return f;
}

168
src/dwarf.h Normal file
View File

@@ -0,0 +1,168 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _DWARF_H
#define _DWARF_H
#include <stddef.h>
#include "arch.h"
const int DW_REG_PLT = 128; // denotes special rule for PLT entries
const int DW_REG_INVALID = 255; // denotes unsupported configuration
const int DW_PC_OFFSET = 1;
const int DW_SAME_FP = 0x80000000;
const int DW_STACK_SLOT = sizeof(void*);
#if defined(__x86_64__)
#define DWARF_SUPPORTED true
const int DW_REG_FP = 6;
const int DW_REG_SP = 7;
const int DW_REG_PC = 16;
const int EMPTY_FRAME_SIZE = DW_STACK_SLOT;
const int LINKED_FRAME_SIZE = 2 * DW_STACK_SLOT;
#elif defined(__i386__)
#define DWARF_SUPPORTED true
const int DW_REG_FP = 5;
const int DW_REG_SP = 4;
const int DW_REG_PC = 8;
const int EMPTY_FRAME_SIZE = DW_STACK_SLOT;
const int LINKED_FRAME_SIZE = 2 * DW_STACK_SLOT;
#elif defined(__aarch64__)
#define DWARF_SUPPORTED true
const int DW_REG_FP = 29;
const int DW_REG_SP = 31;
const int DW_REG_PC = 30;
const int EMPTY_FRAME_SIZE = 0;
const int LINKED_FRAME_SIZE = 0;
#else
#define DWARF_SUPPORTED false
const int DW_REG_FP = 0;
const int DW_REG_SP = 1;
const int DW_REG_PC = 2;
const int EMPTY_FRAME_SIZE = 0;
const int LINKED_FRAME_SIZE = 0;
#endif
struct FrameDesc {
u32 loc;
int cfa;
int fp_off;
int pc_off;
static FrameDesc empty_frame;
static FrameDesc default_frame;
static int comparator(const void* p1, const void* p2) {
FrameDesc* fd1 = (FrameDesc*)p1;
FrameDesc* fd2 = (FrameDesc*)p2;
return (int)(fd1->loc - fd2->loc);
}
};
class DwarfParser {
private:
const char* _name;
const char* _image_base;
const char* _ptr;
int _capacity;
int _count;
FrameDesc* _table;
FrameDesc* _prev;
u32 _code_align;
int _data_align;
const char* add(size_t size) {
const char* ptr = _ptr;
_ptr = ptr + size;
return ptr;
}
u8 get8() {
return *_ptr++;
}
u16 get16() {
return *(u16*)add(2);
}
u32 get32() {
return *(u32*)add(4);
}
u32 getLeb() {
u32 result = 0;
for (u32 shift = 0; ; shift += 7) {
u8 b = *_ptr++;
result |= (b & 0x7f) << shift;
if ((b & 0x80) == 0) {
return result;
}
}
}
int getSLeb() {
int result = 0;
for (u32 shift = 0; ; shift += 7) {
u8 b = *_ptr++;
result |= (b & 0x7f) << shift;
if ((b & 0x80) == 0) {
if ((b & 0x40) != 0 && (shift += 7) < 32) {
result |= -1 << shift;
}
return result;
}
}
}
void skipLeb() {
while (*_ptr++ & 0x80) {}
}
const char* getPtr() {
const char* ptr = _ptr;
return ptr + *(int*)add(4);
}
void parse(const char* eh_frame_hdr);
void parseCie();
void parseFde();
void parseInstructions(u32 loc, const char* end);
int parseExpression();
void addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off, int pc_off);
FrameDesc* addRecordRaw(u32 loc, int cfa, int fp_off, int pc_off);
public:
DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr);
FrameDesc* table() const {
return _table;
}
int count() const {
return _count;
}
};
#endif // _DWARF_H

View File

@@ -1,74 +1,20 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "engine.h"
#include "stackFrame.h"
volatile bool Engine::_enabled;
volatile bool Engine::_enabled = false;
Error Engine::check(Arguments& args) {
return Error::OK;
}
CStack Engine::cstack() {
return CSTACK_FP;
Error Engine::start(Arguments& args) {
return Error::OK;
}
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs) {
const void* pc;
uintptr_t fp;
uintptr_t prev_fp = (uintptr_t)&fp;
uintptr_t bottom = prev_fp + 0x100000;
if (ucontext == NULL) {
pc = __builtin_return_address(0);
fp = (uintptr_t)__builtin_frame_address(1);
} else {
StackFrame frame(ucontext);
pc = (const void*)frame.pc();
fp = frame.fp();
}
int depth = 0;
const void* const valid_pc = (const void* const)0x1000;
// Walk until the bottom of the stack or until the first Java frame
while (depth < max_depth && pc >= valid_pc) {
if (java_methods->contains(pc) || runtime_stubs->contains(pc)) {
break;
}
callchain[depth++] = pc;
// Check if the next frame is below on the current stack
if (fp <= prev_fp || fp >= prev_fp + 0x40000 || fp >= bottom) {
break;
}
// Frame pointer must be word aligned
if ((fp & (sizeof(uintptr_t) - 1)) != 0) {
break;
}
prev_fp = fp;
pc = ((const void**)fp)[1];
fp = ((uintptr_t*)fp)[0];
}
return depth;
void Engine::stop() {
}

View File

@@ -1,67 +1,54 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _ENGINE_H
#define _ENGINE_H
#include "arguments.h"
#include "codeCache.h"
class Engine {
protected:
static volatile bool _enabled;
static bool updateCounter(volatile unsigned long long& counter, unsigned long long value, unsigned long long interval) {
if (interval <= 1) {
return true;
}
while (true) {
unsigned long long prev = counter;
unsigned long long next = prev + value;
if (next < interval) {
if (__sync_bool_compare_and_swap(&counter, prev, next)) {
return false;
}
} else {
if (__sync_bool_compare_and_swap(&counter, prev, next % interval)) {
return true;
}
}
}
}
public:
virtual const char* name() = 0;
virtual const char* units() = 0;
virtual const char* title() {
return "Flame Graph";
}
virtual const char* units() {
return "total";
}
virtual Error check(Arguments& args);
virtual Error start(Arguments& args) = 0;
virtual void stop() = 0;
virtual CStack cstack();
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
CodeCache* java_methods, CodeCache* runtime_stubs);
virtual Error start(Arguments& args);
virtual void stop();
void enableEvents(bool enabled) {
_enabled = enabled;
}
};
class NoopEngine : public Engine {
public:
const char* name() {
return "noop";
}
const char* units() {
return "ns";
}
Error start(Arguments& args) {
return Error::OK;
}
void stop() {
}
CStack cstack() {
return CSTACK_NO;
}
};
#endif // _ENGINE_H

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _EVENT_H
@@ -21,35 +10,61 @@
#include "os.h"
// The order is important: look for event_type comparison
enum EventType {
PERF_SAMPLE,
EXECUTION_SAMPLE,
INSTRUMENTED_METHOD,
ALLOC_SAMPLE,
ALLOC_OUTSIDE_TLAB,
LIVE_OBJECT,
LOCK_SAMPLE,
PARK_SAMPLE,
PROFILING_WINDOW,
};
class Event {
};
class EventWithClassId : public Event {
public:
u32 id() {
return *(u32*)this;
}
u32 _class_id;
};
class ExecutionEvent : public Event {
public:
u64 _start_time;
ThreadState _thread_state;
ExecutionEvent() : _thread_state(THREAD_RUNNING) {
}
ExecutionEvent(u64 start_time) : _start_time(start_time), _thread_state(THREAD_UNKNOWN) {}
};
class AllocEvent : public Event {
class AllocEvent : public EventWithClassId {
public:
u32 _class_id;
u64 _start_time;
u64 _total_size;
u64 _instance_size;
};
class LockEvent : public Event {
class LockEvent : public EventWithClassId {
public:
u32 _class_id;
u64 _start_time;
u64 _end_time;
uintptr_t _address;
long long _timeout;
};
class LiveObject : public EventWithClassId {
public:
u64 _start_time;
u64 _alloc_size;
u64 _alloc_time;
};
class ProfilingWindow : public Event {
public:
u64 _start_time;
u64 _end_time;
};
#endif // _EVENT_H

68
src/fdtransfer.h Normal file
View File

@@ -0,0 +1,68 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _FDTRANSFER_H
#define _FDTRANSFER_H
#ifdef __linux__
#include <linux/perf_event.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#define RESTARTABLE(call) ({ ssize_t ret; while ((ret = call) < 0 && errno == EINTR); ret; })
// base header for all requests
enum request_type {
PERF_FD,
KALLSYMS_FD,
};
struct fd_request {
// of type "enum request_type"
unsigned int type;
};
struct perf_fd_request {
struct fd_request header;
int tid;
struct perf_event_attr attr;
};
struct fd_response {
// of type "enum request_type"
unsigned int type;
// 0 on success, otherwise errno
int error;
};
struct perf_fd_response {
struct fd_response header;
int tid;
};
static inline bool socketPath(const char *path, struct sockaddr_un *sun, socklen_t *addrlen) {
const int path_len = strlen(path);
if (path_len > sizeof(sun->sun_path)) {
return false;
}
memcpy(sun->sun_path, path, path_len);
if (sun->sun_path[0] == '@') {
sun->sun_path[0] = '\0';
}
sun->sun_family = AF_UNIX;
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - path_len);
return true;
}
#endif // __linux__
#endif // _FDTRANSFER_H

44
src/fdtransferClient.h Normal file
View File

@@ -0,0 +1,44 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _FDTRANSFER_CLIENT_H
#define _FDTRANSFER_CLIENT_H
#ifdef __linux__
#include "fdtransfer.h"
class FdTransferClient {
private:
static int _peer;
static int recvFd(unsigned int request_id, struct fd_response *resp, size_t resp_size);
public:
static bool connectToServer(const char *path);
static bool hasPeer() { return _peer != -1; }
static void closePeer() {
if (_peer != -1) {
close(_peer);
_peer = -1;
}
}
static int requestPerfFd(int *tid, struct perf_event_attr *attr);
static int requestKallsymsFd();
};
#else
class FdTransferClient {
public:
static bool connectToServer(const char *path) { return false; }
static bool hasPeer() { return false; }
static void closePeer() { }
};
#endif // __linux__
#endif // _FDTRANSFER_CLIENT_H

View File

@@ -0,0 +1,138 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __linux__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "fdtransferClient.h"
#include "log.h"
int FdTransferClient::_peer = -1;
bool FdTransferClient::connectToServer(const char *path) {
closePeer();
_peer = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (_peer == -1) {
Log::warn("FdTransferClient socket(): %s", strerror(errno));
return false;
}
struct sockaddr_un sun;
socklen_t addrlen;
if (!socketPath(path, &sun, &addrlen)) {
return false;
}
// Do not block for more than 10 seconds when waiting for a response
struct timeval tv = {10, 0};
setsockopt(_peer, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (connect(_peer, (const struct sockaddr *)&sun, addrlen) == -1) {
Log::warn("FdTransferClient connect(): %s", strerror(errno));
return false;
}
return true;
}
int FdTransferClient::requestPerfFd(int *tid, struct perf_event_attr *attr) {
struct perf_fd_request request;
request.header.type = PERF_FD;
request.tid = *tid;
memcpy(&request.attr, attr, sizeof(request.attr));
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
Log::warn("FdTransferClient send(): %s", strerror(errno));
return -1;
}
struct perf_fd_response resp;
int fd = recvFd(request.header.type, &resp.header, sizeof(resp));
if (fd == -1) {
// Update errno for our caller.
errno = resp.header.error;
} else {
// Update the TID of createForThread, in case the multiple threads' requests got mixed up and we're
// now handling the response destined to another. It's alright - the other thread(s) will finish the
// handling of our TID perf fd.
*tid = resp.tid;
}
return fd;
}
int FdTransferClient::requestKallsymsFd() {
struct fd_request request;
request.type = KALLSYMS_FD;
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
Log::warn("FdTransferClient send(): %s", strerror(errno));
return -1;
}
struct fd_response resp;
int fd = recvFd(request.type, &resp, sizeof(resp));
if (fd == -1) {
errno = resp.error;
}
return fd;
}
int FdTransferClient::recvFd(unsigned int type, struct fd_response *resp, size_t resp_size) {
struct msghdr msg = {0};
struct iovec iov[1];
iov[0].iov_base = resp;
iov[0].iov_len = resp_size;
msg.msg_iov = iov;
msg.msg_iovlen = ARRAY_SIZE(iov);
int newfd;
union {
char buf[CMSG_SPACE(sizeof(newfd))];
struct cmsghdr align;
} u;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
ssize_t ret = RESTARTABLE(recvmsg(_peer, &msg, 0));
if (ret < 0) {
Log::warn("FdTransferClient recvmsg(): %s", strerror(errno));
return -1;
}
if (resp->type != type) {
Log::warn("FdTransferClient recvmsg(): bad response type");
return -1;
}
if (resp->error == 0) {
struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg);
if (cmptr != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))
&& cmptr->cmsg_level == SOL_SOCKET && cmptr->cmsg_type == SCM_RIGHTS) {
newfd = *((int*)CMSG_DATA(cmptr));
} else {
Log::warn("FdTransferClient recvmsg(): unexpected response with no SCM_RIGHTS: %s", strerror(errno));
newfd = -1;
}
} else {
newfd = -1;
}
return newfd;
}
#endif // __linux__

View File

@@ -1,422 +1,39 @@
/*
* Copyright 2020 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <algorithm>
#include <vector>
#include <stdio.h>
#include <string.h>
#include "flameGraph.h"
#include "incbin.h"
static const char FLAMEGRAPH_HEADER[] =
"<!DOCTYPE html>\n"
"<html lang='en'>\n"
"<head>\n"
"<meta charset='utf-8'>\n"
"<style>\n"
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n"
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n"
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n"
"\tbutton {font: 12px sans-serif; cursor: pointer}\n"
"\tp {margin: 5px 0 5px 0}\n"
"\ta {color: #0366d6}\n"
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n"
"\t#hl span {padding: 0 3px 0 3px}\n"
"\t#status {overflow: hidden; white-space: nowrap}\n"
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n"
"\t#reset {cursor: pointer}\n"
"</style>\n"
"</head>\n"
"<body style='font: 12px Verdana, sans-serif'>\n"
"<h1>%s</h1>\n"
"<header style='text-align: left'><button id='reverse' title='Reverse'>&#x1f53b;</button>&nbsp;&nbsp;<button id='search' title='Search'>&#x1f50d;</button></header>\n"
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n"
"<canvas id='canvas' style='width: 100%%; height: %dpx'></canvas>\n"
"<div id='hl'><span></span></div>\n"
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>&#x274c;</span></p>\n"
"<p id='status'>&nbsp;</p>\n"
"<script>\n"
"\t// Copyright 2020 Andrei Pangin\n"
"\t// Licensed under the Apache License, Version 2.0.\n"
"\t'use strict';\n"
"\tvar root, rootLevel, px, pattern;\n"
"\tvar reverse = %s;\n"
"\tconst levels = Array(%d);\n"
"\tfor (let h = 0; h < levels.length; h++) {\n"
"\t\tlevels[h] = [];\n"
"\t}\n"
"\n"
"\tconst canvas = document.getElementById('canvas');\n"
"\tconst c = canvas.getContext('2d');\n"
"\tconst hl = document.getElementById('hl');\n"
"\tconst status = document.getElementById('status');\n"
"\n"
"\tconst canvasWidth = canvas.offsetWidth;\n"
"\tconst canvasHeight = canvas.offsetHeight;\n"
"\tcanvas.style.width = canvasWidth + 'px';\n"
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n"
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n"
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n"
"\tc.font = document.body.style.font;\n"
"\n"
"\tconst palette = [\n"
"\t\t[0x50e150, 30, 30, 30],\n"
"\t\t[0x50bebe, 30, 30, 30],\n"
"\t\t[0xe17d00, 30, 30, 0],\n"
"\t\t[0xc8c83c, 30, 30, 10],\n"
"\t\t[0xe15a5a, 30, 40, 40],\n"
"\t];\n"
"\n"
"\tfunction getColor(p) {\n"
"\t\tconst v = Math.random();\n"
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n"
"\t}\n"
"\n"
"\tfunction f(level, left, width, type, title) {\n"
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n"
"\t}\n"
"\n"
"\tfunction samples(n) {\n"
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n"
"\t}\n"
"\n"
"\tfunction pct(a, b) {\n"
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n"
"\t}\n"
"\n"
"\tfunction findFrame(frames, x) {\n"
"\t\tlet left = 0;\n"
"\t\tlet right = frames.length - 1;\n"
"\n"
"\t\twhile (left <= right) {\n"
"\t\t\tconst mid = (left + right) >>> 1;\n"
"\t\t\tconst f = frames[mid];\n"
"\n"
"\t\t\tif (f.left > x) {\n"
"\t\t\t\tright = mid - 1;\n"
"\t\t\t} else if (f.left + f.width <= x) {\n"
"\t\t\t\tleft = mid + 1;\n"
"\t\t\t} else {\n"
"\t\t\t\treturn f;\n"
"\t\t\t}\n"
"\t\t}\n"
"\n"
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n"
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n"
"\n"
"\t\treturn null;\n"
"\t}\n"
"\n"
"\tfunction search(r) {\n"
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n"
"\t\t\treturn;\n"
"\t\t}\n"
"\n"
"\t\tpattern = r ? RegExp(r) : undefined;\n"
"\t\tconst matched = render(root, rootLevel);\n"
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%%';\n"
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n"
"\t}\n"
"\n"
"\tfunction render(newRoot, newLevel) {\n"
"\t\tif (root) {\n"
"\t\t\tc.fillStyle = '#ffffff';\n"
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n"
"\t\t}\n"
"\n"
"\t\troot = newRoot || levels[0][0];\n"
"\t\trootLevel = newLevel || 0;\n"
"\t\tpx = canvasWidth / root.width;\n"
"\n"
"\t\tconst x0 = root.left;\n"
"\t\tconst x1 = x0 + root.width;\n"
"\t\tconst marked = [];\n"
"\n"
"\t\tfunction mark(f) {\n"
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n"
"\t\t}\n"
"\n"
"\t\tfunction totalMarked() {\n"
"\t\t\tlet total = 0;\n"
"\t\t\tlet left = 0;\n"
"\t\t\tfor (let x in marked) {\n"
"\t\t\t\tif (+x >= left) {\n"
"\t\t\t\t\ttotal += marked[x];\n"
"\t\t\t\t\tleft = +x + marked[x];\n"
"\t\t\t\t}\n"
"\t\t\t}\n"
"\t\t\treturn total;\n"
"\t\t}\n"
"\n"
"\t\tfunction drawFrame(f, y, alpha) {\n"
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n"
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n"
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
"\n"
"\t\t\t\tif (f.width * px >= 21) {\n"
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n"
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n"
"\t\t\t\t\tc.fillStyle = '#000000';\n"
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n"
"\t\t\t\t}\n"
"\n"
"\t\t\t\tif (alpha) {\n"
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n"
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
"\t\t\t\t}\n"
"\t\t\t}\n"
"\t\t}\n"
"\n"
"\t\tfor (let h = 0; h < levels.length; h++) {\n"
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n"
"\t\t\tconst frames = levels[h];\n"
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n"
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n"
"\t\t\t}\n"
"\t\t}\n"
"\n"
"\t\treturn totalMarked();\n"
"\t}\n"
"\n"
"\tcanvas.onmousemove = function() {\n"
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n"
"\t\tif (h >= 0 && h < levels.length) {\n"
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n"
"\t\t\tif (f) {\n"
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n"
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n"
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n"
"\t\t\t\thl.firstChild.textContent = f.title;\n"
"\t\t\t\thl.style.display = 'block';\n"
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%%)';\n"
"\t\t\t\tcanvas.style.cursor = 'pointer';\n"
"\t\t\t\tcanvas.onclick = function() {\n"
"\t\t\t\t\tif (f != root) {\n"
"\t\t\t\t\t\trender(f, h);\n"
"\t\t\t\t\t\tcanvas.onmousemove();\n"
"\t\t\t\t\t}\n"
"\t\t\t\t};\n"
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n"
"\t\t\t\treturn;\n"
"\t\t\t}\n"
"\t\t}\n"
"\t\tcanvas.onmouseout();\n"
"\t}\n"
"\n"
"\tcanvas.onmouseout = function() {\n"
"\t\thl.style.display = 'none';\n"
"\t\tstatus.textContent = '\\xa0';\n"
"\t\tcanvas.title = '';\n"
"\t\tcanvas.style.cursor = '';\n"
"\t\tcanvas.onclick = '';\n"
"\t}\n"
"\n"
"\tdocument.getElementById('reverse').onclick = function() {\n"
"\t\treverse = !reverse;\n"
"\t\trender();\n"
"\t}\n"
"\n"
"\tdocument.getElementById('search').onclick = function() {\n"
"\t\tsearch(true);\n"
"\t}\n"
"\n"
"\tdocument.getElementById('reset').onclick = function() {\n"
"\t\tsearch(false);\n"
"\t}\n"
"\n"
"\twindow.onkeydown = function() {\n"
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n"
"\t\t\tevent.preventDefault();\n"
"\t\t\tsearch(true);\n"
"\t\t} else if (event.keyCode === 27) {\n"
"\t\t\tsearch(false);\n"
"\t\t}\n"
"\t}\n";
// Browsers refuse to draw on canvas larger than 32767 px
const int MAX_CANVAS_HEIGHT = 32767;
static const char FLAMEGRAPH_FOOTER[] =
"render();\n"
"</script></body></html>\n";
static const char TREE_HEADER[] =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
"<title>Tree view</title>\n"
"<meta charset=\"utf-8\"/>\n"
"<style>\n"
"body {\n"
" font-family: Arial;\n"
"}\n"
"ul.tree li {\n"
" list-style-type: none;\n"
" position: relative;\n"
"}\n"
"ul.tree ul {\n"
" margin-left: 20px; padding-left: 0;\n"
"}\n"
"ul.tree li ul {\n"
" display: none;\n"
"}\n"
"ul.tree li.open > ul {\n"
" display: block;\n"
"}\n"
"ul.tree li div:before {\n"
" height: 1em;\n"
" padding:0 .1em;\n"
" font-size: .8em;\n"
" display: block;\n"
" position: absolute;\n"
" left: -1.3em;\n"
" top: .2em;\n"
"}\n"
"ul.tree li > div:not(:nth-last-child(2)):before {\n"
" content: '+';\n"
"}\n"
"ul.tree li.open > div:not(:nth-last-child(2)):before {\n"
" content: '-';\n"
"}\n"
".sc {\n"
" text-decoration: underline;\n"
" text-decoration-color: black;\n"
" font-weight: bold;\n"
" background-color: #D9D9D9;\n"
"}\n"
".t0 {\n"
" color: #32c832;\n"
"}\n"
".t1 {\n"
" color: #32a5a5;\n"
"}\n"
".t2 {\n"
" color: #be5a00;\n"
"}\n"
".t3 {\n"
" color: #afaf32;\n"
"}\n"
".t4 {\n"
" color: #c83232;\n"
"}\n"
"ul.tree li > div {\n"
" display: inline;\n"
" cursor: pointer;\n"
" color: black;\n"
" text-decoration: none;\n"
"}\n"
"</style>\n"
"<script>\n"
"function treeView(opt) {\n"
" var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
" for(var i = 0; i < tree.length; i++){\n"
" var parent = tree[i].parentElement;\n"
" var classList = parent.classList;\n"
" if(opt == 0) {\n"
" classList.add('open');\n"
" } else {\n"
" classList.remove('open');\n"
" }\n"
" }\n"
"}\n"
"function openParent(p,t) {\n"
" if(p.parentElement.classList.contains(\"tree\")) {\n"
" return;\n"
" }\n"
" p.parentElement.classList.add('open');\n"
" openParent(p.parentElement,t);\n"
"}\n"
"function search() {\n"
" var tree = document.querySelectorAll('ul.tree span');\n"
" var check = document.getElementById('check');\n"
" for(var i = 0; i < tree.length; i++){\n"
" tree[i].classList.remove('sc');\n"
" if(tree[i].innerHTML.includes(document.getElementById(\"search\").value)) {\n"
" tree[i].classList.add('sc');\n"
" openParent(tree[i].parentElement,tree);\n"
" }\n"
" }\n"
"}\n"
"function openUL(n) {\n"
" var children = n.children;\n"
" if(children.length == 1) {\n"
" openNode(children[0]);\n"
" }\n"
"}\n"
"function openNode(n) {\n"
" var children = n.children;\n"
" for(var i = 0; i < children.length; i++){\n"
" if(children[i].nodeName == 'UL') {\n"
" n.classList.add('open');\n"
" openUL(children[i]);\n"
" }\n"
" }\n"
"}\n"
"function addClickActions() {\n"
"var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
"for(var i = 0; i < tree.length; i++){\n"
" tree[i].addEventListener('click', function(e) {\n"
" var parent = e.target.parentElement;\n"
" var classList = parent.classList;\n"
" if(classList.contains(\"open\")) {\n"
" classList.remove('open');\n"
" var opensubs = parent.querySelectorAll(':scope .open');\n"
" for(var i = 0; i < opensubs.length; i++){\n"
" opensubs[i].classList.remove('open');\n"
" }\n"
" } else {\n"
" if(e.altKey) {\n"
" classList.add('open');\n"
" var opensubs = parent.querySelectorAll('li');\n"
" for(var i = 0; i < opensubs.length; i++){\n"
" opensubs[i].classList.add('open');\n"
" }\n"
" } else {\n"
" openNode(parent);\n"
" }\n"
" }\n"
" });\n"
"}\n"
"}\n"
"</script>\n"
"</head>\n"
"<body>\n"
"<div style=\"padding-left: 25px;\">%s view, total %s: %s </div>\n"
"<div style=\"padding-left: 25px;\"><button type='button' onclick='treeView(0)'>++</button><button type='button' onclick='treeView(1)'>--</button>\n"
"<input type='text' id='search' value='' size='35' onkeypress=\"if(event.keyCode == 13) document.getElementById('searchBtn').click()\">\n"
"<button type='button' id='searchBtn' onclick='search()'>search</button></div>\n"
"<ul class=\"tree\">\n";
static const char TREE_FOOTER[] =
"<script>\n"
"addClickActions();\n"
"</script>\n"
"</ul>\n"
"</body>\n"
"</html>\n";
INCBIN(FLAMEGRAPH_TEMPLATE, "src/res/flame.html")
INCBIN(TREE_TEMPLATE, "src/res/tree.html")
class StringUtils {
public:
static bool endsWith(const std::string& s, const char* suffix, size_t suffixlen) {
size_t len = s.length();
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
static void replace(std::string& s, char c, const char* replacement, size_t rlen) {
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i += rlen) {
s.replace(i, 1, replacement, rlen);
}
}
static void replace(std::string& s, char c, const char* replacement) {
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
s.replace(i, 1, replacement);
static size_t getCommonPrefix(const std::string& a, const std::string& b) {
size_t length = a.size() < b.size() ? a.size() : b.size();
for (size_t i = 0; i < length; i++) {
if (a[i] != b[i] || a[i] > 127) {
return i;
}
}
return length;
}
};
@@ -450,89 +67,186 @@ class Format {
class Node {
public:
std::string _name;
u32 _key;
u32 _order;
const Trie* _trie;
Node(const std::string& name, const Trie& trie) : _name(name), _trie(&trie) {
Node(u32 key, u32 order, const Trie& trie) : _key(key), _order(order), _trie(&trie) {
}
bool operator<(const Node& other) const {
return _trie->_total > other._trie->_total;
static bool orderByName(const Node& a, const Node& b) {
return a._order < b._order;
}
static bool orderByTotal(const Node& a, const Node& b) {
return a._trie->_total > b._trie->_total;
}
};
void FlameGraph::dump(std::ostream& out, bool tree) {
Trie* FlameGraph::addChild(Trie* f, const char* name, FrameTypeId type, u64 value) {
size_t len = strlen(name);
bool has_suffix = len > 4 && name[len - 4] == '_' && name[len - 3] == '[' && name[len - 1] == ']';
std::string s(name, has_suffix ? len - 4 : len);
u32 name_index = _cpool[s];
if (name_index == 0) {
name_index = _cpool[s] = _cpool.size();
}
f->_total += value;
switch (type) {
case FRAME_INLINED:
(f = f->child(name_index, FRAME_JIT_COMPILED))->_inlined += value;
return f;
case FRAME_C1_COMPILED:
(f = f->child(name_index, FRAME_JIT_COMPILED))->_c1_compiled += value;
return f;
case FRAME_INTERPRETED:
(f = f->child(name_index, FRAME_JIT_COMPILED))->_interpreted += value;
return f;
default:
return f->child(name_index, type);
}
}
void FlameGraph::dump(Writer& out, bool tree) {
_name_order = new u32[_cpool.size() + 1]();
_mintotal = _minwidth == 0 && tree ? _root._total / 1000 : (u64)(_root._total * _minwidth / 100);
int depth = _root.depth(_mintotal);
int depth = _root.depth(_mintotal, _name_order);
if (tree) {
char buf[sizeof(TREE_HEADER) + 256];
snprintf(buf, sizeof(buf) - 1, TREE_HEADER,
_reverse ? "Backtrace" : "Call tree",
_counter == COUNTER_SAMPLES ? "samples" : "counter",
Format().thousands(_root._total));
out << buf;
const char* tail = TREE_TEMPLATE;
printTreeFrame(out, _root, 0);
tail = printTill(out, tail, "/*title:*/");
out << (_reverse ? "Backtrace" : "Call tree");
out << TREE_FOOTER;
tail = printTill(out, tail, "/*type:*/");
out << (_counter == COUNTER_SAMPLES ? "samples" : "counter");
tail = printTill(out, tail, "/*count:*/");
out << Format().thousands(_root._total);
tail = printTill(out, tail, "/*tree:*/");
const char** names = new const char*[_cpool.size() + 1];
for (std::map<std::string, u32>::const_iterator it = _cpool.begin(); it != _cpool.end(); ++it) {
names[it->second] = it->first.c_str();
}
printTreeFrame(out, _root, 0, names);
delete[] names;
out << tail;
} else {
char buf[sizeof(FLAMEGRAPH_HEADER) + 256];
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title, depth * 16, _reverse ? "true" : "false", depth);
out << buf;
const char* tail = FLAMEGRAPH_TEMPLATE;
printFrame(out, "all", _root, 0, 0);
tail = printTill(out, tail, "/*height:*/300");
out << std::min(depth * 16, MAX_CANVAS_HEIGHT);
out << FLAMEGRAPH_FOOTER;
tail = printTill(out, tail, "/*title:*/");
out << _title;
tail = printTill(out, tail, "/*reverse:*/false");
out << (_reverse ? "true" : "false");
tail = printTill(out, tail, "/*depth:*/0");
out << depth;
tail = printTill(out, tail, "/*cpool:*/");
printCpool(out);
tail = printTill(out, tail, "/*frames:*/");
printFrame(out, FRAME_NATIVE << 28, _root, 0, 0);
tail = printTill(out, tail, "/*highlight:*/");
out << tail;
}
delete[] _name_order;
}
void FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x) {
std::string name_copy = name;
int type = frameType(name_copy);
StringUtils::replace(name_copy, '\'', "\\'");
void FlameGraph::printFrame(Writer& out, u32 key, const Trie& f, int level, u64 x) {
u32 name_and_type = _name_order[f.nameIndex(key)] << 3 | f.type(key);
bool has_extra_types = (f._inlined | f._c1_compiled | f._interpreted) &&
f._inlined < f._total && f._interpreted < f._total;
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n", level, x, f._total, type, name_copy.c_str());
char* p = _buf;
if (level == _last_level + 1 && x == _last_x) {
p += snprintf(p, 100, "u(%u", name_and_type);
} else if (level == _last_level && x == _last_x + _last_total) {
p += snprintf(p, 100, "n(%u", name_and_type);
} else {
p += snprintf(p, 100, "f(%u,%d,%llu", name_and_type, level, x - _last_x);
}
if (f._total != _last_total || has_extra_types) {
p += snprintf(p, 100, ",%llu", f._total);
if (has_extra_types) {
p += snprintf(p, 100, ",%llu,%llu,%llu", f._inlined, f._c1_compiled, f._interpreted);
}
}
strcpy(p, ")\n");
out << _buf;
_last_level = level;
_last_x = x;
_last_total = f._total;
if (f._children.empty()) {
return;
}
std::vector<Node> children;
children.reserve(f._children.size());
for (std::map<u32, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
children.push_back(Node(it->first, _name_order[f.nameIndex(it->first)], it->second));
}
std::sort(children.begin(), children.end(), Node::orderByName);
x += f._self;
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
if (it->second._total >= _mintotal) {
printFrame(out, it->first, it->second, level + 1, x);
for (size_t i = 0; i < children.size(); i++) {
u32 key = children[i]._key;
const Trie* trie = children[i]._trie;
if (trie->_total >= _mintotal) {
printFrame(out, key, *trie, level + 1, x);
}
x += it->second._total;
x += trie->_total;
}
}
void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
std::vector<Node> subnodes;
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
subnodes.push_back(Node(it->first, it->second));
void FlameGraph::printTreeFrame(Writer& out, const Trie& f, int level, const char** names) {
std::vector<Node> children;
children.reserve(f._children.size());
for (std::map<u32, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
children.push_back(Node(it->first, 0, it->second));
}
std::sort(subnodes.begin(), subnodes.end());
std::sort(children.begin(), children.end(), Node::orderByTotal);
double pct = 100.0 / _root._total;
for (size_t i = 0; i < subnodes.size(); i++) {
std::string name = subnodes[i]._name;
const Trie* trie = subnodes[i]._trie;
for (size_t i = 0; i < children.size(); i++) {
u32 key = children[i]._key;
const Trie* trie = children[i]._trie;
int type = frameType(name);
StringUtils::replace(name, '&', "&amp;");
StringUtils::replace(name, '<', "&lt;");
StringUtils::replace(name, '>', "&gt;");
u32 type = trie->type(key);
std::string name = names[trie->nameIndex(key)];
StringUtils::replace(name, '&', "&amp;", 5);
StringUtils::replace(name, '<', "&lt;", 4);
StringUtils::replace(name, '>', "&gt;", 4);
const char* div_class = trie->_children.empty() ? " class=\"o\"" : "";
if (_reverse) {
snprintf(_buf, sizeof(_buf) - 1,
"<li><div>[%d] %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
level,
trie->_total * pct, Format().thousands(trie->_total),
"<li><div%s>%.2f%% [%s]</div> <span class=\"t%d\">%s</span>\n",
div_class, trie->_total * pct, Format().thousands(trie->_total),
type, name.c_str());
} else {
snprintf(_buf, sizeof(_buf) - 1,
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
level,
trie->_total * pct, Format().thousands(trie->_total),
"<li><div%s>%.2f%% [%s] &#8226; self: %.2f%% [%s]</div> <span class=\"t%d\">%s</span>\n",
div_class, trie->_total * pct, Format().thousands(trie->_total),
trie->_self * pct, Format().thousands(trie->_self),
type, name.c_str());
}
@@ -541,7 +255,7 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
if (trie->_children.size() > 0) {
out << "<ul>\n";
if (trie->_total >= _mintotal) {
printTreeFrame(out, *trie, level + 1);
printTreeFrame(out, *trie, level + 1, names);
} else {
out << "<li>...\n";
}
@@ -550,27 +264,36 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
}
}
int FlameGraph::frameType(std::string& name) {
if (StringUtils::endsWith(name, "_[j]", 4)) {
// Java compiled frame
name = name.substr(0, name.length() - 4);
return 0;
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
// Java inlined frame
name = name.substr(0, name.length() - 4);
return 1;
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
// Kernel function
name = name.substr(0, name.length() - 4);
return 2;
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
// C++ function or Objective C method
return 3;
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
// Java regular method
return 0;
} else {
// Other native code
return 4;
void FlameGraph::printCpool(Writer& out) {
out << "'all'";
std::string prev;
u32 index = 0;
for (std::map<std::string, u32>::const_iterator it = _cpool.begin(); it != _cpool.end(); ++it) {
if (_name_order[it->second]) {
_name_order[it->second] = ++index;
size_t prefix_len = StringUtils::getCommonPrefix(prev, it->first);
prev = it->first;
if (prefix_len > 95) prefix_len = 95;
std::string s(1, (char)(prefix_len + ' '));
s.append(it->first, prefix_len, std::string::npos);
StringUtils::replace(s, '\\', "\\\\", 2);
StringUtils::replace(s, '\'', "\\'", 2);
out << ",\n'";
out.write(s.data(), s.size());
out << "'";
}
}
// Release cpool memory, since frame names are never used beyond this point
_cpool = std::map<std::string, u32>();
}
const char* FlameGraph::printTill(Writer& out, const char* data, const char* till) {
const char* pos = strstr(data, till);
out.write(data, pos - data);
return pos + strlen(till);
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _FLAMEGRAPH_H
@@ -19,39 +8,50 @@
#include <map>
#include <string>
#include <iostream>
#include "arch.h"
#include "arguments.h"
#include "vmEntry.h"
#include "writer.h"
class Trie {
public:
std::map<std::string, Trie> _children;
std::map<u32, Trie> _children;
u64 _total;
u64 _self;
u64 _inlined, _c1_compiled, _interpreted;
Trie() : _children(), _total(0), _self(0) {
}
Trie* addChild(const std::string& key, u64 value) {
_total += value;
return &_children[key];
Trie() : _children(), _total(0), _self(0), _inlined(0), _c1_compiled(0), _interpreted(0) {
}
void addLeaf(u64 value) {
_total += value;
_self += value;
}
int depth(u64 cutoff) const {
if (_total < cutoff) {
return 0;
FrameTypeId type(u32 key) const {
if (_inlined * 3 >= _total) {
return FRAME_INLINED;
} else if (_c1_compiled * 2 >= _total) {
return FRAME_C1_COMPILED;
} else if (_interpreted * 2 >= _total) {
return FRAME_INTERPRETED;
} else {
return (FrameTypeId)(key >> 28);
}
}
u32 nameIndex(u32 key) const {
return key & ((1 << 28) - 1);
}
Trie* child(u32 name_index, FrameTypeId type) {
return &_children[name_index | type << 28];
}
int depth(u64 cutoff, u32* name_order) const {
int max_depth = 0;
for (std::map<std::string, Trie>::const_iterator it = _children.begin(); it != _children.end(); ++it) {
int d = it->second.depth(cutoff);
if (d > max_depth) max_depth = d;
for (std::map<u32, Trie>::const_iterator it = _children.begin(); it != _children.end(); ++it) {
if (it->second._total >= cutoff) {
name_order[nameIndex(it->first)] = 1;
int d = it->second.depth(cutoff, name_order);
if (d > max_depth) max_depth = d;
}
}
return max_depth + 1;
}
@@ -61,25 +61,36 @@ class Trie {
class FlameGraph {
private:
Trie _root;
char _buf[4096];
std::map<std::string, u32> _cpool;
u32* _name_order;
u64 _mintotal;
char _buf[4096];
const char* _title;
Counter _counter;
double _minwidth;
bool _reverse;
void printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x);
void printTreeFrame(std::ostream& out, const Trie& f, int level);
int frameType(std::string& name);
int _last_level;
u64 _last_x;
u64 _last_total;
void printFrame(Writer& out, u32 key, const Trie& f, int level, u64 x);
void printTreeFrame(Writer& out, const Trie& f, int level, const char** names);
void printCpool(Writer& out);
const char* printTill(Writer& out, const char* data, const char* till);
public:
FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) :
_root(),
_cpool(),
_title(title),
_counter(counter),
_minwidth(minwidth),
_reverse(reverse) {
_reverse(reverse),
_last_level(0),
_last_x(0),
_last_total(0) {
_buf[sizeof(_buf) - 1] = 0;
}
@@ -87,7 +98,9 @@ class FlameGraph {
return &_root;
}
void dump(std::ostream& out, bool tree);
Trie* addChild(Trie* f, const char* name, FrameTypeId type, u64 value);
void dump(Writer& out, bool tree);
};
#endif // _FLAMEGRAPH_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2018 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _FLIGHTRECORDER_H
@@ -20,6 +9,7 @@
#include "arch.h"
#include "arguments.h"
#include "event.h"
#include "log.h"
class Recording;
@@ -27,19 +17,27 @@ class FlightRecorder {
private:
Recording* _rec;
Error startMasterRecording(Arguments& args, const char* filename);
void stopMasterRecording();
public:
FlightRecorder() : _rec(NULL) {
}
Error start(Arguments& args, bool reset);
void stop();
void flush();
size_t usedMemory();
bool timerTick(u64 wall_time, u32 gc_id);
bool active() {
bool active() const {
return _rec != NULL;
}
void recordEvent(int lock_index, int tid, u32 call_trace_id,
int event_type, Event* event, u64 counter);
EventType event_type, Event* event);
void recordLog(LogLevel level, const char* message, size_t len);
};
#endif // _FLIGHTRECORDER_H

View File

@@ -1,28 +1,22 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <cxxabi.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "demangle.h"
#include "frameName.h"
#include "profiler.h"
#include "vmStructs.h"
static inline bool isDigit(char c) {
return c >= '0' && c <= '9';
}
Matcher::Matcher(const char* pattern) {
if (pattern[0] == '*') {
_type = MATCH_ENDS_WITH;
@@ -75,26 +69,43 @@ bool Matcher::matches(const char* s) {
}
FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names) :
_cache(),
JMethodCache FrameName::_cache;
FrameName::FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names) :
_class_names(),
_include(),
_exclude(),
_str(),
_style(style),
_cache_epoch((unsigned char)epoch),
_cache_max_age(args._mcache),
_thread_names_lock(thread_names_lock),
_thread_names(thread_names)
_thread_names(thread_names),
_jni(VM::jni())
{
// Require printf to use standard C format regardless of system locale
_saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0));
memset(_buf, 0, sizeof(_buf));
buildFilter(_include, args._buf, args._include);
buildFilter(_exclude, args._buf, args._exclude);
Profiler::_instance.classMap()->collect(_class_names);
Profiler::instance()->classMap()->collect(_class_names);
}
FrameName::~FrameName() {
if (_cache_max_age == 0) {
_cache.clear();
} else {
// Remove stale methods from the cache, leave the fresh ones for the next profiling session
for (JMethodCache::iterator it = _cache.begin(); it != _cache.end(); ) {
if (_cache_epoch - (unsigned char)it->second[0] >= _cache_max_age) {
_cache.erase(it++);
} else {
++it;
}
}
}
freelocale(uselocale(_saved_locale));
}
@@ -105,32 +116,56 @@ void FrameName::buildFilter(std::vector<Matcher>& vector, const char* base, int
}
}
char* FrameName::truncate(char* name, int max_length) {
if (strlen(name) > max_length && max_length >= 4) {
strcpy(name + max_length - 4, "...)");
}
return name;
}
const char* FrameName::decodeNativeSymbol(const char* name) {
const char* lib_name = (_style & STYLE_LIB_NAMES) ? Profiler::instance()->getLibraryName(name) : NULL;
const char* FrameName::cppDemangle(const char* name) {
if (name != NULL && name[0] == '_' && name[1] == 'Z') {
int status;
char* demangled = abi::__cxa_demangle(name, NULL, NULL, &status);
if (name[0] == '_' && name[1] == 'Z') {
char* demangled = Demangle::demangle(name, _style & STYLE_SIGNATURES);
if (demangled != NULL) {
strncpy(_buf, demangled, sizeof(_buf) - 1);
if (lib_name != NULL) {
_str.assign(lib_name).append("`").append(demangled);
} else {
_str.assign(demangled);
}
free(demangled);
return _buf;
return _str.c_str();
}
}
return name;
if (lib_name != NULL) {
return _str.assign(lib_name).append("`").append(name).c_str();
} else {
return name;
}
}
char* FrameName::javaMethodName(jmethodID method) {
const char* FrameName::typeSuffix(FrameTypeId type) {
if (_style & STYLE_ANNOTATE) {
switch (type) {
case FRAME_INTERPRETED: return "_[0]";
case FRAME_JIT_COMPILED: return "_[j]";
case FRAME_INLINED: return "_[i]";
case FRAME_C1_COMPILED: return "_[1]";
default: return NULL;
}
}
return NULL;
}
void FrameName::javaMethodName(jmethodID method) {
if (VMStructs::hasMethodStructs()) {
// Workaround for JDK-8313816
VMMethod* vm_method = VMMethod::fromMethodID(method);
if (vm_method == NULL || vm_method->id() == NULL) {
_str.assign("[stale_jmethodID]");
return;
}
}
jclass method_class;
char* class_name = NULL;
char* method_name = NULL;
char* method_sig = NULL;
char* result;
jvmtiEnv* jvmti = VM::jvmti();
jvmtiError err;
@@ -139,26 +174,31 @@ char* FrameName::javaMethodName(jmethodID method) {
(err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
(err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
// Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;'
result = javaClassName(class_name + 1, strlen(class_name) - 2, _style);
strcat(result, ".");
strcat(result, method_name);
if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
javaClassName(class_name + 1, strlen(class_name) - 2, _style);
_str.append(".").append(method_name);
if (_style & STYLE_SIGNATURES) {
if (_style & STYLE_NO_SEMICOLON) {
for (char* s = method_sig; *s; s++) {
if (*s == ';') *s = '|';
}
}
_str.append(method_sig);
}
} else {
snprintf(_buf, sizeof(_buf) - 1, "[jvmtiError %d]", err);
result = _buf;
char buf[32];
snprintf(buf, sizeof(buf), "[jvmtiError %d]", err);
_str.assign(buf);
}
if (method_class) {
_jni->DeleteLocalRef(method_class);
}
jvmti->Deallocate((unsigned char*)class_name);
jvmti->Deallocate((unsigned char*)method_sig);
jvmti->Deallocate((unsigned char*)method_name);
return result;
}
char* FrameName::javaClassName(const char* symbol, int length, int style) {
char* result = _buf;
void FrameName::javaClassName(const char* symbol, size_t length, int style) {
int array_dimension = 0;
while (*symbol == '[') {
array_dimension++;
@@ -166,42 +206,52 @@ char* FrameName::javaClassName(const char* symbol, int length, int style) {
}
if (array_dimension == 0) {
strncpy(result, symbol, length);
result[length] = 0;
_str.assign(symbol, length);
} else {
switch (*symbol) {
case 'B': strcpy(result, "byte"); break;
case 'C': strcpy(result, "char"); break;
case 'I': strcpy(result, "int"); break;
case 'J': strcpy(result, "long"); break;
case 'S': strcpy(result, "short"); break;
case 'Z': strcpy(result, "boolean"); break;
case 'F': strcpy(result, "float"); break;
case 'D': strcpy(result, "double"); break;
default:
length -= array_dimension + 2;
strncpy(result, symbol + 1, length);
result[length] = 0;
case 'B': _str.assign("byte"); break;
case 'C': _str.assign("char"); break;
case 'I': _str.assign("int"); break;
case 'J': _str.assign("long"); break;
case 'S': _str.assign("short"); break;
case 'Z': _str.assign("boolean"); break;
case 'F': _str.assign("float"); break;
case 'D': _str.assign("double"); break;
default: _str.assign(symbol + 1, length - array_dimension - 2);
}
do {
strcat(result, "[]");
_str += "[]";
} while (--array_dimension > 0);
}
if (style & STYLE_SIMPLE) {
for (char* s = result; *s; s++) {
if (*s == '/') result = s + 1;
if (style & STYLE_NORMALIZE) {
size_t size = _str.size();
for (ssize_t i = size - 2; i > 0; i--) {
if (_str[i] == '/' || _str[i] == '.') {
if (isDigit(_str[i + 1])) {
_str.resize(i);
}
break;
}
}
}
if (style & STYLE_SIMPLE) {
size_t start = 0;
size_t size = _str.size();
for (size_t i = 0; i < size; i++) {
if (_str[i] == '/' && !isDigit(_str[i + 1])) start = i + 1;
}
_str.erase(0, start);
}
if (style & STYLE_DOTTED) {
for (char* s = result; *s; s++) {
if (*s == '/') *s = '.';
size_t size = _str.size();
for (size_t i = 0; i < size; i++) {
if (_str[i] == '/' && !isDigit(_str[i + 1])) _str[i] = '.';
}
}
return result;
}
const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
@@ -211,18 +261,18 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
switch (frame.bci) {
case BCI_NATIVE_FRAME:
return cppDemangle((const char*)frame.method_id);
return decodeNativeSymbol((const char*)frame.method_id);
case BCI_ALLOC:
case BCI_ALLOC_OUTSIDE_TLAB:
case BCI_LOCK:
case BCI_PARK: {
const char* symbol = _class_names[(uintptr_t)frame.method_id];
char* class_name = javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
if (!for_matching && !(_style & STYLE_DOTTED)) {
strcat(class_name, frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]");
_str += frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]";
}
return class_name;
return _str.c_str();
}
case BCI_THREAD_ID: {
@@ -231,32 +281,78 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
ThreadMap::iterator it = _thread_names.find(tid);
if (for_matching) {
return it != _thread_names.end() ? it->second.c_str() : "";
} else if (it != _thread_names.end()) {
snprintf(_buf, sizeof(_buf) - 1, "[%s tid=%d]", it->second.c_str(), tid);
} else {
snprintf(_buf, sizeof(_buf) - 1, "[tid=%d]", tid);
}
return _buf;
char buf[32];
snprintf(buf, sizeof(buf), "tid=%d]", tid);
if (it != _thread_names.end()) {
return _str.assign("[").append(it->second).append(" ").append(buf).c_str();
} else {
return _str.assign("[").append(buf).c_str();
}
}
case BCI_ERROR: {
snprintf(_buf, sizeof(_buf) - 1, "[%s]", (const char*)frame.method_id);
return _buf;
}
case BCI_ERROR:
return _str.assign("[").append((const char*)frame.method_id).append("]").c_str();
default: {
const char* type_suffix = typeSuffix(FrameType::decode(frame.bci));
JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
if (it != _cache.end() && it->first == frame.method_id) {
return it->second.c_str();
it->second[0] = _cache_epoch;
const char* name = it->second.c_str() + 1;
if (type_suffix != NULL) {
return _str.assign(name).append(type_suffix).c_str();
}
return name;
}
const char* newName = javaMethodName(frame.method_id);
_cache.insert(it, JMethodCache::value_type(frame.method_id, newName));
return newName;
javaMethodName(frame.method_id);
_cache.insert(it, JMethodCache::value_type(frame.method_id, std::string(1, _cache_epoch) + _str));
if (type_suffix != NULL) {
_str += type_suffix;
}
return _str.c_str();
}
}
}
FrameTypeId FrameName::type(ASGCT_CallFrame& frame) {
if (frame.method_id == NULL) {
return FRAME_NATIVE;
}
switch (frame.bci) {
case BCI_NATIVE_FRAME: {
const char* name = (const char*)frame.method_id;
if ((name[0] == '_' && name[1] == 'Z') ||
(name[0] == '+' && name[1] == '[') ||
(name[0] == '-' && name[1] == '[')) {
return FRAME_CPP;
} else {
size_t len = strlen(name);
return len > 4 && strcmp(name + len - 4, "_[k]") == 0 ? FRAME_KERNEL : FRAME_NATIVE;
}
}
case BCI_ALLOC:
case BCI_LOCK:
case BCI_PARK:
return FRAME_INLINED;
case BCI_ALLOC_OUTSIDE_TLAB:
return FRAME_KERNEL;
case BCI_THREAD_ID:
case BCI_ERROR:
return FRAME_NATIVE;
default:
return FrameType::decode(frame.bci);
}
}
bool FrameName::include(const char* frame_name) {
for (int i = 0; i < _include.size(); i++) {
if (_include[i].matches(frame_name)) {
@@ -274,4 +370,3 @@ bool FrameName::exclude(const char* frame_name) {
}
return false;
}

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _FRAMENAME_H
@@ -63,27 +52,32 @@ class Matcher {
class FrameName {
private:
JMethodCache _cache;
static JMethodCache _cache;
JNIEnv* _jni;
ClassMap _class_names;
std::vector<Matcher> _include;
std::vector<Matcher> _exclude;
char _buf[800]; // must be large enough for class name + method name + method signature
std::string _str;
int _style;
unsigned char _cache_epoch;
unsigned char _cache_max_age;
Mutex& _thread_names_lock;
ThreadMap& _thread_names;
locale_t _saved_locale;
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
char* truncate(char* name, int max_length);
const char* cppDemangle(const char* name);
char* javaMethodName(jmethodID method);
char* javaClassName(const char* symbol, int length, int style);
const char* decodeNativeSymbol(const char* name);
const char* typeSuffix(FrameTypeId type);
void javaMethodName(jmethodID method);
void javaClassName(const char* symbol, size_t length, int style);
public:
FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names);
FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names);
~FrameName();
const char* name(ASGCT_CallFrame& frame, bool for_matching = false);
FrameTypeId type(ASGCT_CallFrame& frame);
bool hasIncludeList() { return !_include.empty(); }
bool hasExcludeList() { return !_exclude.empty(); }

Binary file not shown.

View File

@@ -0,0 +1,17 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;
/**
* Instrumentation helper for Java method profiling.
*/
public class Instrument {
private Instrument() {
}
public static native void recordSample();
}

Binary file not shown.

View File

@@ -0,0 +1,115 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;
import jdk.jfr.Configuration;
import jdk.jfr.FlightRecorder;
import jdk.jfr.FlightRecorderListener;
import jdk.jfr.Recording;
import jdk.jfr.RecordingState;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.StringTokenizer;
/**
* Synchronize async-profiler recording with an existing JFR recording.
*/
class JfrSync implements FlightRecorderListener {
private static volatile Recording masterRecording;
private JfrSync() {
}
static {
FlightRecorder.addListener(new JfrSync());
}
@Override
public void recordingStateChanged(Recording recording) {
if (recording == masterRecording && recording.getState() == RecordingState.STOPPED) {
masterRecording = null;
stopProfiler();
}
}
public static void start(String fileName, String settings, int eventMask) throws IOException, ParseException {
Recording recording;
if (settings.startsWith("+")) {
recording = new Recording();
for (StringTokenizer st = new StringTokenizer(settings, "+"); st.hasMoreTokens(); ) {
recording.enable(st.nextToken());
}
} else {
try {
recording = new Recording(Configuration.getConfiguration(settings));
} catch (NoSuchFileException e) {
recording = new Recording(Configuration.create(Paths.get(settings)));
}
disableBuiltinEvents(recording, eventMask);
}
masterRecording = recording;
recording.setDestination(Paths.get(fileName));
recording.setToDisk(true);
recording.setDumpOnExit(true);
recording.start();
}
public static void stop() {
Recording recording = masterRecording;
if (recording != null) {
// Disable state change notification before stopping
masterRecording = null;
recording.stop();
}
}
private static void disableBuiltinEvents(Recording recording, int eventMask) {
if ((eventMask & 1) != 0) {
recording.disable("jdk.ExecutionSample");
recording.disable("jdk.NativeMethodSample");
}
if ((eventMask & 2) != 0) {
recording.disable("jdk.ObjectAllocationInNewTLAB");
recording.disable("jdk.ObjectAllocationOutsideTLAB");
recording.disable("jdk.ObjectAllocationSample");
}
if ((eventMask & 4) != 0) {
recording.disable("jdk.JavaMonitorEnter");
recording.disable("jdk.ThreadPark");
}
// Shifted JfrOption values
if ((eventMask & 0x10) != 0) {
recording.disable("jdk.OSInformation");
recording.disable("jdk.CPUInformation");
recording.disable("jdk.JVMInformation");
}
if ((eventMask & 0x20) != 0) {
recording.disable("jdk.InitialSystemProperty");
}
if ((eventMask & 0x40) != 0) {
recording.disable("jdk.NativeLibrary");
}
if ((eventMask & 0x80) != 0) {
recording.disable("jdk.CPULoad");
}
if ((eventMask & 0x100) != 0) {
recording.disable("jdk.GCHeapSummary");
}
}
private static native void stopProfiler();
// JNI helper
static Integer box(int n) {
return n;
}
}

Binary file not shown.

View File

@@ -0,0 +1,102 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
package one.profiler;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
class Server extends Thread implements Executor, HttpHandler {
private static final String[] COMMANDS = "start,resume,stop,dump,check,status,meminfo,list,version".split(",");
private final HttpServer server;
private final AtomicInteger threadNum = new AtomicInteger();
private Server(String address) throws IOException {
super("Async-profiler Server");
setDaemon(true);
int p = address.lastIndexOf(':');
InetSocketAddress socketAddress = p >= 0
? new InetSocketAddress(address.substring(0, p), Integer.parseInt(address.substring(p + 1)))
: new InetSocketAddress(Integer.parseInt(address));
server = HttpServer.create(socketAddress, 0);
server.createContext("/", this);
server.setExecutor(this);
}
public static void start(String address) throws IOException {
new Server(address).start();
}
@Override
public void run() {
server.start();
}
@Override
public void execute(Runnable command) {
Thread t = new Thread(command, "Async-profiler Request #" + threadNum.incrementAndGet());
t.setDaemon(false);
t.start();
}
@Override
public void handle(HttpExchange exchange) throws IOException {
try {
String command = getCommand(exchange.getRequestURI());
if (command == null) {
sendResponse(exchange, 404, "Unknown command");
} else if (command.isEmpty()) {
sendResponse(exchange, 200, "Async-profiler server");
} else {
String response = execute0(command);
sendResponse(exchange, 200, response);
}
} catch (IllegalArgumentException e) {
sendResponse(exchange, 400, e.getMessage());
} catch (Exception e) {
sendResponse(exchange, 500, e.getMessage());
} finally {
exchange.close();
}
}
private String getCommand(URI uri) {
String path = uri.getPath();
if (path.startsWith("/")) {
if ((path = path.substring(1)).isEmpty()) {
return "";
}
for (String command : COMMANDS) {
if (path.startsWith(command)) {
String query = uri.getQuery();
return query == null ? path : path + ',' + query.replace('&', ',');
}
}
}
return null;
}
private void sendResponse(HttpExchange exchange, int code, String body) throws IOException {
String contentType = body.startsWith("<!DOCTYPE html>") ? "text/html; charset=utf-8" : "text/plain";
exchange.getResponseHeaders().add("Content-Type", contentType);
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(code, bodyBytes.length);
exchange.getResponseBody().write(bodyBytes);
}
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
}

182
src/hooks.cpp Normal file
View File

@@ -0,0 +1,182 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <dlfcn.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include "hooks.h"
#include "asprof.h"
#include "cpuEngine.h"
#include "profiler.h"
typedef void* (*ThreadFunc)(void*);
struct ThreadEntry {
ThreadFunc start_routine;
void* arg;
};
typedef int (*pthread_create_t)(pthread_t*, const pthread_attr_t*, ThreadFunc, void*);
static pthread_create_t _orig_pthread_create = NULL;
typedef void (*pthread_exit_t)(void*);
static pthread_exit_t _orig_pthread_exit = NULL;
static void unblock_signals() {
sigset_t set;
sigemptyset(&set);
if (_global_args._signal == 0) {
sigaddset(&set, SIGPROF);
sigaddset(&set, SIGVTALRM);
} else {
for (int s = _global_args._signal; s > 0; s >>= 8) {
sigaddset(&set, s & 0xff);
}
}
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
}
static void* thread_start_wrapper(void* e) {
ThreadEntry* entry = (ThreadEntry*)e;
ThreadFunc start_routine = entry->start_routine;
void* arg = entry->arg;
free(entry);
unblock_signals();
unsigned long current_thread = (unsigned long)(uintptr_t)pthread_self();
Log::debug("thread_start: 0x%lx", current_thread);
CpuEngine::onThreadStart();
void* result = start_routine(arg);
Log::debug("thread_end: 0x%lx", current_thread);
CpuEngine::onThreadEnd();
return result;
}
static int pthread_create_hook(pthread_t* thread, const pthread_attr_t* attr, ThreadFunc start_routine, void* arg) {
ThreadEntry* entry = (ThreadEntry*) malloc(sizeof(ThreadEntry));
entry->start_routine = start_routine;
entry->arg = arg;
int result = _orig_pthread_create(thread, attr, thread_start_wrapper, entry);
if (result != 0) {
free(entry);
}
return result;
}
static void pthread_exit_hook(void* retval) {
Log::debug("thread_exit: 0x%lx", (unsigned long)(uintptr_t)pthread_self());
CpuEngine::onThreadEnd();
_orig_pthread_exit(retval);
}
typedef void* (*dlopen_t)(const char*, int);
static dlopen_t _orig_dlopen = NULL;
static void* dlopen_hook_impl(const char* filename, int flags, bool patch) {
Log::debug("dlopen: %s", filename);
void* result = _orig_dlopen(filename, flags);
if (result != NULL && filename != NULL) {
Profiler::instance()->updateSymbols(false);
if (patch) {
Hooks::patchLibraries();
}
}
return result;
}
static void* dlopen_hook(const char* filename, int flags) {
return dlopen_hook_impl(filename, flags, true);
}
// LD_PRELOAD hooks
extern "C" DLLEXPORT
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, ThreadFunc start_routine, void* arg) {
if (_orig_pthread_create == NULL) {
_orig_pthread_create = (pthread_create_t)dlsym(RTLD_NEXT, "pthread_create");
}
if (Hooks::initialized()) {
return pthread_create_hook(thread, attr, start_routine, arg);
}
return _orig_pthread_create(thread, attr, start_routine, arg);
}
extern "C" DLLEXPORT
void pthread_exit(void* retval) {
if (_orig_pthread_exit == NULL) {
_orig_pthread_exit = (pthread_exit_t)dlsym(RTLD_NEXT, "pthread_exit");
}
if (Hooks::initialized()) {
pthread_exit_hook(retval);
} else {
_orig_pthread_exit(retval);
}
abort(); // to suppress gcc warning
}
extern "C" DLLEXPORT
void* dlopen(const char* filename, int flags) {
if (_orig_dlopen == NULL) {
_orig_dlopen = (dlopen_t)dlsym(RTLD_NEXT, "dlopen");
}
if (Hooks::initialized()) {
return dlopen_hook_impl(filename, flags, false);
}
return _orig_dlopen(filename, flags);
}
Mutex Hooks::_patch_lock;
int Hooks::_patched_libs = 0;
bool Hooks::_initialized = false;
bool Hooks::init(bool attach) {
if (!__sync_bool_compare_and_swap(&_initialized, false, true)) {
return false;
}
Profiler::instance()->updateSymbols(false);
Profiler::setupSignalHandlers();
if (attach) {
_orig_pthread_create = (pthread_create_t)dlsym(RTLD_NEXT, "pthread_create");
_orig_pthread_exit = (pthread_exit_t)dlsym(RTLD_NEXT, "pthread_exit");
_orig_dlopen = (dlopen_t)dlsym(RTLD_NEXT, "dlopen");
patchLibraries();
}
atexit(shutdown);
return true;
}
void Hooks::shutdown() {
Profiler::instance()->shutdown(_global_args);
}
void Hooks::patchLibraries() {
MutexLocker ml(_patch_lock);
CodeCacheArray* native_libs = Profiler::instance()->nativeLibs();
int native_lib_count = native_libs->count();
while (_patched_libs < native_lib_count) {
CodeCache* cc = (*native_libs)[_patched_libs++];
cc->patchImport(im_dlopen, (void*)dlopen_hook);
cc->patchImport(im_pthread_create, (void*)pthread_create_hook);
cc->patchImport(im_pthread_exit, (void*)pthread_exit_hook);
}
}

28
src/hooks.h Normal file
View File

@@ -0,0 +1,28 @@
/*
* Copyright The async-profiler authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _HOOKS_H
#define _HOOKS_H
#include "mutex.h"
class Hooks {
private:
static Mutex _patch_lock;
static int _patched_libs;
static bool _initialized;
public:
static bool init(bool attach);
static void shutdown();
static void patchLibraries();
static bool initialized() {
return _initialized;
}
};
#endif // _HOOKS_H

Some files were not shown because too many files have changed in this diff Show More