mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
299 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a1f7b8e4a | ||
|
|
8128696c0b | ||
|
|
dead47e0fb | ||
|
|
56730dc7b5 | ||
|
|
9fc0495f86 | ||
|
|
db1ca37a2e | ||
|
|
4ffdb05d3a | ||
|
|
26eab2e657 | ||
|
|
8fee26589d | ||
|
|
cdb8704156 | ||
|
|
c722b3972a | ||
|
|
48703edee7 | ||
|
|
63799a6055 | ||
|
|
ebda293a42 | ||
|
|
69c0340a08 | ||
|
|
45074592cf | ||
|
|
32601bccd9 | ||
|
|
8c4824be7f | ||
|
|
b08bf2d574 | ||
|
|
b4b2218782 | ||
|
|
e1f149f3ae | ||
|
|
80808a3f1b | ||
|
|
7eaefdb18f | ||
|
|
5e4a402c7e | ||
|
|
b0a44524ba | ||
|
|
ed092da71b | ||
|
|
58c62fe4e8 | ||
|
|
bdaefa9a3b | ||
|
|
8168c7dc91 | ||
|
|
98a2006386 | ||
|
|
389d6c5daa | ||
|
|
03e6fc5a17 | ||
|
|
9653401b4b | ||
|
|
f1e2b96a2f | ||
|
|
4f6c675504 | ||
|
|
cced4fcb51 | ||
|
|
904da2ac6b | ||
|
|
b5634b9d88 | ||
|
|
32b5fd8e3c | ||
|
|
b7dfd74a63 | ||
|
|
ed30401cc2 | ||
|
|
d4bee9647f | ||
|
|
d93477f680 | ||
|
|
696087c2ab | ||
|
|
1e8301e831 | ||
|
|
64d9f98a0f | ||
|
|
2613894b85 | ||
|
|
746166ccb4 | ||
|
|
b8c8db45d7 | ||
|
|
14f58ed2c7 | ||
|
|
7fa11e768b | ||
|
|
74ecedc671 | ||
|
|
63f2539e5e | ||
|
|
31261ea7be | ||
|
|
9cec0765cd | ||
|
|
55da899511 | ||
|
|
e9de87c0c3 | ||
|
|
56ae519224 | ||
|
|
733cf7c668 | ||
|
|
ee3ef243d3 | ||
|
|
28357c2fb4 | ||
|
|
2459d7eac4 | ||
|
|
cc1682d20a | ||
|
|
9ec48d9666 | ||
|
|
e2abcd2238 | ||
|
|
9ae31b0e91 | ||
|
|
a836ad6f89 | ||
|
|
c6f11d2673 | ||
|
|
989c3747e2 | ||
|
|
ce9ebc2b63 | ||
|
|
52cdc138d0 | ||
|
|
d09be06029 | ||
|
|
859c36ef9c | ||
|
|
cd084b5a97 | ||
|
|
1505345ee9 | ||
|
|
a53a141dd2 | ||
|
|
8547d642ab | ||
|
|
fa989850b6 | ||
|
|
1e4738ba7b | ||
|
|
c6b5c877f1 | ||
|
|
80a3c66969 | ||
|
|
e1d5df7ffd | ||
|
|
6ca5ad2c93 | ||
|
|
7af24609eb | ||
|
|
80302eb43d | ||
|
|
0396bc7d1b | ||
|
|
b29bfd3f3a | ||
|
|
93b8171601 | ||
|
|
721225393e | ||
|
|
58a3cb0d25 | ||
|
|
6754312e83 | ||
|
|
daf844397e | ||
|
|
5e0021a99f | ||
|
|
8d846a01e7 | ||
|
|
37c56c44bb | ||
|
|
88dd2345ea | ||
|
|
ded970fd50 | ||
|
|
a23400b85e | ||
|
|
796aff5555 | ||
|
|
d36e8e91f9 | ||
|
|
323c02e0d7 | ||
|
|
53cfd2b8b1 | ||
|
|
4431121760 | ||
|
|
4a88ee445f | ||
|
|
ce0fc0a2d9 | ||
|
|
768b437593 | ||
|
|
20fa8564c4 | ||
|
|
aaf24effe8 | ||
|
|
3bbeed6267 | ||
|
|
cb51be8eb3 | ||
|
|
7749b72e73 | ||
|
|
7112bc14e5 | ||
|
|
df5fb83354 | ||
|
|
90b7c78643 | ||
|
|
56ca88677b | ||
|
|
8a258d31a5 | ||
|
|
e33a01e0c6 | ||
|
|
5d5138ea61 | ||
|
|
10b5ad0ee5 | ||
|
|
80c3993b7e | ||
|
|
b81be14adf | ||
|
|
b5258cca2c | ||
|
|
0718a09e0e | ||
|
|
f86d1a415b | ||
|
|
c00b6c25de | ||
|
|
fe173c4101 | ||
|
|
4713106ac9 | ||
|
|
c89fea8de5 | ||
|
|
8032daa49d | ||
|
|
fa8b8f8072 | ||
|
|
85f3a68c56 | ||
|
|
60ce15569a | ||
|
|
9838ddb693 | ||
|
|
2f341043ef | ||
|
|
ee55fbe17b | ||
|
|
0309f3dfd3 | ||
|
|
5ec58fd873 | ||
|
|
1fda752243 | ||
|
|
d7a2a4fc8b | ||
|
|
e9b7747015 | ||
|
|
ff305e1d16 | ||
|
|
5cc4721e66 | ||
|
|
fee03218d1 | ||
|
|
dade9b2283 | ||
|
|
b96e07b001 | ||
|
|
ba00ca26c1 | ||
|
|
db9ec2e59e | ||
|
|
f0a7a36825 | ||
|
|
9ed175d73e | ||
|
|
b287816559 | ||
|
|
9a979a712d | ||
|
|
42442ed593 | ||
|
|
432d622aa4 | ||
|
|
f477f8d4c0 | ||
|
|
456ff57115 | ||
|
|
5ec28a86b2 | ||
|
|
e7cd6ee6fb | ||
|
|
8ba8fc748c | ||
|
|
6f869d3eff | ||
|
|
462988d8de | ||
|
|
5f37bf3ad6 | ||
|
|
26e9c7aef2 | ||
|
|
73b5e97c39 | ||
|
|
8efba10acc | ||
|
|
e894420119 | ||
|
|
41121aa64f | ||
|
|
2519bf4307 | ||
|
|
2ddd4d230c | ||
|
|
09bf0f025b | ||
|
|
1398e7ef75 | ||
|
|
3456dd3d90 | ||
|
|
12dfaba106 | ||
|
|
c0d45fecec | ||
|
|
7e5f8a03f3 | ||
|
|
ce91abe6d9 | ||
|
|
4ba7524d7c | ||
|
|
734ef03ebf | ||
|
|
cf17e5efc3 | ||
|
|
35b420a941 | ||
|
|
ee4cd8e2b6 | ||
|
|
6f3134e99f | ||
|
|
c537b8298d | ||
|
|
605550cf96 | ||
|
|
a57bbf3587 | ||
|
|
9131344d61 | ||
|
|
2d0b9c9921 | ||
|
|
30905fda4c | ||
|
|
5a11a71db9 | ||
|
|
d79a82935f | ||
|
|
995048c2fd | ||
|
|
7331e30ed5 | ||
|
|
1f5e4ca8aa | ||
|
|
7ebed4e8e1 | ||
|
|
170451990b | ||
|
|
11a1d6d308 | ||
|
|
955413db8d | ||
|
|
8a701b41e3 | ||
|
|
dccd4c326a | ||
|
|
8771888d28 | ||
|
|
7d25210d2c | ||
|
|
9087bc57d8 | ||
|
|
3e1e1c614a | ||
|
|
b80d163699 | ||
|
|
9705b66864 | ||
|
|
3c33c6aa47 | ||
|
|
4af6b65268 | ||
|
|
03c7b36bca | ||
|
|
f8b15526b1 | ||
|
|
e519fd84c4 | ||
|
|
79e1017088 | ||
|
|
edbb9e7c03 | ||
|
|
7eb15cfcf0 | ||
|
|
eafbbaea8b | ||
|
|
d8228a1fec | ||
|
|
8a447481f8 | ||
|
|
791077354e | ||
|
|
e071b9fa14 | ||
|
|
cc340923b9 | ||
|
|
9c333219b5 | ||
|
|
9acf8c1648 | ||
|
|
5570afed9d | ||
|
|
975a506d83 | ||
|
|
09fb14bd87 | ||
|
|
3256d824de | ||
|
|
cc98710a6f | ||
|
|
452f14c3d2 | ||
|
|
be8bba1900 | ||
|
|
65b5356ace | ||
|
|
e91363c05a | ||
|
|
3e47bf7551 | ||
|
|
9447068af3 | ||
|
|
7e750825da | ||
|
|
eda5779552 | ||
|
|
fc3b1ca84f | ||
|
|
d13de48c0a | ||
|
|
552c699687 | ||
|
|
0fcc4d9bac | ||
|
|
868bfec2a5 | ||
|
|
4a77d68bcb | ||
|
|
a38a375dc6 | ||
|
|
6bcd23fcf0 | ||
|
|
8d2847a032 | ||
|
|
e0998af713 | ||
|
|
01b3e6c517 | ||
|
|
def6eb4b1c | ||
|
|
4032c56caf | ||
|
|
9b789f6516 | ||
|
|
6ddaf9ab71 | ||
|
|
11131499ab | ||
|
|
d2abac1c30 | ||
|
|
b7e9079b52 | ||
|
|
ff49ccccb7 | ||
|
|
7dd075cca6 | ||
|
|
bd8078bc11 | ||
|
|
1622fe5d72 | ||
|
|
44d7941728 | ||
|
|
d23b40048b | ||
|
|
3b2db709ff | ||
|
|
096fc88c82 | ||
|
|
4b0303916d | ||
|
|
9fb2ca800a | ||
|
|
d917cfdb63 | ||
|
|
f2006f3da1 | ||
|
|
c30b22f204 | ||
|
|
f48ebcc72b | ||
|
|
339aee5cfc | ||
|
|
cde3fae978 | ||
|
|
91eab91634 | ||
|
|
3df00e3439 | ||
|
|
c66ac2cfd0 | ||
|
|
f236482228 | ||
|
|
3ff315ea8f | ||
|
|
308074a9eb | ||
|
|
685da8d84f | ||
|
|
bd7bf9726e | ||
|
|
034677435d | ||
|
|
89f7d34456 | ||
|
|
ec8a40431a | ||
|
|
81bc1f2df2 | ||
|
|
40ff09a14f | ||
|
|
d6de541799 | ||
|
|
b807987f1d | ||
|
|
02875138f1 | ||
|
|
d1c19d1904 | ||
|
|
cc2307b92c | ||
|
|
b5d89fef29 | ||
|
|
646a92e2a0 | ||
|
|
bcd2375f39 | ||
|
|
3cbe6aec2f | ||
|
|
2d51c07b23 | ||
|
|
32ead969c1 | ||
|
|
de55fadbba | ||
|
|
da0ac08c64 | ||
|
|
5dd9e86a1d | ||
|
|
81583b9af3 | ||
|
|
7ec5c195e7 | ||
|
|
cb0f1eb72d | ||
|
|
051424890a | ||
|
|
34daf4f540 |
21
.github/workflows/cpp.yml
vendored
Normal file
21
.github/workflows/cpp.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: C++ CI
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2.1.0
|
||||
with:
|
||||
distribution: "adopt"
|
||||
java-version: "11"
|
||||
- run: sudo sysctl kernel.perf_event_paranoid=1
|
||||
- run: make
|
||||
- run: make test
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: build/
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/build/
|
||||
/nbproject/
|
||||
/out/
|
||||
/target/
|
||||
/.idea/
|
||||
/test/*.class
|
||||
.vscode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
language: cpp
|
||||
|
||||
dist: precise
|
||||
dist: bionic
|
||||
|
||||
sudo: required
|
||||
|
||||
|
||||
156
CHANGELOG.md
156
CHANGELOG.md
@@ -1,21 +1,171 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0-b1] - Early access
|
||||
## [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
|
||||
|
||||
113
Makefile
113
Makefile
@@ -1,6 +1,4 @@
|
||||
PROFILER_VERSION=2.0-b1
|
||||
JATTACH_VERSION=1.5
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
PROFILER_VERSION=2.9-bpf
|
||||
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
@@ -11,16 +9,21 @@ JATTACH=jattach
|
||||
API_JAR=async-profiler.jar
|
||||
CONVERTER_JAR=converter.jar
|
||||
|
||||
CFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
CFLAGS=-O3
|
||||
CXXFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
INCLUDES=-I$(JAVA_HOME)/include
|
||||
INCLUDES=-I$(JAVA_HOME)/include -Isrc/res -Isrc/helper
|
||||
LIBS=-ldl -lpthread
|
||||
MERGE=true
|
||||
|
||||
JAVAC=$(JAVA_HOME)/bin/javac
|
||||
JAR=$(JAVA_HOME)/bin/jar
|
||||
JAVA_TARGET=7
|
||||
JAVAC_OPTIONS=-source $(JAVA_TARGET) -target $(JAVA_TARGET) -Xlint:-options
|
||||
|
||||
SOURCES := $(wildcard src/*.cpp)
|
||||
HEADERS := $(wildcard src/*.h)
|
||||
HEADERS := $(wildcard src/*.h src/fdtransfer/*.h)
|
||||
RESOURCES := $(wildcard src/res/*)
|
||||
JAVA_HELPER_CLASSES := $(wildcard src/helper/one/profiler/*.class)
|
||||
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
|
||||
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
|
||||
|
||||
@@ -29,17 +32,33 @@ ifeq ($(JAVA_HOME),)
|
||||
endif
|
||||
|
||||
OS:=$(shell uname -s)
|
||||
ifeq ($(OS), Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
ifeq ($(OS),Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/server
|
||||
INCLUDES += -I$(JAVA_HOME)/include/darwin
|
||||
FDTRANSFER_BIN=
|
||||
SOEXT=dylib
|
||||
PACKAGE_EXT=zip
|
||||
OS_TAG=macos
|
||||
ifeq ($(FAT_BINARY),true)
|
||||
FAT_BINARY_FLAGS=-arch x86_64 -arch arm64 -mmacos-version-min=10.12
|
||||
CFLAGS += $(FAT_BINARY_FLAGS)
|
||||
CXXFLAGS += $(FAT_BINARY_FLAGS)
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)
|
||||
MERGE=false
|
||||
endif
|
||||
else
|
||||
CXXFLAGS += -Wl,-z,defs
|
||||
ifeq ($(MERGE),true)
|
||||
CXXFLAGS += -fwhole-program
|
||||
endif
|
||||
LIBS += -lrt
|
||||
INCLUDES += -I$(JAVA_HOME)/include/linux
|
||||
FDTRANSFER_BIN=build/fdtransfer
|
||||
SOEXT=so
|
||||
PACKAGE_EXT=tar.gz
|
||||
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
|
||||
OS_TAG=linux-musl
|
||||
CXXFLAGS += -D__musl__
|
||||
else
|
||||
OS_TAG=linux
|
||||
endif
|
||||
@@ -50,63 +69,103 @@ ifeq ($(ARCH),x86_64)
|
||||
ARCH_TAG=x64
|
||||
else
|
||||
ifeq ($(findstring arm,$(ARCH)),arm)
|
||||
ARCH_TAG=arm
|
||||
ifeq ($(findstring 64,$(ARCH)),64)
|
||||
ARCH_TAG=arm64
|
||||
else
|
||||
ARCH_TAG=arm32
|
||||
endif
|
||||
else
|
||||
ifeq ($(findstring aarch64,$(ARCH)),aarch64)
|
||||
ARCH_TAG=aarch64
|
||||
ARCH_TAG=arm64
|
||||
else
|
||||
ARCH_TAG=x86
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
ARCH_TAG=ppc64le
|
||||
else
|
||||
ARCH_TAG=x86
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(ARCH),ppc64le)
|
||||
ifneq ($(ARCH_TAG),arm32)
|
||||
CXXFLAGS += -momit-leaf-frame-pointer
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: all release test clean
|
||||
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
.PHONY: all release test native clean
|
||||
|
||||
release: build $(PACKAGE_NAME).tar.gz
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) 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
|
||||
release: build $(PACKAGE_NAME).$(PACKAGE_EXT)
|
||||
|
||||
$(PACKAGE_NAME).tar.gz: $(PACKAGE_DIR)
|
||||
tar czf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
$(PACKAGE_NAME).zip: $(PACKAGE_DIR)
|
||||
codesign -s "Developer ID" -o runtime --timestamp -v $(PACKAGE_DIR)/build/$(JATTACH) $(PACKAGE_DIR)/build/$(LIB_PROFILER_SO)
|
||||
ditto -c -k --keepParent $(PACKAGE_DIR) $@
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
$(PACKAGE_DIR): build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
profiler.sh LICENSE *.md
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
|
||||
chmod -R 755 $(PACKAGE_DIR)
|
||||
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
|
||||
tar cvzf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
%.$(SOEXT): %.so
|
||||
rm -f $@
|
||||
-ln -s $(<F) $@
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
|
||||
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS)
|
||||
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS) $(RESOURCES) $(JAVA_HELPER_CLASSES)
|
||||
ifeq ($(MERGE),true)
|
||||
for f in src/*.cpp; do echo '#include "'$$f'"'; done |\
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ -xc++ - $(LIBS)
|
||||
else
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
|
||||
endif
|
||||
|
||||
build/$(JATTACH): src/jattach/jattach.c
|
||||
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
|
||||
build/$(JATTACH): src/jattach/*.c src/jattach/*.h
|
||||
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(PROFILER_VERSION)-ap\" -o $@ src/jattach/*.c
|
||||
|
||||
build/fdtransfer: src/fdtransfer/*.cpp src/fdtransfer/*.h src/jattach/psutil.c src/jattach/psutil.h
|
||||
$(CXX) $(CFLAGS) -o $@ src/fdtransfer/*.cpp src/jattach/psutil.c
|
||||
|
||||
build/$(API_JAR): $(API_SOURCES)
|
||||
mkdir -p build/api
|
||||
$(JAVAC) -source $(JAVAC_RELEASE_VERSION) -target $(JAVAC_RELEASE_VERSION) -d build/api $^
|
||||
$(JAR) cvf $@ -C build/api .
|
||||
$(JAVAC) $(JAVAC_OPTIONS) -d build/api $^
|
||||
$(JAR) cf $@ -C build/api .
|
||||
$(RM) -r build/api
|
||||
|
||||
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) src/converter/MANIFEST.MF
|
||||
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) $(RESOURCES)
|
||||
mkdir -p build/converter
|
||||
$(JAVAC) -source 7 -target 7 -d build/converter $(CONVERTER_SOURCES)
|
||||
$(JAR) cvfm $@ src/converter/MANIFEST.MF -C build/converter .
|
||||
$(JAVAC) $(JAVAC_OPTIONS) -d build/converter $(CONVERTER_SOURCES)
|
||||
$(JAR) cfe $@ Main -C build/converter . -C src/res .
|
||||
$(RM) -r build/converter
|
||||
|
||||
%.class: %.java
|
||||
$(JAVAC) $(JAVAC_OPTIONS) -g:none $^
|
||||
|
||||
test: all
|
||||
test/smoke-test.sh
|
||||
test/thread-smoke-test.sh
|
||||
test/alloc-smoke-test.sh
|
||||
test/load-library-test.sh
|
||||
test/fdtransfer-smoke-test.sh
|
||||
echo "All tests passed"
|
||||
|
||||
native:
|
||||
mkdir -p native/linux-x64 native/linux-arm64 native/macos
|
||||
tar xfO async-profiler-$(PROFILER_VERSION)-linux-x64.tar.gz */build/libasyncProfiler.so > native/linux-x64/libasyncProfiler.so
|
||||
tar xfO async-profiler-$(PROFILER_VERSION)-linux-arm64.tar.gz */build/libasyncProfiler.so > native/linux-arm64/libasyncProfiler.so
|
||||
unzip -p async-profiler-$(PROFILER_VERSION)-macos.zip */build/libasyncProfiler.so > native/macos/libasyncProfiler.so
|
||||
|
||||
clean:
|
||||
$(RM) -r build
|
||||
|
||||
625
README.md
625
README.md
@@ -1,7 +1,5 @@
|
||||
# async-profiler
|
||||
|
||||
[](https://travis-ci.org/jvm-profiling-tools/async-profiler)
|
||||
|
||||
This project is a low overhead sampling profiler for Java
|
||||
that does not suffer from [Safepoint bias problem](http://psy-lob-saw.blogspot.ru/2016/02/why-most-sampling-java-profilers-are.html).
|
||||
It features HotSpot-specific APIs to collect stack traces
|
||||
@@ -13,41 +11,626 @@ async-profiler can trace the following kinds of events:
|
||||
- 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/jvm-profiling-tools/async-profiler/wiki) or
|
||||
[3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr)
|
||||
to learn about all features.
|
||||
|
||||
## Download
|
||||
|
||||
Latest release (1.8.3):
|
||||
Current release (2.9):
|
||||
|
||||
- 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)
|
||||
|
||||
[Early access](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.0-b1) (2.0-b1):
|
||||
|
||||
- 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)
|
||||
- Linux x64 (glibc): [async-profiler-2.9-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-2.9-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-musl-x64.tar.gz)
|
||||
- Linux arm64: [async-profiler-2.9-linux-arm64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-arm64.tar.gz)
|
||||
- macOS x64/arm64: [async-profiler-2.9-macos.zip](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-macos.zip)
|
||||
- Converters between profile formats: [converter.jar](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/converter.jar)
|
||||
(JFR to Flame Graph, JFR to FlameScope, collapsed stacks to Flame Graph)
|
||||
|
||||
[Previous releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
|
||||
|
||||
Note: async-profiler also comes bundled with IntelliJ IDEA Ultimate 2018.3 and later.
|
||||
For more information refer to [IntelliJ IDEA documentation](https://www.jetbrains.com/help/idea/cpu-and-allocation-profiling-basic-concepts.html).
|
||||
|
||||
## Supported platforms
|
||||
|
||||
- **Linux** / x64 / x86 / ARM / AArch64
|
||||
- **macOS** / x64
|
||||
- **Linux** / x64 / x86 / arm64 / arm32 / ppc64le
|
||||
- **macOS** / x64 / arm64
|
||||
|
||||
Note: macOS profiling is limited to user space code only.
|
||||
### Community supported builds
|
||||
|
||||
- **Windows** / x64 - <img src="https://upload.wikimedia.org/wikipedia/commons/9/9c/IntelliJ_IDEA_Icon.svg" width="16" height="16"/> [IntelliJ IDEA](https://www.jetbrains.com/idea/) 2021.2 and later
|
||||
- [**ap-loader**](https://github.com/jvm-profiling-tools/ap-loader) -
|
||||
all-in-one JAR for using async-profiler in Java programs and as a CLI tool
|
||||
|
||||
## CPU profiling
|
||||
|
||||
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:
|
||||
|
||||
* Works on older Java versions because it doesn't require
|
||||
`-XX:+PreserveFramePointer`, which is only available in JDK 8u60 and later.
|
||||
|
||||
* Does not introduce the performance overhead from `-XX:+PreserveFramePointer`,
|
||||
which can in rare cases be as high as 10%.
|
||||
|
||||
* Does not require generating a map file to map Java code addresses to method
|
||||
names.
|
||||
|
||||
* Works with interpreter frames.
|
||||
|
||||
* Does not require writing out a perf.data file 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
|
||||
|
||||
Instead of detecting CPU-consuming code, 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 (aqua frames in a Flame Graph);
|
||||
- when an object is allocated on a slow path outside TLAB (brown frames).
|
||||
|
||||
This means not each allocation is counted, but only allocations every _N_ kB,
|
||||
where _N_ is the average size of TLAB. This makes heap sampling very cheap
|
||||
and suitable for production. On the other hand, the collected data
|
||||
may be incomplete, though in practice it will often reflect the top allocation
|
||||
sources.
|
||||
|
||||
Sampling interval can be adjusted with `--alloc` option.
|
||||
For example, `--alloc 500k` will take one sample after 500 KB of allocated
|
||||
space on average. However, intervals less than TLAB size will not take effect.
|
||||
|
||||
The minimum supported JDK version is 7u40 where the TLAB callbacks appeared.
|
||||
|
||||
### Installing Debug Symbols
|
||||
|
||||
Prior to JDK 11, the allocation profiler required HotSpot debug symbols.
|
||||
Oracle JDK already has them embedded in `libjvm.so`, but in OpenJDK builds
|
||||
they are typically shipped in a separate package. For example, to install
|
||||
OpenJDK debug symbols on Debian / Ubuntu, run:
|
||||
```
|
||||
# apt install openjdk-8-dbg
|
||||
```
|
||||
or for OpenJDK 11:
|
||||
```
|
||||
# apt install openjdk-11-dbg
|
||||
```
|
||||
|
||||
On CentOS, RHEL and some other RPM-based distributions, this could be done with
|
||||
[debuginfo-install](http://man7.org/linux/man-pages/man1/debuginfo-install.1.html) utility:
|
||||
```
|
||||
# debuginfo-install java-1.8.0-openjdk
|
||||
```
|
||||
|
||||
On Gentoo the `icedtea` OpenJDK package can be built with the per-package setting
|
||||
`FEATURES="nostrip"` to retain symbols.
|
||||
|
||||
The `gdb` tool can be used to verify if the debug symbols are properly installed for the `libjvm` library.
|
||||
For example on Linux:
|
||||
```
|
||||
$ gdb $JAVA_HOME/lib/server/libjvm.so -ex 'info address UseG1GC'
|
||||
```
|
||||
This command's output will either contain `Symbol "UseG1GC" is at 0xxxxx`
|
||||
or `No symbol "UseG1GC" in current context`.
|
||||
|
||||
## Wall-clock profiling
|
||||
|
||||
`-e wall` option tells async-profiler to sample all threads equally every given
|
||||
period of time regardless of thread status: Running, Sleeping or Blocked.
|
||||
For instance, this can be helpful when profiling application start-up time.
|
||||
|
||||
Wall-clock profiler is most useful in per-thread mode: `-t`.
|
||||
|
||||
Example: `./profiler.sh -e wall -t -i 5ms -f result.html 8983`
|
||||
|
||||
## Java method profiling
|
||||
|
||||
`-e ClassName.methodName` option instruments the given Java method
|
||||
in order to record all invocations of this method with the stack traces.
|
||||
|
||||
Example: `-e java.util.Properties.getProperty` will profile all places
|
||||
where `getProperty` method is called from.
|
||||
|
||||
Only non-native Java methods are supported. To profile a native method,
|
||||
use hardware breakpoint event instead, e.g. `-e Java_java_lang_Throwable_fillInStackTrace`
|
||||
|
||||
**Be aware** that if you attach async-profiler at runtime, the first instrumentation
|
||||
of a non-native Java method may cause the [deoptimization](https://github.com/openjdk/jdk/blob/bf2e9ee9d321ed289466b2410f12ad10504d01a2/src/hotspot/share/prims/jvmtiRedefineClasses.cpp#L4092-L4096)
|
||||
of all compiled methods. The subsequent instrumentation flushes only the _dependent code_.
|
||||
|
||||
The massive CodeCache flush doesn't occur if attaching async-profiler as an agent.
|
||||
|
||||
Here are some useful native methods that you may want to profile:
|
||||
* ```G1CollectedHeap::humongous_obj_allocate``` - trace the _humongous allocation_ of the G1 GC,
|
||||
* ```JVM_StartThread``` - trace the new thread creation,
|
||||
* ```Java_java_lang_ClassLoader_defineClass1``` - trace class loading.
|
||||
|
||||
## Building
|
||||
|
||||
Build status: [](https://github.com/jvm-profiling-tools/async-profiler/actions/workflows/cpp.yml)
|
||||
|
||||
Make sure the `JAVA_HOME` environment variable points to your JDK installation,
|
||||
and then run `make`. GCC is required. After building, the profiler agent binary
|
||||
will be in the `build` subdirectory. Additionally, a small application `jattach`
|
||||
that can load the agent into the target process will also be compiled to the
|
||||
`build` subdirectory.
|
||||
`build` subdirectory. If the build fails due to
|
||||
`Source option 7 is no longer supported. Use 8 or later.`, use `make JAVA_TARGET=8`.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
As of Linux 4.6, capturing kernel call stacks using `perf_events` from a non-root
|
||||
process requires setting two runtime variables. You can set them using
|
||||
sysctl or as follows:
|
||||
|
||||
```
|
||||
# sysctl kernel.perf_event_paranoid=1
|
||||
# sysctl kernel.kptr_restrict=0
|
||||
```
|
||||
|
||||
To run the agent and pass commands to it, the helper script `profiler.sh`
|
||||
is provided. 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 in the Java application's standard output.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ ./profiler.sh start 8983
|
||||
$ ./profiler.sh stop 8983
|
||||
```
|
||||
|
||||
The following may be used in lieu of the `pid` (8983):
|
||||
|
||||
- The keyword `jps`, which will use the most recently launched Java process.
|
||||
- The application name as it appears in the `jps` output: e.g. `Computey`
|
||||
|
||||
Alternatively, you may specify `-d` (duration) argument to profile
|
||||
the application for a fixed period of time with a single command.
|
||||
|
||||
```
|
||||
$ ./profiler.sh -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 `profiler.sh` script,
|
||||
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/jvm-profiling-tools/async-profiler/blob/v2.9/src/arguments.cpp#L52).
|
||||
The `profiler.sh` script actually converts command line arguments to that format.
|
||||
|
||||
For instance, `-e wall` is converted to `event=wall`, `-f profile.html`
|
||||
is converted to `file=profile.html`, and so on. However, some arguments are processed
|
||||
directly by `profiler.sh` script. E.g. `-d 5` results in 3 actions:
|
||||
attaching profiler agent with start command, sleeping for 5 seconds,
|
||||
and then attaching the agent again with stop command.
|
||||
|
||||
## Multiple events
|
||||
|
||||
It is possible to profile CPU, allocations, and locks at the same time.
|
||||
Or, instead of CPU, you may choose any other execution event: wall-clock,
|
||||
perf event, tracepoint, Java method, etc.
|
||||
|
||||
The only output format that supports multiple events together is JFR.
|
||||
The recording will contain the following event types:
|
||||
- `jdk.ExecutionSample`
|
||||
- `jdk.ObjectAllocationInNewTLAB` (alloc)
|
||||
- `jdk.ObjectAllocationOutsideTLAB` (alloc)
|
||||
- `jdk.JavaMonitorEnter` (lock)
|
||||
- `jdk.ThreadPark` (lock)
|
||||
|
||||
To start profiling cpu + allocations + locks together, specify
|
||||
```
|
||||
./profiler.sh -e cpu,alloc,lock -f profile.jfr ...
|
||||
```
|
||||
or use `--alloc` and `--lock` parameters with the desired threshold:
|
||||
```
|
||||
./profiler.sh -e cpu --alloc 2m --lock 10ms -f profile.jfr ...
|
||||
```
|
||||
The same, when starting profiler as an agent:
|
||||
```
|
||||
-agentpath:/path/to/libasyncProfiler.so=start,event=cpu,alloc=2m,lock=10ms,file=profile.jfr
|
||||
```
|
||||
|
||||
## Flame Graph visualization
|
||||
|
||||
async-profiler provides out-of-the-box [Flame Graph](https://github.com/BrendanGregg/FlameGraph) support.
|
||||
Specify `-o flamegraph` argument to dump profiling results as an interactive HTML Flame Graph.
|
||||
Also, Flame Graph output format will be chosen automatically if the target filename ends with `.html`.
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ ./profiler.sh -d 30 -f /tmp/flamegraph.html 8983
|
||||
```
|
||||
|
||||
[](https://htmlpreview.github.io/?https://github.com/jvm-profiling-tools/async-profiler/blob/master/demo/flamegraph.html)
|
||||
|
||||
## Profiler Options
|
||||
|
||||
The following is a complete list of the command-line options accepted by
|
||||
`profiler.sh` script.
|
||||
|
||||
* `start` - starts profiling in semi-automatic mode, i.e. profiler will run
|
||||
until `stop` command is explicitly called.
|
||||
|
||||
* `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: `./profiler.sh -d 30 8983`
|
||||
|
||||
* `-e event` - the profiling event: `cpu`, `alloc`, `lock`, `cache-misses` etc.
|
||||
Use `list` to see the complete list of available events.
|
||||
|
||||
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: `./profiler.sh -i 500us 8983`
|
||||
|
||||
* `--alloc N` - allocation profiling interval in bytes or in other units,
|
||||
if N is followed by `k` (kilobytes), `m` (megabytes), or `g` (gigabytes).
|
||||
|
||||
* `--live` - retain allocation samples with live objects only
|
||||
(object that have not been collected by the end of profiling session).
|
||||
Useful for finding Java heap memory leaks.
|
||||
|
||||
* `--lock N` - lock profiling threshold in nanoseconds (or other units).
|
||||
In lock profiling mode, record contended locks that the JVM has waited for
|
||||
longer than the specified duration.
|
||||
|
||||
* `-j N` - sets the Java stack profiling depth. This option will be ignored if N is greater
|
||||
than default 2048.
|
||||
Example: `./profiler.sh -j 30 8983`
|
||||
|
||||
* `-t` - profile threads separately. Each stack trace will end with a frame
|
||||
that denotes a single thread.
|
||||
Example: `./profiler.sh -t 8983`
|
||||
|
||||
* `-s` - print simple class names instead of FQN.
|
||||
|
||||
* `-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.
|
||||
Example: `./profiler.sh -f profile.jfr --chunksize 100m --chunktime 1h 8983`
|
||||
|
||||
* `-I include`, `-X exclude` - filter stack traces by the given pattern(s).
|
||||
`-I` defines the name pattern that *must* be present in the stack traces,
|
||||
while `-X` is the pattern that *must not* occur in any of stack traces in the output.
|
||||
`-I` and `-X` options can be specified multiple times. A pattern may begin or end with
|
||||
a star `*` that denotes any (possibly empty) sequence of characters.
|
||||
Example: `./profiler.sh -I 'Primes.*' -I 'java/*' -X '*Unsafe.park*' 8983`
|
||||
|
||||
* `--title TITLE`, `--minwidth PERCENT`, `--reverse` - FlameGraph parameters.
|
||||
Example: `./profiler.sh -f profile.html --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
|
||||
* `-f FILENAME` - the file name to dump the profile information to.
|
||||
`%p` in the file name is expanded to the PID of the target JVM;
|
||||
`%t` - to the timestamp;
|
||||
`%n{MAX}` - to the sequence number;
|
||||
`%{ENV}` - to the value of the given environment variable.
|
||||
Example: `./profiler.sh -o collapsed -f /tmp/traces-%t.txt 8983`
|
||||
|
||||
* `--loop TIME` - run profiler in a loop (continuous profiling).
|
||||
The argument is either a clock time (`hh:mm:ss`) or
|
||||
a loop duration in `s`econds, `m`inutes, `h`ours, or `d`ays.
|
||||
Make sure the filename includes a timestamp pattern, or the output
|
||||
will be overwritten on each iteration.
|
||||
Example: `./profiler.sh --loop 1h -f /var/log/profile-%t.jfr 8983`
|
||||
|
||||
* `--all-user` - include only user-mode events. This option is helpful when kernel profiling
|
||||
is restricted by `perf_event_paranoid` settings.
|
||||
|
||||
* `--sched` - group threads by Linux-specific scheduling policy: BATCH/IDLE/OTHER.
|
||||
|
||||
* `--cstack MODE` - how to walk native frames (C stack). Possible modes are
|
||||
`fp` (Frame Pointer), `dwarf` (DWARF unwind info),
|
||||
`lbr` (Last Branch Record, available on Haswell since Linux 4.1),
|
||||
and `no` (do not collect C stack).
|
||||
|
||||
By default, C stack is shown in cpu, itimer, wall-clock and perf-events profiles.
|
||||
Java-level events like `alloc` and `lock` collect only Java stack.
|
||||
|
||||
* `--begin function`, `--end function` - automatically start/stop profiling
|
||||
when the specified native function is executed.
|
||||
|
||||
* `--ttsp` - time-to-safepoint profiling. An alias for
|
||||
`--begin SafepointSynchronize::begin --end RuntimeService::record_safepoint_synchronized`
|
||||
It is not a separate event type, but rather a constraint. Whatever event type
|
||||
you choose (e.g. `cpu` or `wall`), the profiler will work as usual, except that
|
||||
only events between the safepoint request and the start of the VM operation
|
||||
will be recorded.
|
||||
|
||||
* `--jfrsync CONFIG` - start Java Flight Recording with the given configuration
|
||||
synchronously with the profiler. The output .jfr file will include all regular
|
||||
JFR events, except that execution samples will be obtained from async-profiler.
|
||||
This option implies `-o jfr`.
|
||||
- `CONFIG` is a predefined JFR profile or a JFR configuration file (.jfc).
|
||||
|
||||
Example: `./profiler.sh -e cpu --jfrsync profile -f combined.jfr 8983`
|
||||
|
||||
* `--fdtransfer` - runs "fdtransfer" alongside, which is a small program providing an interface
|
||||
for the profiler to access `perf_event_open` even while this syscall is unavailable for the
|
||||
profiled process (due to low privileges).
|
||||
See [Profiling Java in a container](#profiling-java-in-a-container).
|
||||
|
||||
* `-v`, `--version` - prints the version of profiler library. If PID is specified,
|
||||
gets the version of the library loaded into the given process.
|
||||
|
||||
## 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 itimer` profiling mode, see [Troubleshooting](#troubleshooting).
|
||||
|
||||
## Restrictions/Limitations
|
||||
|
||||
* macOS profiling is limited to user space code only.
|
||||
|
||||
* On most Linux systems, `perf_events` captures call stacks with a maximum depth
|
||||
of 127 frames. On recent Linux kernels, this can be configured using
|
||||
`sysctl kernel.perf_event_max_stack` or by writing to the
|
||||
`/proc/sys/kernel/perf_event_max_stack` file.
|
||||
|
||||
* 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.
|
||||
|
||||
* There is no bullet-proof guarantee that the `perf_events` overflow signal
|
||||
is delivered to the Java thread in a way that guarantees no other code has run,
|
||||
which means that in some rare cases, the captured Java stack might not match
|
||||
the captured native (user+kernel) stack.
|
||||
|
||||
* You will not see the non-Java frames _preceding_ the Java frames on the
|
||||
stack. For example, if `start_thread` called `JavaMain` and then your Java
|
||||
code started running, you will not see the first two frames in the resulting
|
||||
stack. On the other hand, you _will_ see non-Java frames (user and kernel)
|
||||
invoked by your Java code.
|
||||
|
||||
* No Java stacks will be collected if `-XX:MaxJavaStackTraceDepth` is zero
|
||||
or negative.
|
||||
|
||||
* Too short profiling interval may cause continuous interruption of heavy
|
||||
system calls like `clone()`, so that it will never complete;
|
||||
see [#97](https://github.com/jvm-profiling-tools/async-profiler/issues/97).
|
||||
The workaround is simply to increase the interval.
|
||||
|
||||
* 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.
|
||||
|
||||
```
|
||||
Failed to inject profiler into <pid>
|
||||
```
|
||||
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/jvm-profiling-tools/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 will allow using perf_events.
|
||||
|
||||
If changing the configuration is not possible, you may fall back to
|
||||
`-e itimer` profiling mode. It is similar to `cpu` mode, but does not
|
||||
require perf_events support. As a drawback, there will be no kernel
|
||||
stack traces.
|
||||
|
||||
```
|
||||
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/jvm-profiling-tools/async-profiler/issues/218)).
|
||||
In these cases installing JDK debug symbols may solve the problem.
|
||||
|
||||
```
|
||||
Could not parse symbols from <libname.so>
|
||||
```
|
||||
Async-profiler was unable to parse non-Java function names because of
|
||||
the corrupted contents in `/proc/[pid]/maps`. The problem is known to
|
||||
occur in a container when running Ubuntu with Linux kernel 5.x.
|
||||
This is the OS bug, see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018.
|
||||
|
||||
```
|
||||
Could not open output file
|
||||
```
|
||||
Output file is written by the target JVM process, not by the profiler script.
|
||||
Make sure the path specified in `-f` option is correct and is accessible by the JVM.
|
||||
|
||||
2247
demo/SwingSet2.svg
2247
demo/SwingSet2.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 141 KiB |
882
demo/flamegraph.html
Normal file
882
demo/flamegraph.html
Normal file
@@ -0,0 +1,882 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<style>
|
||||
body {margin: 0; padding: 10px; background-color: #ffffff}
|
||||
h1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}
|
||||
header {margin: -24px 0 5px 0; line-height: 24px}
|
||||
button {font: 12px sans-serif; cursor: pointer}
|
||||
p {margin: 5px 0 5px 0}
|
||||
a {color: #0366d6}
|
||||
#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}
|
||||
#hl span {padding: 0 3px 0 3px}
|
||||
#status {overflow: hidden; white-space: nowrap}
|
||||
#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}
|
||||
#reset {cursor: pointer}
|
||||
</style>
|
||||
</head>
|
||||
<body style='font: 12px Verdana, sans-serif'>
|
||||
<h1>Flame Graph</h1>
|
||||
<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>
|
||||
<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>
|
||||
<canvas id='canvas' style='width: 100%; height: 752px'></canvas>
|
||||
<div id='hl'><span></span></div>
|
||||
<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>
|
||||
<p id='status'> </p>
|
||||
<script>
|
||||
// Copyright 2020 Andrei Pangin
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
'use strict';
|
||||
var root, rootLevel, px, pattern;
|
||||
var reverse = false;
|
||||
const levels = Array(47);
|
||||
for (let h = 0; h < levels.length; h++) {
|
||||
levels[h] = [];
|
||||
}
|
||||
|
||||
const canvas = document.getElementById('canvas');
|
||||
const c = canvas.getContext('2d');
|
||||
const hl = document.getElementById('hl');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
const canvasWidth = canvas.offsetWidth;
|
||||
const canvasHeight = canvas.offsetHeight;
|
||||
canvas.style.width = canvasWidth + 'px';
|
||||
canvas.width = canvasWidth * (devicePixelRatio || 1);
|
||||
canvas.height = canvasHeight * (devicePixelRatio || 1);
|
||||
if (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);
|
||||
c.font = document.body.style.font;
|
||||
|
||||
const palette = [
|
||||
[0x50e150, 30, 30, 30],
|
||||
[0x50bebe, 30, 30, 30],
|
||||
[0xe17d00, 30, 30, 0],
|
||||
[0xc8c83c, 30, 30, 10],
|
||||
[0xe15a5a, 30, 40, 40],
|
||||
];
|
||||
|
||||
function getColor(p) {
|
||||
const v = Math.random();
|
||||
return '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);
|
||||
}
|
||||
|
||||
function f(level, left, width, type, title) {
|
||||
levels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});
|
||||
}
|
||||
|
||||
function samples(n) {
|
||||
return n === 1 ? '1 sample' : n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' samples';
|
||||
}
|
||||
|
||||
function pct(a, b) {
|
||||
return a >= b ? '100' : (100 * a / b).toFixed(2);
|
||||
}
|
||||
|
||||
function findFrame(frames, x) {
|
||||
let left = 0;
|
||||
let right = frames.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = (left + right) >>> 1;
|
||||
const f = frames[mid];
|
||||
|
||||
if (f.left > x) {
|
||||
right = mid - 1;
|
||||
} else if (f.left + f.width <= x) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
if (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];
|
||||
if (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function search(r) {
|
||||
if (r && (r = prompt('Enter regexp to search:', '')) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
pattern = r ? RegExp(r) : undefined;
|
||||
const matched = render(root, rootLevel);
|
||||
document.getElementById('matchval').textContent = pct(matched, root.width) + '%';
|
||||
document.getElementById('match').style.display = r ? 'inherit' : 'none';
|
||||
}
|
||||
|
||||
function render(newRoot, newLevel) {
|
||||
if (root) {
|
||||
c.fillStyle = '#ffffff';
|
||||
c.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
}
|
||||
|
||||
root = newRoot || levels[0][0];
|
||||
rootLevel = newLevel || 0;
|
||||
px = canvasWidth / root.width;
|
||||
|
||||
const x0 = root.left;
|
||||
const x1 = x0 + root.width;
|
||||
const marked = [];
|
||||
|
||||
function mark(f) {
|
||||
return marked[f.left] >= f.width || (marked[f.left] = f.width);
|
||||
}
|
||||
|
||||
function totalMarked() {
|
||||
let total = 0;
|
||||
let left = 0;
|
||||
for (let x in marked) {
|
||||
if (+x >= left) {
|
||||
total += marked[x];
|
||||
left = +x + marked[x];
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function drawFrame(f, y, alpha) {
|
||||
if (f.left < x1 && f.left + f.width > x0) {
|
||||
c.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;
|
||||
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
|
||||
|
||||
if (f.width * px >= 21) {
|
||||
const chars = Math.floor(f.width * px / 7);
|
||||
const title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';
|
||||
c.fillStyle = '#000000';
|
||||
c.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);
|
||||
}
|
||||
|
||||
if (alpha) {
|
||||
c.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
c.fillRect((f.left - x0) * px, y, f.width * px, 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let h = 0; h < levels.length; h++) {
|
||||
const y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;
|
||||
const frames = levels[h];
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
drawFrame(frames[i], y, h < rootLevel);
|
||||
}
|
||||
}
|
||||
|
||||
return totalMarked();
|
||||
}
|
||||
|
||||
canvas.onmousemove = function() {
|
||||
const h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);
|
||||
if (h >= 0 && h < levels.length) {
|
||||
const f = findFrame(levels[h], event.offsetX / px + root.left);
|
||||
if (f) {
|
||||
if (f != root) getSelection().removeAllRanges();
|
||||
hl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';
|
||||
hl.style.width = (Math.min(f.width, root.width) * px) + 'px';
|
||||
hl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';
|
||||
hl.firstChild.textContent = f.title;
|
||||
hl.style.display = 'block';
|
||||
canvas.title = f.title + '\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%)';
|
||||
canvas.style.cursor = 'pointer';
|
||||
canvas.onclick = function() {
|
||||
if (f != root) {
|
||||
render(f, h);
|
||||
canvas.onmousemove();
|
||||
}
|
||||
};
|
||||
status.textContent = 'Function: ' + canvas.title;
|
||||
return;
|
||||
}
|
||||
}
|
||||
canvas.onmouseout();
|
||||
}
|
||||
|
||||
canvas.onmouseout = function() {
|
||||
hl.style.display = 'none';
|
||||
status.textContent = '\xa0';
|
||||
canvas.title = '';
|
||||
canvas.style.cursor = '';
|
||||
canvas.onclick = '';
|
||||
}
|
||||
|
||||
canvas.ondblclick = function() {
|
||||
getSelection().selectAllChildren(hl);
|
||||
}
|
||||
|
||||
document.getElementById('reverse').onclick = function() {
|
||||
reverse = !reverse;
|
||||
render();
|
||||
}
|
||||
|
||||
document.getElementById('search').onclick = function() {
|
||||
search(true);
|
||||
}
|
||||
|
||||
document.getElementById('reset').onclick = function() {
|
||||
search(false);
|
||||
}
|
||||
|
||||
window.onkeydown = function() {
|
||||
if (event.ctrlKey && event.keyCode === 70) {
|
||||
event.preventDefault();
|
||||
search(true);
|
||||
} else if (event.keyCode === 27) {
|
||||
search(false);
|
||||
}
|
||||
}
|
||||
f(0,0,641,4,'all')
|
||||
f(1,0,5,0,'com/sun/glass/ui/InvokeLaterDispatcher.run')
|
||||
f(2,0,4,0,'com/sun/glass/ui/gtk/GtkApplication.submitForLaterInvocation')
|
||||
f(3,0,4,0,'com/sun/glass/ui/gtk/GtkApplication._submitForLaterInvocation')
|
||||
f(4,0,4,4,'Java_com_sun_glass_ui_gtk_GtkApplication__1submitForLaterInvocation')
|
||||
f(5,0,4,4,'__write')
|
||||
f(6,0,4,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(7,0,4,2,'do_syscall_64')
|
||||
f(8,0,4,2,'__x64_sys_write')
|
||||
f(9,0,4,2,'ksys_write')
|
||||
f(10,0,4,2,'vfs_write')
|
||||
f(11,0,4,2,'__vfs_write')
|
||||
f(12,0,4,2,'eventfd_write')
|
||||
f(1,5,408,0,'java/lang/Thread.run')
|
||||
f(2,5,179,0,'com/sun/glass/ui/gtk/GtkApplication$$Lambda$42/1642360923.run')
|
||||
f(3,5,179,0,'com/sun/glass/ui/gtk/GtkApplication.lambda$null$48')
|
||||
f(4,5,179,0,'com/sun/glass/ui/gtk/GtkApplication._runLoop')
|
||||
f(5,5,4,4,'Java_com_sun_glass_ui_gtk_GtkApplication__1runLoop')
|
||||
f(6,6,2,4,'__writev')
|
||||
f(7,6,2,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(8,6,2,2,'do_syscall_64')
|
||||
f(9,6,2,2,'__x64_sys_writev')
|
||||
f(10,6,2,2,'do_writev')
|
||||
f(11,6,2,2,'vfs_writev')
|
||||
f(12,6,2,2,'do_iter_write')
|
||||
f(13,6,2,2,'do_iter_readv_writev')
|
||||
f(14,6,2,2,'sock_write_iter')
|
||||
f(15,6,2,2,'sock_sendmsg')
|
||||
f(16,6,2,2,'inet_sendmsg')
|
||||
f(17,6,2,2,'tcp_sendmsg')
|
||||
f(18,6,2,2,'tcp_sendmsg_locked')
|
||||
f(19,6,2,2,'tcp_push')
|
||||
f(20,6,2,2,'__tcp_push_pending_frames')
|
||||
f(21,6,2,2,'tcp_write_xmit')
|
||||
f(22,6,2,2,'__tcp_transmit_skb')
|
||||
f(23,6,2,2,'ip_queue_xmit')
|
||||
f(24,6,2,2,'__ip_queue_xmit')
|
||||
f(25,6,2,2,'ip_local_out')
|
||||
f(26,6,2,2,'ip_output')
|
||||
f(27,6,2,2,'ip_finish_output')
|
||||
f(28,6,2,2,'__ip_finish_output')
|
||||
f(29,6,2,2,'ip_finish_output2')
|
||||
f(30,6,2,2,'dev_queue_xmit')
|
||||
f(31,6,2,2,'__dev_queue_xmit')
|
||||
f(32,6,2,2,'sch_direct_xmit')
|
||||
f(33,6,2,2,'dev_hard_start_xmit')
|
||||
f(34,6,2,2,'e1000_xmit_frame?[e1000]')
|
||||
f(5,10,161,0,'com/sun/glass/ui/InvokeLaterDispatcher$Future.run')
|
||||
f(6,10,6,3,'InterpreterRuntime::monitorexit(JavaThread*, BasicObjectLock*)')
|
||||
f(7,10,6,3,'ObjectMonitor::ExitEpilog(Thread*, ObjectWaiter*)')
|
||||
f(8,10,6,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(9,10,6,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(10,10,6,2,'do_syscall_64')
|
||||
f(11,10,6,2,'__x64_sys_futex')
|
||||
f(12,10,6,2,'do_futex')
|
||||
f(13,10,6,2,'wake_up_q')
|
||||
f(14,10,6,2,'try_to_wake_up')
|
||||
f(15,10,6,2,'_raw_spin_unlock_irqrestore')
|
||||
f(6,16,58,0,'com/sun/javafx/application/PlatformImpl$$Lambda$53/233530418.run')
|
||||
f(7,16,58,0,'com/sun/javafx/application/PlatformImpl.lambda$runLater$173')
|
||||
f(8,17,57,0,'java/security/AccessController.doPrivileged')
|
||||
f(9,17,57,0,'com/sun/javafx/application/PlatformImpl$$Lambda$54/1140247440.run')
|
||||
f(10,17,57,0,'com/sun/javafx/application/PlatformImpl.lambda$null$172')
|
||||
f(11,17,55,0,'com/sun/javafx/application/PlatformImpl$$Lambda$52/1364335809.run')
|
||||
f(12,17,55,0,'com/sun/javafx/application/PlatformImpl.lambda$runAndWait$174')
|
||||
f(13,17,4,0,'com/sun/javafx/application/LauncherImpl$$Lambda$57/1790390841.run')
|
||||
f(14,17,4,0,'com/sun/javafx/application/LauncherImpl.lambda$launchApplication1$160')
|
||||
f(15,17,4,0,'java/lang/reflect/Constructor.newInstance')
|
||||
f(16,17,4,0,'sun/reflect/DelegatingConstructorAccessorImpl.newInstance')
|
||||
f(17,17,4,0,'sun/reflect/NativeConstructorAccessorImpl.newInstance')
|
||||
f(18,17,4,0,'sun/reflect/NativeConstructorAccessorImpl.newInstance0')
|
||||
f(19,17,4,0,'demo/parallel/Main.<init>')
|
||||
f(20,17,2,0,'java/lang/ClassLoader.loadClass')
|
||||
f(21,17,2,0,'java/lang/ClassLoader.loadClass')
|
||||
f(22,17,2,0,'java/net/URLClassLoader.findClass')
|
||||
f(23,17,2,0,'java/security/AccessController.doPrivileged')
|
||||
f(24,17,2,0,'java/net/URLClassLoader$1.run')
|
||||
f(25,17,2,0,'java/net/URLClassLoader$1.run')
|
||||
f(26,17,2,0,'java/net/URLClassLoader.access$100')
|
||||
f(27,17,2,0,'java/net/URLClassLoader.defineClass')
|
||||
f(28,17,2,0,'java/security/SecureClassLoader.defineClass')
|
||||
f(13,21,50,0,'com/sun/javafx/application/LauncherImpl$$Lambda$63/508611611.run')
|
||||
f(14,21,50,0,'com/sun/javafx/application/LauncherImpl.lambda$launchApplication1$161')
|
||||
f(15,21,49,0,'demo/parallel/Main.start')
|
||||
f(16,22,17,0,'demo/parallel/Main.createContent')
|
||||
f(17,23,14,0,'demo/parallel/Main.createControlPane')
|
||||
f(18,26,8,0,'javafx/scene/control/Control.<clinit>')
|
||||
f(19,26,8,0,'com/sun/javafx/application/PlatformImpl.setDefaultPlatformUserAgentStylesheet')
|
||||
f(20,26,8,0,'com/sun/javafx/application/PlatformImpl.setPlatformUserAgentStylesheet')
|
||||
f(21,26,8,0,'com/sun/javafx/application/PlatformImpl._setPlatformUserAgentStylesheet')
|
||||
f(22,27,7,0,'java/security/AccessController.doPrivileged')
|
||||
f(23,27,7,0,'com/sun/javafx/application/PlatformImpl$$Lambda$68/360857571.run')
|
||||
f(24,27,7,0,'com/sun/javafx/application/PlatformImpl.lambda$_setPlatformUserAgentStylesheet$181')
|
||||
f(25,27,7,0,'com/sun/javafx/css/StyleManager.setUserAgentStylesheets')
|
||||
f(26,27,6,0,'com/sun/javafx/css/StyleManager._setDefaultUserAgentStylesheet')
|
||||
f(27,28,5,0,'com/sun/javafx/css/StyleManager.loadStylesheet')
|
||||
f(28,28,5,0,'com/sun/javafx/css/StyleManager.loadStylesheetUnPrivileged')
|
||||
f(29,28,2,0,'com/sun/javafx/css/StyleManager.getURL')
|
||||
f(30,28,2,0,'java/lang/Class.forName')
|
||||
f(31,28,2,0,'java/lang/Class.forName0')
|
||||
f(32,28,2,0,'com/sun/javafx/scene/control/skin/Utils.<clinit>')
|
||||
f(29,30,3,0,'com/sun/javafx/css/Stylesheet.loadBinary')
|
||||
f(30,30,3,0,'com/sun/javafx/css/Stylesheet.readBinary')
|
||||
f(31,30,3,0,'com/sun/javafx/css/Rule.readBinary')
|
||||
f(32,30,3,0,'com/sun/javafx/css/Selector.readBinary')
|
||||
f(33,30,2,0,'com/sun/javafx/css/CompoundSelector.readBinary')
|
||||
f(16,40,11,0,'javafx/scene/Scene.<init>')
|
||||
f(17,40,11,0,'javafx/scene/Scene.<init>')
|
||||
f(18,40,11,0,'javafx/scene/Scene.setRoot')
|
||||
f(19,40,11,0,'javafx/beans/property/ObjectPropertyBase.set')
|
||||
f(20,40,11,0,'javafx/beans/property/ObjectPropertyBase.markInvalid')
|
||||
f(21,40,11,0,'javafx/scene/Scene$9.invalidated')
|
||||
f(22,40,11,0,'javafx/scene/Node.setScenes')
|
||||
f(23,40,11,0,'javafx/scene/Node.invalidatedScenes')
|
||||
f(24,40,11,0,'javafx/scene/Node.impl_reapplyCSS')
|
||||
f(25,41,10,0,'javafx/scene/Node.reapplyCss')
|
||||
f(26,42,3,0,'javafx/scene/CssStyleHelper.<clinit>')
|
||||
f(27,42,3,0,'javafx/scene/text/Font.getDefault')
|
||||
f(28,42,3,0,'javafx/scene/text/Font.<init>')
|
||||
f(29,42,3,0,'com/sun/javafx/font/PrismFontLoader.loadFont')
|
||||
f(30,42,2,0,'com/sun/javafx/font/PrismFontFactory.createFont')
|
||||
f(31,42,2,0,'com/sun/javafx/font/LogicalFont.getLogicalFont')
|
||||
f(32,42,2,0,'com/sun/javafx/font/LogicalFont.getLogicalFont')
|
||||
f(33,42,2,0,'com/sun/javafx/font/LogicalFont.<init>')
|
||||
f(34,42,2,0,'com/sun/javafx/font/FontConfigManager.getFontConfigFont')
|
||||
f(35,42,2,0,'com/sun/javafx/font/FontConfigManager.initFontConfigLogFonts')
|
||||
f(36,42,2,0,'com/sun/javafx/font/FontConfigManager.getFontConfig')
|
||||
f(26,45,2,0,'javafx/scene/CssStyleHelper.createStyleHelper')
|
||||
f(26,47,4,0,'javafx/scene/Node.reapplyCss')
|
||||
f(27,47,3,0,'javafx/scene/CssStyleHelper.createStyleHelper')
|
||||
f(28,47,3,0,'com/sun/javafx/css/StyleManager.findMatchingStyles')
|
||||
f(29,47,3,0,'com/sun/javafx/css/StyleManager.gatherParentStylesheets')
|
||||
f(30,47,3,0,'com/sun/javafx/css/StyleManager.processStylesheets')
|
||||
f(31,47,3,0,'com/sun/javafx/css/StyleManager.loadStylesheet')
|
||||
f(32,47,3,0,'com/sun/javafx/css/StyleManager.loadStylesheetUnPrivileged')
|
||||
f(33,48,2,0,'com/sun/javafx/css/parser/CSSParser.parse')
|
||||
f(34,48,2,0,'com/sun/javafx/css/parser/CSSParser.parse')
|
||||
f(16,51,19,0,'javafx/stage/Stage.show')
|
||||
f(17,51,19,0,'javafx/stage/Window.show')
|
||||
f(18,51,19,0,'javafx/stage/Window.setShowing')
|
||||
f(19,51,19,0,'javafx/beans/property/BooleanPropertyBase.set')
|
||||
f(20,51,19,0,'javafx/beans/property/BooleanPropertyBase.markInvalid')
|
||||
f(21,51,19,0,'javafx/stage/Window$9.invalidated')
|
||||
f(22,52,3,0,'javafx/scene/Scene.impl_initPeer')
|
||||
f(22,55,14,0,'javafx/scene/Scene.impl_preferredSize')
|
||||
f(23,55,14,0,'javafx/scene/Scene.preferredSize')
|
||||
f(24,55,11,0,'javafx/scene/Scene.doCSSPass')
|
||||
f(25,55,11,0,'javafx/scene/Node.processCSS')
|
||||
f(26,55,11,0,'javafx/scene/Parent.impl_processCSS')
|
||||
f(27,56,10,0,'javafx/scene/Parent.impl_processCSS')
|
||||
f(28,56,10,0,'javafx/scene/control/Control.impl_processCSS')
|
||||
f(29,56,3,0,'javafx/scene/Parent.impl_processCSS')
|
||||
f(30,56,3,0,'javafx/scene/Node.impl_processCSS')
|
||||
f(31,56,3,0,'javafx/scene/CssStyleHelper.transitionToState')
|
||||
f(29,59,4,0,'javafx/scene/control/Button.createDefaultSkin')
|
||||
f(30,59,4,0,'com/sun/javafx/scene/control/skin/ButtonSkin.<init>')
|
||||
f(31,59,4,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.<init>')
|
||||
f(32,61,2,0,'com/sun/javafx/scene/control/skin/LabeledText.<init>')
|
||||
f(33,61,2,0,'javafx/css/StyleableObjectProperty.bind')
|
||||
f(34,61,2,0,'javafx/beans/property/ObjectPropertyBase.bind')
|
||||
f(35,61,2,0,'javafx/beans/property/ObjectPropertyBase.markInvalid')
|
||||
f(36,61,2,0,'javafx/scene/text/Text$5.invalidated')
|
||||
f(37,61,2,0,'javafx/scene/text/Text.access$200')
|
||||
f(38,61,2,0,'javafx/scene/text/Text.needsFullTextLayout')
|
||||
f(39,61,2,0,'javafx/scene/text/Text.getTextLayout')
|
||||
f(40,61,2,0,'com/sun/javafx/text/PrismTextLayout.setContent')
|
||||
f(41,61,2,0,'com/sun/javafx/font/PrismFont.getStrike')
|
||||
f(42,61,2,0,'com/sun/javafx/font/LogicalFont.getStrike')
|
||||
f(43,61,2,0,'com/sun/javafx/font/LogicalFont.getDefaultAAMode')
|
||||
f(44,61,2,0,'com/sun/javafx/font/LogicalFont.getSlot0Resource')
|
||||
f(45,61,2,0,'com/sun/javafx/font/PrismFontFactory.getFontResource')
|
||||
f(46,61,2,0,'com/sun/javafx/font/PrismFontFactory.getFullNameToFileMap')
|
||||
f(29,64,2,0,'javafx/scene/control/ProgressIndicator.createDefaultSkin')
|
||||
f(24,67,2,0,'javafx/scene/Scene.resizeRootToPreferredSize')
|
||||
f(25,67,2,0,'javafx/scene/Scene.getPreferredWidth')
|
||||
f(26,67,2,0,'javafx/scene/layout/Region.prefWidth')
|
||||
f(27,67,2,0,'javafx/scene/Parent.prefWidth')
|
||||
f(28,67,2,0,'javafx/scene/layout/Region.computePrefWidth')
|
||||
f(29,67,2,0,'javafx/scene/Parent.computePrefWidth')
|
||||
f(30,67,2,0,'javafx/scene/layout/Region.prefWidth')
|
||||
f(31,67,2,0,'javafx/scene/Parent.prefWidth')
|
||||
f(32,67,2,0,'javafx/scene/layout/GridPane.computePrefWidth')
|
||||
f(33,67,2,0,'javafx/scene/layout/GridPane.computePrefWidths')
|
||||
f(34,67,2,0,'javafx/scene/layout/Region.computeChildPrefAreaWidth')
|
||||
f(35,67,2,0,'javafx/scene/layout/Region.minWidth')
|
||||
f(36,67,2,0,'javafx/scene/Parent.minWidth')
|
||||
f(37,67,2,0,'javafx/scene/control/Control.computeMinWidth')
|
||||
f(38,67,2,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.computeMinWidth')
|
||||
f(39,67,2,0,'com/sun/javafx/scene/control/skin/LabeledSkinBase.computeMinLabeledPartWidth')
|
||||
f(40,67,2,0,'com/sun/javafx/scene/control/skin/Utils.computeTextWidth')
|
||||
f(41,67,2,0,'com/sun/javafx/text/PrismTextLayout.getBounds')
|
||||
f(42,67,2,0,'com/sun/javafx/text/PrismTextLayout.ensureLayout')
|
||||
f(43,67,2,0,'com/sun/javafx/text/PrismTextLayout.layout')
|
||||
f(44,67,2,0,'com/sun/javafx/text/PrismTextLayout.buildRuns')
|
||||
f(45,67,2,0,'com/sun/javafx/text/GlyphLayout.breakRuns')
|
||||
f(6,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit$$Lambda$46/1696939523.run')
|
||||
f(7,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.lambda$runToolkit$403')
|
||||
f(8,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulseFromQueue')
|
||||
f(9,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulse')
|
||||
f(10,74,12,0,'com/sun/javafx/tk/quantum/QuantumToolkit.pulse')
|
||||
f(11,74,8,0,'com/sun/javafx/tk/Toolkit.firePulse')
|
||||
f(12,74,6,0,'com/sun/javafx/tk/Toolkit.runPulse')
|
||||
f(13,74,6,0,'java/security/AccessController.doPrivileged')
|
||||
f(14,74,6,0,'com/sun/javafx/tk/Toolkit$$Lambda$193/1813875389.run')
|
||||
f(15,74,6,0,'com/sun/javafx/tk/Toolkit.lambda$runPulse$29')
|
||||
f(16,74,6,0,'javafx/scene/Scene$ScenePulseListener.pulse')
|
||||
f(17,76,4,0,'javafx/scene/Scene.doLayoutPass')
|
||||
f(18,76,4,0,'javafx/scene/Parent.layout')
|
||||
f(19,76,4,0,'demo/parallel/Main$1.layoutChildren')
|
||||
f(20,76,4,0,'javafx/scene/Parent.layoutChildren')
|
||||
f(21,76,4,0,'javafx/scene/Node.autosize')
|
||||
f(22,76,2,0,'javafx/scene/layout/Region.prefHeight')
|
||||
f(23,76,2,0,'javafx/scene/Parent.prefHeight')
|
||||
f(24,76,2,0,'javafx/scene/layout/GridPane.computePrefHeight')
|
||||
f(25,76,2,0,'javafx/scene/layout/GridPane.computePrefHeights')
|
||||
f(22,78,2,0,'javafx/scene/layout/Region.prefWidth')
|
||||
f(23,78,2,0,'javafx/scene/Parent.prefWidth')
|
||||
f(24,78,2,0,'javafx/scene/layout/GridPane.computePrefWidth')
|
||||
f(25,78,2,0,'javafx/scene/layout/GridPane.computePrefWidths')
|
||||
f(11,82,3,0,'com/sun/javafx/tk/quantum/PaintCollector.renderAll')
|
||||
f(12,82,3,0,'com/sun/javafx/tk/quantum/ViewScene.repaint')
|
||||
f(13,82,3,0,'com/sun/javafx/tk/quantum/QuantumToolkit.addRenderJob')
|
||||
f(14,82,3,0,'com/sun/javafx/tk/quantum/QuantumRenderer.submitRenderJob')
|
||||
f(15,82,3,0,'java/util/concurrent/AbstractExecutorService.submit')
|
||||
f(16,82,3,0,'java/util/concurrent/ThreadPoolExecutor.execute')
|
||||
f(17,82,3,0,'java/util/concurrent/LinkedBlockingQueue.offer')
|
||||
f(18,82,3,0,'java/util/concurrent/LinkedBlockingQueue.signalNotEmpty')
|
||||
f(19,82,3,0,'java/util/concurrent/locks/ReentrantLock.unlock')
|
||||
f(20,82,3,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
|
||||
f(21,82,3,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
|
||||
f(22,82,3,0,'java/util/concurrent/locks/LockSupport.unpark')
|
||||
f(23,82,3,0,'sun/misc/Unsafe.unpark')
|
||||
f(24,82,3,4,'Unsafe_Unpark')
|
||||
f(25,82,3,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(26,82,3,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(27,82,3,2,'do_syscall_64')
|
||||
f(28,82,3,2,'__x64_sys_futex')
|
||||
f(29,82,3,2,'do_futex')
|
||||
f(30,82,3,2,'wake_up_q')
|
||||
f(31,82,3,2,'try_to_wake_up')
|
||||
f(32,82,3,2,'_raw_spin_unlock_irqrestore')
|
||||
f(6,86,85,0,'com/sun/javafx/tk/quantum/SceneState$$Lambda$205/1725489206.run')
|
||||
f(7,86,85,0,'com/sun/javafx/tk/quantum/SceneState.lambda$uploadPixels$305')
|
||||
f(8,86,85,0,'com/sun/javafx/tk/quantum/SceneState.access$001')
|
||||
f(9,86,85,0,'com/sun/prism/PresentableState.uploadPixels')
|
||||
f(10,86,85,0,'com/sun/glass/ui/View.uploadPixels')
|
||||
f(11,86,85,0,'com/sun/glass/ui/gtk/GtkView._uploadPixels')
|
||||
f(12,86,85,0,'com/sun/glass/ui/gtk/GtkView._uploadPixelsDirect')
|
||||
f(13,86,85,4,'Java_com_sun_glass_ui_gtk_GtkView__1uploadPixelsDirect')
|
||||
f(14,86,85,4,'__writev')
|
||||
f(15,86,85,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(16,86,85,2,'do_syscall_64')
|
||||
f(17,86,85,2,'__x64_sys_writev')
|
||||
f(18,86,85,2,'do_writev')
|
||||
f(19,86,85,2,'vfs_writev')
|
||||
f(20,86,85,2,'do_iter_write')
|
||||
f(21,86,85,2,'do_iter_readv_writev')
|
||||
f(22,86,85,2,'sock_write_iter')
|
||||
f(23,86,85,2,'sock_sendmsg')
|
||||
f(24,86,85,2,'inet_sendmsg')
|
||||
f(25,86,85,2,'tcp_sendmsg')
|
||||
f(26,86,3,2,'lock_sock_nested')
|
||||
f(27,86,3,2,'_raw_spin_lock_bh')
|
||||
f(28,86,3,2,'queued_spin_lock_slowpath')
|
||||
f(29,86,3,2,'native_queued_spin_lock_slowpath')
|
||||
f(26,90,81,2,'tcp_sendmsg_locked')
|
||||
f(27,90,3,2,'__sk_flush_backlog')
|
||||
f(28,90,3,2,'__release_sock')
|
||||
f(29,90,3,2,'tcp_v4_do_rcv')
|
||||
f(30,90,3,2,'tcp_rcv_established')
|
||||
f(31,90,3,2,'__tcp_push_pending_frames')
|
||||
f(32,90,3,2,'tcp_write_xmit')
|
||||
f(33,90,3,2,'__tcp_transmit_skb')
|
||||
f(34,90,3,2,'ip_queue_xmit')
|
||||
f(35,90,3,2,'__ip_queue_xmit')
|
||||
f(36,90,3,2,'ip_local_out')
|
||||
f(37,90,3,2,'ip_output')
|
||||
f(38,90,3,2,'ip_finish_output')
|
||||
f(39,90,3,2,'__ip_finish_output')
|
||||
f(40,90,3,2,'ip_finish_output2')
|
||||
f(41,90,3,2,'dev_queue_xmit')
|
||||
f(42,90,3,2,'__dev_queue_xmit')
|
||||
f(43,90,3,2,'sch_direct_xmit')
|
||||
f(44,90,3,2,'dev_hard_start_xmit')
|
||||
f(45,90,3,2,'e1000_xmit_frame?[e1000]')
|
||||
f(27,93,78,2,'tcp_push_one')
|
||||
f(28,93,78,2,'tcp_write_xmit')
|
||||
f(29,93,78,2,'__tcp_transmit_skb')
|
||||
f(30,93,78,2,'ip_queue_xmit')
|
||||
f(31,93,78,2,'__ip_queue_xmit')
|
||||
f(32,93,78,2,'ip_local_out')
|
||||
f(33,93,78,2,'ip_output')
|
||||
f(34,93,78,2,'ip_finish_output')
|
||||
f(35,93,78,2,'__ip_finish_output')
|
||||
f(36,93,78,2,'ip_finish_output2')
|
||||
f(37,93,9,2,'__local_bh_enable_ip')
|
||||
f(38,93,9,2,'do_softirq')
|
||||
f(39,93,9,2,'do_softirq_own_stack')
|
||||
f(40,93,9,2,'__softirqentry_text_start')
|
||||
f(41,93,9,2,'net_rx_action')
|
||||
f(42,93,9,2,'e1000_clean?[e1000]')
|
||||
f(37,102,69,2,'dev_queue_xmit')
|
||||
f(38,102,69,2,'__dev_queue_xmit')
|
||||
f(39,102,69,2,'sch_direct_xmit')
|
||||
f(40,102,69,2,'dev_hard_start_xmit')
|
||||
f(41,102,69,2,'e1000_xmit_frame?[e1000]')
|
||||
f(5,171,3,0,'com/sun/glass/ui/View.notifyMouse')
|
||||
f(6,171,3,0,'com/sun/glass/ui/View.handleMouseEvent')
|
||||
f(7,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler.handleMouseEvent')
|
||||
f(8,171,3,0,'com/sun/javafx/tk/quantum/QuantumToolkit.runWithoutRenderLock')
|
||||
f(9,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$$Lambda$181/241733715.get')
|
||||
f(10,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler.lambda$handleMouseEvent$353')
|
||||
f(11,171,3,0,'java/security/AccessController.doPrivileged')
|
||||
f(12,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$MouseEventNotification.run')
|
||||
f(13,171,3,0,'com/sun/javafx/tk/quantum/GlassViewEventHandler$MouseEventNotification.run')
|
||||
f(14,171,3,0,'javafx/scene/Scene$ScenePeerListener.mouseEvent')
|
||||
f(15,171,3,0,'javafx/scene/Scene.impl_processMouseEvent')
|
||||
f(16,171,3,0,'javafx/scene/Scene$MouseHandler.access$1500')
|
||||
f(17,171,3,0,'javafx/scene/Scene$MouseHandler.process')
|
||||
f(18,172,2,0,'javafx/scene/Scene.access$6700')
|
||||
f(19,172,2,0,'javafx/scene/Scene.pick')
|
||||
f(20,172,2,0,'javafx/scene/Scene$MouseHandler.access$1600')
|
||||
f(21,172,2,0,'javafx/scene/Scene$MouseHandler.pickNode')
|
||||
f(22,172,2,0,'javafx/scene/Node.impl_pickNode')
|
||||
f(23,172,2,0,'javafx/scene/layout/Region.impl_pickNodeLocal')
|
||||
f(5,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit$$Lambda$47/605813029.run')
|
||||
f(6,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit.lambda$runToolkit$404')
|
||||
f(7,174,2,0,'com/sun/javafx/tk/quantum/QuantumToolkit.postPulse')
|
||||
f(8,174,2,0,'com/sun/glass/ui/Application.invokeLater')
|
||||
f(9,174,2,0,'com/sun/glass/ui/gtk/GtkApplication._invokeLater')
|
||||
f(10,174,2,0,'com/sun/glass/ui/InvokeLaterDispatcher.invokeLater')
|
||||
f(11,174,2,0,'java/util/concurrent/LinkedBlockingDeque.addLast')
|
||||
f(12,174,2,0,'java/util/concurrent/LinkedBlockingDeque.offerLast')
|
||||
f(13,174,2,0,'java/util/concurrent/locks/ReentrantLock.unlock')
|
||||
f(14,174,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
|
||||
f(15,174,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
|
||||
f(16,174,2,0,'java/util/concurrent/locks/LockSupport.unpark')
|
||||
f(17,174,2,0,'sun/misc/Unsafe.unpark')
|
||||
f(18,174,2,4,'Unsafe_Unpark')
|
||||
f(19,174,2,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(20,174,2,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(21,174,2,2,'do_syscall_64')
|
||||
f(22,174,2,2,'__x64_sys_futex')
|
||||
f(23,174,2,2,'do_futex')
|
||||
f(24,174,2,2,'wake_up_q')
|
||||
f(25,174,2,2,'try_to_wake_up')
|
||||
f(26,174,2,2,'_raw_spin_unlock_irqrestore')
|
||||
f(5,176,8,4,'recvmsg')
|
||||
f(6,176,8,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(7,176,8,2,'do_syscall_64')
|
||||
f(8,176,8,2,'__x64_sys_recvmsg')
|
||||
f(9,176,8,2,'__sys_recvmsg')
|
||||
f(10,176,8,2,'___sys_recvmsg')
|
||||
f(11,176,8,2,'sock_recvmsg')
|
||||
f(12,176,8,2,'inet_recvmsg')
|
||||
f(13,176,8,2,'tcp_recvmsg')
|
||||
f(14,176,3,2,'lock_sock_nested')
|
||||
f(15,176,3,2,'_raw_spin_lock_bh')
|
||||
f(16,176,3,2,'queued_spin_lock_slowpath')
|
||||
f(17,176,3,2,'native_queued_spin_lock_slowpath')
|
||||
f(14,180,4,2,'tcp_cleanup_rbuf')
|
||||
f(15,180,4,2,'tcp_send_ack')
|
||||
f(16,180,4,2,'__tcp_send_ack.part.45')
|
||||
f(17,180,4,2,'__tcp_transmit_skb')
|
||||
f(18,180,4,2,'ip_queue_xmit')
|
||||
f(19,180,4,2,'__ip_queue_xmit')
|
||||
f(20,180,4,2,'ip_local_out')
|
||||
f(21,180,4,2,'ip_output')
|
||||
f(22,180,4,2,'ip_finish_output')
|
||||
f(23,180,4,2,'__ip_finish_output')
|
||||
f(24,180,4,2,'ip_finish_output2')
|
||||
f(25,180,4,2,'dev_queue_xmit')
|
||||
f(26,180,4,2,'__dev_queue_xmit')
|
||||
f(27,180,4,2,'sch_direct_xmit')
|
||||
f(28,180,4,2,'dev_hard_start_xmit')
|
||||
f(29,180,4,2,'e1000_xmit_frame?[e1000]')
|
||||
f(2,184,45,0,'com/sun/javafx/tk/quantum/QuantumRenderer$PipelineRunnable.run')
|
||||
f(3,184,45,0,'java/util/concurrent/ThreadPoolExecutor$Worker.run')
|
||||
f(4,184,45,0,'java/util/concurrent/ThreadPoolExecutor.runWorker')
|
||||
f(5,184,45,0,'com/sun/javafx/tk/RenderJob.run')
|
||||
f(6,184,45,0,'java/util/concurrent/FutureTask.runAndReset')
|
||||
f(7,184,45,0,'java/util/concurrent/Executors$RunnableAdapter.call')
|
||||
f(8,184,45,0,'com/sun/javafx/tk/quantum/UploadingPainter.run')
|
||||
f(9,184,2,0,'com/sun/javafx/tk/quantum/SceneState.uploadPixels')
|
||||
f(10,184,2,0,'com/sun/glass/ui/Application.invokeLater')
|
||||
f(11,184,2,0,'com/sun/glass/ui/gtk/GtkApplication._invokeLater')
|
||||
f(12,184,2,0,'com/sun/glass/ui/InvokeLaterDispatcher.invokeLater')
|
||||
f(13,184,2,0,'java/util/concurrent/LinkedBlockingDeque.addLast')
|
||||
f(14,184,2,0,'java/util/concurrent/LinkedBlockingDeque.offerLast')
|
||||
f(15,184,2,0,'java/util/concurrent/locks/ReentrantLock.unlock')
|
||||
f(16,184,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.release')
|
||||
f(17,184,2,0,'java/util/concurrent/locks/AbstractQueuedSynchronizer.unparkSuccessor')
|
||||
f(18,184,2,0,'java/util/concurrent/locks/LockSupport.unpark')
|
||||
f(19,184,2,0,'sun/misc/Unsafe.unpark')
|
||||
f(20,184,2,4,'Unsafe_Unpark')
|
||||
f(21,184,2,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(22,184,2,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(23,184,2,2,'do_syscall_64')
|
||||
f(24,184,2,2,'__x64_sys_futex')
|
||||
f(25,184,2,2,'do_futex')
|
||||
f(26,184,2,2,'wake_up_q')
|
||||
f(27,184,2,2,'try_to_wake_up')
|
||||
f(28,184,2,2,'_raw_spin_unlock_irqrestore')
|
||||
f(9,186,40,0,'com/sun/javafx/tk/quantum/ViewPainter.paintImpl')
|
||||
f(10,188,38,0,'com/sun/javafx/tk/quantum/ViewPainter.doPaint')
|
||||
f(11,188,38,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(12,188,38,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(13,188,38,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(14,188,38,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
|
||||
f(15,188,38,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(16,188,38,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(17,188,21,0,'com/sun/javafx/sg/prism/NGCanvas.renderContent')
|
||||
f(18,188,15,0,'com/sun/javafx/sg/prism/NGCanvas.renderStream')
|
||||
f(19,188,15,0,'com/sun/javafx/sg/prism/NGCanvas.handleRenderOp')
|
||||
f(20,188,11,0,'com/sun/prism/impl/BaseResourceFactory.getCachedTexture')
|
||||
f(21,188,11,0,'com/sun/prism/impl/BaseResourceFactory.getCachedTexture')
|
||||
f(22,189,10,0,'com/sun/prism/sw/SWTexture.update')
|
||||
f(23,189,10,0,'com/sun/prism/sw/SWArgbPreTexture.update')
|
||||
f(24,189,10,0,'com/sun/javafx/image/impl/BaseByteToIntConverter.convert')
|
||||
f(25,189,10,0,'com/sun/javafx/image/impl/BaseByteToIntConverter.convert')
|
||||
f(26,189,10,0,'com/sun/javafx/image/impl/ByteBgra$ToIntArgbSameConv.doConvert')
|
||||
f(20,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(21,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(22,199,3,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(23,199,3,0,'com/sun/pisces/PiscesRenderer.drawImage')
|
||||
f(24,199,3,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
|
||||
f(25,199,3,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
|
||||
f(26,199,3,4,'fillRect')
|
||||
f(27,200,2,4,'emitLinePTSourceOver8888_pre')
|
||||
f(18,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(19,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(20,203,6,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(21,203,6,0,'com/sun/pisces/PiscesRenderer.drawImage')
|
||||
f(22,203,6,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
|
||||
f(23,203,6,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
|
||||
f(24,203,6,4,'fillRect')
|
||||
f(25,204,5,4,'emitLinePTSourceOver8888_pre')
|
||||
f(17,209,17,0,'com/sun/javafx/sg/prism/NGNode.renderOpacity')
|
||||
f(18,210,10,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(19,210,10,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
|
||||
f(20,210,10,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(21,210,10,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(22,210,10,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(23,210,8,0,'com/sun/javafx/sg/prism/NGGroup.renderContent')
|
||||
f(24,210,8,0,'com/sun/javafx/sg/prism/NGNode.render')
|
||||
f(25,210,8,0,'com/sun/javafx/sg/prism/NGNode.doRender')
|
||||
f(26,210,3,0,'com/sun/javafx/sg/prism/NGRegion.renderContent')
|
||||
f(27,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderAsRectangle')
|
||||
f(28,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectangle')
|
||||
f(29,211,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectanglesDirectly')
|
||||
f(30,211,2,0,'com/sun/prism/sw/SWGraphics.fill')
|
||||
f(31,211,2,0,'com/sun/prism/sw/SWGraphics.paintShape')
|
||||
f(32,211,2,0,'com/sun/prism/sw/SWGraphics.paintShapePaintAlreadySet')
|
||||
f(33,211,2,0,'com/sun/prism/sw/SWContext.renderShape')
|
||||
f(34,211,2,0,'com/sun/prism/sw/SWContext$JavaShapeRenderer.renderShape')
|
||||
f(35,211,2,0,'com/sun/prism/impl/shape/OpenPiscesPrismUtils.setupRenderer')
|
||||
f(26,213,5,0,'com/sun/javafx/sg/prism/NGShape.renderContent')
|
||||
f(27,213,5,0,'com/sun/javafx/sg/prism/NGText.renderContent2D')
|
||||
f(28,213,4,0,'com/sun/javafx/sg/prism/NGText.renderText')
|
||||
f(29,213,4,0,'com/sun/prism/sw/SWGraphics.drawString')
|
||||
f(30,214,3,0,'com/sun/prism/sw/SWGraphics.drawGlyph')
|
||||
f(23,218,2,0,'com/sun/javafx/sg/prism/NGRegion.renderAsRectangle')
|
||||
f(24,218,2,0,'com/sun/javafx/sg/prism/NGRegion.renderBackgroundRectangle')
|
||||
f(18,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(19,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(20,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(21,220,5,0,'com/sun/prism/sw/SWGraphics.drawTexture')
|
||||
f(22,220,5,0,'com/sun/pisces/PiscesRenderer.drawImage')
|
||||
f(23,220,5,0,'com/sun/pisces/PiscesRenderer.drawImageImpl')
|
||||
f(24,220,5,4,'Java_com_sun_pisces_PiscesRenderer_drawImageImpl')
|
||||
f(25,220,5,4,'fillRect')
|
||||
f(26,220,3,4,'emitLinePTSourceOver8888_pre')
|
||||
f(26,223,2,4,'genTexturePaintMultiply')
|
||||
f(9,227,2,0,'java/nio/DirectIntBufferU.put')
|
||||
f(10,227,2,0,'java/nio/Bits.copyFromArray')
|
||||
f(11,227,2,0,'sun/misc/Unsafe.copyMemory')
|
||||
f(12,227,2,4,'acl_CopyRight')
|
||||
f(2,229,184,0,'java/util/concurrent/FutureTask.run')
|
||||
f(3,229,184,0,'javafx/concurrent/Task$TaskCallable.call')
|
||||
f(4,229,184,0,'demo/parallel/MandelbrotSetTask.call')
|
||||
f(5,229,184,0,'demo/parallel/MandelbrotSetTask.call')
|
||||
f(6,233,175,0,'java/util/stream/IntPipeline$Head.forEach')
|
||||
f(7,233,68,0,'java/util/stream/IntPipeline.forEach')
|
||||
f(8,233,68,0,'java/util/stream/AbstractPipeline.evaluate')
|
||||
f(9,233,68,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.evaluateParallel')
|
||||
f(10,233,68,0,'java/util/stream/ForEachOps$ForEachOp.evaluateParallel')
|
||||
f(11,233,68,0,'java/util/concurrent/ForkJoinTask.invoke')
|
||||
f(12,233,68,0,'java/util/concurrent/ForkJoinTask.doInvoke')
|
||||
f(13,233,25,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(14,233,25,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(15,233,25,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(16,233,24,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(17,233,24,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(18,233,24,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(19,233,24,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(20,233,24,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(21,233,24,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(22,234,2,3,'SharedRuntime::complete_monitor_locking_C(oopDesc*, BasicLock*, JavaThread*)')
|
||||
f(23,234,2,3,'ObjectMonitor::enter(Thread*)')
|
||||
f(24,234,2,3,'ObjectMonitor::TrySpin_VaryDuration(Thread*)')
|
||||
f(22,236,21,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(23,237,19,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(24,237,19,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(25,248,5,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(25,253,3,0,'demo/parallel/Complex.plus')
|
||||
f(13,258,43,0,'java/util/concurrent/ForkJoinTask.externalAwaitDone')
|
||||
f(14,258,43,0,'java/util/concurrent/ForkJoinPool.externalHelpComplete')
|
||||
f(15,258,43,0,'java/util/concurrent/ForkJoinPool.helpComplete')
|
||||
f(16,258,14,0,'java/util/concurrent/ForkJoinPool$WorkQueue.pollAndExecCC')
|
||||
f(17,258,14,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(18,258,14,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(19,258,14,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(20,258,14,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(21,258,14,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(22,258,14,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(23,258,14,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(24,258,14,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(25,258,14,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(26,258,14,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(27,259,13,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(28,259,13,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(29,267,4,0,'demo/parallel/Complex.plus')
|
||||
f(16,272,29,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(17,272,29,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(18,272,29,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(19,272,29,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(20,272,29,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(21,272,29,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(22,272,29,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(23,272,29,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(24,272,29,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(25,273,28,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(26,274,27,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(27,274,27,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(28,287,3,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(28,290,8,0,'demo/parallel/Complex.plus')
|
||||
f(28,298,3,0,'demo/parallel/Complex.times')
|
||||
f(7,301,107,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(8,301,107,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(9,301,107,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(10,301,105,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(11,309,97,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(12,312,94,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(13,357,23,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(13,380,16,0,'demo/parallel/Complex.plus')
|
||||
f(13,396,10,0,'demo/parallel/Complex.times')
|
||||
f(6,408,5,0,'javafx/scene/image/WritableImage$2.setColor')
|
||||
f(7,408,3,0,'java/lang/Math.round')
|
||||
f(7,411,2,0,'javafx/scene/image/WritableImage$2.setArgb')
|
||||
f(8,411,2,0,'com/sun/prism/Image.setArgb')
|
||||
f(1,413,163,0,'java/util/concurrent/ForkJoinWorkerThread.run')
|
||||
f(2,413,163,0,'java/util/concurrent/ForkJoinPool.runWorker')
|
||||
f(3,413,163,0,'java/util/concurrent/ForkJoinPool$WorkQueue.runTask')
|
||||
f(4,413,38,0,'java/util/concurrent/ForkJoinPool$WorkQueue.execLocalTasks')
|
||||
f(5,413,38,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(6,413,38,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(7,413,38,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(8,413,38,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(9,413,38,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(10,413,38,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(11,413,38,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(12,413,38,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(13,413,38,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(14,415,32,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(15,421,26,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(16,424,22,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(17,432,7,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(17,439,7,0,'demo/parallel/Complex.plus')
|
||||
f(14,447,4,0,'javafx/scene/image/WritableImage$2.setColor')
|
||||
f(15,448,3,0,'javafx/scene/image/WritableImage$2.setArgb')
|
||||
f(16,449,2,0,'javafx/scene/image/Image.getWritablePlatformImage')
|
||||
f(4,451,125,0,'java/util/concurrent/ForkJoinTask.doExec')
|
||||
f(5,451,125,0,'java/util/concurrent/CountedCompleter.exec')
|
||||
f(6,451,125,0,'java/util/stream/ForEachOps$ForEachTask.compute')
|
||||
f(7,452,124,0,'java/util/stream/AbstractPipeline.copyInto')
|
||||
f(8,452,124,0,'java/util/Spliterator$OfInt.forEachRemaining')
|
||||
f(9,452,124,0,'java/util/stream/Streams$RangeIntSpliterator.forEachRemaining')
|
||||
f(10,452,124,0,'java/util/stream/ForEachOps$ForEachOp$OfInt.accept')
|
||||
f(11,452,124,0,'demo/parallel/MandelbrotSetTask$$Lambda$99/105194951.accept')
|
||||
f(12,452,124,0,'demo/parallel/MandelbrotSetTask.lambda$call$19')
|
||||
f(13,454,119,0,'demo/parallel/MandelbrotSetTask.calcAntialiasedPixel')
|
||||
f(14,462,111,0,'demo/parallel/MandelbrotSetTask.calcPixel')
|
||||
f(15,464,109,0,'demo/parallel/MandelbrotSetTask.calc')
|
||||
f(16,504,39,0,'demo/parallel/Complex.lengthSQ')
|
||||
f(16,543,22,0,'demo/parallel/Complex.plus')
|
||||
f(16,565,8,0,'demo/parallel/Complex.times')
|
||||
f(13,573,3,0,'javafx/scene/image/WritableImage$2.setColor')
|
||||
f(1,576,65,4,'start_thread')
|
||||
f(2,576,65,4,'java_start(Thread*)')
|
||||
f(3,576,4,3,'GCTaskThread::run()')
|
||||
f(4,577,3,3,'OldToYoungRootsTask::do_it(GCTaskManager*, unsigned int)')
|
||||
f(5,577,3,3,'CardTableExtension::scavenge_contents_parallel(ObjectStartArray*, MutableSpace*, HeapWord*, PSPromotionManager*, unsigned int, unsigned int)')
|
||||
f(6,577,3,3,'PSPromotionManager::drain_stacks_depth(bool)')
|
||||
f(7,578,2,3,'oopDesc* PSPromotionManager::copy_to_survivor_space<false>(oopDesc*)')
|
||||
f(3,580,61,3,'JavaThread::run()')
|
||||
f(4,580,61,3,'JavaThread::thread_main_inner()')
|
||||
f(5,580,61,3,'CompileBroker::compiler_thread_loop()')
|
||||
f(6,580,58,3,'CompileBroker::invoke_compiler_on_method(CompileTask*)')
|
||||
f(7,580,58,3,'C2Compiler::compile_method(ciEnv*, ciMethod*, int)')
|
||||
f(8,580,58,3,'Compile::Compile(ciEnv*, C2Compiler*, ciMethod*, int, bool, bool, bool)')
|
||||
f(9,580,32,3,'Compile::Code_Gen()')
|
||||
f(10,582,3,3,'PhaseCFG::do_global_code_motion()')
|
||||
f(11,582,3,3,'PhaseCFG::global_code_motion()')
|
||||
f(10,585,27,3,'PhaseChaitin::Register_Allocate()')
|
||||
f(11,585,2,3,'PhaseChaitin::Select()')
|
||||
f(12,585,2,3,'PhaseIFG::re_insert(unsigned int)')
|
||||
f(11,589,9,3,'PhaseChaitin::build_ifg_physical(ResourceArea*)')
|
||||
f(12,592,3,3,'PhaseChaitin::interfere_with_live(unsigned int, IndexSet*)')
|
||||
f(12,596,2,3,'RegMask::smear_to_sets(int)')
|
||||
f(11,599,3,3,'PhaseChaitin::gather_lrg_masks(bool)')
|
||||
f(11,602,5,3,'PhaseChaitin::post_allocate_copy_removal()')
|
||||
f(12,604,2,3,'PhaseChaitin::elide_copy(Node*, int, Block*, Node_List&, Node_List&, bool)')
|
||||
f(11,609,2,3,'PhaseIFG::init(unsigned int)')
|
||||
f(12,609,2,3,'IndexSet::initialize(unsigned int)')
|
||||
f(9,613,15,3,'Compile::Optimize()')
|
||||
f(10,614,13,3,'PhaseIdealLoop::build_and_optimize(bool, bool)')
|
||||
f(11,617,2,3,'PhaseIdealLoop::build_loop_early(VectorSet&, Node_List&, Node_Stack&)')
|
||||
f(11,619,4,3,'PhaseIdealLoop::build_loop_late(VectorSet&, Node_List&, Node_Stack&)')
|
||||
f(11,625,2,3,'PhaseIterGVN::optimize()')
|
||||
f(12,625,2,3,'PhaseIterGVN::transform_old(Node*)')
|
||||
f(9,629,3,3,'ParseGenerator::generate(JVMState*)')
|
||||
f(10,629,3,3,'Parse::Parse(JVMState*, ciMethod*, float)')
|
||||
f(11,629,3,3,'Parse::do_all_blocks()')
|
||||
f(12,629,3,3,'Parse::do_one_block()')
|
||||
f(13,629,3,3,'Parse::do_one_bytecode()')
|
||||
f(14,629,3,3,'Parse::do_call()')
|
||||
f(15,630,2,3,'PredictedCallGenerator::generate(JVMState*)')
|
||||
f(16,630,2,3,'ParseGenerator::generate(JVMState*)')
|
||||
f(17,630,2,3,'Parse::Parse(JVMState*, ciMethod*, float)')
|
||||
f(18,630,2,3,'Parse::do_all_blocks()')
|
||||
f(19,630,2,3,'Parse::do_one_block()')
|
||||
f(20,630,2,3,'Parse::do_one_bytecode()')
|
||||
f(21,630,2,3,'Parse::do_call()')
|
||||
f(9,632,6,3,'ciEnv::register_method(ciMethod*, int, CodeOffsets*, int, CodeBuffer*, int, OopMapSet*, ExceptionHandlerTable*, ImplicitExceptionTable*, AbstractCompiler*, int, bool, bool, RTMState)')
|
||||
f(10,632,6,3,'nmethod::post_compiled_method_load_event()')
|
||||
f(11,632,6,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(12,632,6,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(13,632,6,2,'do_syscall_64')
|
||||
f(14,632,6,2,'__x64_sys_futex')
|
||||
f(15,632,6,2,'do_futex')
|
||||
f(16,632,6,2,'wake_up_q')
|
||||
f(17,632,6,2,'try_to_wake_up')
|
||||
f(18,632,6,2,'_raw_spin_unlock_irqrestore')
|
||||
f(6,638,3,3,'CompileQueue::get()')
|
||||
f(7,638,3,4,'pthread_cond_signal@@GLIBC_2.3.2')
|
||||
f(8,638,3,2,'entry_SYSCALL_64_after_hwframe')
|
||||
f(9,638,3,2,'do_syscall_64')
|
||||
f(10,638,3,2,'__x64_sys_futex')
|
||||
f(11,638,3,2,'do_futex')
|
||||
f(12,638,3,2,'wake_up_q')
|
||||
f(13,638,3,2,'try_to_wake_up')
|
||||
f(14,638,3,2,'_raw_spin_unlock_irqrestore')
|
||||
render();
|
||||
</script></body></html>
|
||||
BIN
demo/flamegraph.png
Normal file
BIN
demo/flamegraph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
119
pom-converter.xml
Normal file
119
pom-converter.xml
Normal file
@@ -0,0 +1,119 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler-converter</artifactId>
|
||||
<version>2.9</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
<url>https://profiler.tools</url>
|
||||
<description>Low overhead sampling profiler for Java</description>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>https://github.com/jvm-profiling-tools/async-profiler</url>
|
||||
<connection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</developerConnection>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>apangin</id>
|
||||
<name>Andrei Pangin</name>
|
||||
<email>noreply@pangin.pro</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/converter</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/res</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>7</source>
|
||||
<target>7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>Main</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
37
pom.xml
37
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler</artifactId>
|
||||
<version>1.8.3</version>
|
||||
<version>2.9</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
@@ -36,20 +36,51 @@
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/api</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>native</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<source>7</source>
|
||||
<target>7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<classifier>${native.platform}</classifier>
|
||||
<includes>
|
||||
<include>${native.platform}/*</include>
|
||||
<include>one/**</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>linux*/**</exclude>
|
||||
<exclude>macos*/**</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
|
||||
179
profiler.sh
179
profiler.sh
@@ -7,9 +7,10 @@ usage() {
|
||||
echo " start start profiling and return immediately"
|
||||
echo " resume resume profiling without resetting collected data"
|
||||
echo " stop stop profiling"
|
||||
echo " jstack get a thread dump"
|
||||
echo " dump dump collected data without stopping profiling session"
|
||||
echo " check check if the specified profiling event is available"
|
||||
echo " status print profiling status"
|
||||
echo " meminfo print profiler memory stats"
|
||||
echo " list list profiling events supported by the target JVM"
|
||||
echo " collect collect profile for the specified period of time"
|
||||
echo " and then stop (default action)"
|
||||
@@ -22,8 +23,9 @@ usage() {
|
||||
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 " -a annotate Java methods"
|
||||
echo " -l prepend library names"
|
||||
echo " -o fmt output format: flat|traces|collapsed|flamegraph|tree|jfr"
|
||||
echo " -I include output only stack traces containing the specified pattern"
|
||||
echo " -X exclude exclude stack traces with the specified pattern"
|
||||
echo " -v, --version display version string"
|
||||
@@ -32,17 +34,27 @@ usage() {
|
||||
echo " --minwidth pct skip frames smaller than pct%"
|
||||
echo " --reverse generate stack-reversed FlameGraph / Call tree"
|
||||
echo ""
|
||||
echo " --all-kernel only include kernel-mode events"
|
||||
echo " --loop time run profiler in a loop"
|
||||
echo " --alloc bytes allocation profiling interval in bytes"
|
||||
echo " --live build allocation profile from live objects only"
|
||||
echo " --lock duration lock profiling threshold in nanoseconds"
|
||||
echo " --total accumulate the total value (time, bytes, etc.)"
|
||||
echo " --all-user only include user-mode events"
|
||||
echo " --cstack mode how to traverse C stack: fp|lbr|no"
|
||||
echo " --sched group threads by scheduling policy"
|
||||
echo " --cstack mode how to traverse C stack: fp|dwarf|lbr|no"
|
||||
echo " --begin function begin profiling when function is executed"
|
||||
echo " --end function end profiling when function is executed"
|
||||
echo " --ttsp time-to-safepoint profiling"
|
||||
echo " --jfrsync config synchronize profiler with JFR recording"
|
||||
echo " --lib path full path to libasyncProfiler.so in the container"
|
||||
echo " --fdtransfer use fdtransfer to serve perf requests"
|
||||
echo " from the non-privileged target"
|
||||
echo ""
|
||||
echo "<pid> is a numeric process ID of the target JVM"
|
||||
echo " or 'jps' keyword to find running JVM automatically"
|
||||
echo " or the application's name as it would appear in the jps tool"
|
||||
echo ""
|
||||
echo "Example: $0 -d 30 -f profile.svg 3456"
|
||||
echo "Example: $0 -d 30 -f profile.html 3456"
|
||||
echo " $0 start -i 999000 jps"
|
||||
echo " $0 stop -o flat jps"
|
||||
echo " $0 -d 5 -e alloc MyAppName"
|
||||
@@ -55,10 +67,25 @@ mirror_output() {
|
||||
if [ -f "$FILE" ]; then
|
||||
cat "$FILE"
|
||||
rm "$FILE"
|
||||
elif [ -f "$ROOT_PREFIX$FILE" ]; then
|
||||
cat "$ROOT_PREFIX$FILE"
|
||||
rm "$ROOT_PREFIX$FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
mirror_log() {
|
||||
# Try to access the log file both directly and through /proc/[pid]/root,
|
||||
# in case the target namespace differs
|
||||
if [ -f "$LOG" ]; then
|
||||
cat "$LOG" >&2
|
||||
rm "$LOG"
|
||||
elif [ -f "$ROOT_PREFIX$LOG" ]; then
|
||||
cat "$ROOT_PREFIX$LOG" >&2
|
||||
rm "$ROOT_PREFIX$LOG"
|
||||
fi
|
||||
}
|
||||
|
||||
check_if_terminated() {
|
||||
if ! kill -0 "$PID" 2> /dev/null; then
|
||||
mirror_output
|
||||
@@ -66,34 +93,51 @@ check_if_terminated() {
|
||||
fi
|
||||
}
|
||||
|
||||
fdtransfer() {
|
||||
if [ "$USE_FDTRANSFER" = "true" ]; then
|
||||
FDTRANSFER_PATH="@async-profiler-$(od -An -N3 -i /dev/random | xargs)"
|
||||
PARAMS="$PARAMS,fdtransfer=$FDTRANSFER_PATH"
|
||||
"$FDTRANSFER" "$FDTRANSFER_PATH" "$PID"
|
||||
fi
|
||||
}
|
||||
|
||||
jattach() {
|
||||
set +e
|
||||
"$JATTACH" "$PID" load "$PROFILER" true "$1" > /dev/null
|
||||
"$JATTACH" "$PID" load "$PROFILER" true "$1,log=$LOG" > /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
|
||||
if [ "$UNAME_S" = "Darwin" ]; then
|
||||
otool -L "$PROFILER"
|
||||
else
|
||||
ldd "$PROFILER"
|
||||
LD_PRELOAD="$PROFILER" /bin/true
|
||||
fi
|
||||
fi
|
||||
|
||||
mirror_log
|
||||
exit $RET
|
||||
fi
|
||||
|
||||
mirror_log
|
||||
mirror_output
|
||||
set -e
|
||||
}
|
||||
|
||||
OPTIND=1
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" > /dev/null 2>&1; pwd -P)"
|
||||
SCRIPT_BIN="$0"
|
||||
while [ -h "$SCRIPT_BIN" ]; do
|
||||
SCRIPT_BIN="$(readlink "$SCRIPT_BIN")"
|
||||
done
|
||||
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_BIN")" > /dev/null 2>&1; pwd -P)"
|
||||
|
||||
JATTACH=$SCRIPT_DIR/build/jattach
|
||||
FDTRANSFER=$SCRIPT_DIR/build/fdtransfer
|
||||
USE_FDTRANSFER="false"
|
||||
FDTRANSFER_PATH=""
|
||||
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
|
||||
ACTION="collect"
|
||||
EVENT="cpu"
|
||||
DURATION="60"
|
||||
FILE=""
|
||||
USE_TMP="true"
|
||||
@@ -107,19 +151,14 @@ while [ $# -gt 0 ]; do
|
||||
-h|"-?")
|
||||
usage
|
||||
;;
|
||||
start|resume|stop|check|status|list|collect)
|
||||
start|resume|stop|dump|check|status|meminfo|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
jstack)
|
||||
ACTION="start"
|
||||
EVENT="jstack"
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
-e)
|
||||
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
|
||||
PARAMS="$PARAMS,event=$2"
|
||||
shift
|
||||
;;
|
||||
-d)
|
||||
@@ -151,6 +190,9 @@ while [ $# -gt 0 ]; do
|
||||
-a)
|
||||
FORMAT="$FORMAT,ann"
|
||||
;;
|
||||
-l)
|
||||
FORMAT="$FORMAT,lib"
|
||||
;;
|
||||
-o)
|
||||
OUTPUT="$2"
|
||||
shift
|
||||
@@ -181,12 +223,29 @@ while [ $# -gt 0 ]; do
|
||||
--reverse)
|
||||
FORMAT="$FORMAT,reverse"
|
||||
;;
|
||||
--all-kernel)
|
||||
PARAMS="$PARAMS,allkernel"
|
||||
--samples|--total)
|
||||
FORMAT="$FORMAT,${1#--}"
|
||||
;;
|
||||
--alloc|--lock|--chunksize|--chunktime)
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--timeout|--loop)
|
||||
if [ "$ACTION" = "collect" ]; then
|
||||
ACTION="start"
|
||||
fi
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--all-user)
|
||||
PARAMS="$PARAMS,alluser"
|
||||
;;
|
||||
--sched)
|
||||
PARAMS="$PARAMS,sched"
|
||||
;;
|
||||
--live)
|
||||
PARAMS="$PARAMS,live"
|
||||
;;
|
||||
--cstack|--call-graph)
|
||||
PARAMS="$PARAMS,cstack=$2"
|
||||
shift
|
||||
@@ -195,6 +254,21 @@ while [ $# -gt 0 ]; do
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--ttsp)
|
||||
PARAMS="$PARAMS,begin=SafepointSynchronize::begin,end=RuntimeService::record_safepoint_synchronized"
|
||||
;;
|
||||
--jfrsync)
|
||||
OUTPUT="jfr"
|
||||
PARAMS="$PARAMS,jfrsync=$2"
|
||||
shift
|
||||
;;
|
||||
--lib)
|
||||
PROFILER="$2"
|
||||
shift
|
||||
;;
|
||||
--fdtransfer)
|
||||
USE_FDTRANSFER="true"
|
||||
;;
|
||||
--safe-mode)
|
||||
PARAMS="$PARAMS,safemode=$2"
|
||||
shift
|
||||
@@ -230,8 +304,19 @@ while [ $# -gt 0 ]; do
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$PID" = "" ] && [ "$ACTION" != "version" ]; then
|
||||
usage
|
||||
if [ "$PID" = "" ]; then
|
||||
case "$ACTION" in
|
||||
version)
|
||||
java "-agentpath:$PROFILER=version=full" -version 2> /dev/null
|
||||
;;
|
||||
list)
|
||||
java "-agentpath:$PROFILER=list" -version 2> /dev/null
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If no -f argument is given, use temporary file to transfer output to caller terminal.
|
||||
@@ -249,34 +334,48 @@ else
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
LOG=/tmp/async-profiler-log.$$.$PID
|
||||
|
||||
UNAME_S=$(uname -s)
|
||||
if [ "$UNAME_S" = "Linux" ]; then
|
||||
ROOT_PREFIX="/proc/$PID/root"
|
||||
else
|
||||
ROOT_PREFIX=""
|
||||
fi
|
||||
|
||||
case $ACTION in
|
||||
start|resume|check)
|
||||
jattach "$ACTION,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
start|resume)
|
||||
fdtransfer
|
||||
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
;;
|
||||
stop)
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
check)
|
||||
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
;;
|
||||
status)
|
||||
jattach "status,file=$FILE"
|
||||
stop|dump)
|
||||
jattach "$ACTION,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
list)
|
||||
jattach "list,file=$FILE"
|
||||
status|meminfo|list)
|
||||
jattach "$ACTION,file=$FILE"
|
||||
;;
|
||||
version)
|
||||
jattach "version=full,file=$FILE"
|
||||
;;
|
||||
collect)
|
||||
jattach "start,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
fdtransfer
|
||||
jattach "start,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
echo Profiling for "$DURATION" seconds >&2
|
||||
set +e
|
||||
trap 'DURATION=0' INT
|
||||
|
||||
while [ "$DURATION" -gt 0 ]; do
|
||||
DURATION=$(( DURATION-1 ))
|
||||
check_if_terminated
|
||||
sleep 1
|
||||
done
|
||||
|
||||
set -e
|
||||
trap - INT
|
||||
echo Done >&2
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
version)
|
||||
if [ "$PID" = "" ]; then
|
||||
java "-agentpath:$PROFILER=version=full" -version 2> /dev/null
|
||||
else
|
||||
jattach "version=full,file=$FILE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -15,22 +15,22 @@
|
||||
*/
|
||||
|
||||
#include "allocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
int AllocTracer::_trap_kind;
|
||||
Trap AllocTracer::_in_new_tlab;
|
||||
Trap AllocTracer::_outside_tlab;
|
||||
Trap AllocTracer::_in_new_tlab(0);
|
||||
Trap AllocTracer::_outside_tlab(1);
|
||||
volatile bool AllocTracer::_use_hook = false;
|
||||
|
||||
u64 AllocTracer::_interval;
|
||||
volatile u64 AllocTracer::_allocated_bytes;
|
||||
|
||||
|
||||
// Called whenever our breakpoint trap is hit
|
||||
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
int event_type;
|
||||
uintptr_t total_size;
|
||||
@@ -51,6 +51,7 @@ void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
instance_size = 0;
|
||||
} else {
|
||||
// Not our trap
|
||||
Profiler::instance()->trapHandler(signo, siginfo, ucontext);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,31 +59,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::inNewTLAB1(uintptr_t klass, void* obj, size_t tlab_size, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, tlab_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC, klass, tlab_size, alloc_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::outsideTLAB1(uintptr_t klass, void* obj, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, alloc_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC_OUTSIDE_TLAB, klass, alloc_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::inNewTLAB2(uintptr_t klass, size_t tlab_size, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, tlab_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC, klass, tlab_size, alloc_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::outsideTLAB2(uintptr_t klass, size_t alloc_size) {
|
||||
if (_use_hook && _enabled && updateCounter(_allocated_bytes, alloc_size, _interval)) {
|
||||
recordAllocation(NULL, BCI_ALLOC_OUTSIDE_TLAB, klass, alloc_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size) {
|
||||
if (_interval) {
|
||||
// Do not record allocation unless allocated at least _interval bytes
|
||||
while (true) {
|
||||
u64 prev = _allocated_bytes;
|
||||
u64 next = prev + total_size;
|
||||
if (next < _interval) {
|
||||
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (__sync_bool_compare_and_swap(&_allocated_bytes, prev, next % _interval)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AllocEvent event;
|
||||
event._class_id = 0;
|
||||
event._total_size = total_size;
|
||||
@@ -90,18 +97,22 @@ void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rkl
|
||||
|
||||
if (VMStructs::hasClassNames()) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(symbol->body(), symbol->length());
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(symbol->body(), symbol->length());
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(ucontext, total_size, event_type, &event);
|
||||
Profiler::instance()->recordSample(ucontext, total_size, event_type, &event);
|
||||
}
|
||||
|
||||
Error AllocTracer::check(Arguments& args) {
|
||||
if (args._live) {
|
||||
return Error("'live' option is supported on OpenJDK 11+");
|
||||
}
|
||||
|
||||
if (_in_new_tlab.entry() != 0 && _outside_tlab.entry() != 0) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
NativeCodeCache* libjvm = VMStructs::libjvm();
|
||||
CodeCache* libjvm = VMStructs::libjvm();
|
||||
const void* ne;
|
||||
const void* oe;
|
||||
|
||||
@@ -118,9 +129,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,18 +142,27 @@ Error AllocTracer::start(Arguments& args) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._interval;
|
||||
_interval = args._alloc > 0 ? args._alloc : 0;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
OS::installSignalHandler(SIGTRAP, signalHandler);
|
||||
if (args._alloc_hook) {
|
||||
if ((_trap_kind == 1 && _in_new_tlab.install((void*)inNewTLAB1) && _outside_tlab.install((void*)outsideTLAB1)) ||
|
||||
(_trap_kind == 2 && _in_new_tlab.install((void*)inNewTLAB2) && _outside_tlab.install((void*)outsideTLAB2))) {
|
||||
_use_hook = true;
|
||||
return Error::OK;
|
||||
}
|
||||
} else if (_in_new_tlab.install() && _outside_tlab.install()) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
_in_new_tlab.install();
|
||||
_outside_tlab.install();
|
||||
|
||||
return Error::OK;
|
||||
return Error("Cannot install allocation breakpoints");
|
||||
}
|
||||
|
||||
void AllocTracer::stop() {
|
||||
_in_new_tlab.uninstall();
|
||||
_outside_tlab.uninstall();
|
||||
if (_use_hook) {
|
||||
_use_hook = false;
|
||||
} else {
|
||||
_in_new_tlab.uninstall();
|
||||
_outside_tlab.uninstall();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,31 +28,33 @@ class AllocTracer : public Engine {
|
||||
static int _trap_kind;
|
||||
static Trap _in_new_tlab;
|
||||
static Trap _outside_tlab;
|
||||
static volatile bool _use_hook;
|
||||
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void inNewTLAB1(uintptr_t klass, void* obj, size_t tlab_size, size_t alloc_size);
|
||||
static void outsideTLAB1(uintptr_t klass, void* obj, size_t alloc_size);
|
||||
static void inNewTLAB2(uintptr_t klass, size_t tlab_size, size_t alloc_size);
|
||||
static void outsideTLAB2(uintptr_t klass, size_t alloc_size);
|
||||
|
||||
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "alloc";
|
||||
const char* title() {
|
||||
return "Allocation profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "bytes";
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
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 +42,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 +136,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 +187,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 +206,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 +236,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 +263,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 +277,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);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,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);
|
||||
}
|
||||
|
||||
62
src/arch.h
62
src/arch.h
@@ -31,28 +31,44 @@ static inline int atomicInc(volatile int& var, int increment = 1) {
|
||||
return __sync_fetch_and_add(&var, increment);
|
||||
}
|
||||
|
||||
static inline u64 loadAcquire(u64& var) {
|
||||
return __atomic_load_n(&var, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
static inline void storeRelease(u64& var, u64 value) {
|
||||
return __atomic_store_n(&var, value, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
|
||||
typedef unsigned char instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xcc;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = 2;
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int ADJUST_RET = 1;
|
||||
const int PROBE_SP_LIMIT = 4;
|
||||
const int PLT_HEADER_SIZE = 16;
|
||||
const int PLT_ENTRY_SIZE = 16;
|
||||
const int PERF_REG_PC = 8; // PERF_REG_X86_IP
|
||||
|
||||
#define spinPause() asm volatile("pause")
|
||||
#define rmb() asm volatile("lfence" : : : "memory")
|
||||
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r"(addr) : "memory")
|
||||
#define flushCache(addr) asm volatile("mfence; clflush (%0); mfence" : : "r" (addr) : "memory")
|
||||
|
||||
#elif defined(__arm__) || defined(__thumb__)
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xe7f001f0;
|
||||
const instruction_t BREAKPOINT_THUMB = 0xde01de01;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int ADJUST_RET = 0;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 20;
|
||||
const int PLT_ENTRY_SIZE = 12;
|
||||
const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
|
||||
@@ -65,16 +81,40 @@ 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 ADJUST_RET = 0;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 32;
|
||||
const int PLT_ENTRY_SIZE = 16;
|
||||
const int PERF_REG_PC = 32; // PERF_REG_ARM64_PC
|
||||
|
||||
#define spinPause() asm volatile("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 ADJUST_RET = 0;
|
||||
const int PROBE_SP_LIMIT = 0;
|
||||
const int PLT_HEADER_SIZE = 24;
|
||||
const int PLT_ENTRY_SIZE = 24;
|
||||
const int PERF_REG_PC = 32; // PERF_REG_POWERPC_NIP
|
||||
|
||||
#define spinPause() asm volatile("yield") // does nothing, but using or 1,1,1 would lead to other problems
|
||||
#define rmb() asm volatile ("sync" : : : "memory") // lwsync would do but better safe than sorry
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#else
|
||||
|
||||
#error "Compiling on unsupported arch"
|
||||
@@ -82,4 +122,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
|
||||
|
||||
@@ -30,60 +30,83 @@ 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[=full] - display the agent version
|
||||
// event=EVENT - which event to trace (cpu, wall, cache-misses, etc.)
|
||||
// alloc[=BYTES] - profile allocations with BYTES interval
|
||||
// live - build allocation profile from live objects only
|
||||
// lock[=DURATION] - profile contended locks longer than DURATION ns
|
||||
// collapsed - dump collapsed stacks (the format used by FlameGraph script)
|
||||
// flamegraph - produce Flame Graph in HTML format
|
||||
// tree - produce call tree in HTML format
|
||||
// jfr - dump events in Java Flight Recorder format
|
||||
// jfrsync[=CONFIG] - start Java Flight Recording with the given config along with the profiler
|
||||
// traces[=N] - dump top N call traces
|
||||
// flat[=N] - dump top N methods (aka flat profile)
|
||||
// samples - count the number of samples (default)
|
||||
// total - count the total value (time, bytes, etc.) instead of samples
|
||||
// chunksize=N - approximate size of JFR chunk in bytes (default: 100 MB)
|
||||
// chunktime=N - duration of JFR chunk in seconds (default: 1 hour)
|
||||
// timeout=TIME - automatically stop profiler at TIME (absolute or relative)
|
||||
// loop=TIME - run profiler in a loop (continuous profiling)
|
||||
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
|
||||
// jstackdepth=N - maximum Java stack depth (default: 2048)
|
||||
// safemode=BITS - disable stack recovery techniques (default: 0, i.e. everything enabled)
|
||||
// file=FILENAME - output file name for dumping
|
||||
// log=FILENAME - log warnings and errors to the given dedicated stream
|
||||
// loglevel=LEVEL - logging level: TRACE, DEBUG, INFO, WARN, ERROR, or NONE
|
||||
// server=ADDRESS - start insecure HTTP server at ADDRESS/PORT
|
||||
// filter=FILTER - thread filter
|
||||
// threads - profile different threads separately
|
||||
// sched - group threads by scheduling policy
|
||||
// cstack=MODE - how to collect C stack frames in addition to Java stack
|
||||
// MODE is 'fp' (Frame Pointer), 'dwarf', 'lbr' (Last Branch Record) or 'no'
|
||||
// allkernel - include only kernel-mode events
|
||||
// alluser - include only user-mode events
|
||||
// fdtransfer - use fdtransfer to pass fds to the profiler
|
||||
// simple - simple class names instead of FQN
|
||||
// dot - dotted class names
|
||||
// sig - print method signatures
|
||||
// ann - annotate Java methods
|
||||
// lib - prepend library names
|
||||
// mcache - max age of jmethodID cache (default: 0 = disabled)
|
||||
// include=PATTERN - include stack traces containing PATTERN
|
||||
// exclude=PATTERN - exclude stack traces containing PATTERN
|
||||
// begin=FUNCTION - begin profiling when FUNCTION is executed
|
||||
// end=FUNCTION - end profiling when FUNCTION is executed
|
||||
// title=TITLE - FlameGraph title
|
||||
// minwidth=PCT - FlameGraph minimum frame width in percent
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree
|
||||
//
|
||||
// It is possible to specify multiple dump options at the same time
|
||||
|
||||
@@ -94,13 +117,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,12 +140,18 @@ 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;
|
||||
|
||||
@@ -128,67 +159,171 @@ Error Arguments::parse(const char* args) {
|
||||
_action = value == NULL ? ACTION_VERSION : ACTION_FULL_VERSION;
|
||||
|
||||
// Output formats
|
||||
CASE2("collapsed", "folded")
|
||||
CASE("collapsed")
|
||||
_output = OUTPUT_COLLAPSED;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE2("flamegraph", "html")
|
||||
CASE("flamegraph")
|
||||
_output = OUTPUT_FLAMEGRAPH;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE("tree")
|
||||
_output = OUTPUT_TREE;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE("jfr")
|
||||
_output = OUTPUT_JFR;
|
||||
if (value != NULL) {
|
||||
_jfr_options = (int)strtol(value, NULL, 0);
|
||||
}
|
||||
|
||||
CASE("jfrsync")
|
||||
_output = OUTPUT_JFR;
|
||||
_jfr_options = JFR_SYNC_OPTS;
|
||||
_jfr_sync = value == NULL ? "default" : value;
|
||||
|
||||
CASE("traces")
|
||||
_output = OUTPUT_TEXT;
|
||||
_dump_traces = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
CASE("flat")
|
||||
_output = OUTPUT_FLAT;
|
||||
_output = OUTPUT_TEXT;
|
||||
_dump_flat = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
CASE("samples")
|
||||
_counter = COUNTER_SAMPLES;
|
||||
|
||||
CASE("total")
|
||||
_counter = COUNTER_TOTAL;
|
||||
|
||||
CASE("chunksize")
|
||||
if (value == NULL || (_chunk_size = parseUnits(value, BYTES)) < 0) {
|
||||
msg = "Invalid chunksize";
|
||||
}
|
||||
|
||||
CASE("chunktime")
|
||||
if (value == NULL || (_chunk_time = parseUnits(value, SECONDS)) < 0) {
|
||||
msg = "Invalid chunktime";
|
||||
}
|
||||
|
||||
// Basic options
|
||||
CASE("event")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("event must not be empty");
|
||||
msg = "event must not be empty";
|
||||
} else if (strcmp(value, EVENT_ALLOC) == 0) {
|
||||
if (_alloc < 0) _alloc = 0;
|
||||
} else if (strcmp(value, EVENT_LOCK) == 0) {
|
||||
if (_lock < 0) _lock = 0;
|
||||
} else if (_event != NULL) {
|
||||
msg = "Duplicate event argument";
|
||||
} else {
|
||||
_event = value;
|
||||
}
|
||||
|
||||
if (!addEvent(value)) {
|
||||
return Error("multiple incompatible events");
|
||||
CASE("timeout")
|
||||
if (value == NULL || (_timeout = parseTimeout(value)) == -1 || !_persistent) {
|
||||
msg = "Invalid timeout";
|
||||
}
|
||||
|
||||
CASE("loop")
|
||||
_loop = true;
|
||||
if (value == NULL || (_timeout = parseTimeout(value)) == -1 || !_persistent) {
|
||||
msg = "Invalid loop duration";
|
||||
}
|
||||
|
||||
CASE("alloc")
|
||||
_alloc = value == NULL ? 0 : parseUnits(value, BYTES);
|
||||
|
||||
CASE("lock")
|
||||
_lock = value == NULL ? 0 : parseUnits(value, NANOS);
|
||||
|
||||
CASE("interval")
|
||||
if (value == NULL || (_interval = parseUnits(value)) <= 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);
|
||||
_safe_mode = value == NULL ? INT_MAX : (int)strtol(value, NULL, 0);
|
||||
|
||||
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;
|
||||
|
||||
CASE("cloud")
|
||||
// Meta option for continuous eBPF-assisted cloud profiling
|
||||
if (_action == ACTION_NONE) {
|
||||
_action = ACTION_START;
|
||||
}
|
||||
if (_event == NULL) {
|
||||
_event = EVENT_BPF;
|
||||
_sched = true;
|
||||
_alloc_hook = true;
|
||||
}
|
||||
if (_fdtransfer_path == NULL) {
|
||||
_fdtransfer = true;
|
||||
_fdtransfer_path = "/one/profile/profile.sock";
|
||||
}
|
||||
if (_file == NULL) {
|
||||
_file = "/one/logs/%{cloud_image}-%t.jfr";
|
||||
}
|
||||
if (_timeout == 0) {
|
||||
_loop = true;
|
||||
_timeout = 0xff0000ff; // rotate at 00:00
|
||||
}
|
||||
if (_chunk_time == 0) {
|
||||
_chunk_time = 300; // 5 min
|
||||
}
|
||||
|
||||
// Filters
|
||||
CASE("filter")
|
||||
_filter = value == NULL ? "" : value;
|
||||
|
||||
CASE("include")
|
||||
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("allochook")
|
||||
_alloc_hook = true;
|
||||
|
||||
CASE("allkernel")
|
||||
_ring = RING_KERNEL;
|
||||
|
||||
@@ -199,6 +334,8 @@ Error Arguments::parse(const char* args) {
|
||||
if (value != NULL) {
|
||||
if (value[0] == 'n') {
|
||||
_cstack = CSTACK_NO;
|
||||
} else if (value[0] == 'd') {
|
||||
_cstack = CSTACK_DWARF;
|
||||
} else if (value[0] == 'l') {
|
||||
_cstack = CSTACK_LBR;
|
||||
} else {
|
||||
@@ -219,6 +356,12 @@ Error Arguments::parse(const char* args) {
|
||||
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 +370,49 @@ 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) {
|
||||
_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;
|
||||
}
|
||||
|
||||
// The linked list of string offsets is embedded right into _buf array
|
||||
@@ -283,11 +430,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 +456,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,31 +496,49 @@ 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);
|
||||
}
|
||||
|
||||
114
src/arguments.h
114
src/arguments.h
@@ -20,27 +20,29 @@
|
||||
#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_BPF = "bpf";
|
||||
const char* const EVENT_ALLOC = "alloc";
|
||||
const char* const EVENT_LOCK = "lock";
|
||||
const char* const EVENT_WALL = "wall";
|
||||
const char* const EVENT_ITIMER = "itimer";
|
||||
const char* const EVENT_JSTACK = "jstack";
|
||||
|
||||
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_FULL_VERSION
|
||||
};
|
||||
|
||||
enum Counter {
|
||||
@@ -54,35 +56,48 @@ enum Ring {
|
||||
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 = 1,
|
||||
STYLE_DOTTED = 2,
|
||||
STYLE_SIGNATURES = 4,
|
||||
STYLE_ANNOTATE = 8,
|
||||
STYLE_LIB_NAMES = 16,
|
||||
STYLE_NO_SEMICOLON = 32
|
||||
};
|
||||
|
||||
enum CStack {
|
||||
CSTACK_DEFAULT,
|
||||
CSTACK_NO,
|
||||
CSTACK_FP,
|
||||
CSTACK_DWARF,
|
||||
CSTACK_LBR
|
||||
};
|
||||
|
||||
enum Output {
|
||||
OUTPUT_NONE,
|
||||
OUTPUT_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,
|
||||
|
||||
JFR_SYNC_OPTS = NO_SYSTEM_INFO | NO_SYSTEM_PROPS | NO_NATIVE_LIBS | NO_CPU_LOAD
|
||||
};
|
||||
|
||||
|
||||
struct Multiplier {
|
||||
char symbol;
|
||||
long multiplier;
|
||||
};
|
||||
|
||||
|
||||
class Error {
|
||||
private:
|
||||
@@ -108,32 +123,53 @@ class Arguments {
|
||||
private:
|
||||
char* _buf;
|
||||
bool _shared;
|
||||
bool _persistent;
|
||||
|
||||
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;
|
||||
long _alloc;
|
||||
long _lock;
|
||||
int _jstackdepth;
|
||||
int _safe_mode;
|
||||
const char* _file;
|
||||
const char* _log;
|
||||
const char* _loglevel;
|
||||
const char* _unknown_arg;
|
||||
const char* _server;
|
||||
const char* _filter;
|
||||
int _include;
|
||||
int _exclude;
|
||||
unsigned char _mcache;
|
||||
bool _loop;
|
||||
bool _threads;
|
||||
bool _sched;
|
||||
bool _live;
|
||||
bool _alloc_hook;
|
||||
bool _fdtransfer;
|
||||
const char* _fdtransfer_path;
|
||||
int _style;
|
||||
CStack _cstack;
|
||||
Output _output;
|
||||
long _chunk_size;
|
||||
long _chunk_time;
|
||||
const char* _jfr_sync;
|
||||
int _jfr_options;
|
||||
int _dump_traces;
|
||||
int _dump_flat;
|
||||
unsigned int _file_num;
|
||||
const char* _begin;
|
||||
const char* _end;
|
||||
// FlameGraph parameters
|
||||
@@ -141,28 +177,49 @@ class Arguments {
|
||||
double _minwidth;
|
||||
bool _reverse;
|
||||
|
||||
Arguments() :
|
||||
Arguments(bool persistent = false) :
|
||||
_buf(NULL),
|
||||
_shared(false),
|
||||
_persistent(persistent),
|
||||
_action(ACTION_NONE),
|
||||
_counter(COUNTER_SAMPLES),
|
||||
_ring(RING_ANY),
|
||||
_events(0),
|
||||
_event_desc(NULL),
|
||||
_event(NULL),
|
||||
_timeout(0),
|
||||
_interval(0),
|
||||
_alloc(-1),
|
||||
_lock(-1),
|
||||
_jstackdepth(DEFAULT_JSTACKDEPTH),
|
||||
_safe_mode(0),
|
||||
_file(NULL),
|
||||
_log(NULL),
|
||||
_loglevel(NULL),
|
||||
_unknown_arg(NULL),
|
||||
_server(NULL),
|
||||
_filter(NULL),
|
||||
_include(0),
|
||||
_exclude(0),
|
||||
_mcache(0),
|
||||
_loop(false),
|
||||
_threads(false),
|
||||
_sched(false),
|
||||
_live(false),
|
||||
_alloc_hook(false),
|
||||
_fdtransfer(false),
|
||||
_fdtransfer_path(NULL),
|
||||
_style(0),
|
||||
_cstack(CSTACK_DEFAULT),
|
||||
_output(OUTPUT_NONE),
|
||||
_chunk_size(100 * 1024 * 1024),
|
||||
_chunk_time(0),
|
||||
_jfr_sync(NULL),
|
||||
_jfr_options(0),
|
||||
_dump_traces(0),
|
||||
_dump_flat(0),
|
||||
_file_num(0),
|
||||
_begin(NULL),
|
||||
_end(NULL),
|
||||
_title("Flame Graph"),
|
||||
_title(NULL),
|
||||
_minwidth(0),
|
||||
_reverse(false) {
|
||||
}
|
||||
@@ -173,7 +230,16 @@ class Arguments {
|
||||
|
||||
Error parse(const char* args);
|
||||
|
||||
bool addEvent(const char* event);
|
||||
const char* file();
|
||||
|
||||
bool hasOutputFile() const {
|
||||
return _file != NULL &&
|
||||
(_action == ACTION_STOP || _action == ACTION_DUMP ? _output != OUTPUT_JFR : _action >= ACTION_STATUS);
|
||||
}
|
||||
|
||||
bool hasOption(JfrOption option) const {
|
||||
return (_jfr_options & option) != 0;
|
||||
}
|
||||
|
||||
friend class FrameName;
|
||||
friend class Recording;
|
||||
|
||||
138
src/bpfClient.cpp
Normal file
138
src/bpfClient.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "bpfClient.h"
|
||||
#include "fdtransferClient.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackWalker.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
// Use different profiling signal to allow running two profilers together
|
||||
const int BPF_SIGNAL = SIGSTKFLT;
|
||||
|
||||
struct BpfStackTrace {
|
||||
u32 pid;
|
||||
u32 tid;
|
||||
u64 counter;
|
||||
u16 event_type;
|
||||
u16 sched_policy;
|
||||
u32 depth;
|
||||
u64 ip[0];
|
||||
};
|
||||
|
||||
struct BpfMap {
|
||||
char* addr;
|
||||
size_t size;
|
||||
u32 salt;
|
||||
u32 mask;
|
||||
u32 entry_size;
|
||||
|
||||
BpfStackTrace* getStackForThread(u32 tid) const {
|
||||
char* base = __atomic_load_n(&addr, __ATOMIC_ACQUIRE);
|
||||
if (base == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
size_t index = (salt + tid) & mask;
|
||||
return (BpfStackTrace*)(base + index * entry_size);
|
||||
}
|
||||
};
|
||||
|
||||
static BpfMap _bpf_map = {0};
|
||||
|
||||
static unsigned int _interval;
|
||||
static unsigned int _counter;
|
||||
|
||||
|
||||
void BpfClient::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
if (_interval <= 1 || __sync_add_and_fetch(&_counter, 1) % _interval == 0) {
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
}
|
||||
|
||||
Error BpfClient::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error BpfClient::start(Arguments& args) {
|
||||
OS::installSignalHandler(BPF_SIGNAL, signalHandler);
|
||||
|
||||
struct bpfmap_params params;
|
||||
int fd = FdTransferClient::requestBpfMapFd(¶ms);
|
||||
if (fd < 0) {
|
||||
return Error("Failed to request bpf map");
|
||||
}
|
||||
|
||||
_interval = args._interval;
|
||||
_counter = 0;
|
||||
|
||||
_bpf_map.salt = params.salt;
|
||||
_bpf_map.mask = params.num_entries - 1;
|
||||
_bpf_map.entry_size = params.entry_size;
|
||||
_bpf_map.size = (size_t)params.entry_size * params.num_entries;
|
||||
|
||||
char* addr = (char*)mmap(NULL, _bpf_map.size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
close(fd);
|
||||
return Error("Failed to mmap stack trace buffer");
|
||||
}
|
||||
|
||||
close(fd);
|
||||
__atomic_store_n(&_bpf_map.addr, addr, __ATOMIC_RELEASE);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void BpfClient::stop() {
|
||||
OS::installSignalHandler(BPF_SIGNAL, NULL, SIG_IGN);
|
||||
|
||||
char* addr = __atomic_exchange_n(&_bpf_map.addr, NULL, __ATOMIC_ACQ_REL);
|
||||
munmap(addr, _bpf_map.size);
|
||||
}
|
||||
|
||||
int BpfClient::walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx) {
|
||||
int depth = 0;
|
||||
|
||||
// Fill kernel frames from bpf map
|
||||
BpfStackTrace* trace = _bpf_map.getStackForThread(tid);
|
||||
if (trace != NULL && trace->tid == tid) {
|
||||
int limit = trace->depth < max_depth ? trace->depth : max_depth;
|
||||
while (depth < limit && (intptr_t)trace->ip[depth] < 0) {
|
||||
callchain[depth] = (const void*)trace->ip[depth];
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add user-space frames by manual stack walking
|
||||
depth += StackWalker::walkDwarf(ucontext, callchain + depth, max_depth - depth, java_ctx);
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
const char* BpfClient::schedPolicy(int tid) {
|
||||
BpfStackTrace* trace = _bpf_map.getStackForThread(tid);
|
||||
if (trace == NULL || trace->tid != tid || trace->sched_policy < SCHED_BATCH) {
|
||||
return "SCHED_OTHER";
|
||||
}
|
||||
return trace->sched_policy >= SCHED_IDLE ? "SCHED_IDLE" : "SCHED_BATCH";
|
||||
}
|
||||
@@ -14,28 +14,34 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _JSTACK_H
|
||||
#define _JSTACK_H
|
||||
#ifndef _BPFCLIENT_H
|
||||
#define _BPFCLIENT_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class JStack : public Engine {
|
||||
class BpfClient : public Engine {
|
||||
private:
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return EVENT_JSTACK;
|
||||
const char* title() {
|
||||
return "CPU profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "samples";
|
||||
return "cycles";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static int walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx);
|
||||
|
||||
static const char* schedPolicy(int tid);
|
||||
};
|
||||
|
||||
#endif // _JSTACK_H
|
||||
#endif // _BPFCLIENT_H
|
||||
@@ -15,36 +15,35 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "callTraceStorage.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
static const u32 INITIAL_CAPACITY = 65536;
|
||||
static const u32 CAPACITY = 131072;
|
||||
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 {
|
||||
private:
|
||||
LongHashTable* _prev;
|
||||
void* _padding0;
|
||||
u32 _capacity;
|
||||
u32 _base;
|
||||
u32 _padding1[15];
|
||||
volatile u32 _size;
|
||||
u32 _padding2[15];
|
||||
|
||||
static size_t getSize(u32 capacity) {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
|
||||
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
|
||||
static size_t getSize() {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * CAPACITY;
|
||||
return (size + OS::page_mask) & ~OS::page_mask;
|
||||
}
|
||||
|
||||
public:
|
||||
static LongHashTable* allocate(LongHashTable* prev, u32 capacity) {
|
||||
LongHashTable* table = (LongHashTable*)OS::safeAlloc(getSize(capacity));
|
||||
static LongHashTable* allocate(LongHashTable* prev, u32 base) {
|
||||
LongHashTable* table = (LongHashTable*)OS::safeAlloc(getSize());
|
||||
if (table != NULL) {
|
||||
table->_prev = prev;
|
||||
table->_capacity = capacity;
|
||||
table->_base = base;
|
||||
table->_size = 0;
|
||||
}
|
||||
return table;
|
||||
@@ -52,19 +51,27 @@ class LongHashTable {
|
||||
|
||||
LongHashTable* destroy() {
|
||||
LongHashTable* prev = _prev;
|
||||
OS::safeFree(this, getSize(_capacity));
|
||||
OS::safeFree(this, getSize());
|
||||
return prev;
|
||||
}
|
||||
|
||||
LongHashTable* prev() {
|
||||
size_t usedMemory() const {
|
||||
return getSize();
|
||||
}
|
||||
|
||||
LongHashTable* trim() {
|
||||
return __atomic_exchange_n(&_prev, NULL, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
LongHashTable* prev() const {
|
||||
return _prev;
|
||||
}
|
||||
|
||||
u32 capacity() {
|
||||
return _capacity;
|
||||
u32 base() const {
|
||||
return _base;
|
||||
}
|
||||
|
||||
u32 size() {
|
||||
u32 size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
@@ -77,18 +84,21 @@ class LongHashTable {
|
||||
}
|
||||
|
||||
CallTraceSample* values() {
|
||||
return (CallTraceSample*)(keys() + _capacity);
|
||||
return (CallTraceSample*)(keys() + CAPACITY);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(keys(), 0, (sizeof(u64) + sizeof(CallTraceSample)) * _capacity);
|
||||
memset(keys(), 0, (sizeof(u64) + sizeof(CallTraceSample)) * CAPACITY);
|
||||
_size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CallTrace CallTraceStorage::_overflow_trace = {1, {BCI_ERROR, (jmethodID)"storage_overflow"}};
|
||||
|
||||
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
|
||||
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
|
||||
_current_table = LongHashTable::allocate(NULL, 1);
|
||||
_overflow = 0;
|
||||
}
|
||||
|
||||
CallTraceStorage::~CallTraceStorage() {
|
||||
@@ -103,29 +113,61 @@ 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;
|
||||
}
|
||||
|
||||
Chunk* CallTraceStorage::trimAllocator() {
|
||||
return _allocator.trim();
|
||||
}
|
||||
|
||||
LongHashTable* CallTraceStorage::trimTable() {
|
||||
return _current_table->trim();
|
||||
}
|
||||
|
||||
void CallTraceStorage::freeMemory(Chunk* chunk, LongHashTable* table) {
|
||||
_allocator.freeChain(chunk);
|
||||
while (table != NULL) {
|
||||
table = table->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
u32 base = table->base();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
|
||||
for (u32 slot = 0; slot < CAPACITY; slot++) {
|
||||
if (keys[slot] != 0 && loadAcquire(values[slot].counter) != 0) {
|
||||
CallTrace* trace = values[slot].acquireTrace();
|
||||
if (trace != NULL) {
|
||||
map[base + slot] = trace;
|
||||
// Reset to make sure each trace is dumped only once
|
||||
values[slot].setTrace(NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_overflow > 0) {
|
||||
map[OVERFLOW_TRACE_ID] = &_overflow_trace;
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
for (u32 slot = 0; slot < CAPACITY; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
samples.push_back(&values[slot]);
|
||||
}
|
||||
@@ -133,6 +175,19 @@ 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();
|
||||
|
||||
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;
|
||||
@@ -178,32 +233,12 @@ CallTrace* CallTraceStorage::storeCallTrace(int num_frames, ASGCT_CallFrame* fra
|
||||
return buf;
|
||||
}
|
||||
|
||||
CallTrace* CallTraceStorage::findCallTrace(LongHashTable* table, u64 hash) {
|
||||
u64* keys = table->keys();
|
||||
u32 capacity = table->capacity();
|
||||
u32 slot = hash & (capacity - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
if (keys[slot] == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (++step >= capacity) {
|
||||
return NULL;
|
||||
}
|
||||
slot = (slot + step) & (capacity - 1);
|
||||
}
|
||||
|
||||
return table->values()[slot].trace;
|
||||
}
|
||||
|
||||
u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter) {
|
||||
u64 hash = calcHash(num_frames, frames);
|
||||
|
||||
LongHashTable* table = _current_table;
|
||||
u64* keys = table->keys();
|
||||
u32 capacity = table->capacity();
|
||||
u32 slot = hash & (capacity - 1);
|
||||
u32 slot = hash & (CAPACITY - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
@@ -213,34 +248,47 @@ u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter)
|
||||
}
|
||||
|
||||
// Increment the table size, and if the load factor exceeds 0.75, reserve a new table
|
||||
if (table->incSize() == capacity * 3 / 4) {
|
||||
LongHashTable* new_table = LongHashTable::allocate(table, capacity * 2);
|
||||
if (table->incSize() == CAPACITY * 3 / 4) {
|
||||
LongHashTable* new_table = LongHashTable::allocate(table, table->base() + CAPACITY);
|
||||
if (new_table != NULL) {
|
||||
__sync_bool_compare_and_swap(&_current_table, table, new_table);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate from a previous table to save space
|
||||
CallTrace* trace = table->prev() == NULL ? NULL : findCallTrace(table->prev(), hash);
|
||||
if (trace == NULL) {
|
||||
trace = storeCallTrace(num_frames, frames);
|
||||
}
|
||||
table->values()[slot].trace = trace;
|
||||
CallTrace* trace = storeCallTrace(num_frames, frames);
|
||||
table->values()[slot].setTrace(trace);
|
||||
break;
|
||||
}
|
||||
|
||||
if (++step >= capacity) {
|
||||
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);
|
||||
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;
|
||||
return table->base() + slot;
|
||||
}
|
||||
|
||||
void CallTraceStorage::add(u32 call_trace_id, u64 counter) {
|
||||
if (call_trace_id == OVERFLOW_TRACE_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
if (call_trace_id >= table->base()) {
|
||||
CallTraceSample& s = table->values()[call_trace_id - table->base()];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,26 +35,54 @@ 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);
|
||||
CallTrace* findCallTrace(LongHashTable* table, u64 hash);
|
||||
|
||||
public:
|
||||
CallTraceStorage();
|
||||
~CallTraceStorage();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
Chunk* trimAllocator();
|
||||
LongHashTable* trimTable();
|
||||
void freeMemory(Chunk* chunk, LongHashTable* table);
|
||||
|
||||
void collectTraces(std::map<u32, CallTrace*>& map);
|
||||
void collectSamples(std::vector<CallTraceSample*>& samples);
|
||||
void collectSamples(std::map<u64, CallTraceSample>& map);
|
||||
|
||||
u32 put(int num_frames, ASGCT_CallFrame* frames, u64 counter);
|
||||
void add(u32 call_trace_id, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _CALLTRACESTORAGE
|
||||
|
||||
@@ -14,29 +14,78 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include "codeCache.h"
|
||||
#include "dwarf.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
char* NativeFunc::create(const char* name, short lib_index) {
|
||||
NativeFunc* f = (NativeFunc*)malloc(sizeof(NativeFunc) + 1 + strlen(name));
|
||||
f->_lib_index = lib_index;
|
||||
f->_mark = 0;
|
||||
return strcpy(f->_name, name);
|
||||
}
|
||||
|
||||
void NativeFunc::destroy(char* name) {
|
||||
free(from(name));
|
||||
}
|
||||
|
||||
size_t NativeFunc::usedMemory(const char* name) {
|
||||
return sizeof(NativeFunc) + 1 + strlen(from(name)->_name);
|
||||
}
|
||||
|
||||
|
||||
CodeCache::CodeCache(const char* name, short lib_index, const void* min_address, const void* max_address) {
|
||||
_name = NativeFunc::create(name, -1);
|
||||
_lib_index = lib_index;
|
||||
_min_address = min_address;
|
||||
_max_address = max_address;
|
||||
_text_base = NULL;
|
||||
|
||||
_got_start = NULL;
|
||||
_got_end = NULL;
|
||||
_got_patchable = false;
|
||||
_debug_symbols = false;
|
||||
|
||||
_dwarf_table = NULL;
|
||||
_dwarf_table_length = 0;
|
||||
|
||||
_capacity = INITIAL_CODE_CACHE_CAPACITY;
|
||||
_count = 0;
|
||||
_blobs = new CodeBlob[_capacity];
|
||||
}
|
||||
|
||||
CodeCache::~CodeCache() {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
NativeFunc::destroy(_blobs[i]._name);
|
||||
}
|
||||
NativeFunc::destroy(_name);
|
||||
delete[] _blobs;
|
||||
free(_dwarf_table);
|
||||
}
|
||||
|
||||
void CodeCache::expand() {
|
||||
CodeBlob* old_blobs = _blobs;
|
||||
CodeBlob* new_blobs = new CodeBlob[_capacity * 2];
|
||||
|
||||
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 +93,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 +115,25 @@ 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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeBlob* CodeCache::find(const void* address) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (address >= _blobs[i]._start && address < _blobs[i]._end) {
|
||||
return &_blobs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* CodeCache::binarySearch(const void* address) {
|
||||
int low = 0;
|
||||
int high = _count - 1;
|
||||
|
||||
@@ -115,21 +144,21 @@ const char* NativeCodeCache::binarySearch(const void* address) {
|
||||
} else if (_blobs[mid]._start > address) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
return (const char*)_blobs[mid]._method;
|
||||
return _blobs[mid]._name;
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with zero size can be valid functions: e.g. ASM entry points or kernel code.
|
||||
// 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) {
|
||||
const void* CodeCache::findSymbol(const char* name) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = (const char*)_blobs[i]._method;
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && strcmp(blob_name, name) == 0) {
|
||||
return _blobs[i]._start;
|
||||
}
|
||||
@@ -137,16 +166,86 @@ const void* NativeCodeCache::findSymbol(const char* name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
CodeBlob* CodeCache::findBlobByPrefix(const char* prefix) {
|
||||
size_t prefix_len = strlen(prefix);
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = _blobs[i]._name;
|
||||
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
|
||||
return &_blobs[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CodeCache::setGlobalOffsetTable(void** start, void** end, bool patchable) {
|
||||
_got_start = start;
|
||||
_got_end = end;
|
||||
_got_patchable = patchable;
|
||||
}
|
||||
|
||||
void** CodeCache::findGlobalOffsetEntry(void* address) {
|
||||
for (void** entry = _got_start; entry < _got_end; entry++) {
|
||||
if (*entry == address) {
|
||||
makeGotPatchable();
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CodeCache::makeGotPatchable() {
|
||||
if (!_got_patchable) {
|
||||
uintptr_t got_start = (uintptr_t)_got_start & ~OS::page_mask;
|
||||
uintptr_t got_size = ((uintptr_t)_got_end - got_start + OS::page_mask) & ~OS::page_mask;
|
||||
mprotect((void*)got_start, got_size, PROT_READ | PROT_WRITE);
|
||||
_got_patchable = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::setDwarfTable(FrameDesc* table, int length) {
|
||||
_dwarf_table = table;
|
||||
_dwarf_table_length = length;
|
||||
}
|
||||
|
||||
FrameDesc* CodeCache::findFrameDesc(const void* pc) {
|
||||
u32 target_loc = (const char*)pc - _text_base;
|
||||
int low = 0;
|
||||
int high = _dwarf_table_length - 1;
|
||||
|
||||
while (low <= high) {
|
||||
int mid = (unsigned int)(low + high) >> 1;
|
||||
if (_dwarf_table[mid].loc < target_loc) {
|
||||
low = mid + 1;
|
||||
} else if (_dwarf_table[mid].loc > target_loc) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
return &_dwarf_table[mid];
|
||||
}
|
||||
}
|
||||
|
||||
return low > 0 ? &_dwarf_table[low - 1] : NULL;
|
||||
}
|
||||
|
||||
size_t CodeCache::usedMemory() {
|
||||
size_t bytes = _capacity * sizeof(CodeBlob);
|
||||
bytes += _dwarf_table_length * sizeof(FrameDesc);
|
||||
bytes += NativeFunc::usedMemory(_name);
|
||||
for (int i = 0; i < _count; i++) {
|
||||
bytes += NativeFunc::usedMemory(_blobs[i]._name);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
163
src/codeCache.h
163
src/codeCache.h
@@ -23,14 +23,48 @@
|
||||
#define NO_MIN_ADDRESS ((const void*)-1)
|
||||
#define NO_MAX_ADDRESS ((const void*)0)
|
||||
|
||||
typedef bool (*NamePredicate)(const char* name);
|
||||
|
||||
const int INITIAL_CODE_CACHE_CAPACITY = 1000;
|
||||
const int MAX_NATIVE_LIBS = 2048;
|
||||
|
||||
|
||||
class NativeFunc {
|
||||
private:
|
||||
short _lib_index;
|
||||
char _mark;
|
||||
char _reserved;
|
||||
char _name[0];
|
||||
|
||||
static NativeFunc* from(const char* name) {
|
||||
return (NativeFunc*)(name - sizeof(NativeFunc));
|
||||
}
|
||||
|
||||
public:
|
||||
static char* create(const char* name, short lib_index);
|
||||
static void destroy(char* name);
|
||||
|
||||
static size_t usedMemory(const char* name);
|
||||
|
||||
static short libIndex(const char* name) {
|
||||
return from(name)->_lib_index;
|
||||
}
|
||||
|
||||
static bool isMarked(const char* name) {
|
||||
return from(name)->_mark != 0;
|
||||
}
|
||||
|
||||
static void mark(const char* name) {
|
||||
from(name)->_mark = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class CodeBlob {
|
||||
public:
|
||||
const void* _start;
|
||||
const void* _end;
|
||||
jmethodID _method;
|
||||
char* _name;
|
||||
|
||||
static int comparator(const void* c1, const void* c2) {
|
||||
CodeBlob* cb1 = (CodeBlob*)c1;
|
||||
@@ -48,60 +82,119 @@ class CodeBlob {
|
||||
};
|
||||
|
||||
|
||||
class FrameDesc;
|
||||
|
||||
class CodeCache {
|
||||
protected:
|
||||
char* _name;
|
||||
short _lib_index;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
const char* _text_base;
|
||||
|
||||
void** _got_start;
|
||||
void** _got_end;
|
||||
bool _got_patchable;
|
||||
bool _debug_symbols;
|
||||
|
||||
FrameDesc* _dwarf_table;
|
||||
int _dwarf_table_length;
|
||||
|
||||
int _capacity;
|
||||
int _count;
|
||||
CodeBlob* _blobs;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
|
||||
void expand();
|
||||
|
||||
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,
|
||||
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** gotStart() const {
|
||||
return _got_start;
|
||||
}
|
||||
|
||||
void** gotEnd() const {
|
||||
return _got_end;
|
||||
}
|
||||
|
||||
bool hasDebugSymbols() const {
|
||||
return _debug_symbols;
|
||||
}
|
||||
|
||||
void setDebugSymbols(bool debug_symbols) {
|
||||
_debug_symbols = debug_symbols;
|
||||
}
|
||||
|
||||
void add(const void* start, int length, const char* name, bool update_bounds = false);
|
||||
void updateBounds(const void* start, const void* end);
|
||||
void sort();
|
||||
void mark(NamePredicate predicate);
|
||||
|
||||
CodeBlob* find(const void* address);
|
||||
const char* binarySearch(const void* address);
|
||||
const void* findSymbol(const char* name);
|
||||
CodeBlob* findBlobByPrefix(const char* name);
|
||||
const void* findSymbolByPrefix(const char* prefix);
|
||||
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
|
||||
|
||||
void setGlobalOffsetTable(void** start, void** end, bool patchable);
|
||||
void** findGlobalOffsetEntry(void* address);
|
||||
void makeGotPatchable();
|
||||
|
||||
void setDwarfTable(FrameDesc* table, int length);
|
||||
FrameDesc* findFrameDesc(const void* pc);
|
||||
|
||||
size_t usedMemory();
|
||||
};
|
||||
|
||||
|
||||
class CodeCacheArray {
|
||||
private:
|
||||
CodeCache* _libs[MAX_NATIVE_LIBS];
|
||||
int _count;
|
||||
|
||||
public:
|
||||
CodeCacheArray() : _count(0) {
|
||||
}
|
||||
|
||||
CodeCache* operator[](int index) {
|
||||
return _libs[index];
|
||||
}
|
||||
|
||||
int count() {
|
||||
return __atomic_load_n(&_count, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
void add(CodeCache* lib) {
|
||||
int index = __atomic_load_n(&_count, __ATOMIC_ACQUIRE);
|
||||
_libs[index] = lib;
|
||||
__atomic_store_n(&_count, index + 1, __ATOMIC_RELEASE);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _CODECACHE_H
|
||||
|
||||
106
src/converter/Arguments.java
Normal file
106
src/converter/Arguments.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Arguments {
|
||||
String title = "Flame Graph";
|
||||
String highlight;
|
||||
Pattern include;
|
||||
Pattern exclude;
|
||||
double minwidth;
|
||||
int skip;
|
||||
boolean reverse;
|
||||
boolean cpu;
|
||||
boolean alloc;
|
||||
boolean live;
|
||||
boolean lock;
|
||||
boolean threads;
|
||||
boolean total;
|
||||
boolean lines;
|
||||
boolean bci;
|
||||
boolean simple;
|
||||
boolean dot;
|
||||
boolean collapsed;
|
||||
long from;
|
||||
long to;
|
||||
String input;
|
||||
String output;
|
||||
|
||||
Arguments(String... args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if (arg.startsWith("--")) {
|
||||
try {
|
||||
Field f = Arguments.class.getDeclaredField(arg.substring(2));
|
||||
if ((f.getModifiers() & (Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL)) != 0) {
|
||||
throw new IllegalStateException(arg);
|
||||
}
|
||||
|
||||
Class<?> type = f.getType();
|
||||
if (type == String.class) {
|
||||
f.set(this, args[++i]);
|
||||
} else if (type == boolean.class) {
|
||||
f.setBoolean(this, true);
|
||||
} else if (type == int.class) {
|
||||
f.setInt(this, Integer.parseInt(args[++i]));
|
||||
} else if (type == double.class) {
|
||||
f.setDouble(this, Double.parseDouble(args[++i]));
|
||||
} else if (type == long.class) {
|
||||
f.setLong(this, parseTimestamp(args[++i]));
|
||||
} else if (type == Pattern.class) {
|
||||
f.set(this, Pattern.compile(args[++i]));
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new IllegalArgumentException(arg);
|
||||
}
|
||||
} else if (!arg.isEmpty()) {
|
||||
if (input == null) {
|
||||
input = arg;
|
||||
} else {
|
||||
output = arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Milliseconds or HH:mm:ss.S or yyyy-MM-dd'T'HH:mm:ss.S
|
||||
private long parseTimestamp(String time) {
|
||||
if (time.indexOf(':') < 0) {
|
||||
return Long.parseLong(time);
|
||||
}
|
||||
|
||||
GregorianCalendar cal = new GregorianCalendar();
|
||||
StringTokenizer st = new StringTokenizer(time, "-:.T");
|
||||
|
||||
if (time.indexOf('T') > 0) {
|
||||
cal.set(Calendar.YEAR, Integer.parseInt(st.nextToken()));
|
||||
cal.set(Calendar.MONTH, Integer.parseInt(st.nextToken()) - 1);
|
||||
cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(st.nextToken()));
|
||||
}
|
||||
cal.set(Calendar.HOUR_OF_DAY, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
cal.set(Calendar.MINUTE, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
cal.set(Calendar.SECOND, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
cal.set(Calendar.MILLISECOND, st.hasMoreTokens() ? Integer.parseInt(st.nextToken()) : 0);
|
||||
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
}
|
||||
50
src/converter/CollapsedStacks.java
Normal file
50
src/converter/CollapsedStacks.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
public class CollapsedStacks extends FlameGraph {
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
private final PrintStream out;
|
||||
|
||||
public CollapsedStacks(Arguments args) throws IOException {
|
||||
super(args);
|
||||
this.out = args.output == null ? System.out : new PrintStream(
|
||||
new BufferedOutputStream(new FileOutputStream(args.output), 32768), false, "UTF-8");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
for (String s : trace) {
|
||||
sb.append(s).append(';');
|
||||
}
|
||||
if (sb.length() > 0) sb.setCharAt(sb.length() - 1, ' ');
|
||||
sb.append(ticks);
|
||||
|
||||
out.println(sb.toString());
|
||||
sb.setLength(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump() {
|
||||
if (out != System.out) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,51 +16,43 @@
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FlameGraph {
|
||||
public String title = "Flame Graph";
|
||||
public boolean reverse;
|
||||
public double minwidth;
|
||||
public int skip;
|
||||
public String input;
|
||||
public String output;
|
||||
public static final byte FRAME_INTERPRETED = 0;
|
||||
public static final byte FRAME_JIT_COMPILED = 1;
|
||||
public static final byte FRAME_INLINED = 2;
|
||||
public static final byte FRAME_NATIVE = 3;
|
||||
public static final byte FRAME_CPP = 4;
|
||||
public static final byte FRAME_KERNEL = 5;
|
||||
public static final byte FRAME_C1_COMPILED = 6;
|
||||
|
||||
private final Frame root = new Frame();
|
||||
private final Arguments args;
|
||||
private final Frame root = new Frame(FRAME_NATIVE);
|
||||
private int depth;
|
||||
private long mintotal;
|
||||
|
||||
public FlameGraph(Arguments args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public FlameGraph(String... args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if (!arg.startsWith("--") && !arg.isEmpty()) {
|
||||
if (input == null) {
|
||||
input = arg;
|
||||
} else {
|
||||
output = arg;
|
||||
}
|
||||
} else if (arg.equals("--title")) {
|
||||
title = args[++i];
|
||||
} else if (arg.equals("--reverse")) {
|
||||
reverse = true;
|
||||
} else if (arg.equals("--minwidth")) {
|
||||
minwidth = Double.parseDouble(args[++i]);
|
||||
} else if (arg.equals("--skip")) {
|
||||
skip = Integer.parseInt(args[++i]);
|
||||
}
|
||||
}
|
||||
this(new Arguments(args));
|
||||
}
|
||||
|
||||
public void parse() throws IOException {
|
||||
parse(new InputStreamReader(new FileInputStream(input), StandardCharsets.UTF_8));
|
||||
parse(new InputStreamReader(new FileInputStream(args.input), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void parse(Reader in) throws IOException {
|
||||
@@ -77,29 +69,30 @@ public class FlameGraph {
|
||||
}
|
||||
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
if (excludeTrace(trace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Frame frame = root;
|
||||
if (reverse) {
|
||||
for (int i = trace.length; --i >= skip; ) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
if (args.reverse) {
|
||||
for (int i = trace.length; --i >= args.skip; ) {
|
||||
frame = frame.addChild(trace[i], ticks);
|
||||
}
|
||||
} else {
|
||||
for (int i = skip; i < trace.length; i++) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
for (int i = args.skip; i < trace.length; i++) {
|
||||
frame = frame.addChild(trace[i], ticks);
|
||||
}
|
||||
}
|
||||
frame.total += ticks;
|
||||
frame.self += ticks;
|
||||
frame.addLeaf(ticks);
|
||||
|
||||
depth = Math.max(depth, trace.length);
|
||||
}
|
||||
|
||||
public void dump() throws IOException {
|
||||
if (output == null) {
|
||||
if (args.output == null) {
|
||||
dump(System.out);
|
||||
} else {
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(args.output), 32768);
|
||||
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
|
||||
dump(out);
|
||||
}
|
||||
@@ -107,47 +100,51 @@ public class FlameGraph {
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
out.print(applyReplacements(HEADER,
|
||||
"{title}", title,
|
||||
"{height}", (depth + 1) * 16,
|
||||
"{depth}", depth + 1,
|
||||
"{reverse}", reverse));
|
||||
mintotal = (long) (root.total * args.minwidth / 100);
|
||||
int depth = mintotal > 1 ? root.depth(mintotal) : this.depth + 1;
|
||||
|
||||
String tail = getResource("/flame.html");
|
||||
|
||||
tail = printTill(out, tail, "/*height:*/300");
|
||||
out.print(Math.min(depth * 16, 32767));
|
||||
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out.print(args.title);
|
||||
|
||||
tail = printTill(out, tail, "/*reverse:*/false");
|
||||
out.print(args.reverse);
|
||||
|
||||
tail = printTill(out, tail, "/*depth:*/0");
|
||||
out.print(depth);
|
||||
|
||||
tail = printTill(out, tail, "/*frames:*/");
|
||||
|
||||
mintotal = (long) (root.total * minwidth / 100);
|
||||
printFrame(out, "all", root, 0, 0);
|
||||
|
||||
out.print(FOOTER);
|
||||
tail = printTill(out, tail, "/*highlight:*/");
|
||||
out.print(args.highlight != null ? "'" + escape(args.highlight) + "'" : "");
|
||||
|
||||
out.print(tail);
|
||||
}
|
||||
|
||||
// Replace ${variables} in the given string with field values
|
||||
private String applyReplacements(String s, Object... params) {
|
||||
StringBuilder result = new StringBuilder(s.length() + 256);
|
||||
|
||||
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 String printTill(PrintStream out, String data, String till) {
|
||||
int index = data.indexOf(till);
|
||||
out.print(data.substring(0, index));
|
||||
return data.substring(index + till.length());
|
||||
}
|
||||
|
||||
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
|
||||
int type = frameType(title);
|
||||
title = stripSuffix(title);
|
||||
if (title.indexOf('\'') >= 0) {
|
||||
title = title.replace("'", "\\'");
|
||||
int type = frame.getType();
|
||||
if (type == FRAME_KERNEL) {
|
||||
title = stripSuffix(title);
|
||||
}
|
||||
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + title + "')");
|
||||
if ((frame.inlined | frame.c1 | frame.interpreted) != 0 && frame.inlined < frame.total && frame.interpreted < frame.total) {
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + escape(title) + "'," +
|
||||
frame.inlined + "," + frame.c1 + "," + frame.interpreted + ")");
|
||||
} else {
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + escape(title) + "')");
|
||||
}
|
||||
|
||||
x += frame.self;
|
||||
for (Map.Entry<String, Frame> e : frame.entrySet()) {
|
||||
@@ -159,33 +156,56 @@ public class FlameGraph {
|
||||
}
|
||||
}
|
||||
|
||||
private String stripSuffix(String title) {
|
||||
int len = title.length();
|
||||
if (len >= 4 && title.charAt(len - 1) == ']' && title.regionMatches(len - 4, "_[", 0, 2)) {
|
||||
return title.substring(0, len - 4);
|
||||
private boolean excludeTrace(String[] trace) {
|
||||
Pattern include = args.include;
|
||||
Pattern exclude = args.exclude;
|
||||
if (include == null && exclude == null) {
|
||||
return false;
|
||||
}
|
||||
return title;
|
||||
|
||||
for (String frame : trace) {
|
||||
if (exclude != null && exclude.matcher(frame).matches()) {
|
||||
return true;
|
||||
}
|
||||
if (include != null && include.matcher(frame).matches()) {
|
||||
include = null;
|
||||
if (exclude == null) break;
|
||||
}
|
||||
}
|
||||
|
||||
return include != null;
|
||||
}
|
||||
|
||||
private int frameType(String title) {
|
||||
if (title.endsWith("_[j]")) {
|
||||
return 0;
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
return 1;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
return 2;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
return 3;
|
||||
} else if (title.indexOf('/') > 0 || title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
return 0;
|
||||
} else {
|
||||
return 4;
|
||||
static String stripSuffix(String title) {
|
||||
return title.substring(0, title.length() - 4);
|
||||
}
|
||||
|
||||
static String escape(String s) {
|
||||
if (s.indexOf('\\') >= 0) s = s.replace("\\", "\\\\");
|
||||
if (s.indexOf('\'') >= 0) s = s.replace("'", "\\'");
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String getResource(String name) {
|
||||
try (InputStream stream = FlameGraph.class.getResourceAsStream(name)) {
|
||||
if (stream == null) {
|
||||
throw new IOException("No resource found");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[64 * 1024];
|
||||
for (int length; (length = stream.read(buffer)) != -1; ) {
|
||||
result.write(buffer, 0, length);
|
||||
}
|
||||
return result.toString("UTF-8");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Can't load resource with name " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
public static void main(String[] cmdline) throws IOException {
|
||||
Arguments args = new Arguments(cmdline);
|
||||
if (args.input == null) {
|
||||
System.out.println("Usage: java " + FlameGraph.class.getName() + " [options] input.collapsed [output.html]");
|
||||
System.out.println();
|
||||
System.out.println("Options:");
|
||||
@@ -193,250 +213,87 @@ public class FlameGraph {
|
||||
System.out.println(" --reverse");
|
||||
System.out.println(" --minwidth PERCENT");
|
||||
System.out.println(" --skip FRAMES");
|
||||
System.out.println(" --include PATTERN");
|
||||
System.out.println(" --exclude PATTERN");
|
||||
System.out.println(" --highlight PATTERN");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
fg.parse();
|
||||
fg.dump();
|
||||
}
|
||||
|
||||
static class Frame extends TreeMap<String, Frame> {
|
||||
final byte type;
|
||||
long total;
|
||||
long self;
|
||||
long inlined, c1, interpreted;
|
||||
|
||||
Frame child(String title) {
|
||||
Frame child = get(title);
|
||||
Frame(byte type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
byte getType() {
|
||||
if (inlined * 3 >= total) {
|
||||
return FRAME_INLINED;
|
||||
} else if (c1 * 2 >= total) {
|
||||
return FRAME_C1_COMPILED;
|
||||
} else if (interpreted * 2 >= total) {
|
||||
return FRAME_INTERPRETED;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private Frame getChild(String title, byte type) {
|
||||
Frame child = super.get(title);
|
||||
if (child == null) {
|
||||
put(title, child = new Frame());
|
||||
super.put(title, child = new Frame(type));
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Frame addChild(String title, long ticks) {
|
||||
total += ticks;
|
||||
|
||||
Frame child;
|
||||
if (title.endsWith("_[j]")) {
|
||||
child = getChild(stripSuffix(title), FRAME_JIT_COMPILED);
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
(child = getChild(stripSuffix(title), FRAME_JIT_COMPILED)).inlined += ticks;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
child = getChild(title, FRAME_KERNEL);
|
||||
} else if (title.endsWith("_[1]")) {
|
||||
(child = getChild(stripSuffix(title), FRAME_JIT_COMPILED)).c1 += ticks;
|
||||
} else if (title.endsWith("_[0]")) {
|
||||
(child = getChild(stripSuffix(title), FRAME_JIT_COMPILED)).interpreted += ticks;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
child = getChild(title, FRAME_CPP);
|
||||
} else if (title.indexOf('/') > 0 && title.charAt(0) != '['
|
||||
|| title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
child = getChild(title, FRAME_JIT_COMPILED);
|
||||
} else {
|
||||
child = getChild(title, FRAME_NATIVE);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
void addLeaf(long ticks) {
|
||||
total += ticks;
|
||||
self += ticks;
|
||||
}
|
||||
|
||||
int depth(long cutoff) {
|
||||
int depth = 0;
|
||||
if (size() > 0) {
|
||||
for (Frame child : values()) {
|
||||
if (child.total >= cutoff) {
|
||||
depth = Math.max(depth, child.depth(cutoff));
|
||||
}
|
||||
}
|
||||
}
|
||||
return depth + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String HEADER = "<!DOCTYPE html>\n" +
|
||||
"<html lang='en'>\n" +
|
||||
"<head>\n" +
|
||||
"<meta charset='utf-8'>\n" +
|
||||
"<style>\n" +
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n" +
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n" +
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n" +
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n" +
|
||||
"\tp {margin: 5px 0 5px 0}\n" +
|
||||
"\ta {color: #0366d6}\n" +
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n" +
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n" +
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n" +
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n" +
|
||||
"\t#reset {cursor: pointer}\n" +
|
||||
"</style>\n" +
|
||||
"</head>\n" +
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n" +
|
||||
"<h1>${title}</h1>\n" +
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n" +
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n" +
|
||||
"<canvas id='canvas' style='width: 100%; height: ${height}px'></canvas>\n" +
|
||||
"<div id='hl'><span></span></div>\n" +
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n" +
|
||||
"<p id='status'> </p>\n" +
|
||||
"<script>\n" +
|
||||
"\t// Copyright 2020 Andrei Pangin\n" +
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n" +
|
||||
"\t'use strict';\n" +
|
||||
"\tvar root, rootLevel, px, pattern;\n" +
|
||||
"\tvar reverse = ${reverse};\n" +
|
||||
"\tconst levels = Array(${depth});\n" +
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n" +
|
||||
"\t\tlevels[h] = [];\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tconst canvas = document.getElementById('canvas');\n" +
|
||||
"\tconst c = canvas.getContext('2d');\n" +
|
||||
"\tconst hl = document.getElementById('hl');\n" +
|
||||
"\tconst status = document.getElementById('status');\n" +
|
||||
"\n" +
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n" +
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n" +
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n" +
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n" +
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n" +
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n" +
|
||||
"\tc.font = document.body.style.font;\n" +
|
||||
"\n" +
|
||||
"\tconst palette = [\n" +
|
||||
"\t\t[0x50e150, 30, 30, 30],\n" +
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n" +
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n" +
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n" +
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n" +
|
||||
"\t];\n" +
|
||||
"\n" +
|
||||
"\tfunction getColor(p) {\n" +
|
||||
"\t\tconst v = Math.random();\n" +
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction f(level, left, width, type, title) {\n" +
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction samples(n) {\n" +
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction pct(a, b) {\n" +
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction findFrame(frames, x) {\n" +
|
||||
"\t\tlet left = 0;\n" +
|
||||
"\t\tlet right = frames.length - 1;\n" +
|
||||
"\n" +
|
||||
"\t\twhile (left <= right) {\n" +
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n" +
|
||||
"\t\t\tconst f = frames[mid];\n" +
|
||||
"\n" +
|
||||
"\t\t\tif (f.left > x) {\n" +
|
||||
"\t\t\t\tright = mid - 1;\n" +
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n" +
|
||||
"\t\t\t\tleft = mid + 1;\n" +
|
||||
"\t\t\t} else {\n" +
|
||||
"\t\t\t\treturn f;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n" +
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n" +
|
||||
"\n" +
|
||||
"\t\treturn null;\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction search(r) {\n" +
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n" +
|
||||
"\t\t\treturn;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n" +
|
||||
"\t\tconst matched = render(root, rootLevel);\n" +
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%';\n" +
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction render(newRoot, newLevel) {\n" +
|
||||
"\t\tif (root) {\n" +
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n" +
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\troot = newRoot || levels[0][0];\n" +
|
||||
"\t\trootLevel = newLevel || 0;\n" +
|
||||
"\t\tpx = canvasWidth / root.width;\n" +
|
||||
"\n" +
|
||||
"\t\tconst x0 = root.left;\n" +
|
||||
"\t\tconst x1 = x0 + root.width;\n" +
|
||||
"\t\tconst marked = [];\n" +
|
||||
"\n" +
|
||||
"\t\tfunction mark(f) {\n" +
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfunction totalMarked() {\n" +
|
||||
"\t\t\tlet total = 0;\n" +
|
||||
"\t\t\tlet left = 0;\n" +
|
||||
"\t\t\tfor (let x in marked) {\n" +
|
||||
"\t\t\t\tif (+x >= left) {\n" +
|
||||
"\t\t\t\t\ttotal += marked[x];\n" +
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t\treturn total;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n" +
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n" +
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n" +
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
|
||||
"\n" +
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n" +
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n" +
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n" +
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n" +
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\t\t\tif (alpha) {\n" +
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n" +
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n" +
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n" +
|
||||
"\t\t\tconst frames = levels[h];\n" +
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n" +
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\treturn totalMarked();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tcanvas.onmousemove = function() {\n" +
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n" +
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n" +
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n" +
|
||||
"\t\t\tif (f) {\n" +
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n" +
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n" +
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n" +
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n" +
|
||||
"\t\t\t\thl.style.display = 'block';\n" +
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%)';\n" +
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n" +
|
||||
"\t\t\t\tcanvas.onclick = function() {\n" +
|
||||
"\t\t\t\t\tif (f != root) {\n" +
|
||||
"\t\t\t\t\t\trender(f, h);\n" +
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n" +
|
||||
"\t\t\t\t\t}\n" +
|
||||
"\t\t\t\t};\n" +
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n" +
|
||||
"\t\t\t\treturn;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\t\tcanvas.onmouseout();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tcanvas.onmouseout = function() {\n" +
|
||||
"\t\thl.style.display = 'none';\n" +
|
||||
"\t\tstatus.textContent = '\\xa0';\n" +
|
||||
"\t\tcanvas.title = '';\n" +
|
||||
"\t\tcanvas.style.cursor = '';\n" +
|
||||
"\t\tcanvas.onclick = '';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n" +
|
||||
"\t\treverse = !reverse;\n" +
|
||||
"\t\trender();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('search').onclick = function() {\n" +
|
||||
"\t\tsearch(true);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n" +
|
||||
"\t\tsearch(false);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\twindow.onkeydown = function() {\n" +
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n" +
|
||||
"\t\t\tevent.preventDefault();\n" +
|
||||
"\t\t\tsearch(true);\n" +
|
||||
"\t\t} else if (event.keyCode === 27) {\n" +
|
||||
"\t\t\tsearch(false);\n" +
|
||||
"\t\t}\n" +
|
||||
"\t}\n";
|
||||
|
||||
private static final String FOOTER = "render();\n" +
|
||||
"</script></body></html>\n";
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Main-Class: Main
|
||||
@@ -24,8 +24,9 @@ public class Main {
|
||||
System.out.println("Usage: java -cp converter.jar <Converter> [options] <input> <output>");
|
||||
System.out.println();
|
||||
System.out.println("Available converters:");
|
||||
System.out.println(" FlameGraph input.collapsed output.html");
|
||||
System.out.println(" jfr2flame input.jfr output.html");
|
||||
System.out.println(" jfr2nflx input.jfr output.nflx");
|
||||
System.out.println(" FlameGraph input.collapsed output.html");
|
||||
System.out.println(" jfr2flame input.jfr output.html");
|
||||
System.out.println(" jfr2nflx input.jfr output.nflx");
|
||||
System.out.println(" jfr2pprof input.jfr output.pprof");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,72 +19,243 @@ import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to HTML Flame Graph.
|
||||
*/
|
||||
public class jfr2flame {
|
||||
|
||||
private static final int FRAME_KERNEL = 5;
|
||||
private static final String[] FRAME_SUFFIX = {"_[0]", "_[j]", "_[i]", "", "", "_[k]", "_[1]"};
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final Arguments args;
|
||||
private final Dictionary<String> methodNames = new Dictionary<>();
|
||||
|
||||
public jfr2flame(JfrReader jfr) {
|
||||
public jfr2flame(JfrReader jfr, Arguments args) {
|
||||
this.jfr = jfr;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void convert(final FlameGraph fg) {
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
String[] trace = new String[methods.length];
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
trace[trace.length - 1 - i] = getMethodName(methods[i], types[i]);
|
||||
public void convert(final FlameGraph fg) throws IOException {
|
||||
EventAggregator agg = new EventAggregator(args.threads, args.total);
|
||||
|
||||
Class<? extends Event> eventClass =
|
||||
args.live ? LiveObject.class :
|
||||
args.alloc ? AllocationSample.class :
|
||||
args.lock ? ContendedLock.class : ExecutionSample.class;
|
||||
int threadState = args.cpu ? getMapKey(jfr.threadStates, "STATE_RUNNABLE") : -1;
|
||||
|
||||
long startTicks = args.from != 0 ? toTicks(args.from) : Long.MIN_VALUE;
|
||||
long endTicks = args.to != 0 ? toTicks(args.to) : Long.MAX_VALUE;
|
||||
|
||||
for (Event event; (event = jfr.readEvent(eventClass)) != null; ) {
|
||||
if (event.time >= startTicks && event.time <= endTicks) {
|
||||
if (threadState < 0 || ((ExecutionSample) event).threadState == threadState) {
|
||||
agg.collect(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
|
||||
final boolean scale = args.total && eventClass == ContendedLock.class && ticksToNanos != 1.0;
|
||||
|
||||
// Don't use lambda for faster startup
|
||||
agg.forEach(new EventAggregator.Visitor() {
|
||||
@Override
|
||||
public void visit(Event event, long value) {
|
||||
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
Arguments args = jfr2flame.this.args;
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
int[] locations = stackTrace.locations;
|
||||
String classFrame = getClassFrame(event);
|
||||
String[] trace = new String[methods.length + (args.threads ? 1 : 0) + (classFrame != null ? 1 : 0)];
|
||||
if (args.threads) {
|
||||
trace[0] = getThreadFrame(event.tid);
|
||||
}
|
||||
int idx = trace.length;
|
||||
if (classFrame != null) {
|
||||
trace[--idx] = classFrame;
|
||||
}
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
String methodName = getMethodName(methods[i], types[i]);
|
||||
int location;
|
||||
if (args.lines && (location = locations[i] >>> 16) != 0) {
|
||||
methodName += ":" + location;
|
||||
} else if (args.bci && (location = locations[i] & 0xffff) != 0) {
|
||||
methodName += "@" + location;
|
||||
}
|
||||
trace[--idx] = methodName + FRAME_SUFFIX[types[i]];
|
||||
}
|
||||
fg.addSample(trace, scale ? (long) (value * ticksToNanos) : value);
|
||||
}
|
||||
fg.addSample(trace, stackTrace.samples);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getMethodName(long methodId, int type) {
|
||||
private String getThreadFrame(int tid) {
|
||||
String threadName = jfr.threads.get(tid);
|
||||
return threadName == null ? "[tid=" + tid + ']' : '[' + threadName + " tid=" + tid + ']';
|
||||
}
|
||||
|
||||
private String getClassFrame(Event event) {
|
||||
long classId;
|
||||
String suffix;
|
||||
if (event instanceof AllocationSample) {
|
||||
classId = ((AllocationSample) event).classId;
|
||||
suffix = ((AllocationSample) event).tlabSize == 0 ? "_[k]" : "_[i]";
|
||||
} else if (event instanceof ContendedLock) {
|
||||
classId = ((ContendedLock) event).classId;
|
||||
suffix = "_[i]";
|
||||
} else if (event instanceof LiveObject) {
|
||||
classId = ((LiveObject) event).classId;
|
||||
suffix = "_[i]";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassRef cls = jfr.classes.get(classId);
|
||||
if (cls == null) {
|
||||
return "null";
|
||||
}
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
|
||||
int arrayDepth = 0;
|
||||
while (className[arrayDepth] == '[') {
|
||||
arrayDepth++;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(toJavaClassName(className, arrayDepth, true));
|
||||
while (arrayDepth-- > 0) {
|
||||
sb.append("[]");
|
||||
}
|
||||
return sb.append(suffix).toString();
|
||||
}
|
||||
|
||||
private String getMethodName(long methodId, byte methodType) {
|
||||
String result = methodNames.get(methodId);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0) {
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = type == FRAME_KERNEL ? methodStr + "_[k]" : methodStr;
|
||||
if (method == null) {
|
||||
result = "unknown";
|
||||
} else {
|
||||
String classStr = new String(className, StandardCharsets.UTF_8);
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = classStr + '.' + methodStr + "_[j]";
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0 || isNativeFrame(methodType)) {
|
||||
result = new String(methodName, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
String classStr = toJavaClassName(className, 0, args.dot);
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = classStr + '.' + methodStr;
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.put(methodId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
private boolean isNativeFrame(byte methodType) {
|
||||
return methodType >= FlameGraph.FRAME_NATIVE && methodType <= FlameGraph.FRAME_KERNEL
|
||||
&& jfr.frameTypes.size() > FlameGraph.FRAME_NATIVE + 1;
|
||||
}
|
||||
|
||||
private String toJavaClassName(byte[] symbol, int start, boolean dotted) {
|
||||
int end = symbol.length;
|
||||
if (start > 0) {
|
||||
switch (symbol[start]) {
|
||||
case 'B':
|
||||
return "byte";
|
||||
case 'C':
|
||||
return "char";
|
||||
case 'S':
|
||||
return "short";
|
||||
case 'I':
|
||||
return "int";
|
||||
case 'J':
|
||||
return "long";
|
||||
case 'Z':
|
||||
return "boolean";
|
||||
case 'F':
|
||||
return "float";
|
||||
case 'D':
|
||||
return "double";
|
||||
case 'L':
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.simple) {
|
||||
for (int i = end - 2; i >= start; i--) {
|
||||
if (symbol[i] == '/' && (symbol[i + 1] < '0' || symbol[i + 1] > '9')) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String s = new String(symbol, start, end - start, StandardCharsets.UTF_8);
|
||||
return dotted ? s.replace('/', '.') : s;
|
||||
}
|
||||
|
||||
// millis can be an absolute timestamp or an offset from the beginning/end of the recording
|
||||
private long toTicks(long millis) {
|
||||
long nanos = millis * 1_000_000;
|
||||
if (millis < 0) {
|
||||
nanos += jfr.endNanos;
|
||||
} else if (millis < 1500000000000L) {
|
||||
nanos += jfr.startNanos;
|
||||
}
|
||||
return jfr.nanosToTicks(nanos);
|
||||
}
|
||||
|
||||
private static int getMapKey(Map<Integer, String> map, String value) {
|
||||
for (Map.Entry<Integer, String> entry : map.entrySet()) {
|
||||
if (value.equals(entry.getValue())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void main(String[] cmdline) throws Exception {
|
||||
Arguments args = new Arguments(cmdline);
|
||||
if (args.input == null) {
|
||||
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
|
||||
System.out.println();
|
||||
System.out.println("options include all supported FlameGraph options, plus the following:");
|
||||
System.out.println(" --cpu CPU Flame Graph");
|
||||
System.out.println(" --alloc Allocation Flame Graph");
|
||||
System.out.println(" --live Include only live objects in allocation profile");
|
||||
System.out.println(" --lock Lock contention Flame Graph");
|
||||
System.out.println(" --threads Split profile by threads");
|
||||
System.out.println(" --total Accumulate the total value (time, bytes, etc.)");
|
||||
System.out.println(" --lines Show line numbers");
|
||||
System.out.println(" --bci Show bytecode indices");
|
||||
System.out.println(" --simple Simple class names instead of FQN");
|
||||
System.out.println(" --dot Dotted class names");
|
||||
System.out.println(" --from TIME Start time in ms (absolute or relative)");
|
||||
System.out.println(" --to TIME End time in ms (absolute or relative)");
|
||||
System.out.println(" --collapsed Use collapsed stacks output format");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try (JfrReader jfr = new JfrReader(fg.input)) {
|
||||
new jfr2flame(jfr).convert(fg);
|
||||
boolean collapsed = args.collapsed || args.output != null && args.output.endsWith(".collapsed");
|
||||
FlameGraph fg = collapsed ? new CollapsedStacks(args) : new FlameGraph(args);
|
||||
|
||||
try (JfrReader jfr = new JfrReader(args.input)) {
|
||||
new jfr2flame(jfr, args).convert(fg);
|
||||
}
|
||||
|
||||
fg.dump();
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.Sample;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.Event;
|
||||
import one.jfr.event.EventAggregator;
|
||||
import one.jfr.event.ExecutionSample;
|
||||
import one.proto.Proto;
|
||||
|
||||
import java.io.File;
|
||||
@@ -27,6 +28,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to nflxprofile format
|
||||
@@ -35,24 +37,27 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class jfr2nflx {
|
||||
|
||||
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel"};
|
||||
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel", "jit"};
|
||||
private static final byte[] NO_STACK = "[no_stack]".getBytes();
|
||||
private static final byte[] UNKNOWN = "[unknown]".getBytes();
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final List<ExecutionSample> samples;
|
||||
|
||||
public jfr2nflx(JfrReader jfr) {
|
||||
public jfr2nflx(JfrReader jfr) throws IOException {
|
||||
this.jfr = jfr;
|
||||
this.samples = jfr.readAllEvents(ExecutionSample.class);
|
||||
}
|
||||
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
int samples = jfr.samples.size();
|
||||
long durationTicks = samples == 0 ? 0 : jfr.samples.get(samples - 1).time - jfr.startTicks + 1;
|
||||
int size = samples.size();
|
||||
long durationTicks = size == 0 ? 0 : samples.get(size - 1).time - jfr.startTicks + 1;
|
||||
|
||||
final Proto profile = new Proto(200000)
|
||||
.field(1, 0.0)
|
||||
.field(2, Math.max(jfr.durationNanos / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(2, Math.max(jfr.durationNanos() / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(3, packSamples())
|
||||
.field(4, packDeltas())
|
||||
.field(6, "async-profiler")
|
||||
@@ -63,15 +68,23 @@ public class jfr2nflx {
|
||||
final Proto nodes = new Proto(10000);
|
||||
final Proto node = new Proto(10000);
|
||||
|
||||
EventAggregator agg = new EventAggregator(false, false);
|
||||
for (ExecutionSample sample : samples) {
|
||||
agg.collect(sample);
|
||||
}
|
||||
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
agg.forEach(new EventAggregator.Visitor() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
profile.field(5, nodes
|
||||
.field(1, (int) id)
|
||||
.field(2, packNode(node, stackTrace)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
public void visit(Event event, long value) {
|
||||
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
profile.field(5, nodes
|
||||
.field(1, event.stackTraceId)
|
||||
.field(2, packNode(node, stackTrace)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,13 +99,13 @@ public class jfr2nflx {
|
||||
byte[] types = stackTrace.types;
|
||||
int top = methods.length - 1;
|
||||
|
||||
node.field(1, top >= 0 ? getMethodName(methods[top]) : NO_STACK);
|
||||
node.field(1, top >= 0 ? getMethodName(methods[top], types[top]) : NO_STACK);
|
||||
node.field(2, 1);
|
||||
node.field(4, top >= 0 ? FRAME_TYPE[types[top]] : "user");
|
||||
|
||||
for (Proto frame = new Proto(100); --top >= 0; frame.reset()) {
|
||||
node.field(10, frame
|
||||
.field(1, getMethodName(methods[top]))
|
||||
.field(1, getMethodName(methods[top], types[top]))
|
||||
.field(2, FRAME_TYPE[types[top]]));
|
||||
}
|
||||
|
||||
@@ -101,7 +114,7 @@ public class jfr2nflx {
|
||||
|
||||
private Proto packSamples() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (Sample sample : jfr.samples) {
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeInt(sample.stackTraceId);
|
||||
}
|
||||
return proto;
|
||||
@@ -111,7 +124,7 @@ public class jfr2nflx {
|
||||
Proto proto = new Proto(10000);
|
||||
double ticksPerSec = jfr.ticksPerSec;
|
||||
long prevTime = jfr.startTicks;
|
||||
for (Sample sample : jfr.samples) {
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
|
||||
prevTime = sample.time;
|
||||
}
|
||||
@@ -120,19 +133,24 @@ public class jfr2nflx {
|
||||
|
||||
private Proto packTids() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (Sample sample : jfr.samples) {
|
||||
for (ExecutionSample sample : samples) {
|
||||
proto.writeInt(sample.tid);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private byte[] getMethodName(long methodId) {
|
||||
private byte[] getMethodName(long methodId, byte methodType) {
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
if (method == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0) {
|
||||
if ((methodType >= FlameGraph.FRAME_NATIVE && methodType <= FlameGraph.FRAME_KERNEL)
|
||||
|| className == null || className.length == 0) {
|
||||
return methodName;
|
||||
} else {
|
||||
byte[] fullName = Arrays.copyOf(className, className.length + 1 + methodName.length);
|
||||
|
||||
227
src/converter/jfr2pprof.java
Normal file
227
src/converter/jfr2pprof.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.ExecutionSample;
|
||||
import one.proto.Proto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Convert a JFR file to pprof
|
||||
* <p>
|
||||
* Protobuf definition: https://github.com/google/pprof/blob/44fc4e887b6b0cfb196973bcdb1fab95f0b3a75b/proto/profile.proto
|
||||
*/
|
||||
public class jfr2pprof {
|
||||
|
||||
public static class Method {
|
||||
final byte[] name;
|
||||
|
||||
public Method(final byte[] name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
return other instanceof Method && Arrays.equals(name, ((Method) other).name);
|
||||
}
|
||||
}
|
||||
|
||||
public static final byte[] METHOD_UNKNOWN = "[unknown]".getBytes();
|
||||
|
||||
// Profile IDs
|
||||
public static final int PROFILE_SAMPLE_TYPE = 1;
|
||||
public static final int PROFILE_SAMPLE = 2;
|
||||
public static final int PROFILE_LOCATION = 4;
|
||||
public static final int PROFILE_FUNCTION = 5;
|
||||
public static final int PROFILE_STRING_TABLE = 6;
|
||||
public static final int PROFILE_TIME_NANOS = 9;
|
||||
public static final int PROFILE_DURATION_NANOS = 10;
|
||||
public static final int PROFILE_COMMENT = 13;
|
||||
public static final int PROFILE_DEFAULT_SAMPLE_TYPE = 14;
|
||||
|
||||
// ValueType IDs
|
||||
public static final int VALUETYPE_TYPE = 1;
|
||||
public static final int VALUETYPE_UNIT = 2;
|
||||
|
||||
// Sample IDs
|
||||
public static final int SAMPLE_LOCATION_ID = 1;
|
||||
public static final int SAMPLE_VALUE = 2;
|
||||
|
||||
// Location IDs
|
||||
public static final int LOCATION_ID = 1;
|
||||
public static final int LOCATION_LINE = 4;
|
||||
|
||||
// Line IDs
|
||||
public static final int LINE_FUNCTION_ID = 1;
|
||||
public static final int LINE_LINE = 2;
|
||||
|
||||
// Function IDs
|
||||
public static final int FUNCTION_ID = 1;
|
||||
public static final int FUNCTION_NAME = 2;
|
||||
|
||||
private final JfrReader reader;
|
||||
|
||||
public jfr2pprof(final JfrReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
// `Proto` instances are mutable, careful with reordering
|
||||
public void dump(final OutputStream out) throws Exception {
|
||||
// Mutable IDs, need to start at 1
|
||||
int functionId = 1;
|
||||
int locationId = 1;
|
||||
int stringId = 1;
|
||||
|
||||
// Used to de-dupe
|
||||
final Map<Method, Integer> functions = new HashMap<>();
|
||||
final Map<Method, Integer> locations = new HashMap<>();
|
||||
|
||||
final Proto profile = new Proto(200_000)
|
||||
.field(PROFILE_TIME_NANOS, reader.startNanos)
|
||||
.field(PROFILE_DURATION_NANOS, reader.durationNanos())
|
||||
.field(PROFILE_DEFAULT_SAMPLE_TYPE, 0L)
|
||||
.field(PROFILE_STRING_TABLE, "".getBytes(StandardCharsets.UTF_8)) // "" needs to be index 0
|
||||
.field(PROFILE_STRING_TABLE, "async-profiler".getBytes(StandardCharsets.UTF_8))
|
||||
.field(PROFILE_COMMENT, stringId++);
|
||||
|
||||
final Proto sampleType = new Proto(100);
|
||||
|
||||
profile.field(PROFILE_STRING_TABLE, "cpu".getBytes(StandardCharsets.UTF_8));
|
||||
sampleType.field(VALUETYPE_TYPE, stringId++);
|
||||
|
||||
profile.field(PROFILE_STRING_TABLE, "nanoseconds".getBytes(StandardCharsets.UTF_8));
|
||||
sampleType.field(VALUETYPE_UNIT, stringId++);
|
||||
|
||||
profile.field(PROFILE_SAMPLE_TYPE, sampleType);
|
||||
|
||||
final List<ExecutionSample> jfrSamples = reader.readAllEvents(ExecutionSample.class);
|
||||
final Dictionary<StackTrace> stackTraces = reader.stackTraces;
|
||||
long previousTime = reader.startTicks; // Mutate this to keep track of time deltas
|
||||
|
||||
// Iterate over samples
|
||||
for (final ExecutionSample jfrSample : jfrSamples) {
|
||||
final StackTrace stackTrace = stackTraces.get(jfrSample.stackTraceId);
|
||||
final long[] methods = stackTrace.methods;
|
||||
final byte[] types = stackTrace.types;
|
||||
|
||||
final long nanosSinceLastSample = (jfrSample.time - previousTime) * 1_000_000_000 / reader.ticksPerSec;
|
||||
final Proto sample = new Proto(1_000).field(SAMPLE_VALUE, nanosSinceLastSample);
|
||||
|
||||
for (int current = 0; current < methods.length; current++) {
|
||||
final byte methodType = types[current];
|
||||
final long methodIdentifier = methods[current];
|
||||
final byte[] methodName = getMethodName(methodIdentifier, methodType);
|
||||
final Method method = new Method(methodName);
|
||||
final int line = stackTrace.locations[current] >>> 16;
|
||||
|
||||
final Integer methodId = functions.get(method);
|
||||
if (null == methodId) {
|
||||
final int funcId = functionId++;
|
||||
profile.field(PROFILE_STRING_TABLE, methodName);
|
||||
final Proto function = new Proto(16)
|
||||
.field(FUNCTION_ID, funcId)
|
||||
.field(FUNCTION_NAME, stringId++);
|
||||
|
||||
profile.field(PROFILE_FUNCTION, function);
|
||||
|
||||
functions.put(method, funcId);
|
||||
}
|
||||
|
||||
final Integer locaId = locations.get(method);
|
||||
if (null == locaId) {
|
||||
final int locId = locationId++;
|
||||
final Proto locLine = new Proto(16).field(LINE_FUNCTION_ID, functions.get(method));
|
||||
if (line > 0) {
|
||||
locLine.field(LINE_LINE, line);
|
||||
}
|
||||
|
||||
final Proto location = new Proto(16)
|
||||
.field(LOCATION_ID, locId)
|
||||
.field(LOCATION_LINE, locLine);
|
||||
|
||||
profile.field(PROFILE_LOCATION, location);
|
||||
|
||||
locations.put(method, locId);
|
||||
}
|
||||
|
||||
sample.field(SAMPLE_LOCATION_ID, locations.get(method));
|
||||
}
|
||||
|
||||
profile.field(PROFILE_SAMPLE, sample);
|
||||
|
||||
previousTime = jfrSample.time;
|
||||
}
|
||||
|
||||
out.write(profile.buffer(), 0, profile.size());
|
||||
}
|
||||
|
||||
private byte[] getMethodName(final long methodId, final byte methodType) {
|
||||
final MethodRef ref = reader.methods.get(methodId);
|
||||
if (null == ref) {
|
||||
return METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
final ClassRef classRef = reader.classes.get(ref.cls);
|
||||
final byte[] className = reader.symbols.get(classRef.name);
|
||||
final byte[] methodName = reader.symbols.get(ref.name);
|
||||
|
||||
if ((methodType >= FlameGraph.FRAME_NATIVE && methodType <= FlameGraph.FRAME_KERNEL) || className == null || className.length == 0) {
|
||||
// Native method
|
||||
return methodName;
|
||||
} else {
|
||||
// JVM method
|
||||
final byte[] fullName = new byte[className.length + 1 + methodName.length];
|
||||
System.arraycopy(className, 0, fullName, 0, className.length);
|
||||
fullName[className.length] = '.';
|
||||
System.arraycopy(methodName, 0, fullName, className.length + 1, methodName.length);
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Usage: java " + jfr2pprof.class.getName() + " input.jfr output.pprof");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File dst = new File(args[1]);
|
||||
if (dst.isDirectory()) {
|
||||
dst = new File(dst, new File(args[0]).getName().replace(".jfr", ".pprof"));
|
||||
}
|
||||
|
||||
try (final JfrReader jfr = new JfrReader(args[0]);
|
||||
final FileOutputStream out = new FileOutputStream(dst)) {
|
||||
new jfr2pprof(jfr).dump(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,22 +31,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 +79,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 +107,7 @@ public class Dictionary<T> {
|
||||
}
|
||||
|
||||
private static int hashCode(long key) {
|
||||
key *= 0xc6a4a7935bd1e995L;
|
||||
return (int) (key ^ (key >>> 32));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import one.jfr.event.*;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
@@ -33,17 +36,19 @@ 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 final FileChannel ch;
|
||||
private final ByteBuffer buf;
|
||||
private ByteBuffer buf;
|
||||
private long filePosition;
|
||||
|
||||
public final long startNanos;
|
||||
public final long durationNanos;
|
||||
public final long startTicks;
|
||||
public final long ticksPerSec;
|
||||
public boolean incomplete;
|
||||
public long startNanos = Long.MAX_VALUE;
|
||||
public long endNanos = Long.MIN_VALUE;
|
||||
public long startTicks = Long.MAX_VALUE;
|
||||
public long ticksPerSec;
|
||||
|
||||
public final Dictionary<JfrClass> types = new Dictionary<>();
|
||||
public final Map<String, JfrClass> typesByName = new HashMap<>();
|
||||
@@ -54,29 +59,38 @@ public class JfrReader implements Closeable {
|
||||
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<>();
|
||||
|
||||
private int executionSample;
|
||||
private int nativeMethodSample;
|
||||
private int allocationInNewTLAB;
|
||||
private int allocationOutsideTLAB;
|
||||
private int allocationSample;
|
||||
private int liveObject;
|
||||
private int monitorEnter;
|
||||
private int threadPark;
|
||||
private int activeSetting;
|
||||
private boolean activeSettingHasStack;
|
||||
|
||||
public JfrReader(String fileName) throws IOException {
|
||||
this.ch = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
|
||||
this.buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
|
||||
this.buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
|
||||
|
||||
if (buf.getInt(0) != 0x464c5200) {
|
||||
throw new IOException("Not a valid JFR file");
|
||||
buf.flip();
|
||||
ensureBytes(CHUNK_HEADER_SIZE);
|
||||
if (!readChunk(0)) {
|
||||
throw new IOException("Incomplete JFR file");
|
||||
}
|
||||
}
|
||||
|
||||
int version = buf.getInt(4);
|
||||
if (version < 0x20000 || version > 0x2ffff) {
|
||||
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
|
||||
public JfrReader(ByteBuffer buf) throws IOException {
|
||||
this.ch = null;
|
||||
this.buf = buf;
|
||||
|
||||
buf.order(ByteOrder.BIG_ENDIAN);
|
||||
if (!readChunk(0)) {
|
||||
throw new IOException("Incomplete JFR file");
|
||||
}
|
||||
|
||||
this.startNanos = buf.getLong(32);
|
||||
this.durationNanos = buf.getLong(40);
|
||||
this.startTicks = buf.getLong(48);
|
||||
this.ticksPerSec = buf.getLong(56);
|
||||
|
||||
readMeta();
|
||||
readConstantPool();
|
||||
readEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,9 +98,157 @@ public class JfrReader implements Closeable {
|
||||
ch.close();
|
||||
}
|
||||
|
||||
private void readMeta() {
|
||||
buf.position(buf.getInt(META_OFFSET + 4));
|
||||
getVarint();
|
||||
public long durationNanos() {
|
||||
return endNanos - startNanos;
|
||||
}
|
||||
|
||||
public long nanosToTicks(long nanos) {
|
||||
return (long) ((nanos - startNanos) * (ticksPerSec / 1e9)) + startTicks;
|
||||
}
|
||||
|
||||
public List<Event> readAllEvents() throws IOException {
|
||||
return readAllEvents(null);
|
||||
}
|
||||
|
||||
public <E extends Event> List<E> readAllEvents(Class<E> cls) throws IOException {
|
||||
ArrayList<E> events = new ArrayList<>();
|
||||
for (E event; (event = readEvent(cls)) != null; ) {
|
||||
events.add(event);
|
||||
}
|
||||
Collections.sort(events);
|
||||
return events;
|
||||
}
|
||||
|
||||
public Event readEvent() throws IOException {
|
||||
return readEvent(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends Event> E readEvent(Class<E> cls) throws IOException {
|
||||
while (ensureBytes(CHUNK_HEADER_SIZE)) {
|
||||
int pos = buf.position();
|
||||
int size = getVarint();
|
||||
int type = getVarint();
|
||||
|
||||
if (type == 'L' && buf.getInt(pos) == CHUNK_SIGNATURE) {
|
||||
if (readChunk(pos)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == executionSample || type == nativeMethodSample) {
|
||||
if (cls == null || cls == ExecutionSample.class) return (E) readExecutionSample();
|
||||
} else if (type == allocationInNewTLAB) {
|
||||
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(true);
|
||||
} else if (type == allocationOutsideTLAB || type == allocationSample) {
|
||||
if (cls == null || cls == AllocationSample.class) return (E) readAllocationSample(false);
|
||||
} else if (type == liveObject) {
|
||||
if (cls == null || cls == LiveObject.class) return (E) readLiveObject();
|
||||
} else if (type == monitorEnter) {
|
||||
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(false);
|
||||
} else if (type == threadPark) {
|
||||
if (cls == null || cls == ContendedLock.class) return (E) readContendedLock(true);
|
||||
} else if (type == activeSetting) {
|
||||
readActiveSetting();
|
||||
}
|
||||
|
||||
seek(filePosition + pos + size);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ExecutionSample readExecutionSample() {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int threadState = getVarint();
|
||||
return new ExecutionSample(time, tid, stackTraceId, threadState);
|
||||
}
|
||||
|
||||
private AllocationSample readAllocationSample(boolean tlab) {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int classId = getVarint();
|
||||
long allocationSize = getVarlong();
|
||||
long tlabSize = tlab ? getVarlong() : 0;
|
||||
return new AllocationSample(time, tid, stackTraceId, classId, allocationSize, tlabSize);
|
||||
}
|
||||
|
||||
private LiveObject readLiveObject() {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int classId = getVarint();
|
||||
long allocationSize = getVarlong();
|
||||
long allocatimeTime = getVarlong();
|
||||
return new LiveObject(time, tid, stackTraceId, classId, allocationSize, allocatimeTime);
|
||||
}
|
||||
|
||||
private ContendedLock readContendedLock(boolean hasTimeout) {
|
||||
long time = getVarlong();
|
||||
long duration = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int classId = getVarint();
|
||||
if (hasTimeout) getVarlong();
|
||||
long until = getVarlong();
|
||||
long address = getVarlong();
|
||||
return new ContendedLock(time, tid, stackTraceId, duration, classId);
|
||||
}
|
||||
|
||||
private void readActiveSetting() {
|
||||
long time = getVarlong();
|
||||
long duration = getVarlong();
|
||||
int tid = getVarint();
|
||||
if (activeSettingHasStack) getVarint();
|
||||
long id = getVarlong();
|
||||
String name = getString();
|
||||
String value = getString();
|
||||
settings.put(name, value);
|
||||
}
|
||||
|
||||
private boolean readChunk(int pos) throws IOException {
|
||||
if (pos + CHUNK_HEADER_SIZE > buf.limit() || buf.getInt(pos) != CHUNK_SIGNATURE) {
|
||||
throw new IOException("Not a valid JFR file");
|
||||
}
|
||||
|
||||
int version = buf.getInt(pos + 4);
|
||||
if (version < 0x20000 || version > 0x2ffff) {
|
||||
throw new IOException("Unsupported JFR version: " + (version >>> 16) + "." + (version & 0xffff));
|
||||
}
|
||||
|
||||
long cpOffset = buf.getLong(pos + 16);
|
||||
long metaOffset = buf.getLong(pos + 24);
|
||||
if (cpOffset == 0 || metaOffset == 0) {
|
||||
incomplete = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
startNanos = Math.min(startNanos, buf.getLong(pos + 32));
|
||||
endNanos = Math.max(endNanos, buf.getLong(pos + 32) + buf.getLong(pos + 40));
|
||||
startTicks = Math.min(startTicks, buf.getLong(pos + 48));
|
||||
ticksPerSec = buf.getLong(pos + 56);
|
||||
|
||||
types.clear();
|
||||
typesByName.clear();
|
||||
|
||||
long chunkStart = filePosition + pos;
|
||||
readMeta(chunkStart + metaOffset);
|
||||
readConstantPool(chunkStart + cpOffset);
|
||||
cacheEventTypes();
|
||||
|
||||
seek(chunkStart + CHUNK_HEADER_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readMeta(long metaOffset) throws IOException {
|
||||
seek(metaOffset);
|
||||
ensureBytes(5);
|
||||
|
||||
int posBeforeSize = buf.position();
|
||||
ensureBytes(getVarint() - (buf.position() - posBeforeSize));
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
@@ -133,15 +295,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 +314,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 +323,10 @@ public class JfrReader implements Closeable {
|
||||
buf.position(buf.position() + (CHUNK_HEADER_SIZE + 3));
|
||||
break;
|
||||
case "java.lang.Thread":
|
||||
readThreads(type.field("group") != null);
|
||||
readThreads(type.fields.size());
|
||||
break;
|
||||
case "java.lang.Class":
|
||||
readClasses(type.field("hidden") != null);
|
||||
readClasses(type.fields.size());
|
||||
break;
|
||||
case "jdk.types.Symbol":
|
||||
readSymbols();
|
||||
@@ -188,7 +348,7 @@ public class JfrReader implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
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 +356,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 +369,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 +401,15 @@ public class JfrReader implements Closeable {
|
||||
int depth = getVarint();
|
||||
long[] methods = new long[depth];
|
||||
byte[] types = new byte[depth];
|
||||
int[] locations = new int[depth];
|
||||
for (int i = 0; i < depth; i++) {
|
||||
methods[i] = getVarlong();
|
||||
int line = getVarint();
|
||||
int bci = getVarint();
|
||||
locations[i] = line << 16 | (bci & 0xffff);
|
||||
types[i] = buf.get();
|
||||
}
|
||||
return new StackTrace(methods, types);
|
||||
return new StackTrace(methods, types, locations);
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
@@ -294,36 +456,23 @@ 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));
|
||||
|
||||
StackTrace stackTrace = stackTraces.get(stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
stackTrace.samples++;
|
||||
}
|
||||
private void cacheEventTypes() {
|
||||
executionSample = getTypeId("jdk.ExecutionSample");
|
||||
nativeMethodSample = getTypeId("jdk.NativeMethodSample");
|
||||
allocationInNewTLAB = getTypeId("jdk.ObjectAllocationInNewTLAB");
|
||||
allocationOutsideTLAB = getTypeId("jdk.ObjectAllocationOutsideTLAB");
|
||||
allocationSample = getTypeId("jdk.ObjectAllocationSample");
|
||||
liveObject = getTypeId("profiler.LiveObject");
|
||||
monitorEnter = getTypeId("jdk.JavaMonitorEnter");
|
||||
threadPark = getTypeId("jdk.ThreadPark");
|
||||
activeSetting = getTypeId("jdk.ActiveSetting");
|
||||
activeSettingHasStack = activeSetting >= 0 && typesByName.get("jdk.ActiveSetting").field("stackTrace") != null;
|
||||
}
|
||||
|
||||
private int getTypeId(String typeName) {
|
||||
@@ -381,4 +530,41 @@ public class JfrReader implements Closeable {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,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;
|
||||
}
|
||||
}
|
||||
|
||||
49
src/converter/one/jfr/event/AllocationSample.java
Normal file
49
src/converter/one/jfr/event/AllocationSample.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class AllocationSample extends Event {
|
||||
public final int classId;
|
||||
public final long allocationSize;
|
||||
public final long tlabSize;
|
||||
|
||||
public AllocationSample(long time, int tid, int stackTraceId, int classId, long allocationSize, long tlabSize) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.classId = classId;
|
||||
this.allocationSize = allocationSize;
|
||||
this.tlabSize = tlabSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return classId * 127 + stackTraceId + (tlabSize == 0 ? 17 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameGroup(Event o) {
|
||||
if (o instanceof AllocationSample) {
|
||||
AllocationSample a = (AllocationSample) o;
|
||||
return classId == a.classId && (tlabSize == 0) == (a.tlabSize == 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long value() {
|
||||
return tlabSize != 0 ? tlabSize : allocationSize;
|
||||
}
|
||||
}
|
||||
47
src/converter/one/jfr/event/ContendedLock.java
Normal file
47
src/converter/one/jfr/event/ContendedLock.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class ContendedLock extends Event {
|
||||
public final long duration;
|
||||
public final int classId;
|
||||
|
||||
public ContendedLock(long time, int tid, int stackTraceId, long duration, int classId) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.duration = duration;
|
||||
this.classId = classId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return classId * 127 + stackTraceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameGroup(Event o) {
|
||||
if (o instanceof ContendedLock) {
|
||||
ContendedLock c = (ContendedLock) o;
|
||||
return classId == c.classId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long value() {
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,23 +14,34 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
package one.jfr.event;
|
||||
|
||||
public class Sample implements Comparable<Sample> {
|
||||
public abstract class Event implements Comparable<Event> {
|
||||
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) {
|
||||
protected Event(long time, int tid, int stackTraceId) {
|
||||
this.time = time;
|
||||
this.tid = tid;
|
||||
this.stackTraceId = stackTraceId;
|
||||
this.threadState = threadState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Sample o) {
|
||||
public int compareTo(Event o) {
|
||||
return Long.compare(time, o.time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return stackTraceId;
|
||||
}
|
||||
|
||||
public boolean sameGroup(Event o) {
|
||||
return getClass() == o.getClass();
|
||||
}
|
||||
|
||||
public long value() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
103
src/converter/one/jfr/event/EventAggregator.java
Normal file
103
src/converter/one/jfr/event/EventAggregator.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class EventAggregator {
|
||||
private static final int INITIAL_CAPACITY = 1024;
|
||||
|
||||
private final boolean threads;
|
||||
private final boolean total;
|
||||
private Event[] keys;
|
||||
private long[] values;
|
||||
private int size;
|
||||
|
||||
public EventAggregator(boolean threads, boolean total) {
|
||||
this.threads = threads;
|
||||
this.total = total;
|
||||
this.keys = new Event[INITIAL_CAPACITY];
|
||||
this.values = new long[INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void collect(Event e) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(e) & mask;
|
||||
while (keys[i] != null) {
|
||||
if (sameGroup(keys[i], e)) {
|
||||
values[i] += total ? e.value() : 1;
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
keys[i] = e;
|
||||
values[i] = total ? e.value() : 1;
|
||||
|
||||
if (++size * 2 > keys.length) {
|
||||
resize(keys.length * 2);
|
||||
}
|
||||
}
|
||||
|
||||
public long getValue(Event e) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(e) & mask;
|
||||
while (keys[i] != null && !sameGroup(keys[i], e)) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
return values[i];
|
||||
}
|
||||
|
||||
public void forEach(Visitor visitor) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != null) {
|
||||
visitor.visit(keys[i], values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hashCode(Event e) {
|
||||
return e.hashCode() + (threads ? e.tid * 31 : 0);
|
||||
}
|
||||
|
||||
private boolean sameGroup(Event e1, Event e2) {
|
||||
return e1.stackTraceId == e2.stackTraceId && (!threads || e1.tid == e2.tid) && e1.sameGroup(e2);
|
||||
}
|
||||
|
||||
private void resize(int newCapacity) {
|
||||
Event[] newKeys = new Event[newCapacity];
|
||||
long[] newValues = new long[newCapacity];
|
||||
int mask = newKeys.length - 1;
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != null) {
|
||||
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
|
||||
if (newKeys[j] == null) {
|
||||
newKeys[j] = keys[i];
|
||||
newValues[j] = values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = newKeys;
|
||||
values = newValues;
|
||||
}
|
||||
|
||||
public interface Visitor {
|
||||
void visit(Event event, long value);
|
||||
}
|
||||
}
|
||||
26
src/converter/one/jfr/event/ExecutionSample.java
Normal file
26
src/converter/one/jfr/event/ExecutionSample.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class ExecutionSample extends Event {
|
||||
public final int threadState;
|
||||
|
||||
public ExecutionSample(long time, int tid, int stackTraceId, int threadState) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.threadState = threadState;
|
||||
}
|
||||
}
|
||||
49
src/converter/one/jfr/event/LiveObject.java
Normal file
49
src/converter/one/jfr/event/LiveObject.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr.event;
|
||||
|
||||
public class LiveObject extends Event {
|
||||
public final int classId;
|
||||
public final long allocationSize;
|
||||
public final long allocationTime;
|
||||
|
||||
public LiveObject(long time, int tid, int stackTraceId, int classId, long allocationSize, long allocationTime) {
|
||||
super(time, tid, stackTraceId);
|
||||
this.classId = classId;
|
||||
this.allocationSize = allocationSize;
|
||||
this.allocationTime = allocationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return classId * 127 + stackTraceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameGroup(Event o) {
|
||||
if (o instanceof LiveObject) {
|
||||
LiveObject a = (LiveObject) o;
|
||||
return classId == a.classId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long value() {
|
||||
return allocationSize;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,12 @@ public class Proto {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, long n) {
|
||||
tag(index, 0);
|
||||
writeLong(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, double d) {
|
||||
tag(index, 1);
|
||||
writeDouble(d);
|
||||
@@ -83,6 +89,17 @@ public class Proto {
|
||||
buf[pos++] = (byte) n;
|
||||
}
|
||||
|
||||
public void writeLong(long n) {
|
||||
int length = n == 0 ? 1 : (70 - Long.numberOfLeadingZeros(n)) / 7;
|
||||
ensureCapacity(length);
|
||||
|
||||
while (n > 0x7f) {
|
||||
buf[pos++] = (byte) (0x80 | (n & 0x7f));
|
||||
n >>>= 7;
|
||||
}
|
||||
buf[pos++] = (byte) n;
|
||||
}
|
||||
|
||||
public void writeDouble(double d) {
|
||||
ensureCapacity(8);
|
||||
long n = Double.doubleToRawLongBits(d);
|
||||
|
||||
127
src/demangle.cpp
Normal file
127
src/demangle.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2023 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "demangle.h"
|
||||
|
||||
|
||||
char* Demangle::demangleCpp(const char* s) {
|
||||
int status;
|
||||
char* result = abi::__cxa_demangle(s, NULL, NULL, &status);
|
||||
if (result == NULL && status == -2) {
|
||||
// Strip compiler-specific suffix (e.g. ".part.123") and retry demangling
|
||||
char buf[512];
|
||||
const char* p = strchr(s, '.');
|
||||
if (p != NULL && p - s < sizeof(buf)) {
|
||||
memcpy(buf, s, p - s);
|
||||
buf[p - s] = 0;
|
||||
result = abi::__cxa_demangle(buf, NULL, NULL, &status);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Demangle::demangleRust(const char* s, const char* e) {
|
||||
// Demangled symbol can be 1.5x longer than original, e.g. 1A1B1C -> A::B::C
|
||||
char* result = (char*)malloc((e - s) * 3 / 2 + 1);
|
||||
if (result == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* r = result;
|
||||
char* tmp;
|
||||
|
||||
while (s < e) {
|
||||
unsigned long len = strtoul(s, &tmp, 10);
|
||||
const char* next = tmp + len;
|
||||
if (len == 0 || next > e) {
|
||||
break;
|
||||
}
|
||||
|
||||
s = tmp;
|
||||
if (s[0] == '_' && s[1] == '$') s++;
|
||||
|
||||
if (r > result) {
|
||||
*r++ = ':';
|
||||
*r++ = ':';
|
||||
}
|
||||
|
||||
while (s < next) {
|
||||
if (s[0] == '$') {
|
||||
if (s[1] == 'L' && s[2] == 'T' && s[3] == '$') {
|
||||
*r++ = '<';
|
||||
s += 4;
|
||||
} else if (s[1] == 'G' && s[2] == 'T' && s[3] == '$') {
|
||||
*r++ = '>';
|
||||
s += 4;
|
||||
} else if (s[1] == 'L' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = '(';
|
||||
s += 4;
|
||||
} else if (s[1] == 'R' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = ')';
|
||||
s += 4;
|
||||
} else if (s[1] == 'S' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = '@';
|
||||
s += 4;
|
||||
} else if (s[1] == 'B' && s[2] == 'P' && s[3] == '$') {
|
||||
*r++ = '*';
|
||||
s += 4;
|
||||
} else if (s[1] == 'R' && s[2] == 'F' && s[3] == '$') {
|
||||
*r++ = '&';
|
||||
s += 4;
|
||||
} else if (s[1] == 'C' && s[2] == '$') {
|
||||
*r++ = ',';
|
||||
s += 3;
|
||||
} else if (s[1] == 'u') {
|
||||
*r++ = (char)strtoul(s + 2, &tmp, 16);
|
||||
s = tmp + 1;
|
||||
} else {
|
||||
*r++ = '$';
|
||||
s++;
|
||||
}
|
||||
} else if (s[0] == '.' && s[1] == '.') {
|
||||
*r++ = ':';
|
||||
*r++ = ':';
|
||||
s += 2;
|
||||
} else {
|
||||
*r++ = *s++;
|
||||
}
|
||||
}
|
||||
|
||||
if (s > next) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*r = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Demangle::demangle(const char* s) {
|
||||
// Check if the mangled symbol ends with a Rust hash "17h<hex>E"
|
||||
const char* e = strrchr(s, 'E');
|
||||
if (e != NULL && e - s > 22 && e[-19] == '1' && e[-18] == '7' && e[-17] == 'h') {
|
||||
const char* h = e - 16;
|
||||
while ((*h >= '0' && *h <= '9') || (*h >= 'a' && *h <= 'f')) h++;
|
||||
if (h == e) {
|
||||
return demangleRust(s + 3, e - 19);
|
||||
}
|
||||
}
|
||||
|
||||
return demangleCpp(s);
|
||||
}
|
||||
30
src/demangle.h
Normal file
30
src/demangle.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2023 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DEMANGLE_H
|
||||
#define _DEMANGLE_H
|
||||
|
||||
|
||||
class Demangle {
|
||||
private:
|
||||
static char* demangleCpp(const char* s);
|
||||
static char* demangleRust(const char* s, const char* e);
|
||||
|
||||
public:
|
||||
static char* demangle(const char* s);
|
||||
};
|
||||
|
||||
#endif // _DEMANGLE_H
|
||||
@@ -20,15 +20,16 @@
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
static inline char* allocateKey(const char* key, size_t length) {
|
||||
char* result = (char*)malloc(length + 1);
|
||||
memcpy(result, key, length);
|
||||
result[length] = 0;
|
||||
return result;
|
||||
static inline DictKey* allocateKey(const char* key, size_t length) {
|
||||
DictKey* dk = (DictKey*)malloc(length + 2);
|
||||
dk->mark = false;
|
||||
memcpy(dk->key, key, length);
|
||||
dk->key[length] = 0;
|
||||
return dk;
|
||||
}
|
||||
|
||||
static inline bool keyEquals(const char* candidate, const char* key, size_t length) {
|
||||
return strncmp(candidate, key, length) == 0 && candidate[length] == 0;
|
||||
static inline bool keyEquals(DictKey* dk, const char* key, size_t length) {
|
||||
return strncmp(dk->key, key, length) == 0 && dk->key[length] == 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +62,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) {
|
||||
@@ -83,11 +99,11 @@ unsigned int Dictionary::lookup(const char* key, size_t length) {
|
||||
DictRow* row = &table->rows[h % ROWS];
|
||||
for (int c = 0; c < CELLS; c++) {
|
||||
if (row->keys[c] == NULL) {
|
||||
char* new_key = allocateKey(key, length);
|
||||
if (__sync_bool_compare_and_swap(&row->keys[c], NULL, new_key)) {
|
||||
DictKey* new_dk = allocateKey(key, length);
|
||||
if (__sync_bool_compare_and_swap(&row->keys[c], NULL, new_dk)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
free(new_key);
|
||||
free(new_dk);
|
||||
}
|
||||
if (keyEquals(row->keys[c], key, length)) {
|
||||
return table->index(h % ROWS, c);
|
||||
@@ -115,8 +131,10 @@ void Dictionary::collect(std::map<unsigned int, const char*>& map, DictTable* ta
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
if (row->keys[j] != NULL) {
|
||||
map[table->index(i, j)] = row->keys[j];
|
||||
DictKey* dk = row->keys[j];
|
||||
if (dk != NULL && !dk->mark) {
|
||||
dk->mark = true;
|
||||
map[table->index(i, j)] = dk->key;
|
||||
}
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
|
||||
@@ -29,8 +29,13 @@
|
||||
|
||||
struct DictTable;
|
||||
|
||||
struct DictKey {
|
||||
bool mark;
|
||||
char key[0];
|
||||
};
|
||||
|
||||
struct DictRow {
|
||||
char* keys[CELLS];
|
||||
DictKey* keys[CELLS];
|
||||
DictTable* next;
|
||||
};
|
||||
|
||||
@@ -50,6 +55,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 +66,7 @@ class Dictionary {
|
||||
~Dictionary();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
|
||||
unsigned int lookup(const char* key);
|
||||
unsigned int lookup(const char* key, size_t length);
|
||||
|
||||
353
src/dwarf.cpp
Normal file
353
src/dwarf.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "dwarf.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
enum {
|
||||
DW_CFA_nop = 0x0,
|
||||
DW_CFA_set_loc = 0x1,
|
||||
DW_CFA_advance_loc1 = 0x2,
|
||||
DW_CFA_advance_loc2 = 0x3,
|
||||
DW_CFA_advance_loc4 = 0x4,
|
||||
DW_CFA_offset_extended = 0x5,
|
||||
DW_CFA_restore_extended = 0x6,
|
||||
DW_CFA_undefined = 0x7,
|
||||
DW_CFA_same_value = 0x8,
|
||||
DW_CFA_register = 0x9,
|
||||
DW_CFA_remember_state = 0xa,
|
||||
DW_CFA_restore_state = 0xb,
|
||||
DW_CFA_def_cfa = 0xc,
|
||||
DW_CFA_def_cfa_register = 0xd,
|
||||
DW_CFA_def_cfa_offset = 0xe,
|
||||
DW_CFA_def_cfa_expression = 0xf,
|
||||
DW_CFA_expression = 0x10,
|
||||
DW_CFA_offset_extended_sf = 0x11,
|
||||
DW_CFA_def_cfa_sf = 0x12,
|
||||
DW_CFA_def_cfa_offset_sf = 0x13,
|
||||
DW_CFA_val_offset = 0x14,
|
||||
DW_CFA_val_offset_sf = 0x15,
|
||||
DW_CFA_val_expression = 0x16,
|
||||
DW_CFA_GNU_args_size = 0x2e,
|
||||
|
||||
DW_CFA_advance_loc = 0x1,
|
||||
DW_CFA_offset = 0x2,
|
||||
DW_CFA_restore = 0x3,
|
||||
};
|
||||
|
||||
enum {
|
||||
DW_OP_breg_pc = 0x70 + DW_REG_PC,
|
||||
DW_OP_const1u = 0x08,
|
||||
DW_OP_const1s = 0x09,
|
||||
DW_OP_const2u = 0x0a,
|
||||
DW_OP_const2s = 0x0b,
|
||||
DW_OP_const4u = 0x0c,
|
||||
DW_OP_const4s = 0x0d,
|
||||
DW_OP_constu = 0x10,
|
||||
DW_OP_consts = 0x11,
|
||||
DW_OP_minus = 0x1c,
|
||||
DW_OP_plus = 0x22,
|
||||
};
|
||||
|
||||
|
||||
FrameDesc FrameDesc::default_frame = {0, DW_REG_FP | (2 * DW_STACK_SLOT) << 8, -2 * DW_STACK_SLOT};
|
||||
|
||||
|
||||
DwarfParser::DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr) {
|
||||
_name = name;
|
||||
_image_base = image_base;
|
||||
|
||||
_capacity = 128;
|
||||
_count = 0;
|
||||
_table = (FrameDesc*)malloc(_capacity * sizeof(FrameDesc));
|
||||
_prev = NULL;
|
||||
|
||||
_code_align = sizeof(instruction_t);
|
||||
_data_align = -(int)sizeof(void*);
|
||||
|
||||
parse(eh_frame_hdr);
|
||||
}
|
||||
|
||||
void DwarfParser::parse(const char* eh_frame_hdr) {
|
||||
u8 version = eh_frame_hdr[0];
|
||||
u8 eh_frame_ptr_enc = eh_frame_hdr[1];
|
||||
u8 fde_count_enc = eh_frame_hdr[2];
|
||||
u8 table_enc = eh_frame_hdr[3];
|
||||
|
||||
if (version != 1 || (eh_frame_ptr_enc & 0x7) != 0x3 || (fde_count_enc & 0x7) != 0x3 || (table_enc & 0xf7) != 0x33) {
|
||||
Log::warn("Unsupported .eh_frame_hdr [%02x%02x%02x%02x] in %s",
|
||||
version, eh_frame_ptr_enc, fde_count_enc, table_enc, _name);
|
||||
return;
|
||||
}
|
||||
|
||||
int fde_count = *(int*)(eh_frame_hdr + 8);
|
||||
int* table = (int*)(eh_frame_hdr + 16);
|
||||
for (int i = 0; i < fde_count; i++) {
|
||||
_ptr = eh_frame_hdr + table[i * 2];
|
||||
parseFde();
|
||||
}
|
||||
}
|
||||
|
||||
void DwarfParser::parseCie() {
|
||||
u32 cie_len = get32();
|
||||
if (cie_len == 0 || cie_len == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* cie_start = _ptr;
|
||||
_ptr += 5;
|
||||
while (*_ptr++) {}
|
||||
_code_align = getLeb();
|
||||
_data_align = getSLeb();
|
||||
_ptr = cie_start + cie_len;
|
||||
}
|
||||
|
||||
void DwarfParser::parseFde() {
|
||||
u32 fde_len = get32();
|
||||
if (fde_len == 0 || fde_len == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* fde_start = _ptr;
|
||||
u32 cie_offset = get32();
|
||||
if (_count == 0) {
|
||||
_ptr = fde_start - cie_offset;
|
||||
parseCie();
|
||||
_ptr = fde_start + 4;
|
||||
}
|
||||
|
||||
u32 range_start = getPtr() - _image_base;
|
||||
u32 range_len = get32();
|
||||
_ptr += getLeb();
|
||||
parseInstructions(range_start, fde_start + fde_len);
|
||||
addRecord(range_start + range_len, DW_REG_FP, 2 * DW_STACK_SLOT, -2 * DW_STACK_SLOT);
|
||||
}
|
||||
|
||||
void DwarfParser::parseInstructions(u32 loc, const char* end) {
|
||||
const u32 code_align = _code_align;
|
||||
const int data_align = _data_align;
|
||||
|
||||
u32 cfa_reg = DW_REG_SP;
|
||||
int cfa_off = DW_STACK_SLOT;
|
||||
int fp_off = DW_SAME_FP;
|
||||
int pc_off = -DW_STACK_SLOT;
|
||||
|
||||
u32 rem_cfa_reg;
|
||||
int rem_cfa_off;
|
||||
int rem_fp_off;
|
||||
int rem_pc_off;
|
||||
|
||||
while (_ptr < end) {
|
||||
u8 op = get8();
|
||||
switch (op >> 6) {
|
||||
case 0:
|
||||
switch (op) {
|
||||
case DW_CFA_nop:
|
||||
case DW_CFA_set_loc:
|
||||
_ptr = end;
|
||||
break;
|
||||
case DW_CFA_advance_loc1:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off);
|
||||
loc += get8() * code_align;
|
||||
break;
|
||||
case DW_CFA_advance_loc2:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off);
|
||||
loc += get16() * code_align;
|
||||
break;
|
||||
case DW_CFA_advance_loc4:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off);
|
||||
loc += get32() * code_align;
|
||||
break;
|
||||
case DW_CFA_offset_extended:
|
||||
switch (getLeb()) {
|
||||
case DW_REG_FP: fp_off = getLeb() * data_align; break;
|
||||
case DW_REG_PC: pc_off = getLeb() * data_align; break;
|
||||
default: skipLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_restore_extended:
|
||||
case DW_CFA_undefined:
|
||||
case DW_CFA_same_value:
|
||||
skipLeb();
|
||||
break;
|
||||
case DW_CFA_register:
|
||||
skipLeb();
|
||||
skipLeb();
|
||||
break;
|
||||
case DW_CFA_remember_state:
|
||||
rem_cfa_reg = cfa_reg;
|
||||
rem_cfa_off = cfa_off;
|
||||
rem_fp_off = fp_off;
|
||||
rem_pc_off = pc_off;
|
||||
break;
|
||||
case DW_CFA_restore_state:
|
||||
cfa_reg = rem_cfa_reg;
|
||||
cfa_off = rem_cfa_off;
|
||||
fp_off = rem_fp_off;
|
||||
pc_off = rem_pc_off;
|
||||
break;
|
||||
case DW_CFA_def_cfa:
|
||||
cfa_reg = getLeb();
|
||||
cfa_off = getLeb();
|
||||
break;
|
||||
case DW_CFA_def_cfa_register:
|
||||
cfa_reg = getLeb();
|
||||
break;
|
||||
case DW_CFA_def_cfa_offset:
|
||||
cfa_off = getLeb();
|
||||
break;
|
||||
case DW_CFA_def_cfa_expression: {
|
||||
u32 len = getLeb();
|
||||
cfa_reg = len == 11 ? DW_REG_PLT : DW_REG_INVALID;
|
||||
cfa_off = DW_STACK_SLOT;
|
||||
_ptr += len;
|
||||
break;
|
||||
}
|
||||
case DW_CFA_expression:
|
||||
skipLeb();
|
||||
_ptr += getLeb();
|
||||
break;
|
||||
case DW_CFA_offset_extended_sf:
|
||||
switch (getLeb()) {
|
||||
case DW_REG_FP: fp_off = getSLeb() * data_align; break;
|
||||
case DW_REG_PC: pc_off = getSLeb() * data_align; break;
|
||||
default: skipLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_def_cfa_sf:
|
||||
cfa_reg = getLeb();
|
||||
cfa_off = getSLeb() * data_align;
|
||||
break;
|
||||
case DW_CFA_def_cfa_offset_sf:
|
||||
cfa_off = getSLeb() * data_align;
|
||||
break;
|
||||
case DW_CFA_val_offset:
|
||||
case DW_CFA_val_offset_sf:
|
||||
skipLeb();
|
||||
skipLeb();
|
||||
break;
|
||||
case DW_CFA_val_expression:
|
||||
if (getLeb() == DW_REG_PC) {
|
||||
int pc_off = parseExpression();
|
||||
if (pc_off != 0) {
|
||||
fp_off = DW_PC_OFFSET | (pc_off << 1);
|
||||
}
|
||||
} else {
|
||||
_ptr += getLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_GNU_args_size:
|
||||
skipLeb();
|
||||
break;
|
||||
default:
|
||||
Log::warn("Unknown DWARF instruction 0x%x in %s", op, _name);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case DW_CFA_advance_loc:
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off);
|
||||
loc += (op & 0x3f) * code_align;
|
||||
break;
|
||||
case DW_CFA_offset:
|
||||
switch (op & 0x3f) {
|
||||
case DW_REG_FP: fp_off = getLeb() * data_align; break;
|
||||
case DW_REG_PC: pc_off = getLeb() * data_align; break;
|
||||
default: skipLeb();
|
||||
}
|
||||
break;
|
||||
case DW_CFA_restore:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addRecord(loc, cfa_reg, cfa_off, fp_off);
|
||||
}
|
||||
|
||||
// Parse a limited subset of DWARF expressions, which is used in DW_CFA_val_expression
|
||||
// to point to the previous PC relative to the current PC.
|
||||
// Returns the offset of the previous PC from the current PC.
|
||||
int DwarfParser::parseExpression() {
|
||||
int pc_off = 0;
|
||||
int tos = 0;
|
||||
|
||||
u32 len = getLeb();
|
||||
const char* end = _ptr + len;
|
||||
|
||||
while (_ptr < end) {
|
||||
u8 op = get8();
|
||||
switch (op) {
|
||||
case DW_OP_breg_pc:
|
||||
pc_off = getSLeb();
|
||||
break;
|
||||
case DW_OP_const1u:
|
||||
tos = get8();
|
||||
break;
|
||||
case DW_OP_const1s:
|
||||
tos = (signed char)get8();
|
||||
break;
|
||||
case DW_OP_const2u:
|
||||
tos = get16();
|
||||
break;
|
||||
case DW_OP_const2s:
|
||||
tos = (short)get16();
|
||||
break;
|
||||
case DW_OP_const4u:
|
||||
case DW_OP_const4s:
|
||||
tos = get32();
|
||||
break;
|
||||
case DW_OP_constu:
|
||||
tos = getLeb();
|
||||
break;
|
||||
case DW_OP_consts:
|
||||
tos = getSLeb();
|
||||
break;
|
||||
case DW_OP_minus:
|
||||
pc_off -= tos;
|
||||
break;
|
||||
case DW_OP_plus:
|
||||
pc_off += tos;
|
||||
break;
|
||||
default:
|
||||
Log::warn("Unknown DWARF opcode 0x%x in %s", op, _name);
|
||||
_ptr = end;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pc_off;
|
||||
}
|
||||
|
||||
void DwarfParser::addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off) {
|
||||
int cfa = cfa_reg | cfa_off << 8;
|
||||
if (_prev == NULL || (_prev->loc == loc && --_count >= 0) || _prev->cfa != cfa || _prev->fp_off != fp_off) {
|
||||
_prev = addRecordRaw(loc, cfa, fp_off);
|
||||
}
|
||||
}
|
||||
|
||||
FrameDesc* DwarfParser::addRecordRaw(u32 loc, int cfa, int fp_off) {
|
||||
if (_count >= _capacity) {
|
||||
_capacity *= 2;
|
||||
_table = (FrameDesc*)realloc(_table, _capacity * sizeof(FrameDesc));
|
||||
}
|
||||
|
||||
FrameDesc* f = &_table[_count++];
|
||||
f->loc = loc;
|
||||
f->cfa = cfa;
|
||||
f->fp_off = fp_off;
|
||||
return f;
|
||||
}
|
||||
160
src/dwarf.h
Normal file
160
src/dwarf.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DWARF_H
|
||||
#define _DWARF_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
#if defined(__x86_64__)
|
||||
|
||||
#define DWARF_SUPPORTED true
|
||||
|
||||
const int DW_REG_FP = 6;
|
||||
const int DW_REG_SP = 7;
|
||||
const int DW_REG_PC = 16;
|
||||
|
||||
#elif defined(__i386__)
|
||||
|
||||
#define DWARF_SUPPORTED true
|
||||
|
||||
const int DW_REG_FP = 5;
|
||||
const int DW_REG_SP = 4;
|
||||
const int DW_REG_PC = 8;
|
||||
|
||||
#else
|
||||
|
||||
#define DWARF_SUPPORTED false
|
||||
|
||||
const int DW_REG_FP = 0;
|
||||
const int DW_REG_SP = 1;
|
||||
const int DW_REG_PC = 2;
|
||||
|
||||
#endif
|
||||
|
||||
const int DW_REG_PLT = 128; // denotes special rule for PLT entries
|
||||
const int DW_REG_INVALID = 255; // denotes unsupported configuration
|
||||
|
||||
const int DW_PC_OFFSET = 1;
|
||||
const int DW_SAME_FP = 0x80000000;
|
||||
const int DW_STACK_SLOT = sizeof(void*);
|
||||
|
||||
|
||||
struct FrameDesc {
|
||||
u32 loc;
|
||||
int cfa;
|
||||
int fp_off;
|
||||
|
||||
static FrameDesc default_frame;
|
||||
|
||||
static int comparator(const void* p1, const void* p2) {
|
||||
FrameDesc* fd1 = (FrameDesc*)p1;
|
||||
FrameDesc* fd2 = (FrameDesc*)p2;
|
||||
return (int)(fd1->loc - fd2->loc);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class DwarfParser {
|
||||
private:
|
||||
const char* _name;
|
||||
const char* _image_base;
|
||||
const char* _ptr;
|
||||
|
||||
int _capacity;
|
||||
int _count;
|
||||
FrameDesc* _table;
|
||||
FrameDesc* _prev;
|
||||
|
||||
u32 _code_align;
|
||||
int _data_align;
|
||||
|
||||
const char* add(size_t size) {
|
||||
const char* ptr = _ptr;
|
||||
_ptr = ptr + size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
u8 get8() {
|
||||
return *_ptr++;
|
||||
}
|
||||
|
||||
u16 get16() {
|
||||
return *(u16*)add(2);
|
||||
}
|
||||
|
||||
u32 get32() {
|
||||
return *(u32*)add(4);
|
||||
}
|
||||
|
||||
u32 getLeb() {
|
||||
u32 result = 0;
|
||||
for (u32 shift = 0; ; shift += 7) {
|
||||
u8 b = *_ptr++;
|
||||
result |= (b & 0x7f) << shift;
|
||||
if ((b & 0x80) == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getSLeb() {
|
||||
int result = 0;
|
||||
for (u32 shift = 0; ; shift += 7) {
|
||||
u8 b = *_ptr++;
|
||||
result |= (b & 0x7f) << shift;
|
||||
if ((b & 0x80) == 0) {
|
||||
if ((b & 0x40) != 0 && (shift += 7) < 32) {
|
||||
result |= -1 << shift;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void skipLeb() {
|
||||
while (*_ptr++ & 0x80) {}
|
||||
}
|
||||
|
||||
const char* getPtr() {
|
||||
const char* ptr = _ptr;
|
||||
return ptr + *(int*)add(4);
|
||||
}
|
||||
|
||||
void parse(const char* eh_frame_hdr);
|
||||
void parseCie();
|
||||
void parseFde();
|
||||
void parseInstructions(u32 loc, const char* end);
|
||||
int parseExpression();
|
||||
|
||||
void addRecord(u32 loc, u32 cfa_reg, int cfa_off, int fp_off);
|
||||
FrameDesc* addRecordRaw(u32 loc, int cfa, int fp_off);
|
||||
|
||||
public:
|
||||
DwarfParser(const char* name, const char* image_base, const char* eh_frame_hdr);
|
||||
|
||||
FrameDesc* table() const {
|
||||
return _table;
|
||||
}
|
||||
|
||||
int count() const {
|
||||
return _count;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _DWARF_H
|
||||
@@ -15,60 +15,17 @@
|
||||
*/
|
||||
|
||||
#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() {
|
||||
}
|
||||
|
||||
62
src/engine.h
62
src/engine.h
@@ -18,50 +18,50 @@
|
||||
#define _ENGINE_H
|
||||
|
||||
#include "arguments.h"
|
||||
#include "codeCache.h"
|
||||
|
||||
|
||||
struct StackContext;
|
||||
|
||||
class Engine {
|
||||
protected:
|
||||
static volatile bool _enabled;
|
||||
|
||||
static bool updateCounter(volatile unsigned long long& counter, unsigned long long value, unsigned long long interval) {
|
||||
if (interval <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
unsigned long long prev = counter;
|
||||
unsigned long long next = prev + value;
|
||||
if (next < interval) {
|
||||
if (__sync_bool_compare_and_swap(&counter, prev, next)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (__sync_bool_compare_and_swap(&counter, prev, next % interval)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
virtual const char* name() = 0;
|
||||
virtual const char* units() = 0;
|
||||
virtual const char* title() {
|
||||
return "Flame Graph";
|
||||
}
|
||||
|
||||
virtual 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
|
||||
|
||||
@@ -52,4 +52,11 @@ class LockEvent : public Event {
|
||||
long long _timeout;
|
||||
};
|
||||
|
||||
class LiveObject : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _alloc_size;
|
||||
u64 _alloc_time;
|
||||
};
|
||||
|
||||
#endif // _EVENT_H
|
||||
|
||||
98
src/fdtransfer/fdtransfer.h
Normal file
98
src/fdtransfer/fdtransfer.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _FDTRANSFER_H
|
||||
#define _FDTRANSFER_H
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <linux/perf_event.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
|
||||
#define RESTARTABLE(call) ({ ssize_t ret; while ((ret = call) < 0 && errno == EINTR); ret; })
|
||||
|
||||
|
||||
// base header for all requests
|
||||
enum request_type {
|
||||
PERF_FD,
|
||||
KALLSYMS_FD,
|
||||
BPFMAP_FD,
|
||||
};
|
||||
|
||||
struct fd_request {
|
||||
// of type "enum request_type"
|
||||
unsigned int type;
|
||||
};
|
||||
|
||||
struct perf_fd_request {
|
||||
struct fd_request header;
|
||||
int tid;
|
||||
struct perf_event_attr attr;
|
||||
};
|
||||
|
||||
struct bpfmap_fd_request {
|
||||
struct fd_request header;
|
||||
int version;
|
||||
};
|
||||
|
||||
struct fd_response {
|
||||
// of type "enum request_type"
|
||||
unsigned int type;
|
||||
// 0 on success, otherwise errno
|
||||
int error;
|
||||
};
|
||||
|
||||
struct perf_fd_response {
|
||||
struct fd_response header;
|
||||
int tid;
|
||||
};
|
||||
|
||||
struct bpfmap_params {
|
||||
unsigned long interval;
|
||||
unsigned int num_entries;
|
||||
unsigned int entry_size;
|
||||
unsigned int salt;
|
||||
};
|
||||
|
||||
struct bpfmap_fd_response {
|
||||
struct fd_response header;
|
||||
struct bpfmap_params params;
|
||||
};
|
||||
|
||||
|
||||
static inline bool socketPath(const char *path, struct sockaddr_un *sun, socklen_t *addrlen) {
|
||||
const int path_len = strlen(path);
|
||||
if (path_len > sizeof(sun->sun_path)) {
|
||||
return false;
|
||||
}
|
||||
memcpy(sun->sun_path, path, path_len);
|
||||
if (sun->sun_path[0] == '@') {
|
||||
sun->sun_path[0] = '\0';
|
||||
}
|
||||
|
||||
sun->sun_family = AF_UNIX;
|
||||
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - path_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _FDTRANSFER_H
|
||||
388
src/fdtransfer/fdtransferServer.cpp
Normal file
388
src/fdtransfer/fdtransferServer.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "fdtransfer.h"
|
||||
#include "../jattach/psutil.h"
|
||||
|
||||
|
||||
class FdTransferServer {
|
||||
private:
|
||||
static int _server;
|
||||
static int _peer;
|
||||
static int copyFile(const char* src_name, const char* dst_name, mode_t mode);
|
||||
static bool sendFd(int fd, struct fd_response *resp, size_t resp_size);
|
||||
|
||||
public:
|
||||
static void closeServer() { close(_server); }
|
||||
static void closePeer() { close(_peer); }
|
||||
static bool bindServer(struct sockaddr_un *sun, socklen_t addrlen, int accept_timeout);
|
||||
static bool acceptPeer(int *peer_pid);
|
||||
static bool serveRequests(int peer_pid);
|
||||
};
|
||||
|
||||
int FdTransferServer::_server;
|
||||
int FdTransferServer::_peer;
|
||||
|
||||
bool FdTransferServer::bindServer(struct sockaddr_un *sun, socklen_t addrlen, int accept_timeout) {
|
||||
_server = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (_server == -1) {
|
||||
perror("FdTransfer socket()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arbitrary timeout, to prevent it from listening forever.
|
||||
if (accept_timeout > 0) {
|
||||
const struct timeval timeout = {accept_timeout, 0};
|
||||
if (setsockopt(_server, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
|
||||
perror("FdTransfer setsockopt(SO_RCVTIMEO)");
|
||||
close(_server);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(_server, (const struct sockaddr*)sun, addrlen) < 0) {
|
||||
perror("FdTransfer bind()");
|
||||
close(_server);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(_server, 1) < 0) {
|
||||
perror("FdTransfer listen()");
|
||||
close(_server);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FdTransferServer::acceptPeer(int *peer_pid) {
|
||||
_peer = accept(_server, NULL, NULL);
|
||||
if (_peer == -1) {
|
||||
perror("FdTransfer accept()");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ucred cred;
|
||||
socklen_t len = sizeof(cred);
|
||||
if (getsockopt(_peer, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
|
||||
perror("getsockopt(SO_PEERCRED)");
|
||||
close(_peer);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*peer_pid != 0) {
|
||||
if (cred.pid != *peer_pid) {
|
||||
fprintf(stderr, "Unexpected connection from PID %d, expected from %d\n", cred.pid, *peer_pid);
|
||||
close(_peer);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
*peer_pid = cred.pid;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FdTransferServer::serveRequests(int peer_pid) {
|
||||
// Close the server side, don't need it anymore.
|
||||
FdTransferServer::closeServer();
|
||||
|
||||
void *perf_mmap_ringbuf[1024] = {};
|
||||
size_t ringbuf_index = 0;
|
||||
const size_t perf_mmap_size = 2 * sysconf(_SC_PAGESIZE);
|
||||
|
||||
while (1) {
|
||||
unsigned char request_buf[1024];
|
||||
struct fd_request *req = (struct fd_request *)request_buf;
|
||||
|
||||
ssize_t ret = RESTARTABLE(recv(_peer, req, sizeof(request_buf), 0));
|
||||
|
||||
if (ret == 0) {
|
||||
// EOF means done
|
||||
return true;
|
||||
} else if (ret < 0) {
|
||||
perror("recv()");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (req->type) {
|
||||
case PERF_FD: {
|
||||
struct perf_fd_request *request = (struct perf_fd_request*)req;
|
||||
int perf_fd = -1;
|
||||
int error;
|
||||
|
||||
// In pid == 0 mode, allow all perf_event_open requests.
|
||||
// Otherwise, verify the thread belongs to PID.
|
||||
if (peer_pid == 0 || syscall(__NR_tgkill, peer_pid, request->tid, 0) == 0) {
|
||||
perf_fd = syscall(__NR_perf_event_open, &request->attr, request->tid, -1, -1, 0);
|
||||
error = perf_fd < 0 ? errno : 0;
|
||||
} else {
|
||||
fprintf(stderr, "Target has requested perf_event_open for TID %d which is not a thread of process %d\n", request->tid, peer_pid);
|
||||
error = ESRCH;
|
||||
}
|
||||
|
||||
// Map the perf buffer here (mapping perf fds may require privileges, and fdtransfer has them while the target application does not
|
||||
// necessarily; if pages are already mapped, the same physical pages will be used when the profiler agent maps them again, requiring
|
||||
// no privileges this time)
|
||||
if (error == 0) {
|
||||
// Settings match the mmap() done in PerfEvents::createForThread().
|
||||
void *map_result = mmap(NULL, perf_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, perf_fd, 0);
|
||||
// Ignore errors - if this fails, let it fail again in the profiler again & produce a proper error for the user.
|
||||
|
||||
// Free next entry in the ring buffer, if it was previously allocated.
|
||||
if (perf_mmap_ringbuf[ringbuf_index] != NULL && perf_mmap_ringbuf[ringbuf_index] != MAP_FAILED) {
|
||||
(void)munmap(perf_mmap_ringbuf[ringbuf_index], perf_mmap_size);
|
||||
}
|
||||
// Store it in the ring buffer so we can free it later.
|
||||
perf_mmap_ringbuf[ringbuf_index] = map_result;
|
||||
|
||||
ringbuf_index++;
|
||||
ringbuf_index = ringbuf_index % ARRAY_SIZE(perf_mmap_ringbuf);
|
||||
}
|
||||
|
||||
struct perf_fd_response resp;
|
||||
resp.header.type = request->header.type;
|
||||
resp.header.error = error;
|
||||
resp.tid = request->tid;
|
||||
sendFd(perf_fd, &resp.header, sizeof(resp));
|
||||
close(perf_fd);
|
||||
break;
|
||||
}
|
||||
|
||||
case KALLSYMS_FD: {
|
||||
// can't directly pass the fd of /proc/kallsyms, because before Linux 4.15 the permission check
|
||||
// was conducted on each read.
|
||||
// it's simpler to copy the file to a temporary location and pass the fd of it (compared to passing the
|
||||
// entire contents over the peer socket)
|
||||
char tmp_path[256];
|
||||
snprintf(tmp_path, sizeof(tmp_path), "/tmp/async-profiler-kallsyms.%d", getpid());
|
||||
|
||||
int kallsyms_fd = -1;
|
||||
int error = copyFile("/proc/kallsyms", tmp_path, 0600);
|
||||
if (error == 0) {
|
||||
kallsyms_fd = open(tmp_path, O_RDONLY);
|
||||
if (kallsyms_fd == -1) {
|
||||
error = errno;
|
||||
} else {
|
||||
unlink(tmp_path);
|
||||
}
|
||||
}
|
||||
|
||||
struct fd_response resp;
|
||||
resp.type = req->type;
|
||||
resp.error = error;
|
||||
sendFd(kallsyms_fd, &resp, sizeof(resp));
|
||||
close(kallsyms_fd);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown request type %u\n", req->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int FdTransferServer::copyFile(const char* src_name, const char* dst_name, mode_t mode) {
|
||||
int src = open(src_name, O_RDONLY);
|
||||
if (src == -1) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
int dst = creat(dst_name, mode);
|
||||
if (dst == -1) {
|
||||
int result = errno;
|
||||
close(src);
|
||||
return result;
|
||||
}
|
||||
|
||||
// copy_file_range() doesn't exist in older kernels, sendfile() no longer works in newer ones
|
||||
char buf[65536];
|
||||
ssize_t r;
|
||||
while ((r = read(src, buf, sizeof(buf))) > 0) {
|
||||
ssize_t w = write(dst, buf, r);
|
||||
(void)w;
|
||||
}
|
||||
|
||||
close(dst);
|
||||
close(src);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool FdTransferServer::sendFd(int fd, struct fd_response *resp, size_t resp_size) {
|
||||
struct msghdr msg = {0};
|
||||
|
||||
struct iovec iov[1];
|
||||
iov[0].iov_base = resp;
|
||||
iov[0].iov_len = resp_size;
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = ARRAY_SIZE(iov);
|
||||
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(fd))];
|
||||
struct cmsghdr align;
|
||||
} u;
|
||||
|
||||
if (fd != -1) {
|
||||
msg.msg_control = u.buf;
|
||||
msg.msg_controllen = sizeof(u.buf);
|
||||
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
|
||||
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
|
||||
}
|
||||
|
||||
ssize_t ret = RESTARTABLE(sendmsg(_peer, &msg, 0));
|
||||
if (ret < 0) {
|
||||
perror("sendmsg()");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int single_pid_server(int pid, const char *path) {
|
||||
// get its nspid prior to moving to its PID namespace.
|
||||
int nspid;
|
||||
uid_t _target_uid;
|
||||
gid_t _target_gid;
|
||||
if (get_process_info(pid, &_target_uid, &_target_gid, &nspid)) {
|
||||
fprintf(stderr, "Process %d not found\n", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct sockaddr_un sun;
|
||||
socklen_t addrlen;
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
fprintf(stderr, "Path too long\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the server before forking, so w're ready to accept connections once our parent
|
||||
// exits.
|
||||
|
||||
// Abstract namespace UDS requires us to move network namespace.
|
||||
if (sun.sun_path[0] == '\0') {
|
||||
if (enter_ns(pid, "net") == -1) {
|
||||
fprintf(stderr, "Failed to enter the net NS of target process %d\n", pid);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FdTransferServer::bindServer(&sun, addrlen, 10)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!enter_ns(pid, "pid") == -1) {
|
||||
fprintf(stderr, "Failed to enter the PID NS of target process %d\n", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// CLONE_NEWPID affects children only - so we fork here.
|
||||
if (0 == fork()) {
|
||||
return FdTransferServer::acceptPeer(&nspid) && FdTransferServer::serveRequests(nspid) ? 0 : 1;
|
||||
} else {
|
||||
// Exit now, let our caller continue.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int path_server(const char *path) {
|
||||
struct sockaddr_un sun;
|
||||
socklen_t addrlen;
|
||||
|
||||
struct sigaction sigchld_action;
|
||||
sigchld_action.sa_handler = SIG_DFL;
|
||||
sigchld_action.sa_flags = SA_NOCLDWAIT;
|
||||
sigaction(SIGCHLD, &sigchld_action, NULL);
|
||||
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
fprintf(stderr, "Path '%s' is too long\n", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!FdTransferServer::bindServer(&sun, addrlen, 0)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Server ready at '%s'\n", path);
|
||||
|
||||
while (1) {
|
||||
int peer_pid = 0;
|
||||
if (!FdTransferServer::acceptPeer(&peer_pid)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enter its PID namespace.
|
||||
if (enter_ns(peer_pid, "pid") == -1) {
|
||||
fprintf(stderr, "Failed to enter the PID NS of target process %d\n", peer_pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Serving PID %d\n", peer_pid);
|
||||
|
||||
// We fork(), to actually move a PID namespace.
|
||||
if (0 == fork()) {
|
||||
return FdTransferServer::serveRequests(0) ? 0 : 1;
|
||||
} else {
|
||||
FdTransferServer::closePeer();
|
||||
}
|
||||
|
||||
// Move back to our original PID namespace (reverts pid_for_children)
|
||||
if (enter_ns(getpid(), "pid") == -1) {
|
||||
fprintf(stderr, "Failed to exit the PID NS of target process %d\n", peer_pid);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
int pid = 0;
|
||||
if (argc == 3) {
|
||||
pid = atoi(argv[2]);
|
||||
} else if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <path> [<pid>]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2 modes:
|
||||
// pid is not given - bind on a path and accept requests forever, from any PID, until being killed.
|
||||
// pid is given - bind on an path for that PID, accept requests only from that PID until the single connection is closed.
|
||||
if (pid != 0) {
|
||||
return single_pid_server(pid, argv[1]);
|
||||
} else {
|
||||
return path_server(argv[1]);
|
||||
}
|
||||
}
|
||||
56
src/fdtransferClient.h
Normal file
56
src/fdtransferClient.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _FDTRANSFER_CLIENT_H
|
||||
#define _FDTRANSFER_CLIENT_H
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include "fdtransfer/fdtransfer.h"
|
||||
|
||||
class FdTransferClient {
|
||||
private:
|
||||
static int _peer;
|
||||
|
||||
static int recvFd(unsigned int request_id, struct fd_response *resp, size_t resp_size);
|
||||
|
||||
public:
|
||||
static bool connectToServer(const char *path);
|
||||
static bool hasPeer() { return _peer != -1; }
|
||||
static void closePeer() {
|
||||
if (_peer != -1) {
|
||||
close(_peer);
|
||||
_peer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int requestPerfFd(int *tid, struct perf_event_attr *attr);
|
||||
static int requestKallsymsFd();
|
||||
static int requestBpfMapFd(struct bpfmap_params* params);
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class FdTransferClient {
|
||||
public:
|
||||
static bool connectToServer(const char *path) { return false; }
|
||||
static bool hasPeer() { return false; }
|
||||
static void closePeer() { }
|
||||
};
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _FDTRANSFER_CLIENT_H
|
||||
169
src/fdtransferClient_linux.cpp
Normal file
169
src/fdtransferClient_linux.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fdtransferClient.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
int FdTransferClient::_peer = -1;
|
||||
|
||||
bool FdTransferClient::connectToServer(const char *path) {
|
||||
closePeer();
|
||||
|
||||
_peer = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (_peer == -1) {
|
||||
Log::warn("FdTransferClient socket(): %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sockaddr_un sun;
|
||||
socklen_t addrlen;
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not block for more than 10 seconds when waiting for a response
|
||||
struct timeval tv = {10, 0};
|
||||
setsockopt(_peer, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
if (connect(_peer, (const struct sockaddr *)&sun, addrlen) == -1) {
|
||||
Log::warn("FdTransferClient connect(): %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int FdTransferClient::requestPerfFd(int *tid, struct perf_event_attr *attr) {
|
||||
struct perf_fd_request request;
|
||||
request.header.type = PERF_FD;
|
||||
request.tid = *tid;
|
||||
memcpy(&request.attr, attr, sizeof(request.attr));
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct perf_fd_response resp;
|
||||
int fd = recvFd(request.header.type, &resp.header, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
// Update errno for our caller.
|
||||
errno = resp.header.error;
|
||||
} else {
|
||||
// Update the TID of createForThread, in case the multiple threads' requests got mixed up and we're
|
||||
// now handling the response destined to another. It's alright - the other thread(s) will finish the
|
||||
// handling of our TID perf fd.
|
||||
*tid = resp.tid;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int FdTransferClient::requestKallsymsFd() {
|
||||
struct fd_request request;
|
||||
request.type = KALLSYMS_FD;
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct fd_response resp;
|
||||
int fd = recvFd(request.type, &resp, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
errno = resp.error;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int FdTransferClient::requestBpfMapFd(struct bpfmap_params* params) {
|
||||
struct bpfmap_fd_request request;
|
||||
request.header.type = BPFMAP_FD;
|
||||
request.version = 1;
|
||||
|
||||
if (RESTARTABLE(send(_peer, &request, sizeof(request), 0)) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct bpfmap_fd_response resp;
|
||||
int fd = recvFd(request.header.type, &resp.header, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
errno = resp.header.error;
|
||||
} else {
|
||||
*params = resp.params;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int FdTransferClient::recvFd(unsigned int type, struct fd_response *resp, size_t resp_size) {
|
||||
struct msghdr msg = {0};
|
||||
|
||||
struct iovec iov[1];
|
||||
iov[0].iov_base = resp;
|
||||
iov[0].iov_len = resp_size;
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = ARRAY_SIZE(iov);
|
||||
|
||||
int newfd;
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(newfd))];
|
||||
struct cmsghdr align;
|
||||
} u;
|
||||
msg.msg_control = u.buf;
|
||||
msg.msg_controllen = sizeof(u.buf);
|
||||
|
||||
ssize_t ret = RESTARTABLE(recvmsg(_peer, &msg, 0));
|
||||
if (ret < 0) {
|
||||
Log::warn("FdTransferClient recvmsg(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (resp->type != type) {
|
||||
Log::warn("FdTransferClient recvmsg(): bad response type");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (resp->error == 0) {
|
||||
struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg);
|
||||
if (cmptr != NULL && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))
|
||||
&& cmptr->cmsg_level == SOL_SOCKET && cmptr->cmsg_type == SCM_RIGHTS) {
|
||||
|
||||
newfd = *((int*)CMSG_DATA(cmptr));
|
||||
} else {
|
||||
Log::warn("FdTransferClient recvmsg(): unexpected response with no SCM_RIGHTS: %s", strerror(errno));
|
||||
newfd = -1;
|
||||
}
|
||||
} else {
|
||||
newfd = -1;
|
||||
}
|
||||
|
||||
return newfd;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
@@ -17,393 +17,17 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "flameGraph.h"
|
||||
#include "incbin.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
static const char FLAMEGRAPH_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang='en'>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<style>\n"
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n"
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n"
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n"
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n"
|
||||
"\tp {margin: 5px 0 5px 0}\n"
|
||||
"\ta {color: #0366d6}\n"
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n"
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n"
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n"
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n"
|
||||
"\t#reset {cursor: pointer}\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n"
|
||||
"<h1>%s</h1>\n"
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n"
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n"
|
||||
"<canvas id='canvas' style='width: 100%%; height: %dpx'></canvas>\n"
|
||||
"<div id='hl'><span></span></div>\n"
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n"
|
||||
"<p id='status'> </p>\n"
|
||||
"<script>\n"
|
||||
"\t// Copyright 2020 Andrei Pangin\n"
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n"
|
||||
"\t'use strict';\n"
|
||||
"\tvar root, rootLevel, px, pattern;\n"
|
||||
"\tvar reverse = %s;\n"
|
||||
"\tconst levels = Array(%d);\n"
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\tlevels[h] = [];\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tconst canvas = document.getElementById('canvas');\n"
|
||||
"\tconst c = canvas.getContext('2d');\n"
|
||||
"\tconst hl = document.getElementById('hl');\n"
|
||||
"\tconst status = document.getElementById('status');\n"
|
||||
"\n"
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n"
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n"
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n"
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n"
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n"
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n"
|
||||
"\tc.font = document.body.style.font;\n"
|
||||
"\n"
|
||||
"\tconst palette = [\n"
|
||||
"\t\t[0x50e150, 30, 30, 30],\n"
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n"
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n"
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n"
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n"
|
||||
"\t];\n"
|
||||
"\n"
|
||||
"\tfunction getColor(p) {\n"
|
||||
"\t\tconst v = Math.random();\n"
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction f(level, left, width, type, title) {\n"
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction samples(n) {\n"
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction pct(a, b) {\n"
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction findFrame(frames, x) {\n"
|
||||
"\t\tlet left = 0;\n"
|
||||
"\t\tlet right = frames.length - 1;\n"
|
||||
"\n"
|
||||
"\t\twhile (left <= right) {\n"
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n"
|
||||
"\t\t\tconst f = frames[mid];\n"
|
||||
"\n"
|
||||
"\t\t\tif (f.left > x) {\n"
|
||||
"\t\t\t\tright = mid - 1;\n"
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n"
|
||||
"\t\t\t\tleft = mid + 1;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\treturn f;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n"
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n"
|
||||
"\n"
|
||||
"\t\treturn null;\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction search(r) {\n"
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n"
|
||||
"\t\tconst matched = render(root, rootLevel);\n"
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%%';\n"
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction render(newRoot, newLevel) {\n"
|
||||
"\t\tif (root) {\n"
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n"
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\troot = newRoot || levels[0][0];\n"
|
||||
"\t\trootLevel = newLevel || 0;\n"
|
||||
"\t\tpx = canvasWidth / root.width;\n"
|
||||
"\n"
|
||||
"\t\tconst x0 = root.left;\n"
|
||||
"\t\tconst x1 = x0 + root.width;\n"
|
||||
"\t\tconst marked = [];\n"
|
||||
"\n"
|
||||
"\t\tfunction mark(f) {\n"
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction totalMarked() {\n"
|
||||
"\t\t\tlet total = 0;\n"
|
||||
"\t\t\tlet left = 0;\n"
|
||||
"\t\t\tfor (let x in marked) {\n"
|
||||
"\t\t\t\tif (+x >= left) {\n"
|
||||
"\t\t\t\t\ttotal += marked[x];\n"
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\treturn total;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n"
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n"
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n"
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n"
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n"
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n"
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n"
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (alpha) {\n"
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n"
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n"
|
||||
"\t\t\tconst frames = levels[h];\n"
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n"
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\treturn totalMarked();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tcanvas.onmousemove = function() {\n"
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n"
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n"
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n"
|
||||
"\t\t\tif (f) {\n"
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n"
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n"
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n"
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n"
|
||||
"\t\t\t\thl.style.display = 'block';\n"
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%%)';\n"
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n"
|
||||
"\t\t\t\tcanvas.onclick = function() {\n"
|
||||
"\t\t\t\t\tif (f != root) {\n"
|
||||
"\t\t\t\t\t\trender(f, h);\n"
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t};\n"
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n"
|
||||
"\t\t\t\treturn;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tcanvas.onmouseout();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tcanvas.onmouseout = function() {\n"
|
||||
"\t\thl.style.display = 'none';\n"
|
||||
"\t\tstatus.textContent = '\\xa0';\n"
|
||||
"\t\tcanvas.title = '';\n"
|
||||
"\t\tcanvas.style.cursor = '';\n"
|
||||
"\t\tcanvas.onclick = '';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n"
|
||||
"\t\treverse = !reverse;\n"
|
||||
"\t\trender();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('search').onclick = function() {\n"
|
||||
"\t\tsearch(true);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n"
|
||||
"\t\tsearch(false);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\twindow.onkeydown = function() {\n"
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n"
|
||||
"\t\t\tevent.preventDefault();\n"
|
||||
"\t\t\tsearch(true);\n"
|
||||
"\t\t} else if (event.keyCode === 27) {\n"
|
||||
"\t\t\tsearch(false);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n";
|
||||
// Browsers refuse to draw on canvas larger than 32767 px
|
||||
const int MAX_CANVAS_HEIGHT = 32767;
|
||||
|
||||
static const char FLAMEGRAPH_FOOTER[] =
|
||||
"render();\n"
|
||||
"</script></body></html>\n";
|
||||
|
||||
|
||||
static const char TREE_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"en\">\n"
|
||||
"<head>\n"
|
||||
"<title>Tree view</title>\n"
|
||||
"<meta charset=\"utf-8\"/>\n"
|
||||
"<style>\n"
|
||||
"body {\n"
|
||||
" font-family: Arial;\n"
|
||||
"}\n"
|
||||
"ul.tree li {\n"
|
||||
" list-style-type: none;\n"
|
||||
" position: relative;\n"
|
||||
"}\n"
|
||||
"ul.tree ul {\n"
|
||||
" margin-left: 20px; padding-left: 0;\n"
|
||||
"}\n"
|
||||
"ul.tree li ul {\n"
|
||||
" display: none;\n"
|
||||
"}\n"
|
||||
"ul.tree li.open > ul {\n"
|
||||
" display: block;\n"
|
||||
"}\n"
|
||||
"ul.tree li div:before {\n"
|
||||
" height: 1em;\n"
|
||||
" padding:0 .1em;\n"
|
||||
" font-size: .8em;\n"
|
||||
" display: block;\n"
|
||||
" position: absolute;\n"
|
||||
" left: -1.3em;\n"
|
||||
" top: .2em;\n"
|
||||
"}\n"
|
||||
"ul.tree li > div:not(:nth-last-child(2)):before {\n"
|
||||
" content: '+';\n"
|
||||
"}\n"
|
||||
"ul.tree li.open > div:not(:nth-last-child(2)):before {\n"
|
||||
" content: '-';\n"
|
||||
"}\n"
|
||||
".sc {\n"
|
||||
" text-decoration: underline;\n"
|
||||
" text-decoration-color: black;\n"
|
||||
" font-weight: bold;\n"
|
||||
" background-color: #D9D9D9;\n"
|
||||
"}\n"
|
||||
".t0 {\n"
|
||||
" color: #32c832;\n"
|
||||
"}\n"
|
||||
".t1 {\n"
|
||||
" color: #32a5a5;\n"
|
||||
"}\n"
|
||||
".t2 {\n"
|
||||
" color: #be5a00;\n"
|
||||
"}\n"
|
||||
".t3 {\n"
|
||||
" color: #afaf32;\n"
|
||||
"}\n"
|
||||
".t4 {\n"
|
||||
" color: #c83232;\n"
|
||||
"}\n"
|
||||
"ul.tree li > div {\n"
|
||||
" display: inline;\n"
|
||||
" cursor: pointer;\n"
|
||||
" color: black;\n"
|
||||
" text-decoration: none;\n"
|
||||
"}\n"
|
||||
"</style>\n"
|
||||
"<script>\n"
|
||||
"function treeView(opt) {\n"
|
||||
" var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
|
||||
" for(var i = 0; i < tree.length; i++){\n"
|
||||
" var parent = tree[i].parentElement;\n"
|
||||
" var classList = parent.classList;\n"
|
||||
" if(opt == 0) {\n"
|
||||
" classList.add('open');\n"
|
||||
" } else {\n"
|
||||
" classList.remove('open');\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function openParent(p,t) {\n"
|
||||
" if(p.parentElement.classList.contains(\"tree\")) {\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" p.parentElement.classList.add('open');\n"
|
||||
" openParent(p.parentElement,t);\n"
|
||||
"}\n"
|
||||
"function search() {\n"
|
||||
" var tree = document.querySelectorAll('ul.tree span');\n"
|
||||
" var check = document.getElementById('check');\n"
|
||||
" for(var i = 0; i < tree.length; i++){\n"
|
||||
" tree[i].classList.remove('sc');\n"
|
||||
" if(tree[i].innerHTML.includes(document.getElementById(\"search\").value)) {\n"
|
||||
" tree[i].classList.add('sc');\n"
|
||||
" openParent(tree[i].parentElement,tree);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function openUL(n) {\n"
|
||||
" var children = n.children;\n"
|
||||
" if(children.length == 1) {\n"
|
||||
" openNode(children[0]);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function openNode(n) {\n"
|
||||
" var children = n.children;\n"
|
||||
" for(var i = 0; i < children.length; i++){\n"
|
||||
" if(children[i].nodeName == 'UL') {\n"
|
||||
" n.classList.add('open');\n"
|
||||
" openUL(children[i]);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"function addClickActions() {\n"
|
||||
"var tree = document.querySelectorAll('ul.tree div:not(:last-child)');\n"
|
||||
"for(var i = 0; i < tree.length; i++){\n"
|
||||
" tree[i].addEventListener('click', function(e) {\n"
|
||||
" var parent = e.target.parentElement;\n"
|
||||
" var classList = parent.classList;\n"
|
||||
" if(classList.contains(\"open\")) {\n"
|
||||
" classList.remove('open');\n"
|
||||
" var opensubs = parent.querySelectorAll(':scope .open');\n"
|
||||
" for(var i = 0; i < opensubs.length; i++){\n"
|
||||
" opensubs[i].classList.remove('open');\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" if(e.altKey) {\n"
|
||||
" classList.add('open');\n"
|
||||
" var opensubs = parent.querySelectorAll('li');\n"
|
||||
" for(var i = 0; i < opensubs.length; i++){\n"
|
||||
" opensubs[i].classList.add('open');\n"
|
||||
" }\n"
|
||||
" } else {\n"
|
||||
" openNode(parent);\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" });\n"
|
||||
"}\n"
|
||||
"}\n"
|
||||
"</script>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<div style=\"padding-left: 25px;\">%s view, total %s: %s </div>\n"
|
||||
"<div style=\"padding-left: 25px;\"><button type='button' onclick='treeView(0)'>++</button><button type='button' onclick='treeView(1)'>--</button>\n"
|
||||
"<input type='text' id='search' value='' size='35' onkeypress=\"if(event.keyCode == 13) document.getElementById('searchBtn').click()\">\n"
|
||||
"<button type='button' id='searchBtn' onclick='search()'>search</button></div>\n"
|
||||
"<ul class=\"tree\">\n";
|
||||
|
||||
static const char TREE_FOOTER[] =
|
||||
"<script>\n"
|
||||
"addClickActions();\n"
|
||||
"</script>\n"
|
||||
"</ul>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
INCBIN(FLAMEGRAPH_TEMPLATE, "flame.html")
|
||||
INCBIN(TREE_TEMPLATE, "tree.html")
|
||||
|
||||
|
||||
class StringUtils {
|
||||
@@ -413,9 +37,9 @@ class StringUtils {
|
||||
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
|
||||
}
|
||||
|
||||
static void replace(std::string& s, char c, const char* replacement) {
|
||||
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
|
||||
s.replace(i, 1, replacement);
|
||||
static void 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -467,33 +91,59 @@ void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
int depth = _root.depth(_mintotal);
|
||||
|
||||
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;
|
||||
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out << (_reverse ? "Backtrace" : "Call tree");
|
||||
|
||||
tail = printTill(out, tail, "/*type:*/");
|
||||
out << (_counter == COUNTER_SAMPLES ? "samples" : "counter");
|
||||
|
||||
tail = printTill(out, tail, "/*count:*/");
|
||||
out << Format().thousands(_root._total);
|
||||
|
||||
tail = printTill(out, tail, "/*tree:*/");
|
||||
|
||||
printTreeFrame(out, _root, 0);
|
||||
|
||||
out << TREE_FOOTER;
|
||||
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;
|
||||
|
||||
tail = printTill(out, tail, "/*height:*/300");
|
||||
out << std::min(depth * 16, MAX_CANVAS_HEIGHT);
|
||||
|
||||
tail = printTill(out, tail, "/*title:*/");
|
||||
out << _title;
|
||||
|
||||
tail = printTill(out, tail, "/*reverse:*/false");
|
||||
out << (_reverse ? "true" : "false");
|
||||
|
||||
tail = printTill(out, tail, "/*depth:*/0");
|
||||
out << depth;
|
||||
|
||||
tail = printTill(out, tail, "/*frames:*/");
|
||||
|
||||
printFrame(out, "all", _root, 0, 0);
|
||||
|
||||
out << FLAMEGRAPH_FOOTER;
|
||||
tail = printTill(out, tail, "/*highlight:*/");
|
||||
|
||||
out << tail;
|
||||
}
|
||||
}
|
||||
|
||||
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, '\'', "\\'");
|
||||
int type = frameType(name_copy, f);
|
||||
StringUtils::replace(name_copy, '\'', "\\'", 2);
|
||||
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n", level, x, f._total, type, name_copy.c_str());
|
||||
if (f._inlined | f._c1_compiled | f._interpreted) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s',%llu,%llu,%llu)\n",
|
||||
level, x, f._total, type, name_copy.c_str(), f._inlined, f._c1_compiled, f._interpreted);
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n",
|
||||
level, x, f._total, type, name_copy.c_str());
|
||||
}
|
||||
out << _buf;
|
||||
|
||||
x += f._self;
|
||||
@@ -517,10 +167,10 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
std::string name = subnodes[i]._name;
|
||||
const Trie* trie = subnodes[i]._trie;
|
||||
|
||||
int type = frameType(name);
|
||||
StringUtils::replace(name, '&', "&");
|
||||
StringUtils::replace(name, '<', "<");
|
||||
StringUtils::replace(name, '>', ">");
|
||||
int type = frameType(name, f);
|
||||
StringUtils::replace(name, '&', "&", 5);
|
||||
StringUtils::replace(name, '<', "<", 4);
|
||||
StringUtils::replace(name, '>', ">", 4);
|
||||
|
||||
if (_reverse) {
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
@@ -550,27 +200,37 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
}
|
||||
}
|
||||
|
||||
int FlameGraph::frameType(std::string& name) {
|
||||
const char* FlameGraph::printTill(std::ostream& out, const char* data, const char* till) {
|
||||
const char* pos = strstr(data, till);
|
||||
out.write(data, pos - data);
|
||||
return pos + strlen(till);
|
||||
}
|
||||
|
||||
// TODO: Reuse frame type embedded in ASGCT_CallFrame
|
||||
int FlameGraph::frameType(std::string& name, const Trie& f) {
|
||||
if (f._inlined * 3 >= f._total) {
|
||||
return FRAME_INLINED;
|
||||
} else if (f._c1_compiled * 2 >= f._total) {
|
||||
return FRAME_C1_COMPILED;
|
||||
} else if (f._interpreted * 2 >= f._total) {
|
||||
return FRAME_INTERPRETED;
|
||||
}
|
||||
|
||||
if (StringUtils::endsWith(name, "_[j]", 4)) {
|
||||
// Java compiled frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return 0;
|
||||
return FRAME_JIT_COMPILED;
|
||||
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
|
||||
// Java inlined frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return 1;
|
||||
return FRAME_INLINED;
|
||||
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
|
||||
// Kernel function
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return 2;
|
||||
return FRAME_KERNEL;
|
||||
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
|
||||
// C++ function or Objective C method
|
||||
return 3;
|
||||
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
// Java regular method
|
||||
return 0;
|
||||
return FRAME_CPP;
|
||||
} else if (((int)name.find('/') > 0 && name[0] != '[')
|
||||
|| ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
return FRAME_JIT_COMPILED;
|
||||
} else {
|
||||
// Other native code
|
||||
return 4;
|
||||
return FRAME_NATIVE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <iostream>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
class Trie {
|
||||
@@ -29,10 +30,11 @@ class Trie {
|
||||
std::map<std::string, Trie> _children;
|
||||
u64 _total;
|
||||
u64 _self;
|
||||
u64 _inlined, _c1_compiled, _interpreted;
|
||||
|
||||
Trie() : _children(), _total(0), _self(0) {
|
||||
Trie() : _children(), _total(0), _self(0), _inlined(0), _c1_compiled(0), _interpreted(0) {
|
||||
}
|
||||
|
||||
|
||||
Trie* addChild(const std::string& key, u64 value) {
|
||||
_total += value;
|
||||
return &_children[key];
|
||||
@@ -43,6 +45,15 @@ class Trie {
|
||||
_self += value;
|
||||
}
|
||||
|
||||
void addCompilationDetails(int bci, u64 counter) {
|
||||
switch (FrameType::decode(bci)) {
|
||||
case FRAME_INLINED: _inlined += counter; break;
|
||||
case FRAME_C1_COMPILED: _c1_compiled += counter; break;
|
||||
case FRAME_INTERPRETED: _interpreted += counter; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
int depth(u64 cutoff) const {
|
||||
if (_total < cutoff) {
|
||||
return 0;
|
||||
@@ -71,7 +82,8 @@ class FlameGraph {
|
||||
|
||||
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);
|
||||
const char* printTill(std::ostream& out, const char* data, const char* till);
|
||||
int frameType(std::string& name, const Trie& f);
|
||||
|
||||
public:
|
||||
FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) :
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "event.h"
|
||||
#include "log.h"
|
||||
|
||||
class Recording;
|
||||
|
||||
@@ -27,19 +28,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);
|
||||
|
||||
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);
|
||||
int event_type, Event* event);
|
||||
|
||||
void recordLog(LogLevel level, const char* message, size_t len);
|
||||
};
|
||||
|
||||
#endif // _FLIGHTRECORDER_H
|
||||
|
||||
@@ -14,15 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "demangle.h"
|
||||
#include "frameName.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
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 +80,42 @@ bool Matcher::matches(const char* s) {
|
||||
}
|
||||
|
||||
|
||||
FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_cache(),
|
||||
JMethodCache FrameName::_cache;
|
||||
|
||||
FrameName::FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_class_names(),
|
||||
_include(),
|
||||
_exclude(),
|
||||
_str(),
|
||||
_style(style),
|
||||
_cache_epoch((unsigned char)epoch),
|
||||
_cache_max_age(args._mcache),
|
||||
_thread_names_lock(thread_names_lock),
|
||||
_thread_names(thread_names)
|
||||
{
|
||||
// Require printf to use standard C format regardless of system locale
|
||||
_saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0));
|
||||
memset(_buf, 0, sizeof(_buf));
|
||||
|
||||
buildFilter(_include, args._buf, args._include);
|
||||
buildFilter(_exclude, args._buf, args._exclude);
|
||||
|
||||
Profiler::_instance.classMap()->collect(_class_names);
|
||||
Profiler::instance()->classMap()->collect(_class_names);
|
||||
}
|
||||
|
||||
FrameName::~FrameName() {
|
||||
if (_cache_max_age == 0) {
|
||||
_cache.clear();
|
||||
} else {
|
||||
// Remove stale methods from the cache, leave the fresh ones for the next profiling session
|
||||
for (JMethodCache::iterator it = _cache.begin(); it != _cache.end(); ) {
|
||||
if (_cache_epoch - (unsigned char)it->second[0] >= _cache_max_age) {
|
||||
_cache.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freelocale(uselocale(_saved_locale));
|
||||
}
|
||||
|
||||
@@ -105,32 +126,47 @@ 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);
|
||||
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) {
|
||||
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 +175,28 @@ char* FrameName::javaMethodName(jmethodID method) {
|
||||
(err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
|
||||
(err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
|
||||
// Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;'
|
||||
result = javaClassName(class_name + 1, strlen(class_name) - 2, _style);
|
||||
strcat(result, ".");
|
||||
strcat(result, method_name);
|
||||
if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
|
||||
if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
|
||||
javaClassName(class_name + 1, strlen(class_name) - 2, _style);
|
||||
_str.append(".").append(method_name);
|
||||
if (_style & STYLE_SIGNATURES) {
|
||||
if (_style & STYLE_NO_SEMICOLON) {
|
||||
for (char* s = method_sig; *s; s++) {
|
||||
if (*s == ';') *s = '|';
|
||||
}
|
||||
}
|
||||
_str.append(method_sig);
|
||||
}
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[jvmtiError %d]", err);
|
||||
result = _buf;
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "[jvmtiError %d]", err);
|
||||
_str.assign(buf);
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
jvmti->Deallocate((unsigned char*)method_sig);
|
||||
jvmti->Deallocate((unsigned char*)method_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char* FrameName::javaClassName(const char* symbol, int length, int style) {
|
||||
char* result = _buf;
|
||||
|
||||
void FrameName::javaClassName(const char* symbol, size_t length, int style) {
|
||||
int array_dimension = 0;
|
||||
while (*symbol == '[') {
|
||||
array_dimension++;
|
||||
@@ -166,42 +204,40 @@ 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;
|
||||
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 +247,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,28 +267,38 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
ThreadMap::iterator it = _thread_names.find(tid);
|
||||
if (for_matching) {
|
||||
return it != _thread_names.end() ? it->second.c_str() : "";
|
||||
} else if (it != _thread_names.end()) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[%s tid=%d]", it->second.c_str(), tid);
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[tid=%d]", tid);
|
||||
}
|
||||
return _buf;
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "tid=%d]", tid);
|
||||
if (it != _thread_names.end()) {
|
||||
return _str.assign("[").append(it->second).append(" ").append(buf).c_str();
|
||||
} else {
|
||||
return _str.assign("[").append(buf).c_str();
|
||||
}
|
||||
}
|
||||
|
||||
case BCI_ERROR: {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[%s]", (const char*)frame.method_id);
|
||||
return _buf;
|
||||
}
|
||||
case BCI_ERROR:
|
||||
return _str.assign("[").append((const char*)frame.method_id).append("]").c_str();
|
||||
|
||||
default: {
|
||||
const char* type_suffix = typeSuffix(FrameType::decode(frame.bci));
|
||||
|
||||
JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
|
||||
if (it != _cache.end() && it->first == frame.method_id) {
|
||||
return it->second.c_str();
|
||||
it->second[0] = _cache_epoch;
|
||||
if (type_suffix != NULL) {
|
||||
return _str.assign(it->second, 1, std::string::npos).append(type_suffix).c_str();
|
||||
}
|
||||
return it->second.c_str() + 1;
|
||||
}
|
||||
|
||||
const char* newName = javaMethodName(frame.method_id);
|
||||
_cache.insert(it, JMethodCache::value_type(frame.method_id, newName));
|
||||
return newName;
|
||||
javaMethodName(frame.method_id);
|
||||
_cache.insert(it, JMethodCache::value_type(frame.method_id, std::string(1, _cache_epoch) + _str));
|
||||
if (type_suffix != NULL) {
|
||||
_str += type_suffix;
|
||||
}
|
||||
return _str.c_str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,24 +63,27 @@ class Matcher {
|
||||
|
||||
class FrameName {
|
||||
private:
|
||||
JMethodCache _cache;
|
||||
static JMethodCache _cache;
|
||||
|
||||
ClassMap _class_names;
|
||||
std::vector<Matcher> _include;
|
||||
std::vector<Matcher> _exclude;
|
||||
char _buf[800]; // must be large enough for class name + method name + method signature
|
||||
std::string _str;
|
||||
int _style;
|
||||
unsigned char _cache_epoch;
|
||||
unsigned char _cache_max_age;
|
||||
Mutex& _thread_names_lock;
|
||||
ThreadMap& _thread_names;
|
||||
locale_t _saved_locale;
|
||||
|
||||
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
|
||||
char* truncate(char* name, int max_length);
|
||||
const char* cppDemangle(const char* name);
|
||||
char* javaMethodName(jmethodID method);
|
||||
char* javaClassName(const char* symbol, int length, int style);
|
||||
const char* decodeNativeSymbol(const char* name);
|
||||
const char* typeSuffix(FrameTypeId type);
|
||||
void javaMethodName(jmethodID method);
|
||||
void javaClassName(const char* symbol, size_t length, int style);
|
||||
|
||||
public:
|
||||
FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names);
|
||||
FrameName(Arguments& args, int style, int epoch, Mutex& thread_names_lock, ThreadMap& thread_names);
|
||||
~FrameName();
|
||||
|
||||
const char* name(ASGCT_CallFrame& frame, bool for_matching = false);
|
||||
|
||||
BIN
src/helper/one/profiler/Instrument.class
Normal file
BIN
src/helper/one/profiler/Instrument.class
Normal file
Binary file not shown.
28
src/helper/one/profiler/Instrument.java
Normal file
28
src/helper/one/profiler/Instrument.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
/**
|
||||
* Instrumentation helper for Java method profiling.
|
||||
*/
|
||||
public class Instrument {
|
||||
|
||||
private Instrument() {
|
||||
}
|
||||
|
||||
public static native void recordSample();
|
||||
}
|
||||
BIN
src/helper/one/profiler/JfrSync.class
Normal file
BIN
src/helper/one/profiler/JfrSync.class
Normal file
Binary file not shown.
117
src/helper/one/profiler/JfrSync.java
Normal file
117
src/helper/one/profiler/JfrSync.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
import jdk.jfr.Configuration;
|
||||
import jdk.jfr.FlightRecorder;
|
||||
import jdk.jfr.FlightRecorderListener;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.RecordingState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Synchronize async-profiler recording with an existing JFR recording.
|
||||
*/
|
||||
class JfrSync implements FlightRecorderListener {
|
||||
private static volatile Recording masterRecording;
|
||||
|
||||
private JfrSync() {
|
||||
}
|
||||
|
||||
static {
|
||||
FlightRecorder.addListener(new JfrSync());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordingStateChanged(Recording recording) {
|
||||
if (recording == masterRecording && recording.getState() == RecordingState.STOPPED) {
|
||||
masterRecording = null;
|
||||
stopProfiler();
|
||||
}
|
||||
}
|
||||
|
||||
public static void start(String fileName, String settings, int eventMask) throws IOException, ParseException {
|
||||
Configuration config;
|
||||
try {
|
||||
config = Configuration.getConfiguration(settings);
|
||||
} catch (NoSuchFileException e) {
|
||||
config = Configuration.create(Paths.get(settings));
|
||||
}
|
||||
|
||||
Recording recording = new Recording(config);
|
||||
masterRecording = recording;
|
||||
|
||||
disableBuiltinEvents(recording, eventMask);
|
||||
|
||||
recording.setDestination(Paths.get(fileName));
|
||||
recording.setToDisk(true);
|
||||
recording.setDumpOnExit(true);
|
||||
recording.start();
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
Recording recording = masterRecording;
|
||||
if (recording != null) {
|
||||
// Disable state change notification before stopping
|
||||
masterRecording = null;
|
||||
recording.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void disableBuiltinEvents(Recording recording, int eventMask) {
|
||||
if ((eventMask & 1) != 0) {
|
||||
recording.disable("jdk.ExecutionSample");
|
||||
recording.disable("jdk.NativeMethodSample");
|
||||
}
|
||||
if ((eventMask & 2) != 0) {
|
||||
recording.disable("jdk.ObjectAllocationInNewTLAB");
|
||||
recording.disable("jdk.ObjectAllocationOutsideTLAB");
|
||||
recording.disable("jdk.ObjectAllocationSample");
|
||||
}
|
||||
if ((eventMask & 4) != 0) {
|
||||
recording.disable("jdk.JavaMonitorEnter");
|
||||
recording.disable("jdk.ThreadPark");
|
||||
}
|
||||
|
||||
// Shifted JfrOption values
|
||||
if ((eventMask & 0x10) != 0) {
|
||||
recording.disable("jdk.OSInformation");
|
||||
recording.disable("jdk.CPUInformation");
|
||||
recording.disable("jdk.JVMInformation");
|
||||
}
|
||||
if ((eventMask & 0x20) != 0) {
|
||||
recording.disable("jdk.InitialSystemProperty");
|
||||
}
|
||||
if ((eventMask & 0x40) != 0) {
|
||||
recording.disable("jdk.NativeLibrary");
|
||||
}
|
||||
if ((eventMask & 0x80) != 0) {
|
||||
recording.disable("jdk.CPULoad");
|
||||
}
|
||||
}
|
||||
|
||||
private static native void stopProfiler();
|
||||
|
||||
// JNI helper
|
||||
static Integer box(int n) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
BIN
src/helper/one/profiler/Server.class
Normal file
BIN
src/helper/one/profiler/Server.class
Normal file
Binary file not shown.
111
src/helper/one/profiler/Server.java
Normal file
111
src/helper/one/profiler/Server.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.profiler;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
class Server extends Thread implements Executor, HttpHandler {
|
||||
private static final String[] COMMANDS = "start,resume,stop,dump,check,status,meminfo,list,version".split(",");
|
||||
|
||||
private final HttpServer server;
|
||||
private final AtomicInteger threadNum = new AtomicInteger();
|
||||
|
||||
private Server(String address) throws IOException {
|
||||
super("Async-profiler Server");
|
||||
setDaemon(true);
|
||||
|
||||
int p = address.lastIndexOf(':');
|
||||
InetSocketAddress socketAddress = p >= 0
|
||||
? new InetSocketAddress(address.substring(0, p), Integer.parseInt(address.substring(p + 1)))
|
||||
: new InetSocketAddress(Integer.parseInt(address));
|
||||
|
||||
server = HttpServer.create(socketAddress, 0);
|
||||
server.createContext("/", this);
|
||||
server.setExecutor(this);
|
||||
}
|
||||
|
||||
public static void start(String address) throws IOException {
|
||||
new Server(address).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Thread t = new Thread(command, "Async-profiler Request #" + threadNum.incrementAndGet());
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
try {
|
||||
String command = getCommand(exchange.getRequestURI());
|
||||
if (command == null) {
|
||||
sendResponse(exchange, 404, "Unknown command");
|
||||
} else {
|
||||
String response = execute0(command);
|
||||
sendResponse(exchange, 200, response);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendResponse(exchange, 400, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
sendResponse(exchange, 500, e.getMessage());
|
||||
} finally {
|
||||
exchange.close();
|
||||
}
|
||||
}
|
||||
|
||||
private String getCommand(URI uri) {
|
||||
String path = uri.getPath();
|
||||
if (path.startsWith("/")) {
|
||||
if ((path = path.substring(1)).isEmpty()) {
|
||||
return "version=full";
|
||||
}
|
||||
for (String command : COMMANDS) {
|
||||
if (path.startsWith(command)) {
|
||||
String query = uri.getQuery();
|
||||
return query == null ? path : path + ',' + query.replace('&', ',');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void sendResponse(HttpExchange exchange, int code, String body) throws IOException {
|
||||
String contentType = body.startsWith("<!DOCTYPE html>") ? "text/html; charset=utf-8" : "text/plain";
|
||||
exchange.getResponseHeaders().add("Content-Type", contentType);
|
||||
|
||||
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(code, bodyBytes.length);
|
||||
exchange.getResponseBody().write(bodyBytes);
|
||||
}
|
||||
|
||||
private native String execute0(String command) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
}
|
||||
43
src/incbin.h
Normal file
43
src/incbin.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _INCBIN_H
|
||||
#define _INCBIN_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define INCBIN_SECTION ".const_data"
|
||||
# define INCBIN_SYMBOL "_"
|
||||
#else
|
||||
# define INCBIN_SECTION ".section \".rodata\", \"a\", @progbits"
|
||||
# define INCBIN_SYMBOL
|
||||
#endif
|
||||
|
||||
#define INCBIN(NAME, FILE) \
|
||||
extern const char NAME[];\
|
||||
extern const char NAME##_END[];\
|
||||
asm(INCBIN_SECTION "\n"\
|
||||
".global " INCBIN_SYMBOL #NAME "\n"\
|
||||
INCBIN_SYMBOL #NAME ":\n"\
|
||||
".incbin \"" FILE "\"\n"\
|
||||
".global " INCBIN_SYMBOL #NAME "_END\n"\
|
||||
INCBIN_SYMBOL #NAME "_END:\n"\
|
||||
".byte 0x00\n"\
|
||||
".previous\n"\
|
||||
);
|
||||
|
||||
#define INCBIN_SIZEOF(NAME) (NAME##_END - NAME)
|
||||
|
||||
#endif // _INCBIN_H
|
||||
@@ -18,34 +18,14 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "arch.h"
|
||||
#include "incbin.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmEntry.h"
|
||||
#include "instrument.h"
|
||||
|
||||
|
||||
// A class with a single native recordSample() method
|
||||
static const char INSTRUMENT_CLASS[] =
|
||||
"\xCA\xFE\xBA\xBE" // magic
|
||||
"\x00\x00\x00\x32" // version: 50
|
||||
"\x00\x07" // constant_pool_count: 7
|
||||
"\x07\x00\x02" // #1 = CONSTANT_Class: #2
|
||||
"\x01\x00\x17one/profiler/Instrument" // #2 = CONSTANT_Utf8: "one/profiler/Instrument"
|
||||
"\x07\x00\x04" // #3 = CONSTANT_Class: #4
|
||||
"\x01\x00\x10java/lang/Object" // #4 = CONSTANT_Utf8: "java/lang/Object"
|
||||
"\x01\x00\x0CrecordSample" // #5 = CONSTANT_Utf8: "recordSample"
|
||||
"\x01\x00\x03()V" // #6 = CONSTANT_Utf8: "()V"
|
||||
"\x00\x21" // access_flags: public super
|
||||
"\x00\x01" // this_class: #1
|
||||
"\x00\x03" // super_class: #3
|
||||
"\x00\x00" // interfaces_count: 0
|
||||
"\x00\x00" // fields_count: 0
|
||||
"\x00\x01" // methods_count: 1
|
||||
"\x01\x09" // access_flags: public static native
|
||||
"\x00\x05" // name_index: #5
|
||||
"\x00\x06" // descriptor_index: #6
|
||||
"\x00\x00" // attributes_count: 0
|
||||
"\x00"; // attributes_count: 0
|
||||
INCBIN(INSTRUMENT_CLASS, "one/profiler/Instrument.class")
|
||||
|
||||
|
||||
enum ConstantTag {
|
||||
@@ -255,6 +235,7 @@ class BytecodeRewriter {
|
||||
void rewriteCode();
|
||||
void rewriteBytecodeTable(int data_len);
|
||||
void rewriteStackMapTable();
|
||||
void rewriteVerificationTypeInfo();
|
||||
void rewriteAttributes(Scope scope);
|
||||
void rewriteMembers(Scope scope);
|
||||
bool rewriteClass();
|
||||
@@ -315,7 +296,7 @@ void BytecodeRewriter::rewriteCode() {
|
||||
put32(code_length + EXTRA_BYTECODES);
|
||||
|
||||
// invokestatic "one/profiler/Instrument.recordSample()V"
|
||||
// nop after invoke helps to prepend StackMapTable without rewriting
|
||||
// nop ensures that tableswitch/lookupswitch needs no realignment
|
||||
put8(0xb8);
|
||||
put16(_cpool_len);
|
||||
put8(0);
|
||||
@@ -366,7 +347,53 @@ void BytecodeRewriter::rewriteStackMapTable() {
|
||||
|
||||
// Prepend same_frame
|
||||
put8(EXTRA_BYTECODES - 1);
|
||||
put(get(attribute_length - 2), attribute_length - 2);
|
||||
|
||||
for (int i = 0; i < number_of_entries; i++) {
|
||||
u8 frame_type = get8();
|
||||
put8(frame_type);
|
||||
|
||||
if (frame_type <= 63) {
|
||||
// same_frame
|
||||
} else if (frame_type <= 127) {
|
||||
// same_locals_1_stack_item_frame
|
||||
rewriteVerificationTypeInfo();
|
||||
} else if (frame_type == 247) {
|
||||
// same_locals_1_stack_item_frame_extended
|
||||
put16(get16());
|
||||
rewriteVerificationTypeInfo();
|
||||
} else if (frame_type <= 251) {
|
||||
// chop_frame or same_frame_extended
|
||||
put16(get16());
|
||||
} else if (frame_type <= 254) {
|
||||
// append_frame
|
||||
put16(get16());
|
||||
for (int j = 0; j < frame_type - 251; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
} else {
|
||||
// full_frame
|
||||
put16(get16());
|
||||
u16 number_of_locals = get16();
|
||||
put16(number_of_locals);
|
||||
for (int j = 0; j < number_of_locals; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
u16 number_of_stack_items = get16();
|
||||
put16(number_of_stack_items);
|
||||
for (int j = 0; j < number_of_stack_items; j++) {
|
||||
rewriteVerificationTypeInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteVerificationTypeInfo() {
|
||||
u8 tag = get8();
|
||||
put8(tag);
|
||||
if (tag >= 7) {
|
||||
// Adjust ITEM_Uninitialized offset
|
||||
put16(tag == 8 ? EXTRA_BYTECODES + get16() : get16());
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteAttributes(Scope scope) {
|
||||
@@ -487,9 +514,9 @@ Error Instrument::check(Arguments& args) {
|
||||
JNIEnv* jni = VM::jni();
|
||||
const JNINativeMethod native_method = {(char*)"recordSample", (char*)"()V", (void*)recordSample};
|
||||
|
||||
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, sizeof(INSTRUMENT_CLASS));
|
||||
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, INCBIN_SIZEOF(INSTRUMENT_CLASS));
|
||||
if (cls == NULL || jni->RegisterNatives(cls, &native_method, 1) != 0) {
|
||||
jni->ExceptionClear();
|
||||
jni->ExceptionDescribe();
|
||||
return Error("Could not load Instrument class");
|
||||
}
|
||||
|
||||
@@ -509,7 +536,7 @@ Error Instrument::start(Arguments& args) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
|
||||
setupTargetClassAndMethod(args._event_desc);
|
||||
setupTargetClassAndMethod(args._event);
|
||||
_interval = args._interval ? args._interval : 1;
|
||||
_calls = 0;
|
||||
_running = true;
|
||||
@@ -588,6 +615,6 @@ void JNICALL Instrument::recordSample(JNIEnv* jni, jobject unused) {
|
||||
|
||||
if (_interval <= 1 || ((atomicInc(_calls) + 1) % _interval) == 0) {
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
|
||||
Profiler::instance()->recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,18 +30,14 @@ class Instrument : public Engine {
|
||||
static volatile bool _running;
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "instrument";
|
||||
const char* title() {
|
||||
return "Java method profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "calls";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
@@ -16,18 +16,32 @@
|
||||
|
||||
#include <sys/time.h>
|
||||
#include "itimer.h"
|
||||
#include "j9StackTraces.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackWalker.h"
|
||||
|
||||
|
||||
long ITimer::_interval;
|
||||
CStack ITimer::_cstack;
|
||||
|
||||
|
||||
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
Profiler::instance()->recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
void ITimer::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (!_enabled) return;
|
||||
|
||||
J9StackTraceNotification notif;
|
||||
StackContext java_ctx;
|
||||
notif.num_frames = _cstack == CSTACK_NO ? 0 : _cstack == CSTACK_DWARF
|
||||
? StackWalker::walkDwarf(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx)
|
||||
: StackWalker::walkFP(ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx);
|
||||
J9StackTraces::checkpoint(_interval, ¬if);
|
||||
}
|
||||
|
||||
Error ITimer::check(Arguments& args) {
|
||||
@@ -49,11 +63,21 @@ Error ITimer::start(Arguments& args) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
|
||||
_cstack = args._cstack;
|
||||
|
||||
OS::installSignalHandler(SIGPROF, signalHandler);
|
||||
if (VM::isOpenJ9()) {
|
||||
if (_cstack == CSTACK_DEFAULT) _cstack = CSTACK_DWARF;
|
||||
OS::installSignalHandler(SIGPROF, signalHandlerJ9);
|
||||
Error error = J9StackTraces::start(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
} else {
|
||||
OS::installSignalHandler(SIGPROF, signalHandler);
|
||||
}
|
||||
|
||||
long sec = _interval / 1000000000;
|
||||
long usec = (_interval % 1000000000) / 1000;
|
||||
time_t sec = _interval / 1000000000;
|
||||
suseconds_t usec = (_interval % 1000000000) / 1000;
|
||||
struct itimerval tv = {{sec, usec}, {sec, usec}};
|
||||
|
||||
if (setitimer(ITIMER_PROF, &tv, NULL) != 0) {
|
||||
@@ -66,4 +90,6 @@ Error ITimer::start(Arguments& args) {
|
||||
void ITimer::stop() {
|
||||
struct itimerval tv = {{0, 0}, {0, 0}};
|
||||
setitimer(ITIMER_PROF, &tv, NULL);
|
||||
|
||||
J9StackTraces::stop();
|
||||
}
|
||||
|
||||
@@ -24,12 +24,14 @@
|
||||
class ITimer : public Engine {
|
||||
private:
|
||||
static long _interval;
|
||||
static CStack _cstack;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "itimer";
|
||||
const char* title() {
|
||||
return "CPU profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
|
||||
71
src/j9Ext.cpp
Normal file
71
src/j9Ext.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "j9Ext.h"
|
||||
#include "j9ObjectSampler.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
jvmtiEnv* J9Ext::_jvmti;
|
||||
|
||||
void* (*J9Ext::_j9thread_self)() = NULL;
|
||||
|
||||
jvmtiExtensionFunction J9Ext::_GetOSThreadID = NULL;
|
||||
jvmtiExtensionFunction J9Ext::_GetJ9vmThread = NULL;
|
||||
jvmtiExtensionFunction J9Ext::_GetStackTraceExtended = NULL;
|
||||
jvmtiExtensionFunction J9Ext::_GetAllStackTracesExtended = NULL;
|
||||
|
||||
int J9Ext::InstrumentableObjectAlloc_id = -1;
|
||||
|
||||
|
||||
// Look for OpenJ9-specific JVM TI extension
|
||||
bool J9Ext::initialize(jvmtiEnv* jvmti, const void* j9thread_self) {
|
||||
_jvmti = jvmti;
|
||||
_j9thread_self = (void* (*)())j9thread_self;
|
||||
|
||||
jint ext_count;
|
||||
jvmtiExtensionFunctionInfo* ext_functions;
|
||||
if (jvmti->GetExtensionFunctions(&ext_count, &ext_functions) == 0) {
|
||||
for (int i = 0; i < ext_count; i++) {
|
||||
if (strcmp(ext_functions[i].id, "com.ibm.GetOSThreadID") == 0) {
|
||||
_GetOSThreadID = ext_functions[i].func;
|
||||
} else if (strcmp(ext_functions[i].id, "com.ibm.GetJ9vmThread") == 0) {
|
||||
_GetJ9vmThread = ext_functions[i].func;
|
||||
} else if (strcmp(ext_functions[i].id, "com.ibm.GetStackTraceExtended") == 0) {
|
||||
_GetStackTraceExtended = ext_functions[i].func;
|
||||
} else if (strcmp(ext_functions[i].id, "com.ibm.GetAllStackTracesExtended") == 0) {
|
||||
_GetAllStackTracesExtended = ext_functions[i].func;
|
||||
}
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)ext_functions);
|
||||
}
|
||||
|
||||
jvmtiExtensionEventInfo* ext_events;
|
||||
if (jvmti->GetExtensionEvents(&ext_count, &ext_events) == 0) {
|
||||
for (int i = 0; i < ext_count; i++) {
|
||||
if (strcmp(ext_events[i].id, "com.ibm.InstrumentableObjectAlloc") == 0) {
|
||||
InstrumentableObjectAlloc_id = ext_events[i].extension_event_index;
|
||||
// If we don't set a callback now, we won't be able to enable it later in runtime
|
||||
jvmti->SetExtensionEventCallback(InstrumentableObjectAlloc_id, (jvmtiExtensionEvent)J9ObjectSampler::JavaObjectAlloc);
|
||||
jvmti->SetExtensionEventCallback(InstrumentableObjectAlloc_id, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)ext_events);
|
||||
}
|
||||
|
||||
return _GetOSThreadID != NULL && _GetStackTraceExtended != NULL && _GetAllStackTracesExtended != NULL;
|
||||
}
|
||||
91
src/j9Ext.h
Normal file
91
src/j9Ext.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _J9EXT_H
|
||||
#define _J9EXT_H
|
||||
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
#define JVMTI_EXT(f, ...) ((jvmtiError (*)(jvmtiEnv*, __VA_ARGS__))f)
|
||||
|
||||
struct jvmtiFrameInfoExtended {
|
||||
jmethodID method;
|
||||
jlocation location;
|
||||
jlocation machinepc;
|
||||
jint type;
|
||||
void* native_frame_address;
|
||||
};
|
||||
|
||||
struct jvmtiStackInfoExtended {
|
||||
jthread thread;
|
||||
jint state;
|
||||
jvmtiFrameInfoExtended* frame_buffer;
|
||||
jint frame_count;
|
||||
};
|
||||
|
||||
enum {
|
||||
SHOW_COMPILED_FRAMES = 4,
|
||||
SHOW_INLINED_FRAMES = 8
|
||||
};
|
||||
|
||||
|
||||
class J9Ext {
|
||||
private:
|
||||
static jvmtiEnv* _jvmti;
|
||||
|
||||
static void* (*_j9thread_self)();
|
||||
|
||||
static jvmtiExtensionFunction _GetOSThreadID;
|
||||
static jvmtiExtensionFunction _GetJ9vmThread;
|
||||
static jvmtiExtensionFunction _GetStackTraceExtended;
|
||||
static jvmtiExtensionFunction _GetAllStackTracesExtended;
|
||||
|
||||
public:
|
||||
static bool initialize(jvmtiEnv* jvmti, const void* j9thread_self);
|
||||
|
||||
static int GetOSThreadID(jthread thread) {
|
||||
jlong thread_id;
|
||||
return JVMTI_EXT(_GetOSThreadID, jthread, jlong*)(_jvmti, thread, &thread_id) == 0 ? (int)thread_id : -1;
|
||||
}
|
||||
|
||||
static JNIEnv* GetJ9vmThread(jthread thread) {
|
||||
JNIEnv* result;
|
||||
return JVMTI_EXT(_GetJ9vmThread, jthread, JNIEnv**)(_jvmti, thread, &result) == 0 ? result : NULL;
|
||||
}
|
||||
|
||||
static jvmtiError GetStackTraceExtended(jthread thread, jint start_depth, jint max_frame_count,
|
||||
void* frame_buffer, jint* count_ptr) {
|
||||
return JVMTI_EXT(_GetStackTraceExtended, jint, jthread, jint, jint, void*, jint*)(
|
||||
_jvmti, SHOW_COMPILED_FRAMES | SHOW_INLINED_FRAMES,
|
||||
thread, start_depth, max_frame_count, frame_buffer, count_ptr);
|
||||
}
|
||||
|
||||
static jvmtiError GetAllStackTracesExtended(jint max_frame_count, void** stack_info_ptr, jint* thread_count_ptr) {
|
||||
return JVMTI_EXT(_GetAllStackTracesExtended, jint, jint, void**, jint*)(
|
||||
_jvmti, SHOW_COMPILED_FRAMES | SHOW_INLINED_FRAMES,
|
||||
max_frame_count, stack_info_ptr, thread_count_ptr);
|
||||
}
|
||||
|
||||
static void* j9thread_self() {
|
||||
return _j9thread_self != NULL ? _j9thread_self() : NULL;
|
||||
}
|
||||
|
||||
static int InstrumentableObjectAlloc_id;
|
||||
};
|
||||
|
||||
|
||||
#endif // _J9EXT_H
|
||||
71
src/j9ObjectSampler.cpp
Normal file
71
src/j9ObjectSampler.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "j9ObjectSampler.h"
|
||||
#include "j9Ext.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
void J9ObjectSampler::JavaObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
if (_enabled && updateCounter(_allocated_bytes, size, _interval)) {
|
||||
recordAllocation(jvmti, jni, BCI_ALLOC, object, object_klass, size);
|
||||
}
|
||||
}
|
||||
|
||||
void J9ObjectSampler::VMObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
if (_enabled && updateCounter(_allocated_bytes, size, _interval)) {
|
||||
recordAllocation(jvmti, jni, BCI_ALLOC_OUTSIDE_TLAB, object, object_klass, size);
|
||||
}
|
||||
}
|
||||
|
||||
Error J9ObjectSampler::check(Arguments& args) {
|
||||
if (J9Ext::InstrumentableObjectAlloc_id < 0) {
|
||||
return Error("InstrumentableObjectAlloc is not supported on this JVM");
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error J9ObjectSampler::start(Arguments& args) {
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._alloc > 0 ? args._alloc : DEFAULT_ALLOC_INTERVAL;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
initLiveRefs(args._live);
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
if (jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, (jvmtiExtensionEvent)JavaObjectAlloc) != 0) {
|
||||
return Error("Could not enable InstrumentableObjectAlloc callback");
|
||||
}
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9ObjectSampler::stop() {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
|
||||
jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, NULL);
|
||||
|
||||
dumpLiveRefs();
|
||||
}
|
||||
36
src/j9ObjectSampler.h
Normal file
36
src/j9ObjectSampler.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _J9OBJECTSAMPLER_H
|
||||
#define _J9OBJECTSAMPLER_H
|
||||
|
||||
#include "objectSampler.h"
|
||||
|
||||
|
||||
class J9ObjectSampler : public ObjectSampler {
|
||||
public:
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static void JNICALL JavaObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size);
|
||||
|
||||
static void JNICALL VMObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size);
|
||||
};
|
||||
|
||||
#endif // _J9OBJECTSAMPLER_H
|
||||
186
src/j9StackTraces.cpp
Normal file
186
src/j9StackTraces.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <map>
|
||||
#include "j9StackTraces.h"
|
||||
#include "j9Ext.h"
|
||||
#include "profiler.h"
|
||||
#include "perfEvents.h"
|
||||
|
||||
|
||||
enum {
|
||||
J9_STOPPED = 0x40,
|
||||
J9_HALT_THREAD_INSPECTION = 0x8000
|
||||
};
|
||||
|
||||
class J9VMThread {
|
||||
private:
|
||||
uintptr_t _unused1[10];
|
||||
uintptr_t _overflow_mark;
|
||||
uintptr_t _unused2[8];
|
||||
uintptr_t _flags;
|
||||
|
||||
public:
|
||||
uintptr_t getAndSetFlag(uintptr_t flag) {
|
||||
return __sync_fetch_and_or(&_flags, flag);
|
||||
}
|
||||
|
||||
void clearFlag(uintptr_t flag) {
|
||||
__sync_fetch_and_and(&_flags, ~flag);
|
||||
}
|
||||
|
||||
void setOverflowMark() {
|
||||
__atomic_store_n(&_overflow_mark, (uintptr_t)-1, __ATOMIC_RELEASE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
pthread_t J9StackTraces::_thread = 0;
|
||||
int J9StackTraces::_max_stack_depth;
|
||||
int J9StackTraces::_pipe[2];
|
||||
|
||||
static JNIEnv* _self_env = NULL;
|
||||
|
||||
|
||||
Error J9StackTraces::start(Arguments& args) {
|
||||
_max_stack_depth = args._jstackdepth;
|
||||
|
||||
if (pipe(_pipe) != 0) {
|
||||
return Error("Failed to create pipe");
|
||||
}
|
||||
fcntl(_pipe[1], F_SETFL, O_NONBLOCK);
|
||||
|
||||
if (pthread_create(&_thread, NULL, threadEntry, NULL) != 0) {
|
||||
close(_pipe[0]);
|
||||
close(_pipe[1]);
|
||||
return Error("Unable to create sampler thread");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9StackTraces::stop() {
|
||||
if (_thread != 0) {
|
||||
close(_pipe[1]);
|
||||
pthread_join(_thread, NULL);
|
||||
close(_pipe[0]);
|
||||
_thread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void J9StackTraces::timerLoop() {
|
||||
JNIEnv* jni = VM::attachThread("Async-profiler Sampler");
|
||||
__atomic_store_n(&_self_env, jni, __ATOMIC_RELEASE);
|
||||
|
||||
jni->PushLocalFrame(64);
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
char notification_buf[65536];
|
||||
std::map<void*, jthread> known_threads;
|
||||
|
||||
int max_frames = _max_stack_depth + MAX_J9_NATIVE_FRAMES + RESERVED_FRAMES;
|
||||
ASGCT_CallFrame* frames = (ASGCT_CallFrame*)malloc(max_frames * sizeof(ASGCT_CallFrame));
|
||||
jvmtiFrameInfoExtended* jvmti_frames = (jvmtiFrameInfoExtended*)malloc(max_frames * sizeof(jvmtiFrameInfoExtended));
|
||||
|
||||
while (true) {
|
||||
ssize_t bytes = read(_pipe[0], notification_buf, sizeof(notification_buf));
|
||||
if (bytes <= 0) {
|
||||
if (bytes < 0 && errno == EAGAIN) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ssize_t ptr = 0;
|
||||
while (ptr < bytes) {
|
||||
J9StackTraceNotification* notif = (J9StackTraceNotification*)(notification_buf + ptr);
|
||||
|
||||
jthread thread = known_threads[notif->env];
|
||||
jint num_jvmti_frames;
|
||||
if (thread == NULL || J9Ext::GetStackTraceExtended(thread, 0, _max_stack_depth, jvmti_frames, &num_jvmti_frames) != 0) {
|
||||
jni->PopLocalFrame(NULL);
|
||||
jni->PushLocalFrame(64);
|
||||
|
||||
jint thread_count;
|
||||
jthread* threads;
|
||||
if (jvmti->GetAllThreads(&thread_count, &threads) == 0) {
|
||||
known_threads.clear();
|
||||
for (jint i = 0; i < thread_count; i++) {
|
||||
known_threads[J9Ext::GetJ9vmThread(threads[i])] = threads[i];
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)threads);
|
||||
}
|
||||
|
||||
if ((thread = known_threads[notif->env]) == NULL ||
|
||||
J9Ext::GetStackTraceExtended(thread, 0, _max_stack_depth, jvmti_frames, &num_jvmti_frames) != 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int num_frames = Profiler::instance()->convertNativeTrace(notif->num_frames, notif->addr, frames);
|
||||
|
||||
for (int j = 0; j < num_jvmti_frames; j++) {
|
||||
frames[num_frames].method_id = jvmti_frames[j].method;
|
||||
frames[num_frames].bci = FrameType::encode(jvmti_frames[j].type, jvmti_frames[j].location);
|
||||
num_frames++;
|
||||
}
|
||||
|
||||
int tid = J9Ext::GetOSThreadID(thread);
|
||||
ExecutionEvent event;
|
||||
Profiler::instance()->recordExternalSample(notif->counter, tid, 0, &event, num_frames, frames);
|
||||
|
||||
ptr += notif->size();
|
||||
}
|
||||
}
|
||||
|
||||
free(jvmti_frames);
|
||||
free(frames);
|
||||
|
||||
__atomic_store_n(&_self_env, NULL, __ATOMIC_RELEASE);
|
||||
VM::detachThread();
|
||||
}
|
||||
|
||||
void J9StackTraces::checkpoint(u64 counter, J9StackTraceNotification* notif) {
|
||||
JNIEnv* self_env = __atomic_load_n(&_self_env, __ATOMIC_ACQUIRE);
|
||||
if (self_env == NULL) {
|
||||
// Sampler thread is not ready
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv* env = VM::jni();
|
||||
if (env != NULL && env != self_env) {
|
||||
J9VMThread* vm_thread = (J9VMThread*)env;
|
||||
uintptr_t flags = vm_thread->getAndSetFlag(J9_HALT_THREAD_INSPECTION);
|
||||
if (flags & J9_HALT_THREAD_INSPECTION) {
|
||||
// Thread is already scheduled for inspection, no need to notify again
|
||||
return;
|
||||
} else if (!(flags & J9_STOPPED)) {
|
||||
vm_thread->setOverflowMark();
|
||||
notif->env = env;
|
||||
notif->counter = counter;
|
||||
if (write(_pipe[1], notif, notif->size()) > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Something went wrong - rollback
|
||||
vm_thread->clearFlag(J9_HALT_THREAD_INSPECTION);
|
||||
}
|
||||
}
|
||||
60
src/j9StackTraces.h
Normal file
60
src/j9StackTraces.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _J9STACKTRACES_H
|
||||
#define _J9STACKTRACES_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
|
||||
|
||||
const int MAX_J9_NATIVE_FRAMES = 128;
|
||||
|
||||
struct J9StackTraceNotification {
|
||||
void* env;
|
||||
u64 counter;
|
||||
int num_frames;
|
||||
int reserved;
|
||||
const void* addr[MAX_J9_NATIVE_FRAMES];
|
||||
|
||||
size_t size() {
|
||||
return sizeof(*this) - sizeof(this->addr) + num_frames * sizeof(const void*);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class J9StackTraces {
|
||||
private:
|
||||
static pthread_t _thread;
|
||||
static int _max_stack_depth;
|
||||
static int _pipe[2];
|
||||
|
||||
static void* threadEntry(void* unused) {
|
||||
timerLoop();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void timerLoop();
|
||||
|
||||
public:
|
||||
static Error start(Arguments& args);
|
||||
static void stop();
|
||||
|
||||
static void checkpoint(u64 counter, J9StackTraceNotification* notif);
|
||||
};
|
||||
|
||||
#endif // _J9STACKTRACES_H
|
||||
86
src/j9WallClock.cpp
Normal file
86
src/j9WallClock.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "j9WallClock.h"
|
||||
#include "j9Ext.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
long J9WallClock::_interval;
|
||||
|
||||
Error J9WallClock::start(Arguments& args) {
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL * 5;
|
||||
_max_stack_depth = args._jstackdepth;
|
||||
|
||||
_running = true;
|
||||
|
||||
if (pthread_create(&_thread, NULL, threadEntry, this) != 0) {
|
||||
return Error("Unable to create timer thread");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9WallClock::stop() {
|
||||
_running = false;
|
||||
pthread_kill(_thread, WAKEUP_SIGNAL);
|
||||
pthread_join(_thread, NULL);
|
||||
}
|
||||
|
||||
void J9WallClock::timerLoop() {
|
||||
JNIEnv* jni = VM::attachThread("Async-profiler Sampler");
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
|
||||
int max_frames = _max_stack_depth + MAX_NATIVE_FRAMES + RESERVED_FRAMES;
|
||||
ASGCT_CallFrame* frames = (ASGCT_CallFrame*)malloc(max_frames * sizeof(ASGCT_CallFrame));
|
||||
|
||||
while (_running) {
|
||||
if (!_enabled) {
|
||||
OS::sleep(_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
jni->PushLocalFrame(64);
|
||||
|
||||
jvmtiStackInfoExtended* stack_infos;
|
||||
jint thread_count;
|
||||
if (J9Ext::GetAllStackTracesExtended(_max_stack_depth, (void**)&stack_infos, &thread_count) == 0) {
|
||||
for (int i = 0; i < thread_count; i++) {
|
||||
jvmtiStackInfoExtended* si = &stack_infos[i];
|
||||
for (int j = 0; j < si->frame_count; j++) {
|
||||
jvmtiFrameInfoExtended* fi = &si->frame_buffer[j];
|
||||
frames[j].method_id = fi->method;
|
||||
frames[j].bci = FrameType::encode(fi->type, fi->location);
|
||||
}
|
||||
|
||||
int tid = J9Ext::GetOSThreadID(si->thread);
|
||||
ExecutionEvent event;
|
||||
event._thread_state = (si->state & JVMTI_THREAD_STATE_RUNNABLE) ? THREAD_RUNNING : THREAD_SLEEPING;
|
||||
Profiler::instance()->recordExternalSample(_interval, tid, 0, &event, si->frame_count, frames);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)stack_infos);
|
||||
}
|
||||
|
||||
jni->PopLocalFrame(NULL);
|
||||
|
||||
OS::sleep(_interval);
|
||||
}
|
||||
|
||||
free(frames);
|
||||
|
||||
VM::detachThread();
|
||||
}
|
||||
52
src/j9WallClock.h
Normal file
52
src/j9WallClock.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _J9WALLCLOCK_H
|
||||
#define _J9WALLCLOCK_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class J9WallClock : public Engine {
|
||||
private:
|
||||
static long _interval;
|
||||
|
||||
int _max_stack_depth;
|
||||
volatile bool _running;
|
||||
pthread_t _thread;
|
||||
|
||||
static void* threadEntry(void* wall_clock) {
|
||||
((J9WallClock*)wall_clock)->timerLoop();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void timerLoop();
|
||||
|
||||
public:
|
||||
const char* title() {
|
||||
return "Wall clock profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif // _J9WALLCLOCK_H
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016 Andrei Pangin
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,388 +16,35 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAX_PATH 1024
|
||||
#define TMP_PATH (MAX_PATH - 64)
|
||||
|
||||
static char tmp_path[TMP_PATH] = {0};
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
// Parse /proc/pid/status to find process credentials
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/status", pid);
|
||||
FILE* status_file = fopen(path, "r");
|
||||
if (status_file == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* line = NULL;
|
||||
size_t size;
|
||||
|
||||
while (getline(&line, &size, status_file) != -1) {
|
||||
if (strncmp(line, "Uid:", 4) == 0) {
|
||||
// Get the effective UID, which is the second value in the line
|
||||
*uid = (uid_t)atoi(strchr(line + 5, '\t'));
|
||||
} else if (strncmp(line, "Gid:", 4) == 0) {
|
||||
// Get the effective GID, which is the second value in the line
|
||||
*gid = (gid_t)atoi(strchr(line + 5, '\t'));
|
||||
} else if (strncmp(line, "NStgid:", 7) == 0) {
|
||||
// PID namespaces can be nested; the last one is the innermost one
|
||||
*nspid = atoi(strrchr(line, '\t'));
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(status_file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int get_tmp_path(int pid) {
|
||||
// A process may have its own root path (when running in chroot environment)
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/root", pid);
|
||||
|
||||
// Append /tmp to the resolved root symlink
|
||||
ssize_t path_size = readlink(path, tmp_path, sizeof(tmp_path) - 10);
|
||||
strcpy(tmp_path + (path_size > 1 ? path_size : 0), "/tmp");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int enter_mount_ns(int pid) {
|
||||
#ifdef __NR_setns
|
||||
char path[128];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid);
|
||||
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat("/proc/self/ns/mnt", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
|
||||
// Don't try to call setns() if we're in the same namespace already
|
||||
if (oldns_stat.st_ino != newns_stat.st_ino) {
|
||||
int newns = open(path, O_RDONLY);
|
||||
if (newns < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Some ancient Linux distributions do not have setns() function
|
||||
int result = syscall(__NR_setns, newns, 0);
|
||||
close(newns);
|
||||
return result < 0 ? 0 : 1;
|
||||
}
|
||||
}
|
||||
#endif // __NR_setns
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The first line of /proc/pid/sched looks like
|
||||
// java (1234, #threads: 12)
|
||||
// where 1234 is the required host PID
|
||||
int sched_get_host_pid(const char* path) {
|
||||
static char* line = NULL;
|
||||
size_t size;
|
||||
int result = -1;
|
||||
|
||||
FILE* sched_file = fopen(path, "r");
|
||||
if (sched_file != NULL) {
|
||||
if (getline(&line, &size, sched_file) != -1) {
|
||||
char* c = strrchr(line, '(');
|
||||
if (c != NULL) {
|
||||
result = atoi(c + 1);
|
||||
}
|
||||
}
|
||||
fclose(sched_file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
|
||||
// Fortunately, /proc/pid/sched in a container exposes a host PID,
|
||||
// so the idea is to scan all container PIDs to find which one matches the host PID.
|
||||
int alt_lookup_nspid(int pid) {
|
||||
int namespace_differs = 0;
|
||||
char path[300];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
|
||||
|
||||
// Don't bother looking for container PID if we are already in the same PID namespace
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
|
||||
if (oldns_stat.st_ino == newns_stat.st_ino) {
|
||||
return pid;
|
||||
}
|
||||
namespace_differs = 1;
|
||||
}
|
||||
|
||||
// Otherwise browse all PIDs in the namespace of the target process
|
||||
// trying to find which one corresponds to the host PID
|
||||
snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
|
||||
DIR* dir = opendir(path);
|
||||
if (dir != NULL) {
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
|
||||
// Check if /proc/<container-pid>/sched points back to <host-pid>
|
||||
snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
|
||||
if (sched_get_host_pid(path) == pid) {
|
||||
closedir(dir);
|
||||
return atoi(entry->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
if (namespace_differs) {
|
||||
printf("WARNING: couldn't find container pid of the target process\n");
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
size_t len = sizeof(info);
|
||||
|
||||
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*uid = info.kp_eproc.e_ucred.cr_uid;
|
||||
*gid = info.kp_eproc.e_ucred.cr_gid;
|
||||
*nspid = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// macOS has a secure per-user temporary directory
|
||||
int get_tmp_path(int pid) {
|
||||
int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, tmp_path, sizeof(tmp_path));
|
||||
return path_size > 0 && path_size <= sizeof(tmp_path);
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_mount_ns(int pid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Not used on macOS and FreeBSD
|
||||
int alt_lookup_nspid(int pid) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
#else // __FreeBSD__
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
size_t len = sizeof(info);
|
||||
|
||||
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*uid = info.ki_uid;
|
||||
*gid = info.ki_groups[0];
|
||||
*nspid = pid;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Use default /tmp path on FreeBSD
|
||||
int get_tmp_path(int pid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_mount_ns(int pid) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Not used on macOS and FreeBSD
|
||||
int alt_lookup_nspid(int pid) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
#endif
|
||||
extern int is_openj9_process(int pid);
|
||||
extern int jattach_openj9(int pid, int nspid, int argc, char** argv);
|
||||
extern int jattach_hotspot(int pid, int nspid, int argc, char** argv);
|
||||
|
||||
|
||||
// Check if remote JVM has already opened socket for Dynamic Attach
|
||||
static int check_socket(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
|
||||
}
|
||||
|
||||
// Check if a file is owned by current user
|
||||
static int check_file_owner(const char* path) {
|
||||
struct stat stats;
|
||||
if (stat(path, &stats) == 0 && stats.st_uid == geteuid()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Some mounted filesystems may change the ownership of the file.
|
||||
// JVM will not trust such file, so it's better to remove it and try a different path
|
||||
unlink(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Force remote JVM to start Attach listener.
|
||||
// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
|
||||
static int start_attach_mechanism(int pid, int nspid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
|
||||
|
||||
int fd = creat(path, 0660);
|
||||
if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
|
||||
// Failed to create attach trigger in current directory. Retry in /tmp
|
||||
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
|
||||
fd = creat(path, 0660);
|
||||
if (fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// We have to still use the host namespace pid here for the kill() call
|
||||
kill(pid, SIGQUIT);
|
||||
|
||||
// Start with 20 ms sleep and increment delay each iteration
|
||||
struct timespec ts = {0, 20000000};
|
||||
int result;
|
||||
do {
|
||||
nanosleep(&ts, NULL);
|
||||
result = check_socket(nspid);
|
||||
} while (!result && (ts.tv_nsec += 20000000) < 300000000);
|
||||
|
||||
unlink(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Connect to UNIX domain socket created by JVM for Dynamic Attach
|
||||
static int connect_socket(int pid) {
|
||||
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
|
||||
if (bytes >= sizeof(addr.sun_path)) {
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
}
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Send command with arguments to socket
|
||||
static int write_command(int fd, int argc, char** argv) {
|
||||
// Protocol version
|
||||
if (write(fd, "1", 2) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
const char* arg = i < argc ? argv[i] : "";
|
||||
if (write(fd, arg, strlen(arg) + 1) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd) {
|
||||
char buf[8192];
|
||||
ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
|
||||
if (bytes <= 0) {
|
||||
perror("Error reading response");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// First line of response is the command result code
|
||||
buf[bytes] = 0;
|
||||
int result = atoi(buf);
|
||||
|
||||
do {
|
||||
fwrite(buf, 1, bytes, stdout);
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
} while (bytes > 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
|
||||
"Copyright 2018 Andrei Pangin\n"
|
||||
"\n"
|
||||
"Usage: jattach <pid> <cmd> [args ...]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pid = atoi(argv[1]);
|
||||
if (pid == 0) {
|
||||
perror("Invalid pid provided");
|
||||
return 1;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default")))
|
||||
int jattach(int pid, int argc, char** argv) {
|
||||
uid_t my_uid = geteuid();
|
||||
gid_t my_gid = getegid();
|
||||
uid_t target_uid = my_uid;
|
||||
gid_t target_gid = my_gid;
|
||||
int nspid = -1;
|
||||
if (!get_process_info(pid, &target_uid, &target_gid, &nspid)) {
|
||||
int nspid;
|
||||
if (get_process_info(pid, &target_uid, &target_gid, &nspid) < 0) {
|
||||
fprintf(stderr, "Process %d not found\n", pid);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nspid < 0) {
|
||||
nspid = alt_lookup_nspid(pid);
|
||||
}
|
||||
// Container support: switch to the target namespaces.
|
||||
// Network and IPC namespaces are essential for OpenJ9 connection.
|
||||
enter_ns(pid, "net");
|
||||
enter_ns(pid, "ipc");
|
||||
int mnt_changed = enter_ns(pid, "mnt");
|
||||
|
||||
// Get attach socket path of the target process (usually /tmp)
|
||||
char* jattach_path = getenv("JATTACH_PATH");
|
||||
if (jattach_path != NULL && strlen(jattach_path) < TMP_PATH) {
|
||||
strcpy(tmp_path, jattach_path);
|
||||
} else {
|
||||
// Make sure our /tmp and target /tmp is the same
|
||||
if (!get_tmp_path(pid)) {
|
||||
strcpy(tmp_path, "/tmp");
|
||||
}
|
||||
if (!enter_mount_ns(pid)) {
|
||||
printf("WARNING: couldn't enter target process mnt namespace\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic attach is allowed only for the clients with the same euid/egid.
|
||||
// In HotSpot, dynamic attach is allowed only for the clients with the same euid/egid.
|
||||
// If we are running under root, switch to the required euid/egid automatically.
|
||||
if ((my_gid != target_gid && setegid(target_gid) != 0) ||
|
||||
(my_uid != target_uid && seteuid(target_uid) != 0)) {
|
||||
@@ -405,33 +52,41 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Make write() return EPIPE instead of silent process termination
|
||||
get_tmp_path(mnt_changed > 0 ? nspid : pid);
|
||||
|
||||
// Make write() return EPIPE instead of abnormal process termination
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if (!check_socket(nspid) && !start_attach_mechanism(pid, nspid)) {
|
||||
perror("Could not start attach mechanism");
|
||||
return 1;
|
||||
if (is_openj9_process(nspid)) {
|
||||
return jattach_openj9(pid, nspid, argc, argv);
|
||||
} else {
|
||||
return jattach_hotspot(pid, nspid, argc, argv);
|
||||
}
|
||||
|
||||
int fd = connect_socket(nspid);
|
||||
if (fd == -1) {
|
||||
perror("Could not connect to socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Connected to remote JVM\n");
|
||||
if (!write_command(fd, argc - 2, argv + 2)) {
|
||||
perror("Error writing to socket");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Response code = ");
|
||||
fflush(stdout);
|
||||
|
||||
int result = read_response(fd);
|
||||
printf("\n");
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef JATTACH_VERSION
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
printf("jattach " JATTACH_VERSION " built on " __DATE__ "\n"
|
||||
"Copyright 2021 Andrei Pangin\n"
|
||||
"\n"
|
||||
"Usage: jattach <pid> <cmd> [args ...]\n"
|
||||
"\n"
|
||||
"Commands:\n"
|
||||
" load threaddump dumpheap setflag properties\n"
|
||||
" jcmd inspectheap datadump printflag agentProperties\n"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pid = atoi(argv[1]);
|
||||
if (pid <= 0) {
|
||||
fprintf(stderr, "%s is not a valid process ID\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return jattach(pid, argc - 2, argv + 2);
|
||||
}
|
||||
|
||||
#endif // JATTACH_VERSION
|
||||
|
||||
184
src/jattach/jattach_hotspot.c
Normal file
184
src/jattach/jattach_hotspot.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
// Check if remote JVM has already opened socket for Dynamic Attach
|
||||
static int check_socket(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode) ? 0 : -1;
|
||||
}
|
||||
|
||||
// Check if a file is owned by current user
|
||||
static uid_t get_file_owner(const char* path) {
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 ? stats.st_uid : (uid_t)-1;
|
||||
}
|
||||
|
||||
// Force remote JVM to start Attach listener.
|
||||
// HotSpot will start Attach listener in response to SIGQUIT if it sees .attach_pid file
|
||||
static int start_attach_mechanism(int pid, int nspid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "/proc/%d/cwd/.attach_pid%d", nspid, nspid);
|
||||
|
||||
int fd = creat(path, 0660);
|
||||
if (fd == -1 || (close(fd) == 0 && get_file_owner(path) != geteuid())) {
|
||||
// Some mounted filesystems may change the ownership of the file.
|
||||
// JVM will not trust such file, so it's better to remove it and try a different path
|
||||
unlink(path);
|
||||
|
||||
// Failed to create attach trigger in current directory. Retry in /tmp
|
||||
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
|
||||
fd = creat(path, 0660);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// We have to still use the host namespace pid here for the kill() call
|
||||
kill(pid, SIGQUIT);
|
||||
|
||||
// Start with 20 ms sleep and increment delay each iteration. Total timeout is 6000 ms
|
||||
struct timespec ts = {0, 20000000};
|
||||
int result;
|
||||
do {
|
||||
nanosleep(&ts, NULL);
|
||||
result = check_socket(nspid);
|
||||
} while (result != 0 && (ts.tv_nsec += 20000000) < 500000000);
|
||||
|
||||
unlink(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Connect to UNIX domain socket created by JVM for Dynamic Attach
|
||||
static int connect_socket(int pid) {
|
||||
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", tmp_path, pid);
|
||||
if (bytes >= sizeof(addr.sun_path)) {
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
}
|
||||
|
||||
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Send command with arguments to socket
|
||||
static int write_command(int fd, int argc, char** argv) {
|
||||
// Protocol version
|
||||
if (write(fd, "1", 2) <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
const char* arg = i < argc ? argv[i] : "";
|
||||
if (write(fd, arg, strlen(arg) + 1) <= 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd, int argc, char** argv) {
|
||||
char buf[8192];
|
||||
ssize_t bytes = read(fd, buf, sizeof(buf) - 1);
|
||||
if (bytes == 0) {
|
||||
fprintf(stderr, "Unexpected EOF reading response\n");
|
||||
return 1;
|
||||
} else if (bytes < 0) {
|
||||
perror("Error reading response");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// First line of response is the command result code
|
||||
buf[bytes] = 0;
|
||||
int result = atoi(buf);
|
||||
|
||||
// Special treatment of 'load' command
|
||||
if (result == 0 && argc > 0 && strcmp(argv[0], "load") == 0) {
|
||||
size_t total = bytes;
|
||||
while (total < sizeof(buf) - 1 && (bytes = read(fd, buf + total, sizeof(buf) - 1 - total)) > 0) {
|
||||
total += (size_t)bytes;
|
||||
}
|
||||
bytes = total;
|
||||
|
||||
// The second line is the result of 'load' command; since JDK 9 it starts from "return code: "
|
||||
buf[bytes] = 0;
|
||||
result = atoi(strncmp(buf + 2, "return code: ", 13) == 0 ? buf + 15 : buf + 2);
|
||||
}
|
||||
|
||||
// Mirror JVM response to stdout
|
||||
printf("JVM response code = ");
|
||||
do {
|
||||
fwrite(buf, 1, bytes, stdout);
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
} while (bytes > 0);
|
||||
printf("\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int jattach_hotspot(int pid, int nspid, int argc, char** argv) {
|
||||
if (check_socket(nspid) != 0 && start_attach_mechanism(pid, nspid) != 0) {
|
||||
perror("Could not start attach mechanism");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fd = connect_socket(nspid);
|
||||
if (fd == -1) {
|
||||
perror("Could not connect to socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Connected to remote JVM\n");
|
||||
|
||||
if (write_command(fd, argc, argv) != 0) {
|
||||
perror("Error writing to socket");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = read_response(fd, argc, argv);
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
}
|
||||
444
src/jattach/jattach_openj9.c
Normal file
444
src/jattach/jattach_openj9.c
Normal file
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/sem.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
#define MAX_NOTIF_FILES 256
|
||||
static int notif_lock[MAX_NOTIF_FILES];
|
||||
|
||||
|
||||
// Translate HotSpot command to OpenJ9 equivalent
|
||||
static void translate_command(char* buf, size_t bufsize, int argc, char** argv) {
|
||||
const char* cmd = argv[0];
|
||||
|
||||
if (strcmp(cmd, "load") == 0 && argc >= 2) {
|
||||
if (argc > 2 && strcmp(argv[2], "true") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_LOADAGENTPATH(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
|
||||
} else {
|
||||
snprintf(buf, bufsize, "ATTACH_LOADAGENT(%s,%s)", argv[1], argc > 3 ? argv[3] : "");
|
||||
}
|
||||
|
||||
} else if (strcmp(cmd, "jcmd") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:%s,%s", argc > 1 ? argv[1] : "help", argc > 2 ? argv[2] : "");
|
||||
|
||||
} else if (strcmp(cmd, "threaddump") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Thread.print,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "dumpheap") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.heap,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "inspectheap") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:GC.class_histogram,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "datadump") == 0) {
|
||||
snprintf(buf, bufsize, "ATTACH_DIAGNOSTICS:Dump.java,%s", argc > 1 ? argv[1] : "");
|
||||
|
||||
} else if (strcmp(cmd, "properties") == 0) {
|
||||
strcpy(buf, "ATTACH_GETSYSTEMPROPERTIES");
|
||||
|
||||
} else if (strcmp(cmd, "agentProperties") == 0) {
|
||||
strcpy(buf, "ATTACH_GETAGENTPROPERTIES");
|
||||
|
||||
} else {
|
||||
snprintf(buf, bufsize, "%s", cmd);
|
||||
}
|
||||
|
||||
buf[bufsize - 1] = 0;
|
||||
}
|
||||
|
||||
// Unescape a string and print it on stdout
|
||||
static void print_unescaped(char* str) {
|
||||
char* p = strchr(str, '\n');
|
||||
if (p != NULL) {
|
||||
*p = 0;
|
||||
}
|
||||
|
||||
while ((p = strchr(str, '\\')) != NULL) {
|
||||
switch (p[1]) {
|
||||
case 0:
|
||||
break;
|
||||
case 'f':
|
||||
*p = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
*p = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
*p = '\r';
|
||||
break;
|
||||
case 't':
|
||||
*p = '\t';
|
||||
break;
|
||||
default:
|
||||
*p = p[1];
|
||||
}
|
||||
fwrite(str, 1, p - str + 1, stdout);
|
||||
str = p + 2;
|
||||
}
|
||||
|
||||
fwrite(str, 1, strlen(str), stdout);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Send command with arguments to socket
|
||||
static int write_command(int fd, const char* cmd) {
|
||||
size_t len = strlen(cmd) + 1;
|
||||
size_t off = 0;
|
||||
while (off < len) {
|
||||
ssize_t bytes = write(fd, cmd + off, len - off);
|
||||
if (bytes <= 0) {
|
||||
return -1;
|
||||
}
|
||||
off += bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mirror response from remote JVM to stdout
|
||||
static int read_response(int fd, const char* cmd) {
|
||||
size_t size = 8192;
|
||||
char* buf = malloc(size);
|
||||
|
||||
size_t off = 0;
|
||||
while (buf != NULL) {
|
||||
ssize_t bytes = read(fd, buf + off, size - off);
|
||||
if (bytes == 0) {
|
||||
fprintf(stderr, "Unexpected EOF reading response\n");
|
||||
return 1;
|
||||
} else if (bytes < 0) {
|
||||
perror("Error reading response");
|
||||
return 1;
|
||||
}
|
||||
|
||||
off += bytes;
|
||||
if (buf[off - 1] == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (off >= size) {
|
||||
buf = realloc(buf, size *= 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (buf == NULL) {
|
||||
fprintf(stderr, "Failed to allocate memory for response\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
if (strncmp(cmd, "ATTACH_LOADAGENT", 16) == 0) {
|
||||
if (strncmp(buf, "ATTACH_ACK", 10) != 0) {
|
||||
// AgentOnLoad error code comes right after AgentInitializationException
|
||||
result = strncmp(buf, "ATTACH_ERR AgentInitializationException", 39) == 0 ? atoi(buf + 39) : -1;
|
||||
}
|
||||
} else if (strncmp(cmd, "ATTACH_DIAGNOSTICS:", 19) == 0) {
|
||||
char* p = strstr(buf, "openj9_diagnostics.string_result=");
|
||||
if (p != NULL) {
|
||||
// The result of a diagnostic command is encoded in Java Properties format
|
||||
print_unescaped(p + 33);
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
buf[off - 1] = '\n';
|
||||
fwrite(buf, 1, off, stdout);
|
||||
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void detach(int fd) {
|
||||
if (write_command(fd, "ATTACH_DETACHED") != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
ssize_t bytes;
|
||||
do {
|
||||
bytes = read(fd, buf, sizeof(buf));
|
||||
} while (bytes > 0 && buf[bytes - 1] != 0);
|
||||
}
|
||||
|
||||
static void close_with_errno(int fd) {
|
||||
int saved_errno = errno;
|
||||
close(fd);
|
||||
errno = saved_errno;
|
||||
}
|
||||
|
||||
static int acquire_lock(const char* subdir, const char* filename) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%s/%s", tmp_path, subdir, filename);
|
||||
|
||||
int lock_fd = open(path, O_WRONLY | O_CREAT, 0666);
|
||||
if (lock_fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (flock(lock_fd, LOCK_EX) < 0) {
|
||||
close_with_errno(lock_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return lock_fd;
|
||||
}
|
||||
|
||||
static void release_lock(int lock_fd) {
|
||||
flock(lock_fd, LOCK_UN);
|
||||
close(lock_fd);
|
||||
}
|
||||
|
||||
static int create_attach_socket(int* port) {
|
||||
// Try IPv6 socket first, then fall back to IPv4
|
||||
int s = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (s != -1) {
|
||||
struct sockaddr_in6 addr = {AF_INET6, 0};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
|
||||
&& getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
|
||||
*port = ntohs(addr.sin6_port);
|
||||
return s;
|
||||
}
|
||||
} else if ((s = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
|
||||
struct sockaddr_in addr = {AF_INET, 0};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (bind(s, (struct sockaddr*)&addr, addrlen) == 0 && listen(s, 0) == 0
|
||||
&& getsockname(s, (struct sockaddr*)&addr, &addrlen) == 0) {
|
||||
*port = ntohs(addr.sin_port);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
close_with_errno(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void close_attach_socket(int s, int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
|
||||
unlink(path);
|
||||
|
||||
close(s);
|
||||
}
|
||||
|
||||
static unsigned long long random_key() {
|
||||
unsigned long long key = time(NULL) * 0xc6a4a7935bd1e995ULL;
|
||||
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
ssize_t r = read(fd, &key, sizeof(key));
|
||||
(void)r;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static int write_reply_info(int pid, int port, unsigned long long key) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/replyInfo", tmp_path, pid);
|
||||
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int chars = snprintf(path, sizeof(path), "%016llx\n%d\n", key, port);
|
||||
ssize_t r = write(fd, path, chars);
|
||||
(void)r;
|
||||
close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int notify_semaphore(int value, int notif_count) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/_notifier", tmp_path);
|
||||
|
||||
key_t sem_key = ftok(path, 0xa1);
|
||||
int sem = semget(sem_key, 1, IPC_CREAT | 0666);
|
||||
if (sem < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sembuf op = {0, value, value < 0 ? IPC_NOWAIT : 0};
|
||||
while (notif_count-- > 0) {
|
||||
semop(sem, &op, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int accept_client(int s, unsigned long long key) {
|
||||
struct timeval tv = {5, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
|
||||
int client = accept(s, NULL, NULL);
|
||||
if (client < 0) {
|
||||
perror("JVM did not respond");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buf[35];
|
||||
size_t off = 0;
|
||||
while (off < sizeof(buf)) {
|
||||
ssize_t bytes = recv(client, buf + off, sizeof(buf) - off, 0);
|
||||
if (bytes <= 0) {
|
||||
fprintf(stderr, "The JVM connection was prematurely closed\n");
|
||||
close(client);
|
||||
return -1;
|
||||
}
|
||||
off += bytes;
|
||||
}
|
||||
|
||||
char expected[35];
|
||||
snprintf(expected, sizeof(expected), "ATTACH_CONNECTED %016llx ", key);
|
||||
if (memcmp(buf, expected, sizeof(expected) - 1) != 0) {
|
||||
fprintf(stderr, "Unexpected JVM response\n");
|
||||
close(client);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reset the timeout, as the command execution may take arbitrary long time
|
||||
struct timeval tv0 = {0, 0};
|
||||
setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &tv0, sizeof(tv0));
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
static int lock_notification_files() {
|
||||
int count = 0;
|
||||
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach", tmp_path);
|
||||
|
||||
DIR* dir = opendir(path);
|
||||
if (dir != NULL) {
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL && count < MAX_NOTIF_FILES) {
|
||||
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9' &&
|
||||
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN)) {
|
||||
notif_lock[count++] = acquire_lock(entry->d_name, "attachNotificationSync");
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void unlock_notification_files(int count) {
|
||||
int i;
|
||||
for (i = 0; i < count; i++) {
|
||||
if (notif_lock[i] >= 0) {
|
||||
release_lock(notif_lock[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int is_openj9_process(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.com_ibm_tools_attach/%d/attachInfo", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0;
|
||||
}
|
||||
|
||||
int jattach_openj9(int pid, int nspid, int argc, char** argv) {
|
||||
int attach_lock = acquire_lock("", "_attachlock");
|
||||
if (attach_lock < 0) {
|
||||
perror("Could not acquire attach lock");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int notif_count = 0;
|
||||
int port;
|
||||
int s = create_attach_socket(&port);
|
||||
if (s < 0) {
|
||||
perror("Failed to listen to attach socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned long long key = random_key();
|
||||
if (write_reply_info(nspid, port, key) != 0) {
|
||||
perror("Could not write replyInfo");
|
||||
goto error;
|
||||
}
|
||||
|
||||
notif_count = lock_notification_files();
|
||||
if (notify_semaphore(1, notif_count) != 0) {
|
||||
perror("Could not notify semaphore");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int fd = accept_client(s, key);
|
||||
if (fd < 0) {
|
||||
// The error message has been already printed
|
||||
goto error;
|
||||
}
|
||||
|
||||
close_attach_socket(s, nspid);
|
||||
unlock_notification_files(notif_count);
|
||||
notify_semaphore(-1, notif_count);
|
||||
release_lock(attach_lock);
|
||||
|
||||
printf("Connected to remote JVM\n");
|
||||
|
||||
char cmd[8192];
|
||||
translate_command(cmd, sizeof(cmd), argc, argv);
|
||||
|
||||
if (write_command(fd, cmd) != 0) {
|
||||
perror("Error writing to socket");
|
||||
close(fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int result = read_response(fd, cmd);
|
||||
if (result != 1) {
|
||||
detach(fd);
|
||||
}
|
||||
close(fd);
|
||||
|
||||
return result;
|
||||
|
||||
error:
|
||||
if (s >= 0) {
|
||||
close_attach_socket(s, nspid);
|
||||
}
|
||||
if (notif_count > 0) {
|
||||
unlock_notification_files(notif_count);
|
||||
notify_semaphore(-1, notif_count);
|
||||
}
|
||||
release_lock(attach_lock);
|
||||
|
||||
return 1;
|
||||
}
|
||||
243
src/jattach/psutil.c
Normal file
243
src/jattach/psutil.c
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "psutil.h"
|
||||
|
||||
|
||||
// Less than MAX_PATH to leave some space for appending
|
||||
char tmp_path[MAX_PATH - 100];
|
||||
|
||||
// Called just once to fill in tmp_path buffer
|
||||
void get_tmp_path(int pid) {
|
||||
// Try user-provided alternative path first
|
||||
const char* jattach_path = getenv("JATTACH_PATH");
|
||||
if (jattach_path != NULL && strlen(jattach_path) < sizeof(tmp_path)) {
|
||||
strcpy(tmp_path, jattach_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_tmp_path_r(pid, tmp_path, sizeof(tmp_path)) != 0) {
|
||||
strcpy(tmp_path, "/tmp");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
// The first line of /proc/pid/sched looks like
|
||||
// java (1234, #threads: 12)
|
||||
// where 1234 is the host PID (before Linux 4.1)
|
||||
static int sched_get_host_pid(const char* path) {
|
||||
static char* line = NULL;
|
||||
size_t size;
|
||||
int result = -1;
|
||||
|
||||
FILE* sched_file = fopen(path, "r");
|
||||
if (sched_file != NULL) {
|
||||
if (getline(&line, &size, sched_file) != -1) {
|
||||
char* c = strrchr(line, '(');
|
||||
if (c != NULL) {
|
||||
result = atoi(c + 1);
|
||||
}
|
||||
}
|
||||
fclose(sched_file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Linux kernels < 4.1 do not export NStgid field in /proc/pid/status.
|
||||
// Fortunately, /proc/pid/sched in a container exposes a host PID,
|
||||
// so the idea is to scan all container PIDs to find which one matches the host PID.
|
||||
static int alt_lookup_nspid(int pid) {
|
||||
char path[300];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/pid", pid);
|
||||
|
||||
// Don't bother looking for container PID if we are already in the same PID namespace
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat("/proc/self/ns/pid", &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
|
||||
if (oldns_stat.st_ino == newns_stat.st_ino) {
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise browse all PIDs in the namespace of the target process
|
||||
// trying to find which one corresponds to the host PID
|
||||
snprintf(path, sizeof(path), "/proc/%d/root/proc", pid);
|
||||
DIR* dir = opendir(path);
|
||||
if (dir != NULL) {
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] >= '1' && entry->d_name[0] <= '9') {
|
||||
// Check if /proc/<container-pid>/sched points back to <host-pid>
|
||||
snprintf(path, sizeof(path), "/proc/%d/root/proc/%s/sched", pid, entry->d_name);
|
||||
if (sched_get_host_pid(path) == pid) {
|
||||
pid = atoi(entry->d_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
|
||||
if (snprintf(buf, bufsize, "/proc/%d/root/tmp", pid) >= bufsize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if the remote /tmp can be accessed via /proc/[pid]/root
|
||||
struct stat stats;
|
||||
return stat(buf, &stats);
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
// Parse /proc/pid/status to find process credentials
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/status", pid);
|
||||
FILE* status_file = fopen(path, "r");
|
||||
if (status_file == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char* line = NULL;
|
||||
size_t size;
|
||||
int nspid_found = 0;
|
||||
|
||||
while (getline(&line, &size, status_file) != -1) {
|
||||
if (strncmp(line, "Uid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
|
||||
// Get the effective UID, which is the second value in the line
|
||||
*uid = (uid_t)atoi(strtok(NULL, "\t "));
|
||||
} else if (strncmp(line, "Gid:", 4) == 0 && strtok(line + 4, "\t ") != NULL) {
|
||||
// Get the effective GID, which is the second value in the line
|
||||
*gid = (gid_t)atoi(strtok(NULL, "\t "));
|
||||
} else if (strncmp(line, "NStgid:", 7) == 0) {
|
||||
// PID namespaces can be nested; the last one is the innermost one
|
||||
char* s;
|
||||
for (s = strtok(line + 7, "\t "); s != NULL; s = strtok(NULL, "\t ")) {
|
||||
*nspid = atoi(s);
|
||||
}
|
||||
nspid_found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(status_file);
|
||||
|
||||
if (!nspid_found) {
|
||||
*nspid = alt_lookup_nspid(pid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int enter_ns(int pid, const char* type) {
|
||||
#ifdef __NR_setns
|
||||
char path[64], selfpath[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, type);
|
||||
snprintf(selfpath, sizeof(selfpath), "/proc/self/ns/%s", type);
|
||||
|
||||
struct stat oldns_stat, newns_stat;
|
||||
if (stat(selfpath, &oldns_stat) == 0 && stat(path, &newns_stat) == 0) {
|
||||
// Don't try to call setns() if we're in the same namespace already
|
||||
if (oldns_stat.st_ino != newns_stat.st_ino) {
|
||||
int newns = open(path, O_RDONLY);
|
||||
if (newns < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Some ancient Linux distributions do not have setns() function
|
||||
int result = syscall(__NR_setns, newns, 0);
|
||||
close(newns);
|
||||
return result < 0 ? -1 : 1;
|
||||
}
|
||||
}
|
||||
#endif // __NR_setns
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
// macOS has a secure per-user temporary directory
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
|
||||
size_t path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, buf, bufsize);
|
||||
return path_size > 0 && path_size <= sizeof(tmp_path) ? 0 : -1;
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
size_t len = sizeof(info);
|
||||
|
||||
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*uid = info.kp_eproc.e_ucred.cr_uid;
|
||||
*gid = info.kp_eproc.e_ucred.cr_gid;
|
||||
*nspid = pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_ns(int pid, const char* type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else // __FreeBSD__
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
// Use default /tmp path on FreeBSD
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
size_t len = sizeof(info);
|
||||
|
||||
if (sysctl(mib, 4, &info, &len, NULL, 0) < 0 || len <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*uid = info.ki_uid;
|
||||
*gid = info.ki_groups[0];
|
||||
*nspid = pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a Linux-specific API; nothing to do on macOS and FreeBSD
|
||||
int enter_ns(int pid, const char* type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
46
src/jattach/psutil.h
Normal file
46
src/jattach/psutil.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _PSUTIL_H
|
||||
#define _PSUTIL_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
#define MAX_PATH 1024
|
||||
extern char tmp_path[];
|
||||
|
||||
// Gets /tmp path of the specified process, as it can be accessed from the host.
|
||||
// The obtained path is stored in the global tmp_path buffer.
|
||||
void get_tmp_path(int pid);
|
||||
|
||||
// The reentrant version of get_tmp_path.
|
||||
// Stores the process-specific temporary path into the provided buffer.
|
||||
// Returns 0 on success, -1 on failure.
|
||||
int get_tmp_path_r(int pid, char* buf, size_t bufsize);
|
||||
|
||||
// Gets the owner uid/gid of the target process, and also its pid inside the container.
|
||||
// Returns 0 on success, -1 on failure.
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid);
|
||||
|
||||
// Tries to enter the namespace of the target process.
|
||||
// type of the namespace can be "mnt", "net", "pid", etc.
|
||||
// Returns 1, if the namespace has been successfully changed,
|
||||
// 0, if the target process is in the same namespace as the host,
|
||||
// -1, if the attempt failed.
|
||||
int enter_ns(int pid, const char* type);
|
||||
|
||||
#endif // _PSUTIL_H
|
||||
117
src/javaApi.cpp
117
src/javaApi.cpp
@@ -18,37 +18,55 @@
|
||||
#include <sstream>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "incbin.h"
|
||||
#include "javaApi.h"
|
||||
#include "arguments.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
INCBIN(SERVER_CLASS, "one/profiler/Server.class")
|
||||
|
||||
|
||||
static void throwNew(JNIEnv* env, const char* exception_class, const char* message) {
|
||||
jclass cls = env->FindClass(exception_class);
|
||||
if (cls != NULL) {
|
||||
env->ThrowNew(cls, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" DLLEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval, jboolean reset) {
|
||||
Arguments args;
|
||||
const char* event_str = env->GetStringUTFChars(event, NULL);
|
||||
args.addEvent(event_str);
|
||||
args._interval = interval;
|
||||
Error error = Profiler::_instance.start(args, reset);
|
||||
if (strcmp(event_str, EVENT_ALLOC) == 0) {
|
||||
args._alloc = interval > 0 ? interval : 0;
|
||||
} else if (strcmp(event_str, EVENT_LOCK) == 0) {
|
||||
args._lock = interval > 0 ? interval : 0;
|
||||
} else {
|
||||
args._event = event_str;
|
||||
args._interval = interval;
|
||||
}
|
||||
|
||||
Error error = Profiler::instance()->start(args, reset);
|
||||
env->ReleaseStringUTFChars(event, event_str);
|
||||
|
||||
if (error) {
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
extern "C" DLLEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_stop0(JNIEnv* env, jobject unused) {
|
||||
Error error = Profiler::_instance.stop();
|
||||
Error error = Profiler::instance()->stop();
|
||||
|
||||
if (error) {
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
extern "C" DLLEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring command) {
|
||||
Arguments args;
|
||||
const char* command_str = env->GetStringUTFChars(command, NULL);
|
||||
@@ -56,48 +74,54 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co
|
||||
env->ReleaseStringUTFChars(command, command_str);
|
||||
|
||||
if (error) {
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalArgumentException", error.message());
|
||||
throwNew(env, "java/lang/IllegalArgumentException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (args._file == NULL || args._output == OUTPUT_JFR) {
|
||||
Log::open(args);
|
||||
|
||||
if (!args.hasOutputFile()) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.runInternal(args, out);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
if (!error) {
|
||||
if (out.tellp() >= 0x3fffffff) {
|
||||
throwNew(env, "java/lang/IllegalStateException", "Output exceeds string size limit");
|
||||
return NULL;
|
||||
}
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
}
|
||||
} else {
|
||||
std::ofstream out(args._file, std::ios::out | std::ios::trunc);
|
||||
if (out.is_open()) {
|
||||
Profiler::_instance.runInternal(args, out);
|
||||
out.close();
|
||||
return env->NewStringUTF("OK");
|
||||
} else {
|
||||
JavaAPI::throwNew(env, "java/io/IOException", strerror(errno));
|
||||
std::ofstream out(args.file(), std::ios::out | std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
throwNew(env, "java/io/IOException", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
error = Profiler::instance()->runInternal(args, out);
|
||||
out.close();
|
||||
if (!error) {
|
||||
return env->NewStringUTF("OK");
|
||||
}
|
||||
}
|
||||
|
||||
throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
extern "C" DLLEXPORT jlong JNICALL
|
||||
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
||||
return (jlong)Profiler::_instance.total_samples();
|
||||
return (jlong)Profiler::instance()->total_samples();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
extern "C" DLLEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_filterThread0(JNIEnv* env, jobject unused, jthread thread, jboolean enable) {
|
||||
int thread_id;
|
||||
if (thread == NULL) {
|
||||
thread_id = OS::threadId();
|
||||
} else if (VMThread::hasNativeId()) {
|
||||
VMThread* vmThread = VMThread::fromJavaThread(env, thread);
|
||||
if (vmThread == NULL) {
|
||||
return;
|
||||
}
|
||||
thread_id = vmThread->osThreadId();
|
||||
} else {
|
||||
} else if ((thread_id = VMThread::nativeThreadId(env, thread)) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
|
||||
ThreadFilter* thread_filter = Profiler::instance()->threadFilter();
|
||||
if (enable) {
|
||||
thread_filter->add(thread_id);
|
||||
} else {
|
||||
@@ -116,16 +140,11 @@ static const JNINativeMethod profiler_natives[] = {
|
||||
F(filterThread0, "(Ljava/lang/Thread;Z)V"),
|
||||
};
|
||||
|
||||
static const JNINativeMethod* execute0 = &profiler_natives[2];
|
||||
|
||||
#undef F
|
||||
|
||||
|
||||
void JavaAPI::throwNew(JNIEnv* env, const char* exception_class, const char* message) {
|
||||
jclass cls = env->FindClass(exception_class);
|
||||
if (cls != NULL) {
|
||||
env->ThrowNew(cls, message);
|
||||
}
|
||||
}
|
||||
|
||||
// Since AsyncProfiler class can be renamed or moved to another package (shaded),
|
||||
// we look for the actual class in the stack trace.
|
||||
void JavaAPI::registerNatives(jvmtiEnv* jvmti, JNIEnv* jni) {
|
||||
@@ -155,3 +174,23 @@ void JavaAPI::registerNatives(jvmtiEnv* jvmti, JNIEnv* jni) {
|
||||
|
||||
jni->ExceptionClear();
|
||||
}
|
||||
|
||||
bool JavaAPI::startHttpServer(jvmtiEnv* jvmti, JNIEnv* jni, const char* address) {
|
||||
jclass handler = jni->FindClass("com/sun/net/httpserver/HttpHandler");
|
||||
jobject loader;
|
||||
if (handler != NULL && jvmti->GetClassLoader(handler, &loader) == 0) {
|
||||
jclass cls = jni->DefineClass(NULL, loader, (const jbyte*)SERVER_CLASS, INCBIN_SIZEOF(SERVER_CLASS));
|
||||
if (cls != NULL && jni->RegisterNatives(cls, execute0, 1) == 0) {
|
||||
jmethodID method = jni->GetStaticMethodID(cls, "start", "(Ljava/lang/String;)V");
|
||||
if (method != NULL) {
|
||||
jni->CallStaticVoidMethod(cls, method, jni->NewStringUTF(address));
|
||||
if (!jni->ExceptionCheck()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jni->ExceptionDescribe();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
class JavaAPI {
|
||||
public:
|
||||
static void throwNew(JNIEnv* env, const char* exception_class, const char* message);
|
||||
static void registerNatives(jvmtiEnv* jvmti, JNIEnv* jni);
|
||||
static bool startHttpServer(jvmtiEnv* jvmti, JNIEnv* jni, const char* address);
|
||||
};
|
||||
|
||||
#endif // _JAVAAPI_H
|
||||
|
||||
@@ -82,6 +82,9 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< (type("jdk.types.Symbol", T_SYMBOL, "Symbol", true)
|
||||
<< field("string", T_STRING, "String"))
|
||||
|
||||
<< (type("profiler.types.LogLevel", T_LOG_LEVEL, "Log Level", true)
|
||||
<< field("name", T_STRING, "Name"))
|
||||
|
||||
<< (type("jdk.ExecutionSample", T_EXECUTION_SAMPLE, "Method Profiling Sample")
|
||||
<< category("Java Virtual Machine", "Profiling")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
@@ -113,6 +116,7 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("monitorClass", T_CLASS, "Monitor Class", F_CPOOL)
|
||||
<< field("previousOwner", T_THREAD, "Previous Monitor Owner", F_CPOOL)
|
||||
<< field("address", T_LONG, "Monitor Address", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.ThreadPark", T_THREAD_PARK, "Java Thread Park")
|
||||
@@ -123,6 +127,7 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("parkedClass", T_CLASS, "Class Parked On", F_CPOOL)
|
||||
<< field("timeout", T_LONG, "Park Timeout", F_DURATION_NANOS)
|
||||
<< field("until", T_LONG, "Park Until", F_TIME_MILLIS)
|
||||
<< field("address", T_LONG, "Address of Object Parked", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.CPULoad", T_CPU_LOAD, "CPU Load")
|
||||
@@ -132,7 +137,7 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< field("jvmSystem", T_FLOAT, "JVM System", F_PERCENTAGE)
|
||||
<< field("machineTotal", T_FLOAT, "Machine Total", F_PERCENTAGE))
|
||||
|
||||
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Flight Recording")
|
||||
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Async-profiler Recording")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
@@ -145,11 +150,12 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< field("recordingStart", T_LONG, "Start Time", F_TIME_MILLIS)
|
||||
<< field("recordingDuration", T_LONG, "Recording Duration", F_DURATION_MILLIS))
|
||||
|
||||
<< (type("jdk.ActiveSetting", T_ACTIVE_SETTING, "Recording Setting")
|
||||
<< (type("jdk.ActiveSetting", T_ACTIVE_SETTING, "Async-profiler Setting")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("id", T_LONG, "Event Id")
|
||||
<< field("name", T_STRING, "Setting Name")
|
||||
<< field("value", T_STRING, "Setting Value"))
|
||||
@@ -185,6 +191,28 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< field("key", T_STRING, "Key")
|
||||
<< field("value", T_STRING, "Value"))
|
||||
|
||||
<< (type("jdk.NativeLibrary", T_NATIVE_LIBRARY, "Native Library")
|
||||
<< category("Java Virtual Machine", "Runtime")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("name", T_STRING, "Name")
|
||||
<< field("baseAddress", T_LONG, "Base Address", F_ADDRESS)
|
||||
<< field("topAddress", T_LONG, "Top Address", F_ADDRESS))
|
||||
|
||||
<< (type("profiler.Log", T_LOG, "Log Message")
|
||||
<< category("Profiler")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("level", T_LOG_LEVEL, "Level", F_CPOOL)
|
||||
<< field("message", T_STRING, "Message"))
|
||||
|
||||
<< (type("profiler.LiveObject", T_LIVE_OBJECT, "Live Object")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES)
|
||||
<< field("allocationTime", T_LONG, "Allocation Time", F_TIME_TICKS))
|
||||
|
||||
<< (type("jdk.jfr.Label", T_LABEL, NULL)
|
||||
<< field("value", T_STRING))
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ enum JfrType {
|
||||
T_METHOD = 28,
|
||||
T_PACKAGE = 29,
|
||||
T_SYMBOL = 30,
|
||||
T_LOG_LEVEL = 31,
|
||||
|
||||
T_EVENT = 100,
|
||||
T_EXECUTION_SAMPLE = 101,
|
||||
@@ -62,6 +63,9 @@ enum JfrType {
|
||||
T_CPU_INFORMATION = 110,
|
||||
T_JVM_INFORMATION = 111,
|
||||
T_INITIAL_SYSTEM_PROPERTY = 112,
|
||||
T_NATIVE_LIBRARY = 113,
|
||||
T_LOG = 114,
|
||||
T_LIVE_OBJECT = 115,
|
||||
|
||||
T_ANNOTATION = 200,
|
||||
T_LABEL = 201,
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include "jstack.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
// Wait at most this number of milliseconds to finish processing of pending signals
|
||||
const int MAX_WAIT_MILLIS = 2000;
|
||||
|
||||
|
||||
void JStack::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = Profiler::_instance.getThreadState(ucontext);
|
||||
Profiler::_instance.recordSample(ucontext, 1, 0, &event);
|
||||
}
|
||||
|
||||
Error JStack::start(Arguments& args) {
|
||||
OS::installSignalHandler(SIGVTALRM, signalHandler);
|
||||
|
||||
int self = OS::threadId();
|
||||
u64 required_samples = Profiler::_instance.total_samples();
|
||||
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
|
||||
bool thread_filter_enabled = thread_filter->enabled();
|
||||
|
||||
ThreadList* thread_list = OS::listThreads();
|
||||
int thread_id;
|
||||
while ((thread_id = thread_list->next()) != -1) {
|
||||
if (thread_id != self && (!thread_filter_enabled || thread_filter->accept(thread_id))) {
|
||||
if (OS::sendSignalToThread(thread_id, SIGVTALRM)) {
|
||||
required_samples++;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete thread_list;
|
||||
|
||||
// Get our own stack trace after all other threads
|
||||
if (!thread_filter_enabled || thread_filter->accept(self)) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(NULL, 1, 0, &event);
|
||||
required_samples++;
|
||||
}
|
||||
|
||||
// Wait until all asynchronous stack traces collected
|
||||
for (int i = 0; Profiler::_instance.total_samples() < required_samples && i < MAX_WAIT_MILLIS; i++) {
|
||||
struct timespec timeout = {0, 1000000};
|
||||
nanosleep(&timeout, NULL);
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void JStack::stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
@@ -41,6 +41,26 @@ void LinearAllocator::clear() {
|
||||
_tail->offs = sizeof(Chunk);
|
||||
}
|
||||
|
||||
size_t LinearAllocator::usedMemory() {
|
||||
size_t bytes = _reserve->prev == _tail ? _chunk_size : 0;
|
||||
for (Chunk* chunk = _tail; chunk != NULL; chunk = chunk->prev) {
|
||||
bytes += _chunk_size;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::trim() {
|
||||
return __atomic_exchange_n(&_tail->prev, NULL, __ATOMIC_ACQ_REL);
|
||||
}
|
||||
|
||||
void LinearAllocator::freeChain(Chunk* chunk) {
|
||||
while (chunk != NULL) {
|
||||
Chunk* current = chunk;
|
||||
chunk = chunk->prev;
|
||||
freeChunk(current);
|
||||
}
|
||||
}
|
||||
|
||||
void* LinearAllocator::alloc(size_t size) {
|
||||
Chunk* chunk = _tail;
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ class LinearAllocator {
|
||||
~LinearAllocator();
|
||||
|
||||
void clear();
|
||||
size_t usedMemory();
|
||||
Chunk* trim();
|
||||
void freeChain(Chunk* chunk);
|
||||
|
||||
void* alloc(size_t size);
|
||||
};
|
||||
|
||||
@@ -16,31 +16,37 @@
|
||||
|
||||
#include <string.h>
|
||||
#include "lockTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
#include "tsc.h"
|
||||
|
||||
|
||||
double LockTracer::_ticks_to_nanos;
|
||||
jlong LockTracer::_threshold;
|
||||
jlong LockTracer::_start_time = 0;
|
||||
jclass LockTracer::_UnsafeClass = NULL;
|
||||
jclass LockTracer::_LockSupport = NULL;
|
||||
jmethodID LockTracer::_getBlocker = NULL;
|
||||
RegisterNativesFunc LockTracer::_orig_RegisterNatives = NULL;
|
||||
UnsafeParkFunc LockTracer::_orig_Unsafe_park = NULL;
|
||||
bool LockTracer::_initialized = false;
|
||||
|
||||
Error LockTracer::start(Arguments& args) {
|
||||
_ticks_to_nanos = 1e9 / TSC::frequency();
|
||||
_threshold = (jlong)(args._lock * (TSC::frequency() / 1e9));
|
||||
|
||||
if (!_initialized) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
// Enable Java Monitor events
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
|
||||
_start_time = OS::nanotime();
|
||||
_start_time = TSC::ticks();
|
||||
|
||||
if (_getBlocker == NULL) {
|
||||
JNIEnv* env = VM::jni();
|
||||
_LockSupport = (jclass)env->NewGlobalRef(env->FindClass("java/util/concurrent/locks/LockSupport"));
|
||||
_getBlocker = env->GetStaticMethodID(_LockSupport, "getBlocker", "(Ljava/lang/Thread;)Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
// Intercent Unsafe.park() for tracing contended ReentrantLocks
|
||||
if (VMStructs::_unsafe_park != NULL) {
|
||||
bindUnsafePark(UnsafeParkTrap);
|
||||
// Intercept Unsafe.park() for tracing contended ReentrantLocks
|
||||
if (_orig_Unsafe_park != NULL) {
|
||||
bindUnsafePark(UnsafeParkHook);
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
@@ -53,48 +59,98 @@ void LockTracer::stop() {
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
|
||||
|
||||
// Reset Unsafe.park() trap
|
||||
if (VMStructs::_unsafe_park != NULL) {
|
||||
bindUnsafePark(VMStructs::_unsafe_park);
|
||||
if (_orig_Unsafe_park != NULL) {
|
||||
bindUnsafePark(_orig_Unsafe_park);
|
||||
}
|
||||
}
|
||||
|
||||
void LockTracer::initialize() {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
JNIEnv* env = VM::jni();
|
||||
|
||||
// Try JDK 9+ package first, then fallback to JDK 8 package
|
||||
jclass unsafe = env->FindClass("jdk/internal/misc/Unsafe");
|
||||
if (unsafe == NULL) {
|
||||
env->ExceptionClear();
|
||||
if ((unsafe = env->FindClass("sun/misc/Unsafe")) == NULL) {
|
||||
env->ExceptionClear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_UnsafeClass = (jclass)env->NewGlobalRef(unsafe);
|
||||
jmethodID register_natives = env->GetStaticMethodID(_UnsafeClass, "registerNatives", "()V");
|
||||
jniNativeInterface* jni_functions;
|
||||
if (register_natives != NULL && jvmti->GetJNIFunctionTable(&jni_functions) == 0) {
|
||||
_orig_RegisterNatives = jni_functions->RegisterNatives;
|
||||
jni_functions->RegisterNatives = RegisterNativesHook;
|
||||
jvmti->SetJNIFunctionTable(jni_functions);
|
||||
|
||||
// Trace Unsafe.registerNatives() to find the original address of Unsafe.park() native
|
||||
env->CallStaticVoidMethod(_UnsafeClass, register_natives);
|
||||
|
||||
jni_functions->RegisterNatives = _orig_RegisterNatives;
|
||||
jvmti->SetJNIFunctionTable(jni_functions);
|
||||
}
|
||||
|
||||
_LockSupport = (jclass)env->NewGlobalRef(env->FindClass("java/util/concurrent/locks/LockSupport"));
|
||||
_getBlocker = env->GetStaticMethodID(_LockSupport, "getBlocker", "(Ljava/lang/Thread;)Ljava/lang/Object;");
|
||||
|
||||
env->ExceptionClear();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
||||
jlong enter_time = OS::nanotime();
|
||||
jlong enter_time = TSC::ticks();
|
||||
jvmti->SetTag(thread, enter_time);
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
||||
jlong entered_time = OS::nanotime();
|
||||
jlong entered_time = TSC::ticks();
|
||||
jlong enter_time;
|
||||
jvmti->GetTag(thread, &enter_time);
|
||||
|
||||
// Time is meaningless if lock attempt has started before profiling
|
||||
if (_enabled && enter_time >= _start_time) {
|
||||
if (_enabled && entered_time - enter_time >= _threshold && enter_time >= _start_time) {
|
||||
char* lock_name = getLockName(jvmti, env, object);
|
||||
recordContendedLock(BCI_LOCK, enter_time, entered_time, lock_name, object, 0);
|
||||
jvmti->Deallocate((unsigned char*)lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::UnsafeParkTrap(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time) {
|
||||
jint JNICALL LockTracer::RegisterNativesHook(JNIEnv* env, jclass cls, const JNINativeMethod* methods, jint nMethods) {
|
||||
if (env->IsSameObject(cls, _UnsafeClass)) {
|
||||
for (jint i = 0; i < nMethods; i++) {
|
||||
if (strcmp(methods[i].name, "park") == 0 && strcmp(methods[i].signature, "(ZJ)V") == 0) {
|
||||
_orig_Unsafe_park = (UnsafeParkFunc)methods[i].fnPtr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return _orig_RegisterNatives(env, cls, methods, nMethods);
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::UnsafeParkHook(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time) {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jobject park_blocker = _enabled ? getParkBlocker(jvmti, env) : NULL;
|
||||
jlong park_start_time, park_end_time;
|
||||
|
||||
if (park_blocker != NULL) {
|
||||
park_start_time = OS::nanotime();
|
||||
park_start_time = TSC::ticks();
|
||||
}
|
||||
|
||||
VMStructs::_unsafe_park(env, instance, isAbsolute, time);
|
||||
_orig_Unsafe_park(env, instance, isAbsolute, time);
|
||||
|
||||
if (park_blocker != NULL) {
|
||||
park_end_time = OS::nanotime();
|
||||
|
||||
char* lock_name = getLockName(jvmti, env, park_blocker);
|
||||
if (lock_name == NULL || isConcurrentLock(lock_name)) {
|
||||
recordContendedLock(BCI_PARK, park_start_time, park_end_time, lock_name, park_blocker, time);
|
||||
park_end_time = TSC::ticks();
|
||||
if (park_end_time - park_start_time >= _threshold) {
|
||||
char* lock_name = getLockName(jvmti, env, park_blocker);
|
||||
if (lock_name == NULL || isConcurrentLock(lock_name)) {
|
||||
recordContendedLock(BCI_PARK, park_start_time, park_end_time, lock_name, park_blocker, time);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)lock_name);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,26 +190,20 @@ void LockTracer::recordContendedLock(int event_type, u64 start_time, u64 end_tim
|
||||
|
||||
if (lock_name != NULL) {
|
||||
if (lock_name[0] == 'L') {
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(lock_name + 1, strlen(lock_name) - 2);
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(lock_name + 1, strlen(lock_name) - 2);
|
||||
} else {
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(lock_name);
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(NULL, end_time - start_time, event_type, &event);
|
||||
u64 duration_nanos = (u64)((end_time - start_time) * _ticks_to_nanos);
|
||||
Profiler::instance()->recordSample(NULL, duration_nanos, event_type, &event);
|
||||
}
|
||||
|
||||
void LockTracer::bindUnsafePark(UnsafeParkFunc entry) {
|
||||
JNIEnv* env = VM::jni();
|
||||
|
||||
// Try JDK 9+ package first, then fallback to JDK 8 package
|
||||
jclass unsafe = env->FindClass("jdk/internal/misc/Unsafe");
|
||||
if (unsafe == NULL) unsafe = env->FindClass("sun/misc/Unsafe");
|
||||
|
||||
if (unsafe != NULL) {
|
||||
const JNINativeMethod unsafe_park = {(char*)"park", (char*)"(ZJ)V", (void*)entry};
|
||||
env->RegisterNatives(unsafe, &unsafe_park, 1);
|
||||
const JNINativeMethod park = {(char*)"park", (char*)"(ZJ)V", (void*)entry};
|
||||
if (env->RegisterNatives(_UnsafeClass, &park, 1) != 0) {
|
||||
env->ExceptionClear();
|
||||
}
|
||||
|
||||
env->ExceptionClear();
|
||||
}
|
||||
|
||||
@@ -22,13 +22,26 @@
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
typedef jint (JNICALL *RegisterNativesFunc)(JNIEnv*, jclass, const JNINativeMethod*, jint);
|
||||
typedef void (JNICALL *UnsafeParkFunc)(JNIEnv*, jobject, jboolean, jlong);
|
||||
|
||||
class LockTracer : public Engine {
|
||||
private:
|
||||
static double _ticks_to_nanos;
|
||||
static jlong _threshold;
|
||||
static jlong _start_time;
|
||||
static jclass _UnsafeClass;
|
||||
static jclass _LockSupport;
|
||||
static jmethodID _getBlocker;
|
||||
static bool _initialized;
|
||||
|
||||
static void initialize();
|
||||
|
||||
static RegisterNativesFunc _orig_RegisterNatives;
|
||||
static jint JNICALL RegisterNativesHook(JNIEnv* env, jclass cls, const JNINativeMethod* methods, jint nMethods);
|
||||
|
||||
static UnsafeParkFunc _orig_Unsafe_park;
|
||||
static void JNICALL UnsafeParkHook(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time);
|
||||
|
||||
static jobject getParkBlocker(jvmtiEnv* jvmti, JNIEnv* env);
|
||||
static char* getLockName(jvmtiEnv* jvmti, JNIEnv* env, jobject lock);
|
||||
@@ -38,24 +51,19 @@ class LockTracer : public Engine {
|
||||
static void bindUnsafePark(UnsafeParkFunc entry);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "lock";
|
||||
const char* title() {
|
||||
return "Lock profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static void JNICALL MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object);
|
||||
static void JNICALL MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object);
|
||||
static void JNICALL UnsafeParkTrap(JNIEnv* env, jobject instance, jboolean isAbsolute, jlong time);
|
||||
};
|
||||
|
||||
#endif // _LOCKTRACER_H
|
||||
|
||||
127
src/log.cpp
Normal file
127
src/log.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "log.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
const char* const Log::LEVEL_NAME[] = {
|
||||
"TRACE",
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARN",
|
||||
"ERROR",
|
||||
"NONE"
|
||||
};
|
||||
|
||||
FILE* Log::_file = stdout;
|
||||
LogLevel Log::_level = LOG_TRACE;
|
||||
|
||||
|
||||
void Log::open(Arguments& args) {
|
||||
open(args._log, args._loglevel);
|
||||
|
||||
if (args._unknown_arg != NULL) {
|
||||
warn("Unknown argument: %s", args._unknown_arg);
|
||||
}
|
||||
}
|
||||
|
||||
void Log::open(const char* file_name, const char* level) {
|
||||
if (_file != stdout && _file != stderr) {
|
||||
fclose(_file);
|
||||
}
|
||||
|
||||
if (file_name == NULL || strcmp(file_name, "stdout") == 0) {
|
||||
_file = stdout;
|
||||
} else if (strcmp(file_name, "stderr") == 0) {
|
||||
_file = stderr;
|
||||
} else if ((_file = fopen(file_name, "w")) == NULL) {
|
||||
_file = stdout;
|
||||
warn("Could not open log file: %s", file_name);
|
||||
}
|
||||
|
||||
LogLevel l = LOG_TRACE;
|
||||
if (level != NULL) {
|
||||
for (int i = LOG_TRACE; i <= LOG_NONE; i++) {
|
||||
if (strcasecmp(LEVEL_NAME[i], level) == 0) {
|
||||
l = (LogLevel)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
__atomic_store_n(&_level, l, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
void Log::close() {
|
||||
if (_file != stdout && _file != stderr) {
|
||||
fclose(_file);
|
||||
_file = stdout;
|
||||
}
|
||||
}
|
||||
|
||||
void Log::log(LogLevel level, const char* msg, va_list args) {
|
||||
char buf[1024];
|
||||
size_t len = vsnprintf(buf, sizeof(buf), msg, args);
|
||||
if (len >= sizeof(buf)) {
|
||||
len = sizeof(buf) - 1;
|
||||
buf[len] = 0;
|
||||
}
|
||||
|
||||
if (level < LOG_ERROR) {
|
||||
Profiler::instance()->writeLog(level, buf, len);
|
||||
}
|
||||
|
||||
if (level >= _level) {
|
||||
fprintf(_file, "[%s] %s\n", LEVEL_NAME[level], buf);
|
||||
fflush(_file);
|
||||
}
|
||||
}
|
||||
|
||||
void Log::trace(const char* msg, ...) {
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
log(LOG_TRACE, msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Log::debug(const char* msg, ...) {
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
log(LOG_DEBUG, msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Log::info(const char* msg, ...) {
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
log(LOG_INFO, msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Log::warn(const char* msg, ...) {
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
log(LOG_WARN, msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Log::error(const char* msg, ...) {
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
log(LOG_ERROR, msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
63
src/log.h
Normal file
63
src/log.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _LOG_H
|
||||
#define _LOG_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define ATTR_FORMAT __attribute__((format(printf, 1, 2)))
|
||||
#else
|
||||
#define ATTR_FORMAT
|
||||
#endif
|
||||
|
||||
|
||||
enum LogLevel {
|
||||
LOG_TRACE,
|
||||
LOG_DEBUG,
|
||||
LOG_INFO,
|
||||
LOG_WARN,
|
||||
LOG_ERROR,
|
||||
LOG_NONE
|
||||
};
|
||||
|
||||
|
||||
class Arguments;
|
||||
|
||||
class Log {
|
||||
private:
|
||||
static FILE* _file;
|
||||
static LogLevel _level;
|
||||
|
||||
public:
|
||||
static const char* const LEVEL_NAME[];
|
||||
|
||||
static void open(Arguments& args);
|
||||
static void open(const char* file_name, const char* level);
|
||||
static void close();
|
||||
|
||||
static void log(LogLevel level, const char* msg, va_list args);
|
||||
|
||||
static void ATTR_FORMAT trace(const char* msg, ...);
|
||||
static void ATTR_FORMAT debug(const char* msg, ...);
|
||||
static void ATTR_FORMAT info(const char* msg, ...);
|
||||
static void ATTR_FORMAT warn(const char* msg, ...);
|
||||
static void ATTR_FORMAT error(const char* msg, ...);
|
||||
};
|
||||
|
||||
#endif // _LOG_H
|
||||
@@ -31,3 +31,16 @@ void Mutex::lock() {
|
||||
void Mutex::unlock() {
|
||||
pthread_mutex_unlock(&_mutex);
|
||||
}
|
||||
|
||||
WaitableMutex::WaitableMutex() : Mutex() {
|
||||
pthread_cond_init(&_cond, NULL);
|
||||
}
|
||||
|
||||
bool WaitableMutex::waitUntil(u64 wall_time) {
|
||||
struct timespec ts = {(time_t)(wall_time / 1000000), (long)(wall_time % 1000000) * 1000};
|
||||
return pthread_cond_timedwait(&_cond, &_mutex, &ts) != 0;
|
||||
}
|
||||
|
||||
void WaitableMutex::notify() {
|
||||
pthread_cond_signal(&_cond);
|
||||
}
|
||||
|
||||
13
src/mutex.h
13
src/mutex.h
@@ -18,10 +18,11 @@
|
||||
#define _MUTEX_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
class Mutex {
|
||||
private:
|
||||
protected:
|
||||
pthread_mutex_t _mutex;
|
||||
|
||||
public:
|
||||
@@ -31,6 +32,16 @@ class Mutex {
|
||||
void unlock();
|
||||
};
|
||||
|
||||
class WaitableMutex : public Mutex {
|
||||
protected:
|
||||
pthread_cond_t _cond;
|
||||
|
||||
public:
|
||||
WaitableMutex();
|
||||
|
||||
bool waitUntil(u64 wall_time);
|
||||
void notify();
|
||||
};
|
||||
|
||||
class MutexLocker {
|
||||
private:
|
||||
|
||||
211
src/objectSampler.cpp
Normal file
211
src/objectSampler.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2022 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "objectSampler.h"
|
||||
#include "profiler.h"
|
||||
#include "tsc.h"
|
||||
|
||||
|
||||
u64 ObjectSampler::_interval;
|
||||
bool ObjectSampler::_live;
|
||||
volatile u64 ObjectSampler::_allocated_bytes;
|
||||
|
||||
|
||||
static u32 lookupClassId(jvmtiEnv* jvmti, jclass cls) {
|
||||
u32 class_id = 0;
|
||||
char* class_name;
|
||||
if (jvmti->GetClassSignature(cls, &class_name, NULL) == 0) {
|
||||
if (class_name[0] == 'L') {
|
||||
class_id = Profiler::instance()->classMap()->lookup(class_name + 1, strlen(class_name) - 2);
|
||||
} else {
|
||||
class_id = Profiler::instance()->classMap()->lookup(class_name);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
}
|
||||
return class_id;
|
||||
}
|
||||
|
||||
|
||||
class LiveRefs {
|
||||
private:
|
||||
enum { MAX_REFS = 1024 };
|
||||
|
||||
SpinLock _lock;
|
||||
jweak _refs[MAX_REFS];
|
||||
struct {
|
||||
jlong size;
|
||||
u64 trace;
|
||||
u64 time;
|
||||
} _values[MAX_REFS];
|
||||
bool _full;
|
||||
|
||||
static inline bool collected(jweak w) {
|
||||
return *(void**)((uintptr_t)w & ~(uintptr_t)1) == NULL;
|
||||
}
|
||||
|
||||
public:
|
||||
LiveRefs() : _lock(1) {
|
||||
}
|
||||
|
||||
void init() {
|
||||
memset(_refs, 0, sizeof(_refs));
|
||||
memset(_values, 0, sizeof(_values));
|
||||
_full = false;
|
||||
_lock.unlock();
|
||||
}
|
||||
|
||||
void gc() {
|
||||
_full = false;
|
||||
}
|
||||
|
||||
void add(JNIEnv* jni, jobject object, jlong size, u64 trace) {
|
||||
if (_full) {
|
||||
return;
|
||||
}
|
||||
|
||||
jweak wobject = jni->NewWeakGlobalRef(object);
|
||||
if (wobject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lock.tryLock()) {
|
||||
u32 start = (((uintptr_t)object >> 4) * 31 + ((uintptr_t)jni >> 4) + trace) & (MAX_REFS - 1);
|
||||
u32 i = start;
|
||||
do {
|
||||
jweak w = _refs[i];
|
||||
if (w == NULL || collected(w)) {
|
||||
if (w != NULL) jni->DeleteWeakGlobalRef(w);
|
||||
_refs[i] = wobject;
|
||||
_values[i].size = size;
|
||||
_values[i].trace = trace;
|
||||
_values[i].time = TSC::ticks();
|
||||
_lock.unlock();
|
||||
return;
|
||||
}
|
||||
} while ((i = (i + 1) & (MAX_REFS - 1)) != start);
|
||||
|
||||
_full = true;
|
||||
_lock.unlock();
|
||||
}
|
||||
|
||||
jni->DeleteWeakGlobalRef(wobject);
|
||||
}
|
||||
|
||||
void dump(JNIEnv* jni) {
|
||||
_lock.lock();
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
Profiler* profiler = Profiler::instance();
|
||||
|
||||
for (u32 i = 0; i < MAX_REFS; i++) {
|
||||
if ((i % 32) == 0) jni->PushLocalFrame(64);
|
||||
|
||||
jweak w = _refs[i];
|
||||
if (w != NULL) {
|
||||
jobject obj = jni->NewLocalRef(w);
|
||||
if (obj != NULL) {
|
||||
LiveObject event;
|
||||
event._alloc_size = _values[i].size;
|
||||
event._alloc_time = _values[i].time;
|
||||
event._class_id = lookupClassId(jvmti, jni->GetObjectClass(obj));
|
||||
|
||||
int tid = _values[i].trace >> 32;
|
||||
u32 call_trace_id = (u32)_values[i].trace;
|
||||
profiler->recordExternalSample(event._alloc_size, tid, BCI_LIVE_OBJECT, &event, call_trace_id);
|
||||
}
|
||||
jni->DeleteWeakGlobalRef(w);
|
||||
}
|
||||
|
||||
if ((i % 32) == 31 || i == MAX_REFS - 1) jni->PopLocalFrame(NULL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static LiveRefs live_refs;
|
||||
|
||||
|
||||
void ObjectSampler::SampledObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
if (_enabled) {
|
||||
recordAllocation(jvmti, jni, BCI_ALLOC, object, object_klass, size);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectSampler::GarbageCollectionStart(jvmtiEnv* jvmti) {
|
||||
live_refs.gc();
|
||||
}
|
||||
|
||||
void ObjectSampler::recordAllocation(jvmtiEnv* jvmti, JNIEnv* jni, int event_type,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
AllocEvent event;
|
||||
event._total_size = size > _interval ? size : _interval;
|
||||
event._instance_size = size;
|
||||
event._class_id = lookupClassId(jvmti, object_klass);
|
||||
|
||||
if (_live) {
|
||||
u64 trace = Profiler::instance()->recordSample(NULL, 0, event_type, &event);
|
||||
live_refs.add(jni, object, size, trace);
|
||||
} else {
|
||||
Profiler::instance()->recordSample(NULL, size, event_type, &event);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectSampler::initLiveRefs(bool live) {
|
||||
_live = live;
|
||||
if (_live) {
|
||||
live_refs.init();
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectSampler::dumpLiveRefs() {
|
||||
if (_live) {
|
||||
live_refs.dump(VM::jni());
|
||||
}
|
||||
}
|
||||
|
||||
Error ObjectSampler::check(Arguments& args) {
|
||||
if (!VM::canSampleObjects()) {
|
||||
return Error("SampledObjectAlloc is not supported on this JVM");
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error ObjectSampler::start(Arguments& args) {
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._alloc > 0 ? args._alloc : DEFAULT_ALLOC_INTERVAL;
|
||||
|
||||
initLiveRefs(args._live);
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetHeapSamplingInterval(_interval);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void ObjectSampler::stop() {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_GARBAGE_COLLECTION_START, NULL);
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL);
|
||||
|
||||
dumpLiveRefs();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user