mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56ca88677b | ||
|
|
8a258d31a5 | ||
|
|
e33a01e0c6 | ||
|
|
5d5138ea61 | ||
|
|
10b5ad0ee5 | ||
|
|
b5258cca2c | ||
|
|
0718a09e0e | ||
|
|
fe173c4101 | ||
|
|
8032daa49d | ||
|
|
fa8b8f8072 | ||
|
|
85f3a68c56 | ||
|
|
60ce15569a | ||
|
|
9838ddb693 | ||
|
|
2f341043ef | ||
|
|
ee55fbe17b | ||
|
|
d7a2a4fc8b | ||
|
|
e9b7747015 | ||
|
|
b96e07b001 | ||
|
|
ba00ca26c1 | ||
|
|
9ed175d73e | ||
|
|
b287816559 | ||
|
|
9a979a712d | ||
|
|
42442ed593 | ||
|
|
432d622aa4 | ||
|
|
f477f8d4c0 | ||
|
|
456ff57115 | ||
|
|
5ec28a86b2 | ||
|
|
e7cd6ee6fb | ||
|
|
8ba8fc748c | ||
|
|
5f37bf3ad6 | ||
|
|
26e9c7aef2 | ||
|
|
8efba10acc | ||
|
|
e894420119 | ||
|
|
2ddd4d230c | ||
|
|
1398e7ef75 | ||
|
|
3456dd3d90 | ||
|
|
c0d45fecec | ||
|
|
7e5f8a03f3 | ||
|
|
ce91abe6d9 | ||
|
|
4ba7524d7c | ||
|
|
734ef03ebf | ||
|
|
cf17e5efc3 | ||
|
|
35b420a941 | ||
|
|
ee4cd8e2b6 | ||
|
|
6f3134e99f | ||
|
|
c537b8298d | ||
|
|
605550cf96 | ||
|
|
a57bbf3587 | ||
|
|
9131344d61 | ||
|
|
2d0b9c9921 | ||
|
|
30905fda4c | ||
|
|
5a11a71db9 | ||
|
|
d79a82935f | ||
|
|
995048c2fd | ||
|
|
7331e30ed5 | ||
|
|
1f5e4ca8aa | ||
|
|
7ebed4e8e1 | ||
|
|
170451990b | ||
|
|
11a1d6d308 | ||
|
|
955413db8d | ||
|
|
8a701b41e3 | ||
|
|
dccd4c326a | ||
|
|
8771888d28 | ||
|
|
7d25210d2c | ||
|
|
9087bc57d8 | ||
|
|
3e1e1c614a | ||
|
|
b80d163699 | ||
|
|
9705b66864 | ||
|
|
3c33c6aa47 | ||
|
|
4af6b65268 | ||
|
|
03c7b36bca | ||
|
|
f8b15526b1 | ||
|
|
e519fd84c4 | ||
|
|
79e1017088 | ||
|
|
edbb9e7c03 | ||
|
|
7eb15cfcf0 | ||
|
|
eafbbaea8b | ||
|
|
d8228a1fec | ||
|
|
8a447481f8 | ||
|
|
791077354e | ||
|
|
e071b9fa14 | ||
|
|
cc340923b9 | ||
|
|
9c333219b5 | ||
|
|
9acf8c1648 | ||
|
|
5570afed9d | ||
|
|
975a506d83 | ||
|
|
09fb14bd87 | ||
|
|
3256d824de | ||
|
|
cc98710a6f | ||
|
|
452f14c3d2 | ||
|
|
be8bba1900 | ||
|
|
65b5356ace | ||
|
|
e91363c05a | ||
|
|
3e47bf7551 | ||
|
|
9447068af3 | ||
|
|
7e750825da | ||
|
|
eda5779552 | ||
|
|
fc3b1ca84f | ||
|
|
d13de48c0a | ||
|
|
552c699687 | ||
|
|
0fcc4d9bac | ||
|
|
868bfec2a5 | ||
|
|
4a77d68bcb | ||
|
|
a38a375dc6 | ||
|
|
6bcd23fcf0 | ||
|
|
8d2847a032 | ||
|
|
e0998af713 | ||
|
|
01b3e6c517 | ||
|
|
def6eb4b1c | ||
|
|
4032c56caf | ||
|
|
9b789f6516 | ||
|
|
6ddaf9ab71 | ||
|
|
11131499ab | ||
|
|
d2abac1c30 | ||
|
|
b7e9079b52 | ||
|
|
ff49ccccb7 | ||
|
|
7dd075cca6 | ||
|
|
bd8078bc11 | ||
|
|
1622fe5d72 | ||
|
|
44d7941728 | ||
|
|
d23b40048b | ||
|
|
3b2db709ff | ||
|
|
096fc88c82 | ||
|
|
4b0303916d | ||
|
|
9fb2ca800a | ||
|
|
d917cfdb63 | ||
|
|
f2006f3da1 | ||
|
|
c30b22f204 | ||
|
|
f48ebcc72b | ||
|
|
339aee5cfc | ||
|
|
cde3fae978 | ||
|
|
91eab91634 | ||
|
|
3df00e3439 | ||
|
|
c66ac2cfd0 | ||
|
|
f236482228 | ||
|
|
3ff315ea8f | ||
|
|
308074a9eb | ||
|
|
685da8d84f | ||
|
|
bd7bf9726e | ||
|
|
034677435d | ||
|
|
89f7d34456 | ||
|
|
ec8a40431a | ||
|
|
81bc1f2df2 | ||
|
|
40ff09a14f | ||
|
|
d6de541799 | ||
|
|
b807987f1d | ||
|
|
02875138f1 | ||
|
|
d1c19d1904 | ||
|
|
cc2307b92c | ||
|
|
b5d89fef29 | ||
|
|
646a92e2a0 | ||
|
|
bcd2375f39 | ||
|
|
3cbe6aec2f | ||
|
|
2d51c07b23 | ||
|
|
32ead969c1 | ||
|
|
de55fadbba | ||
|
|
da0ac08c64 | ||
|
|
5dd9e86a1d | ||
|
|
81583b9af3 | ||
|
|
7ec5c195e7 | ||
|
|
cb0f1eb72d | ||
|
|
051424890a | ||
|
|
34daf4f540 |
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,6 +1,6 @@
|
||||
language: cpp
|
||||
|
||||
dist: precise
|
||||
dist: bionic
|
||||
|
||||
sudo: required
|
||||
|
||||
|
||||
97
CHANGELOG.md
97
CHANGELOG.md
@@ -1,21 +1,112 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0-b1] - Early access
|
||||
## [2.8] - Early Access
|
||||
|
||||
### 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
|
||||
|
||||
92
Makefile
92
Makefile
@@ -1,6 +1,4 @@
|
||||
PROFILER_VERSION=2.0-b1
|
||||
JATTACH_VERSION=1.5
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
PROFILER_VERSION=2.8-ea
|
||||
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
@@ -11,16 +9,19 @@ 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
|
||||
LIBS=-ldl -lpthread
|
||||
MERGE=true
|
||||
|
||||
JAVAC=$(JAVA_HOME)/bin/javac
|
||||
JAR=$(JAVA_HOME)/bin/jar
|
||||
JAVAC_RELEASE_VERSION=7
|
||||
|
||||
SOURCES := $(wildcard src/*.cpp)
|
||||
HEADERS := $(wildcard src/*.h)
|
||||
HEADERS := $(wildcard src/*.h src/fdtransfer/*.h)
|
||||
JAVA_HEADERS := $(patsubst %.java,%.class.h,$(wildcard src/helper/one/profiler/*.java))
|
||||
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
|
||||
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
|
||||
|
||||
@@ -29,15 +30,29 @@ ifeq ($(JAVA_HOME),)
|
||||
endif
|
||||
|
||||
OS:=$(shell uname -s)
|
||||
ifeq ($(OS), Darwin)
|
||||
ifeq ($(OS),Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
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)
|
||||
endif
|
||||
else
|
||||
CXXFLAGS += -Wl,-z,defs -Wl,-z,nodelete
|
||||
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
|
||||
else
|
||||
@@ -50,62 +65,97 @@ 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)
|
||||
CXXFLAGS += -momit-leaf-frame-pointer
|
||||
endif
|
||||
|
||||
|
||||
.PHONY: all release test clean
|
||||
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) $(FDTRANSFER_BIN) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
|
||||
release: build $(PACKAGE_NAME).tar.gz
|
||||
release: build $(PACKAGE_NAME).$(PACKAGE_EXT)
|
||||
|
||||
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
profiler.sh LICENSE *.md
|
||||
$(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) $(JAVA_HEADERS)
|
||||
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 .
|
||||
$(JAR) cf $@ -C build/api .
|
||||
$(RM) -r build/api
|
||||
|
||||
build/$(CONVERTER_JAR): $(CONVERTER_SOURCES) src/converter/MANIFEST.MF
|
||||
mkdir -p build/converter
|
||||
$(JAVAC) -source 7 -target 7 -d build/converter $(CONVERTER_SOURCES)
|
||||
$(JAR) cvfm $@ src/converter/MANIFEST.MF -C build/converter .
|
||||
$(JAR) cfm $@ src/converter/MANIFEST.MF -C build/converter .
|
||||
$(RM) -r build/converter
|
||||
|
||||
%.class.h: %.class
|
||||
hexdump -v -e '1/1 "%u,"' $^ > $@
|
||||
|
||||
%.class: %.java
|
||||
$(JAVAC) -g:none -source $(JAVAC_RELEASE_VERSION) -target $(JAVAC_RELEASE_VERSION) $(*D)/*.java
|
||||
|
||||
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"
|
||||
|
||||
clean:
|
||||
|
||||
613
README.md
613
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,616 @@ 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.7):
|
||||
|
||||
- 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.7-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.7/async-profiler-2.7-linux-x64.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-2.7-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.7/async-profiler-2.7-linux-musl-x64.tar.gz)
|
||||
- Linux arm64: [async-profiler-2.7-linux-arm64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.7/async-profiler-2.7-linux-arm64.tar.gz)
|
||||
- macOS x64/arm64: [async-profiler-2.7-macos.zip](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.7/async-profiler-2.7-macos.zip)
|
||||
- Converters between profile formats: [converter.jar](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.7/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-profiler.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
|
||||
|
||||
## 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
|
||||
|
||||
The allocation profiler requires HotSpot debug symbols. Oracle JDK already has them
|
||||
embedded in `libjvm.so`, but in OpenJDK builds they are typically shipped
|
||||
in a separate package. For example, to install OpenJDK debug symbols on
|
||||
Debian / Ubuntu, run:
|
||||
```
|
||||
# apt 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.
|
||||
|
||||
## 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.7/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.
|
||||
|
||||
* `list` - show the list of available profiling events. This option still
|
||||
requires PID, since supported events may differ depending on JVM version.
|
||||
|
||||
* `-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).
|
||||
|
||||
* `--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]` and inlined methods with `_[i]`.
|
||||
|
||||
* `-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;
|
||||
`%{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 |
877
demo/flamegraph.html
Normal file
877
demo/flamegraph.html
Normal file
@@ -0,0 +1,877 @@
|
||||
<!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) {
|
||||
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 = '';
|
||||
}
|
||||
|
||||
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 |
2
pom.xml
2
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.8-ea</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
|
||||
136
profiler.sh
136
profiler.sh
@@ -7,6 +7,7 @@ usage() {
|
||||
echo " start start profiling and return immediately"
|
||||
echo " resume resume profiling without resetting collected data"
|
||||
echo " stop stop profiling"
|
||||
echo " dump dump collected data without stopping profiling session"
|
||||
echo " check check if the specified profiling event is available"
|
||||
echo " status print profiling status"
|
||||
echo " list list profiling events supported by the target JVM"
|
||||
@@ -21,8 +22,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"
|
||||
@@ -31,17 +33,25 @@ 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 " --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 " --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"
|
||||
@@ -54,10 +64,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
|
||||
@@ -65,34 +90,48 @@ check_if_terminated() {
|
||||
fi
|
||||
}
|
||||
|
||||
fdtransfer() {
|
||||
if [ "$USE_FDTRANSFER" = "true" ]; then
|
||||
"$FDTRANSFER" "$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"
|
||||
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
|
||||
ACTION="collect"
|
||||
EVENT="cpu"
|
||||
DURATION="60"
|
||||
FILE=""
|
||||
USE_TMP="true"
|
||||
@@ -106,14 +145,14 @@ while [ $# -gt 0 ]; do
|
||||
-h|"-?")
|
||||
usage
|
||||
;;
|
||||
start|resume|stop|check|status|list|collect)
|
||||
start|resume|stop|dump|check|status|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
-e)
|
||||
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
|
||||
PARAMS="$PARAMS,event=$2"
|
||||
shift
|
||||
;;
|
||||
-d)
|
||||
@@ -145,6 +184,9 @@ while [ $# -gt 0 ]; do
|
||||
-a)
|
||||
FORMAT="$FORMAT,ann"
|
||||
;;
|
||||
-l)
|
||||
FORMAT="$FORMAT,lib"
|
||||
;;
|
||||
-o)
|
||||
OUTPUT="$2"
|
||||
shift
|
||||
@@ -175,12 +217,26 @@ 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"
|
||||
;;
|
||||
--cstack|--call-graph)
|
||||
PARAMS="$PARAMS,cstack=$2"
|
||||
shift
|
||||
@@ -189,6 +245,18 @@ 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
|
||||
;;
|
||||
--fdtransfer)
|
||||
PARAMS="$PARAMS,fdtransfer"
|
||||
USE_FDTRANSFER="true"
|
||||
;;
|
||||
--safe-mode)
|
||||
PARAMS="$PARAMS,safemode=$2"
|
||||
shift
|
||||
@@ -243,27 +311,45 @@ 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|list)
|
||||
jattach "$ACTION,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)
|
||||
|
||||
@@ -15,22 +15,21 @@
|
||||
*/
|
||||
|
||||
#include "allocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
int AllocTracer::_trap_kind;
|
||||
Trap AllocTracer::_in_new_tlab;
|
||||
Trap AllocTracer::_outside_tlab;
|
||||
Trap AllocTracer::_in_new_tlab(0);
|
||||
Trap AllocTracer::_outside_tlab(1);
|
||||
|
||||
u64 AllocTracer::_interval;
|
||||
volatile u64 AllocTracer::_allocated_bytes;
|
||||
|
||||
|
||||
// Called whenever our breakpoint trap is hit
|
||||
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
void AllocTracer::trapHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
int event_type;
|
||||
uintptr_t total_size;
|
||||
@@ -51,6 +50,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 +58,13 @@ void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
uintptr_t klass = frame.arg0();
|
||||
frame.ret();
|
||||
|
||||
if (_enabled) {
|
||||
// TODO: _enabled also uses traps
|
||||
if (_enabled && updateCounter(_allocated_bytes, total_size, _interval)) {
|
||||
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
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,10 +72,10 @@ 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) {
|
||||
@@ -101,7 +83,7 @@ Error AllocTracer::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
NativeCodeCache* libjvm = VMStructs::libjvm();
|
||||
CodeCache* libjvm = VMStructs::libjvm();
|
||||
const void* ne;
|
||||
const void* oe;
|
||||
|
||||
@@ -118,9 +100,9 @@ Error AllocTracer::check(Arguments& args) {
|
||||
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
|
||||
}
|
||||
|
||||
if (!_in_new_tlab.assign(ne) || !_outside_tlab.assign(oe)) {
|
||||
return Error("Unable to install allocation trap");
|
||||
}
|
||||
_in_new_tlab.assign(ne);
|
||||
_outside_tlab.assign(oe);
|
||||
_in_new_tlab.pair(_outside_tlab);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
@@ -131,13 +113,12 @@ Error AllocTracer::start(Arguments& args) {
|
||||
return error;
|
||||
}
|
||||
|
||||
_interval = args._interval;
|
||||
_interval = args._alloc > 0 ? args._alloc : 0;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
OS::installSignalHandler(SIGTRAP, signalHandler);
|
||||
|
||||
_in_new_tlab.install();
|
||||
_outside_tlab.install();
|
||||
if (!_in_new_tlab.install() || !_outside_tlab.install()) {
|
||||
return Error("Cannot install allocation breakpoints");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
@@ -32,27 +32,23 @@ class AllocTracer : public Engine {
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
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
|
||||
|
||||
@@ -58,6 +58,9 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
*/
|
||||
@Override
|
||||
public void start(String event, long interval) throws IllegalStateException {
|
||||
if (event == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
start0(event, interval, true);
|
||||
}
|
||||
|
||||
@@ -71,6 +74,9 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
*/
|
||||
@Override
|
||||
public void resume(String event, long interval) throws IllegalStateException {
|
||||
if (event == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
start0(event, interval, false);
|
||||
}
|
||||
|
||||
@@ -116,7 +122,10 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
* @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 +138,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 +168,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 +195,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
|
||||
@@ -186,6 +210,6 @@ 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,42 @@ static inline int atomicInc(volatile int& var, int increment = 1) {
|
||||
return __sync_fetch_and_add(&var, increment);
|
||||
}
|
||||
|
||||
static inline u64 loadAcquire(u64& var) {
|
||||
return __atomic_load_n(&var, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
static inline void storeRelease(u64& var, u64 value) {
|
||||
return __atomic_store_n(&var, value, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
|
||||
typedef unsigned char instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xcc;
|
||||
const int BREAKPOINT_OFFSET = 0;
|
||||
|
||||
const bool CAN_MOVE_SP = true;
|
||||
const int SYSCALL_SIZE = 2;
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
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 bool CAN_MOVE_SP = true;
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
const int PLT_HEADER_SIZE = 20;
|
||||
const int PLT_ENTRY_SIZE = 12;
|
||||
const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
|
||||
@@ -65,16 +79,42 @@ 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 bool CAN_MOVE_SP = true;
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 1;
|
||||
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;
|
||||
|
||||
// The sp may not be moved on ppc. There is a valid back link to the previous frame at all times.
|
||||
// The callee stores the return address in the caller's frame before it constructs its own frame
|
||||
// with one atomic operation.
|
||||
const bool CAN_MOVE_SP = false;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int FRAME_PC_SLOT = 2;
|
||||
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,80 @@ 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)
|
||||
// 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
|
||||
// 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
|
||||
// include=PATTERN - include stack traces containing PATTERN
|
||||
// exclude=PATTERN - exclude stack traces containing PATTERN
|
||||
// begin=FUNCTION - begin profiling when FUNCTION is executed
|
||||
// end=FUNCTION - end profiling when FUNCTION is executed
|
||||
// title=TITLE - FlameGraph title
|
||||
// minwidth=PCT - FlameGraph minimum frame width in percent
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree
|
||||
//
|
||||
// It is possible to specify multiple dump options at the same time
|
||||
|
||||
@@ -94,13 +114,15 @@ Error Arguments::parse(const char* args) {
|
||||
|
||||
size_t len = strlen(args);
|
||||
free(_buf);
|
||||
_buf = (char*)malloc(len + EXTRA_BUF_SIZE);
|
||||
_buf = (char*)malloc(len + EXTRA_BUF_SIZE + 1);
|
||||
if (_buf == NULL) {
|
||||
return Error("Not enough memory to parse arguments");
|
||||
}
|
||||
strcpy(_buf, args);
|
||||
char* args_copy = strcpy(_buf + EXTRA_BUF_SIZE, args);
|
||||
|
||||
for (char* arg = strtok(_buf, ","); arg != NULL; arg = strtok(NULL, ",")) {
|
||||
const char* msg = NULL;
|
||||
|
||||
for (char* arg = strtok(args_copy, ","); arg != NULL; arg = strtok(NULL, ",")) {
|
||||
char* value = strchr(arg, '=');
|
||||
if (value != NULL) *value++ = 0;
|
||||
|
||||
@@ -115,6 +137,9 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("stop")
|
||||
_action = ACTION_STOP;
|
||||
|
||||
CASE("dump")
|
||||
_action = ACTION_DUMP;
|
||||
|
||||
CASE("check")
|
||||
_action = ACTION_CHECK;
|
||||
|
||||
@@ -128,54 +153,125 @@ 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);
|
||||
if (_alloc < 0) {
|
||||
msg = "alloc must be >= 0";
|
||||
}
|
||||
|
||||
CASE("lock")
|
||||
_lock = value == NULL ? 0 : parseUnits(value, NANOS);
|
||||
if (_lock < 0) {
|
||||
msg = "lock must be >= 0";
|
||||
}
|
||||
|
||||
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;
|
||||
_fdtransfer_path = value;
|
||||
|
||||
// Filters
|
||||
CASE("filter")
|
||||
_filter = value == NULL ? "" : value;
|
||||
@@ -189,6 +285,9 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("threads")
|
||||
_threads = true;
|
||||
|
||||
CASE("sched")
|
||||
_sched = true;
|
||||
|
||||
CASE("allkernel")
|
||||
_ring = RING_KERNEL;
|
||||
|
||||
@@ -199,6 +298,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 +320,9 @@ Error Arguments::parse(const char* args) {
|
||||
CASE("ann")
|
||||
_style |= STYLE_ANNOTATE;
|
||||
|
||||
CASE("lib")
|
||||
_style |= STYLE_LIB_NAMES;
|
||||
|
||||
CASE("begin")
|
||||
_begin = value;
|
||||
|
||||
@@ -227,45 +331,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(_buf, EXTRA_BUF_SIZE - 1, _file);
|
||||
}
|
||||
return true;
|
||||
return _file;
|
||||
}
|
||||
|
||||
// The linked list of string offsets is embedded right into _buf array
|
||||
@@ -306,6 +414,19 @@ 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 == '{') {
|
||||
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;
|
||||
@@ -324,31 +445,46 @@ 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#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";
|
||||
@@ -34,12 +35,12 @@ enum Action {
|
||||
ACTION_START,
|
||||
ACTION_RESUME,
|
||||
ACTION_STOP,
|
||||
ACTION_DUMP,
|
||||
ACTION_CHECK,
|
||||
ACTION_STATUS,
|
||||
ACTION_LIST,
|
||||
ACTION_VERSION,
|
||||
ACTION_FULL_VERSION,
|
||||
ACTION_DUMP
|
||||
ACTION_FULL_VERSION
|
||||
};
|
||||
|
||||
enum Counter {
|
||||
@@ -53,35 +54,47 @@ 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_ANNOTATE = 8,
|
||||
STYLE_LIB_NAMES = 16
|
||||
};
|
||||
|
||||
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:
|
||||
@@ -107,31 +120,48 @@ class Arguments {
|
||||
private:
|
||||
char* _buf;
|
||||
bool _shared;
|
||||
bool _persistent;
|
||||
|
||||
void appendToEmbeddedList(int& list, char* value);
|
||||
|
||||
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;
|
||||
bool _loop;
|
||||
bool _threads;
|
||||
bool _sched;
|
||||
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;
|
||||
const char* _begin;
|
||||
const char* _end;
|
||||
@@ -140,28 +170,45 @@ 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),
|
||||
_loop(false),
|
||||
_threads(false),
|
||||
_sched(false),
|
||||
_fdtransfer(false),
|
||||
_fdtransfer_path(NULL),
|
||||
_style(0),
|
||||
_cstack(CSTACK_DEFAULT),
|
||||
_output(OUTPUT_NONE),
|
||||
_chunk_size(100 * 1024 * 1024),
|
||||
_chunk_time(3600),
|
||||
_jfr_sync(NULL),
|
||||
_jfr_options(0),
|
||||
_dump_traces(0),
|
||||
_dump_flat(0),
|
||||
_begin(NULL),
|
||||
_end(NULL),
|
||||
_title("Flame Graph"),
|
||||
_title(NULL),
|
||||
_minwidth(0),
|
||||
_reverse(false) {
|
||||
}
|
||||
@@ -172,7 +219,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;
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "callTraceStorage.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
static const u32 INITIAL_CAPACITY = 65536;
|
||||
static const u32 CALL_TRACE_CHUNK = 8 * 1024 * 1024;
|
||||
static const size_t PAGE_ALIGNMENT = sysconf(_SC_PAGESIZE) - 1;
|
||||
static const u32 OVERFLOW_TRACE_ID = 0x7fffffff;
|
||||
|
||||
|
||||
class LongHashTable {
|
||||
@@ -36,7 +35,7 @@ class LongHashTable {
|
||||
|
||||
static size_t getSize(u32 capacity) {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
|
||||
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
|
||||
return (size + OS::page_mask) & ~OS::page_mask;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -87,8 +86,11 @@ class LongHashTable {
|
||||
};
|
||||
|
||||
|
||||
CallTrace CallTraceStorage::_overflow_trace = {1, {BCI_ERROR, (jmethodID)"storage_overflow"}};
|
||||
|
||||
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
|
||||
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
|
||||
_overflow = 0;
|
||||
}
|
||||
|
||||
CallTraceStorage::~CallTraceStorage() {
|
||||
@@ -103,6 +105,7 @@ void CallTraceStorage::clear() {
|
||||
}
|
||||
_current_table->clear();
|
||||
_allocator.clear();
|
||||
_overflow = 0;
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
@@ -112,11 +115,20 @@ void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
|
||||
if (keys[slot] != 0 && loadAcquire(values[slot].samples) != 0) {
|
||||
// Reset samples to avoid duplication of call traces between JFR chunks
|
||||
values[slot].samples = 0;
|
||||
CallTrace* trace = values[slot].acquireTrace();
|
||||
if (trace != NULL) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_overflow > 0) {
|
||||
map[OVERFLOW_TRACE_ID] = &_overflow_trace;
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
@@ -133,6 +145,20 @@ void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::map<u64, CallTraceSample>& map) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0 && values[slot].acquireTrace() != NULL) {
|
||||
map[keys[slot]] += values[slot];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptation of MurmurHash64A by Austin Appleby
|
||||
u64 CallTraceStorage::calcHash(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const u64 M = 0xc6a4a7935bd1e995ULL;
|
||||
@@ -225,19 +251,19 @@ u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter)
|
||||
if (trace == NULL) {
|
||||
trace = storeCallTrace(num_frames, frames);
|
||||
}
|
||||
table->values()[slot].trace = trace;
|
||||
table->values()[slot].setTrace(trace);
|
||||
break;
|
||||
}
|
||||
|
||||
if (++step >= capacity) {
|
||||
// Very unlikely case of a table overflow
|
||||
return 0;
|
||||
atomicInc(_overflow);
|
||||
return OVERFLOW_TRACE_ID;
|
||||
}
|
||||
// Improved version of linear probing
|
||||
slot = (slot + step) & (capacity - 1);
|
||||
}
|
||||
|
||||
// TODO: check overhead
|
||||
CallTraceSample& s = table->values()[slot];
|
||||
atomicInc(s.samples);
|
||||
atomicInc(s.counter, counter);
|
||||
|
||||
@@ -35,12 +35,34 @@ struct CallTraceSample {
|
||||
CallTrace* trace;
|
||||
u64 samples;
|
||||
u64 counter;
|
||||
|
||||
CallTrace* acquireTrace() {
|
||||
return __atomic_load_n(&trace, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
void setTrace(CallTrace* value) {
|
||||
return __atomic_store_n(&trace, value, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
CallTraceSample& operator+=(const CallTraceSample& s) {
|
||||
trace = s.trace;
|
||||
samples += s.samples;
|
||||
counter += s.counter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator<(const CallTraceSample& other) const {
|
||||
return counter > other.counter;
|
||||
}
|
||||
};
|
||||
|
||||
class CallTraceStorage {
|
||||
private:
|
||||
static CallTrace _overflow_trace;
|
||||
|
||||
LinearAllocator _allocator;
|
||||
LongHashTable* _current_table;
|
||||
u64 _overflow;
|
||||
|
||||
u64 calcHash(int num_frames, ASGCT_CallFrame* frames);
|
||||
CallTrace* storeCallTrace(int num_frames, ASGCT_CallFrame* frames);
|
||||
@@ -53,6 +75,7 @@ class CallTraceStorage {
|
||||
void clear();
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -17,26 +17,66 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "codeCache.h"
|
||||
#include "dwarf.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));
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
_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 +84,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 +106,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 +135,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 +157,54 @@ 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;
|
||||
}
|
||||
|
||||
void CodeCache::setGlobalOffsetTable(void** table, unsigned int count) {
|
||||
_got_start = table;
|
||||
_got_end = table + count;
|
||||
}
|
||||
|
||||
void** CodeCache::findGlobalOffsetEntry(void* address) {
|
||||
for (void** entry = _got_start; entry < _got_end; entry++) {
|
||||
if (*entry == address) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
147
src/codeCache.h
147
src/codeCache.h
@@ -23,14 +23,46 @@
|
||||
#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 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 +80,105 @@ 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
const void* findSymbolByPrefix(const char* prefix);
|
||||
const void* findSymbolByPrefix(const char* prefix, int prefix_len);
|
||||
|
||||
void setGlobalOffsetTable(void** table, unsigned int count);
|
||||
void** findGlobalOffsetEntry(void* address);
|
||||
|
||||
void setDwarfTable(FrameDesc* table, int length);
|
||||
FrameDesc* findFrameDesc(const void* pc);
|
||||
};
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -107,13 +107,15 @@ public class FlameGraph {
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
mintotal = (long) (root.total * minwidth / 100);
|
||||
int depth = mintotal > 1 ? root.depth(mintotal) : this.depth + 1;
|
||||
|
||||
out.print(applyReplacements(HEADER,
|
||||
"{title}", title,
|
||||
"{height}", (depth + 1) * 16,
|
||||
"{depth}", depth + 1,
|
||||
"{height}", Math.min(depth * 16, 32767),
|
||||
"{depth}", depth,
|
||||
"{reverse}", reverse));
|
||||
|
||||
mintotal = (long) (root.total * minwidth / 100);
|
||||
printFrame(out, "all", root, 0, 0);
|
||||
|
||||
out.print(FOOTER);
|
||||
@@ -143,6 +145,9 @@ public class FlameGraph {
|
||||
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("\\", "\\\\");
|
||||
}
|
||||
if (title.indexOf('\'') >= 0) {
|
||||
title = title.replace("'", "\\'");
|
||||
}
|
||||
@@ -169,17 +174,20 @@ public class FlameGraph {
|
||||
|
||||
private int frameType(String title) {
|
||||
if (title.endsWith("_[j]")) {
|
||||
return 0;
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
return 1;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
return 2;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
return 5;
|
||||
} else if (title.endsWith("_[1]")) {
|
||||
return 6;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
return 3;
|
||||
} else if (title.indexOf('/') > 0 || title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
return 4;
|
||||
} else if (title.indexOf('/') > 0 && title.charAt(0) != '['
|
||||
|| title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
return 0;
|
||||
} else {
|
||||
return 4;
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +219,18 @@ public class FlameGraph {
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
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" +
|
||||
@@ -264,11 +284,13 @@ public class FlameGraph {
|
||||
"\tc.font = document.body.style.font;\n" +
|
||||
"\n" +
|
||||
"\tconst palette = [\n" +
|
||||
"\t\t[0xb2e1b2, 20, 20, 20],\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[0x50cccc, 30, 30, 30],\n" +
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n" +
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n" +
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n" +
|
||||
"\t\t[0xcce880, 20, 20, 20],\n" +
|
||||
"\t];\n" +
|
||||
"\n" +
|
||||
"\tfunction getColor(p) {\n" +
|
||||
@@ -343,12 +365,12 @@ public class FlameGraph {
|
||||
"\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\tObject.keys(marked).sort(function(a, b) { return a - b; }).forEach(function(x) {\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\t});\n" +
|
||||
"\t\t\treturn total;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
|
||||
@@ -19,15 +19,26 @@ import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
import one.jfr.event.AllocationSample;
|
||||
import one.jfr.event.ContendedLock;
|
||||
import one.jfr.event.Event;
|
||||
import one.jfr.event.EventAggregator;
|
||||
import one.jfr.event.ExecutionSample;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
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 = {"", "_[j]", "_[i]", "", "", "_[k]", "_[1]"};
|
||||
private static final byte NATIVE_TYPE_MIN = 3;
|
||||
private static final byte NATIVE_TYPE_MAX = 5;
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final Dictionary<String> methodNames = new Dictionary<>();
|
||||
@@ -36,55 +47,186 @@ public class jfr2flame {
|
||||
this.jfr = jfr;
|
||||
}
|
||||
|
||||
public void convert(final FlameGraph fg) {
|
||||
public void convert(final FlameGraph fg, final boolean threads, final boolean total,
|
||||
final boolean lines, final boolean bci, final int threadState,
|
||||
final Class<? extends Event> eventClass) throws IOException {
|
||||
EventAggregator agg = new EventAggregator(threads, total);
|
||||
for (Event event; (event = jfr.readEvent(eventClass)) != null; ) {
|
||||
if (threadState < 0 || ((ExecutionSample) event).threadState == threadState) {
|
||||
agg.collect(event);
|
||||
}
|
||||
}
|
||||
|
||||
final double ticksToNanos = 1e9 / jfr.ticksPerSec;
|
||||
final boolean scale = total && eventClass == ContendedLock.class && ticksToNanos != 1.0;
|
||||
|
||||
// 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) {
|
||||
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 visit(Event event, long value) {
|
||||
StackTrace stackTrace = jfr.stackTraces.get(event.stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
int[] locations = stackTrace.locations;
|
||||
String classFrame = getClassFrame(event);
|
||||
String[] trace = new String[methods.length + (threads ? 1 : 0) + (classFrame != null ? 1 : 0)];
|
||||
if (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 (lines && (location = locations[i] >>> 16) != 0) {
|
||||
methodName += ":" + location;
|
||||
} else if (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 {
|
||||
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));
|
||||
while (arrayDepth-- > 0) {
|
||||
sb.append("[]");
|
||||
}
|
||||
return sb.append(suffix).toString();
|
||||
}
|
||||
|
||||
private String toJavaClassName(byte[] symbol, int start) {
|
||||
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':
|
||||
return new String(symbol, start + 1, symbol.length - start - 2, StandardCharsets.UTF_8).replace('/', '.');
|
||||
default:
|
||||
return new String(symbol, start, symbol.length - start, StandardCharsets.UTF_8).replace('/', '.');
|
||||
}
|
||||
}
|
||||
|
||||
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 ((methodType >= NATIVE_TYPE_MIN && methodType <= NATIVE_TYPE_MAX) || className == null || className.length == 0) {
|
||||
result = new String(methodName, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
String classStr = new String(className, StandardCharsets.UTF_8);
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = classStr + '.' + methodStr;
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.put(methodId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
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[] args) throws Exception {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
|
||||
System.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(" --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.exit(1);
|
||||
}
|
||||
|
||||
HashSet<String> options = new HashSet<>(Arrays.asList(args));
|
||||
boolean threads = options.contains("--threads");
|
||||
boolean total = options.contains("--total");
|
||||
boolean lines = options.contains("--lines");
|
||||
boolean bci = options.contains("--bci");
|
||||
|
||||
Class<? extends Event> eventClass;
|
||||
boolean cpu = false;
|
||||
if (options.contains("--alloc")) {
|
||||
eventClass = AllocationSample.class;
|
||||
} else if (options.contains("--lock")) {
|
||||
eventClass = ContendedLock.class;
|
||||
} else {
|
||||
eventClass = ExecutionSample.class;
|
||||
cpu = options.contains("--cpu");
|
||||
}
|
||||
|
||||
try (JfrReader jfr = new JfrReader(fg.input)) {
|
||||
new jfr2flame(jfr).convert(fg);
|
||||
int threadState = cpu ? getMapKey(jfr.threadStates, "STATE_RUNNABLE") : -1;
|
||||
new jfr2flame(jfr).convert(fg, threads, total, lines, bci, threadState, eventClass);
|
||||
}
|
||||
|
||||
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,29 @@ 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 NATIVE_TYPE_MIN = 3;
|
||||
private static final byte NATIVE_TYPE_MAX = 5;
|
||||
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 +70,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 +101,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 +116,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 +126,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 +135,23 @@ 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 >= NATIVE_TYPE_MIN && methodType <= NATIVE_TYPE_MAX) || className == null || className.length == 0) {
|
||||
return methodName;
|
||||
} else {
|
||||
byte[] fullName = Arrays.copyOf(className, className.length + 1 + methodName.length);
|
||||
|
||||
@@ -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,6 +16,11 @@
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import one.jfr.event.AllocationSample;
|
||||
import one.jfr.event.ContendedLock;
|
||||
import one.jfr.event.Event;
|
||||
import one.jfr.event.ExecutionSample;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -33,17 +38,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 +61,27 @@ 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 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));
|
||||
}
|
||||
|
||||
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 +89,144 @@ 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 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 == 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();
|
||||
}
|
||||
|
||||
if ((pos += size) <= buf.limit()) {
|
||||
buf.position(pos);
|
||||
} else {
|
||||
seek(filePosition + pos);
|
||||
}
|
||||
}
|
||||
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 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);
|
||||
|
||||
ensureBytes(getVarint() - buf.position());
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
@@ -133,15 +273,17 @@ 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);
|
||||
|
||||
ensureBytes(getVarint() - buf.position());
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
long delta = getVarlong();
|
||||
delta = getVarlong();
|
||||
getVarint();
|
||||
|
||||
int poolCount = getVarint();
|
||||
@@ -149,12 +291,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) {
|
||||
@@ -241,13 +378,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 +433,16 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
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 +500,32 @@ public class JfrReader implements Closeable {
|
||||
buf.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void seek(long pos) throws IOException {
|
||||
filePosition = pos;
|
||||
ch.position(pos);
|
||||
buf.rewind().flip();
|
||||
}
|
||||
|
||||
private boolean ensureBytes(int needed) throws IOException {
|
||||
if (buf.remaining() >= needed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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_SP, DW_STACK_SLOT, DW_SAME_FP);
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
60
src/engine.h
60
src/engine.h
@@ -18,50 +18,48 @@
|
||||
#define _ENGINE_H
|
||||
|
||||
#include "arguments.h"
|
||||
#include "codeCache.h"
|
||||
|
||||
|
||||
class Engine {
|
||||
protected:
|
||||
static volatile bool _enabled;
|
||||
|
||||
static bool updateCounter(volatile unsigned long long& counter, unsigned long long value, unsigned long long interval) {
|
||||
if (interval <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
unsigned long long prev = counter;
|
||||
unsigned long long next = prev + value;
|
||||
if (next < interval) {
|
||||
if (__sync_bool_compare_and_swap(&counter, prev, next)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (__sync_bool_compare_and_swap(&counter, prev, next % interval)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
virtual const char* name() = 0;
|
||||
virtual const char* units() = 0;
|
||||
virtual const char* title() {
|
||||
return "Flame Graph";
|
||||
}
|
||||
|
||||
virtual const char* units() {
|
||||
return "total";
|
||||
}
|
||||
|
||||
virtual Error check(Arguments& args);
|
||||
virtual Error start(Arguments& args) = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual CStack cstack();
|
||||
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs);
|
||||
virtual Error start(Arguments& args);
|
||||
virtual void stop();
|
||||
|
||||
void enableEvents(bool enabled) {
|
||||
_enabled = enabled;
|
||||
}
|
||||
};
|
||||
|
||||
class NoopEngine : public Engine {
|
||||
public:
|
||||
const char* name() {
|
||||
return "noop";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error start(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _ENGINE_H
|
||||
|
||||
90
src/fdtransfer/fdtransfer.h
Normal file
90
src/fdtransfer/fdtransfer.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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]))
|
||||
|
||||
|
||||
// base header for all requests
|
||||
enum request_type {
|
||||
PERF_FD,
|
||||
KALLSYMS_FD,
|
||||
};
|
||||
|
||||
struct fd_request {
|
||||
// of type "enum request_type"
|
||||
unsigned int type;
|
||||
};
|
||||
|
||||
struct perf_fd_request {
|
||||
struct fd_request header;
|
||||
int tid;
|
||||
struct perf_event_attr attr;
|
||||
};
|
||||
|
||||
struct fd_response {
|
||||
// of type "enum request_type"
|
||||
unsigned int type;
|
||||
// 0 on success, otherwise errno
|
||||
int error;
|
||||
};
|
||||
|
||||
struct perf_fd_response {
|
||||
struct fd_response header;
|
||||
int tid;
|
||||
};
|
||||
|
||||
|
||||
static inline bool socketPathForPid(int pid, struct sockaddr_un *sun, socklen_t *addrlen) {
|
||||
sun->sun_path[0] = '\0';
|
||||
const int max_size = sizeof(sun->sun_path) - 1;
|
||||
const int path_len = snprintf(sun->sun_path + 1, max_size, "async-profiler-%d", pid);
|
||||
if (path_len > max_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sun->sun_family = AF_UNIX;
|
||||
// +1 for the first \0 byte
|
||||
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - (path_len + 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
sun->sun_family = AF_UNIX;
|
||||
*addrlen = sizeof(*sun) - (sizeof(sun->sun_path) - path_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _FDTRANSFER_H
|
||||
378
src/fdtransfer/fdtransferServer.cpp
Normal file
378
src/fdtransfer/fdtransferServer.cpp
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* 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 || 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 = 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 = sendmsg(_peer, &msg, 0);
|
||||
if (ret < 0) {
|
||||
perror("sendmsg()");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int single_pid_server(int pid) {
|
||||
// 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 (!socketPathForPid(nspid, &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.
|
||||
|
||||
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) {
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <pid>|<path>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int pid = atoi(argv[1]);
|
||||
// 2 modes:
|
||||
// pid == 0 - bind on a path and accept requests forever, from any PID, until being killed
|
||||
// pid != 0 - bind on an abstract namespace UDS for that PID, accept requests only from that PID
|
||||
// until the single connection is closed.
|
||||
if (pid != 0) {
|
||||
return single_pid_server(pid);
|
||||
} else {
|
||||
return path_server(argv[1]);
|
||||
}
|
||||
}
|
||||
55
src/fdtransferClient.h
Normal file
55
src/fdtransferClient.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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, int pid);
|
||||
static bool hasPeer() { return _peer != -1; }
|
||||
static void closePeer() {
|
||||
if (_peer != -1) {
|
||||
close(_peer);
|
||||
_peer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int requestPerfFd(int *tid, struct perf_event_attr *attr);
|
||||
static int requestKallsymsFd();
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class FdTransferClient {
|
||||
public:
|
||||
static bool connectToServer(const char *path, int pid) { return false; }
|
||||
static bool hasPeer() { return false; }
|
||||
static void closePeer() { }
|
||||
};
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#endif // _FDTRANSFER_CLIENT_H
|
||||
149
src/fdtransferClient_linux.cpp
Normal file
149
src/fdtransferClient_linux.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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, int pid) {
|
||||
_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 (path != NULL) {
|
||||
if (!socketPath(path, &sun, &addrlen)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!socketPathForPid(pid, &sun, &addrlen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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 (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 (send(_peer, &request, sizeof(request), 0) != sizeof(request)) {
|
||||
Log::warn("FdTransferClient send(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct fd_response resp;
|
||||
int fd = recvFd(request.type, &resp, sizeof(resp));
|
||||
if (fd == -1) {
|
||||
errno = resp.error;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int FdTransferClient::recvFd(unsigned int type, struct fd_response *resp, size_t resp_size) {
|
||||
struct msghdr msg = {0};
|
||||
|
||||
struct iovec iov[1];
|
||||
iov[0].iov_base = resp;
|
||||
iov[0].iov_len = resp_size;
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = ARRAY_SIZE(iov);
|
||||
|
||||
int newfd;
|
||||
union {
|
||||
char buf[CMSG_SPACE(sizeof(newfd))];
|
||||
struct cmsghdr align;
|
||||
} u;
|
||||
msg.msg_control = u.buf;
|
||||
msg.msg_controllen = sizeof(u.buf);
|
||||
|
||||
ssize_t ret = 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__
|
||||
@@ -18,8 +18,12 @@
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include "flameGraph.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
// Browsers refuse to draw on canvas larger than 32767 px
|
||||
const int MAX_CANVAS_HEIGHT = 32767;
|
||||
|
||||
static const char FLAMEGRAPH_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang='en'>\n"
|
||||
@@ -72,11 +76,13 @@ static const char FLAMEGRAPH_HEADER[] =
|
||||
"\tc.font = document.body.style.font;\n"
|
||||
"\n"
|
||||
"\tconst palette = [\n"
|
||||
"\t\t[0xb2e1b2, 20, 20, 20],\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[0x50cccc, 30, 30, 30],\n"
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n"
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n"
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n"
|
||||
"\t\t[0xcce880, 20, 20, 20],\n"
|
||||
"\t];\n"
|
||||
"\n"
|
||||
"\tfunction getColor(p) {\n"
|
||||
@@ -84,8 +90,10 @@ static const char FLAMEGRAPH_HEADER[] =
|
||||
"\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"
|
||||
"\tfunction f(level, left, width, type, title, inln, c1, int) {\n"
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title,\n"
|
||||
"\t\t\tdetails: (int ? ', int=' + int : '') + (c1 ? ', c1=' + c1 : '') + (inln ? ', inln=' + inln : '')\n"
|
||||
"\t\t});\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction samples(n) {\n"
|
||||
@@ -151,12 +159,12 @@ static const char FLAMEGRAPH_HEADER[] =
|
||||
"\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\tObject.keys(marked).sort(function(a, b) { return a - b; }).forEach(function(x) {\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\t});\n"
|
||||
"\t\t\treturn total;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
@@ -200,7 +208,7 @@ static const char FLAMEGRAPH_HEADER[] =
|
||||
"\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.title = f.title + '\\n(' + samples(f.width) + f.details + ', ' + 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"
|
||||
@@ -413,9 +421,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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -479,7 +487,8 @@ void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
out << TREE_FOOTER;
|
||||
} else {
|
||||
char buf[sizeof(FLAMEGRAPH_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title, depth * 16, _reverse ? "true" : "false", depth);
|
||||
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title,
|
||||
std::min(depth * 16, MAX_CANVAS_HEIGHT), _reverse ? "true" : "false", depth);
|
||||
out << buf;
|
||||
|
||||
printFrame(out, "all", _root, 0, 0);
|
||||
@@ -490,10 +499,16 @@ void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
|
||||
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 +532,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 +565,31 @@ void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
}
|
||||
}
|
||||
|
||||
int FlameGraph::frameType(std::string& name) {
|
||||
// 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,7 @@ 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);
|
||||
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,26 @@ class FlightRecorder {
|
||||
private:
|
||||
Recording* _rec;
|
||||
|
||||
Error startMasterRecording(Arguments& args);
|
||||
void stopMasterRecording();
|
||||
|
||||
public:
|
||||
FlightRecorder() : _rec(NULL) {
|
||||
}
|
||||
|
||||
Error start(Arguments& args, bool reset);
|
||||
void stop();
|
||||
void flush();
|
||||
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);
|
||||
|
||||
void recordLog(LogLevel level, const char* message, size_t len);
|
||||
};
|
||||
|
||||
#endif // _FLIGHTRECORDER_H
|
||||
|
||||
@@ -23,6 +23,11 @@
|
||||
#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;
|
||||
@@ -91,7 +96,7 @@ FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, Threa
|
||||
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() {
|
||||
@@ -112,17 +117,41 @@ char* FrameName::truncate(char* name, int max_length) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const char* FrameName::cppDemangle(const char* name) {
|
||||
if (name != NULL && name[0] == '_' && name[1] == 'Z') {
|
||||
const char* FrameName::decodeNativeSymbol(const char* name) {
|
||||
const char* lib_name = (_style & STYLE_LIB_NAMES) ? Profiler::instance()->getLibraryName(name) : NULL;
|
||||
|
||||
if (name[0] == '_' && name[1] == 'Z') {
|
||||
int status;
|
||||
char* demangled = abi::__cxa_demangle(name, NULL, NULL, &status);
|
||||
if (demangled != NULL) {
|
||||
strncpy(_buf, demangled, sizeof(_buf) - 1);
|
||||
if (lib_name != NULL) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "%s`%s", lib_name, demangled);
|
||||
} else {
|
||||
strncpy(_buf, demangled, sizeof(_buf) - 1);
|
||||
}
|
||||
free(demangled);
|
||||
return _buf;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
|
||||
if (lib_name != NULL) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "%s`%s", lib_name, name);
|
||||
return _buf;
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
const char* FrameName::typeSuffix(FrameTypeId type) {
|
||||
if (_style & STYLE_ANNOTATE) {
|
||||
switch (type) {
|
||||
case FRAME_JIT_COMPILED: return "_[j]";
|
||||
case FRAME_C1_COMPILED: return "_[j]";
|
||||
case FRAME_INLINED: return "_[i]";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* FrameName::javaMethodName(jmethodID method) {
|
||||
@@ -143,7 +172,6 @@ char* FrameName::javaMethodName(jmethodID method) {
|
||||
strcat(result, ".");
|
||||
strcat(result, method_name);
|
||||
if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
|
||||
if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[jvmtiError %d]", err);
|
||||
result = _buf;
|
||||
@@ -191,13 +219,13 @@ char* FrameName::javaClassName(const char* symbol, int length, int style) {
|
||||
|
||||
if (style & STYLE_SIMPLE) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') result = s + 1;
|
||||
if (*s == '/' && !isDigit(s[1])) result = s + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (style & STYLE_DOTTED) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') *s = '.';
|
||||
if (*s == '/' && !isDigit(s[1])) *s = '.';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +239,7 @@ 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:
|
||||
@@ -245,14 +273,20 @@ const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (type_suffix != NULL) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "%s%s", it->second.c_str(), type_suffix);
|
||||
return _buf;
|
||||
}
|
||||
return it->second.c_str();
|
||||
}
|
||||
|
||||
const char* newName = javaMethodName(frame.method_id);
|
||||
char* newName = javaMethodName(frame.method_id);
|
||||
_cache.insert(it, JMethodCache::value_type(frame.method_id, newName));
|
||||
return newName;
|
||||
return type_suffix != NULL ? strcat(newName, type_suffix) : newName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ class FrameName {
|
||||
|
||||
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
|
||||
char* truncate(char* name, int max_length);
|
||||
const char* cppDemangle(const char* name);
|
||||
const char* decodeNativeSymbol(const char* name);
|
||||
const char* typeSuffix(FrameTypeId type);
|
||||
char* javaMethodName(jmethodID method);
|
||||
char* javaClassName(const char* symbol, int length, int style);
|
||||
|
||||
|
||||
1
src/helper/one/profiler/Instrument.class.h
Normal file
1
src/helper/one/profiler/Instrument.class.h
Normal file
@@ -0,0 +1 @@
|
||||
202,254,186,190,0,0,0,50,0,11,10,0,3,0,8,7,0,9,7,0,10,1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,1,0,4,67,111,100,101,1,0,12,114,101,99,111,114,100,83,97,109,112,108,101,12,0,4,0,5,1,0,23,111,110,101,47,112,114,111,102,105,108,101,114,47,73,110,115,116,114,117,109,101,110,116,1,0,16,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,0,33,0,2,0,3,0,0,0,0,0,2,0,2,0,4,0,5,0,1,0,6,0,0,0,17,0,1,0,1,0,0,0,5,42,183,0,1,177,0,0,0,0,1,9,0,7,0,5,0,0,0,0,
|
||||
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();
|
||||
}
|
||||
1
src/helper/one/profiler/JfrSync.class.h
Normal file
1
src/helper/one/profiler/JfrSync.class.h
Normal file
File diff suppressed because one or more lines are too long
101
src/helper/one/profiler/JfrSync.java
Normal file
101
src/helper/one/profiler/JfrSync.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
private static native void stopProfiler();
|
||||
|
||||
// JNI helper
|
||||
static Integer box(int n) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
1
src/helper/one/profiler/Server.class.h
Normal file
1
src/helper/one/profiler/Server.class.h
Normal file
File diff suppressed because one or more lines are too long
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,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;
|
||||
}
|
||||
@@ -24,28 +24,9 @@
|
||||
#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
|
||||
static const unsigned char INSTRUMENT_CLASS[] = {
|
||||
#include "helper/one/profiler/Instrument.class.h"
|
||||
};
|
||||
|
||||
|
||||
enum ConstantTag {
|
||||
@@ -255,6 +236,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 +297,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 +348,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) {
|
||||
@@ -489,7 +517,7 @@ Error Instrument::check(Arguments& args) {
|
||||
|
||||
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, 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 +537,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 +616,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
|
||||
65
src/j9ObjectSampler.cpp
Normal file
65
src/j9ObjectSampler.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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, BCI_ALLOC, 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, BCI_ALLOC_OUTSIDE_TLAB, 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;
|
||||
|
||||
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);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void J9ObjectSampler::stop() {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
|
||||
jvmti->SetExtensionEventCallback(J9Ext::InstrumentableObjectAlloc_id, NULL);
|
||||
}
|
||||
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, &event, tid, 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, &event, tid, 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,37 @@ 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
440
src/jattach/jattach_openj9.c
Normal file
440
src/jattach/jattach_openj9.c
Normal file
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
241
src/jattach/psutil.c
Normal file
241
src/jattach/psutil.c
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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) {
|
||||
closedir(dir);
|
||||
return atoi(entry->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
// Could not find container pid; return host pid as the last resort
|
||||
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) {
|
||||
// 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'));
|
||||
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
|
||||
118
src/javaApi.cpp
118
src/javaApi.cpp
@@ -19,36 +19,55 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "javaApi.h"
|
||||
#include "arguments.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
static const unsigned char SERVER_CLASS[] = {
|
||||
#include "helper/one/profiler/Server.class.h"
|
||||
};
|
||||
|
||||
|
||||
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 +75,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 +141,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 +175,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, 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,19 @@ 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("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,8 @@ enum JfrType {
|
||||
T_CPU_INFORMATION = 110,
|
||||
T_JVM_INFORMATION = 111,
|
||||
T_INITIAL_SYSTEM_PROPERTY = 112,
|
||||
T_NATIVE_LIBRARY = 113,
|
||||
T_LOG = 114,
|
||||
|
||||
T_ANNOTATION = 200,
|
||||
T_LABEL = 201,
|
||||
|
||||
@@ -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
|
||||
77
src/objectSampler.cpp
Normal file
77
src/objectSampler.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
|
||||
u64 ObjectSampler::_interval;
|
||||
volatile u64 ObjectSampler::_allocated_bytes;
|
||||
|
||||
|
||||
void ObjectSampler::SampledObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size) {
|
||||
if (_enabled) {
|
||||
recordAllocation(jvmti, BCI_ALLOC, object_klass, size);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectSampler::recordAllocation(jvmtiEnv* jvmti, int event_type, jclass object_klass, jlong size) {
|
||||
AllocEvent event;
|
||||
event._class_id = 0;
|
||||
event._total_size = size > _interval ? size : _interval;
|
||||
event._instance_size = size;
|
||||
|
||||
char* class_name;
|
||||
if (jvmti->GetClassSignature(object_klass, &class_name, NULL) == 0) {
|
||||
if (class_name[0] == 'L') {
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(class_name + 1, strlen(class_name) - 2);
|
||||
} else {
|
||||
event._class_id = Profiler::instance()->classMap()->lookup(class_name);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
}
|
||||
|
||||
Profiler::instance()->recordSample(NULL, size, event_type, &event);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetHeapSamplingInterval(_interval);
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void ObjectSampler::stop() {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SAMPLED_OBJECT_ALLOC, NULL);
|
||||
}
|
||||
49
src/objectSampler.h
Normal file
49
src/objectSampler.h
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.
|
||||
*/
|
||||
|
||||
#ifndef _OBJECTSAMPLER_H
|
||||
#define _OBJECTSAMPLER_H
|
||||
|
||||
#include <jvmti.h>
|
||||
#include "arch.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class ObjectSampler : public Engine {
|
||||
protected:
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void recordAllocation(jvmtiEnv* jvmti, int event_type, jclass object_klass, jlong size);
|
||||
|
||||
public:
|
||||
const char* title() {
|
||||
return "Allocation profile";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "bytes";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
static void JNICALL SampledObjectAlloc(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread,
|
||||
jobject object, jclass object_klass, jlong size);
|
||||
};
|
||||
|
||||
#endif // _OBJECTSAMPLER_H
|
||||
43
src/os.h
43
src/os.h
@@ -19,9 +19,17 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
typedef void (*SigAction)(int, siginfo_t*, void*);
|
||||
typedef void (*SigHandler)(int);
|
||||
typedef void (*TimerCallback)(void*);
|
||||
|
||||
// Interrupt threads with this signal. The same signal is used inside JDK to interrupt I/O operations.
|
||||
const int WAKEUP_SIGNAL = SIGIO;
|
||||
|
||||
enum ThreadState {
|
||||
THREAD_INVALID,
|
||||
THREAD_RUNNING,
|
||||
@@ -29,10 +37,6 @@ enum ThreadState {
|
||||
};
|
||||
|
||||
|
||||
class Timer {
|
||||
};
|
||||
|
||||
|
||||
class ThreadList {
|
||||
public:
|
||||
virtual ~ThreadList() {}
|
||||
@@ -42,16 +46,27 @@ class ThreadList {
|
||||
};
|
||||
|
||||
|
||||
class OS {
|
||||
// W^X memory support
|
||||
class JitWriteProtection {
|
||||
private:
|
||||
typedef void (*SigAction)(int, siginfo_t*, void*);
|
||||
typedef void (*SigHandler)(int);
|
||||
typedef void (*TimerCallback)(void*);
|
||||
u64 _prev;
|
||||
bool _restore;
|
||||
|
||||
public:
|
||||
JitWriteProtection(bool enable);
|
||||
~JitWriteProtection();
|
||||
};
|
||||
|
||||
|
||||
class OS {
|
||||
public:
|
||||
static const size_t page_size;
|
||||
static const size_t page_mask;
|
||||
|
||||
static u64 nanotime();
|
||||
static u64 millis();
|
||||
static u64 micros();
|
||||
static u64 processStartTime();
|
||||
static void sleep(u64 nanos);
|
||||
|
||||
static u64 hton64(u64 x);
|
||||
static u64 ntoh64(u64 x);
|
||||
@@ -59,24 +74,26 @@ class OS {
|
||||
static int getMaxThreadId();
|
||||
static int processId();
|
||||
static int threadId();
|
||||
static const char* schedPolicy(int thread_id);
|
||||
static bool threadName(int thread_id, char* name_buf, size_t name_len);
|
||||
static ThreadState threadState(int thread_id);
|
||||
static ThreadList* listThreads();
|
||||
|
||||
static bool isJavaLibraryVisible();
|
||||
|
||||
static void installSignalHandler(int signo, SigAction action, SigHandler handler = NULL);
|
||||
static SigAction installSignalHandler(int signo, SigAction action, SigHandler handler = NULL);
|
||||
static SigAction replaceCrashHandler(SigAction action);
|
||||
static bool sendSignalToThread(int thread_id, int signo);
|
||||
|
||||
static void* safeAlloc(size_t size);
|
||||
static void safeFree(void* addr, size_t size);
|
||||
|
||||
static Timer* startTimer(u64 interval, TimerCallback callback, void* arg);
|
||||
static void stopTimer(Timer* timer);
|
||||
|
||||
static bool getCpuDescription(char* buf, size_t size);
|
||||
static u64 getProcessCpuTime(u64* utime, u64* stime);
|
||||
static u64 getTotalCpuTime(u64* utime, u64* stime);
|
||||
|
||||
static void copyFile(int src_fd, int dst_fd, off_t offset, size_t size);
|
||||
static void freePageCache(int fd, off_t start_offset);
|
||||
};
|
||||
|
||||
#endif // _OS_H
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
#include <byteswap.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
@@ -107,16 +109,28 @@ class LinuxThreadList : public ThreadList {
|
||||
};
|
||||
|
||||
|
||||
u64 OS::nanotime() {
|
||||
struct timespec tp;
|
||||
clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
return (u64)tp.tv_sec * 1000000000 + tp.tv_nsec;
|
||||
JitWriteProtection::JitWriteProtection(bool enable) {
|
||||
// Not used on Linux
|
||||
}
|
||||
|
||||
u64 OS::millis() {
|
||||
JitWriteProtection::~JitWriteProtection() {
|
||||
// Not used on Linux
|
||||
}
|
||||
|
||||
|
||||
const size_t OS::page_size = sysconf(_SC_PAGESIZE);
|
||||
const size_t OS::page_mask = OS::page_size - 1;
|
||||
|
||||
u64 OS::nanotime() {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (u64)ts.tv_sec * 1000000000 + ts.tv_nsec;
|
||||
}
|
||||
|
||||
u64 OS::micros() {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
return (u64)tv.tv_sec * 1000000 + tv.tv_usec;
|
||||
}
|
||||
|
||||
u64 OS::processStartTime() {
|
||||
@@ -135,6 +149,11 @@ u64 OS::processStartTime() {
|
||||
return start_time;
|
||||
}
|
||||
|
||||
void OS::sleep(u64 nanos) {
|
||||
struct timespec ts = {(time_t)(nanos / 1000000000), (long)(nanos % 1000000000)};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
u64 OS::hton64(u64 x) {
|
||||
return htonl(1) == 1 ? x : bswap_64(x);
|
||||
}
|
||||
@@ -164,6 +183,14 @@ int OS::threadId() {
|
||||
return syscall(__NR_gettid);
|
||||
}
|
||||
|
||||
const char* OS::schedPolicy(int thread_id) {
|
||||
int sched_policy = sched_getscheduler(thread_id);
|
||||
if (sched_policy >= SCHED_BATCH) {
|
||||
return sched_policy >= SCHED_IDLE ? "SCHED_IDLE" : "SCHED_BATCH";
|
||||
}
|
||||
return "SCHED_OTHER";
|
||||
}
|
||||
|
||||
bool OS::threadName(int thread_id, char* name_buf, size_t name_len) {
|
||||
char buf[64];
|
||||
sprintf(buf, "/proc/self/task/%d/comm", thread_id);
|
||||
@@ -208,8 +235,9 @@ bool OS::isJavaLibraryVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
SigAction OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
struct sigaction sa;
|
||||
struct sigaction oldsa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
if (handler != NULL) {
|
||||
@@ -220,7 +248,17 @@ void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
}
|
||||
|
||||
sigaction(signo, &sa, NULL);
|
||||
sigaction(signo, &sa, &oldsa);
|
||||
return oldsa.sa_sigaction;
|
||||
}
|
||||
|
||||
SigAction OS::replaceCrashHandler(SigAction action) {
|
||||
struct sigaction sa;
|
||||
sigaction(SIGSEGV, NULL, &sa);
|
||||
SigAction old_action = sa.sa_sigaction;
|
||||
sa.sa_sigaction = action;
|
||||
sigaction(SIGSEGV, &sa, NULL);
|
||||
return old_action;
|
||||
}
|
||||
|
||||
bool OS::sendSignalToThread(int thread_id, int signo) {
|
||||
@@ -241,30 +279,6 @@ void OS::safeFree(void* addr, size_t size) {
|
||||
syscall(__NR_munmap, addr, size);
|
||||
}
|
||||
|
||||
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
|
||||
struct sigevent sev;
|
||||
sev.sigev_notify = SIGEV_THREAD;
|
||||
sev.sigev_value.sival_ptr = arg;
|
||||
sev.sigev_notify_function = (void (*)(union sigval)) callback;
|
||||
sev.sigev_notify_attributes = NULL;
|
||||
|
||||
timer_t timer;
|
||||
if (timer_create(CLOCK_MONOTONIC, &sev, &timer) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct itimerspec spec;
|
||||
spec.it_interval.tv_sec = spec.it_value.tv_sec = interval / 1000000000;
|
||||
spec.it_interval.tv_nsec = spec.it_value.tv_nsec = interval % 1000000000;
|
||||
timer_settime(timer, 0, &spec, NULL);
|
||||
|
||||
return (Timer*)timer;
|
||||
}
|
||||
|
||||
void OS::stopTimer(Timer* timer) {
|
||||
timer_delete((timer_t)timer);
|
||||
}
|
||||
|
||||
bool OS::getCpuDescription(char* buf, size_t size) {
|
||||
int fd = open("/proc/cpuinfo", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
@@ -316,4 +330,19 @@ u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
return real;
|
||||
}
|
||||
|
||||
void OS::copyFile(int src_fd, int dst_fd, off_t offset, size_t size) {
|
||||
// copy_file_range() is probably better, but not supported on all kernels
|
||||
while (size > 0) {
|
||||
ssize_t bytes = sendfile(dst_fd, src_fd, &offset, size);
|
||||
if (bytes <= 0) {
|
||||
break;
|
||||
}
|
||||
size -= (size_t)bytes;
|
||||
}
|
||||
}
|
||||
|
||||
void OS::freePageCache(int fd, off_t start_offset) {
|
||||
posix_fadvise(fd, start_offset & ~page_mask, 0, POSIX_FADV_DONTNEED);
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
130
src/os_macos.cpp
130
src/os_macos.cpp
@@ -16,7 +16,6 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach.h>
|
||||
@@ -28,6 +27,8 @@
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/times.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
@@ -81,6 +82,39 @@ class MacThreadList : public ThreadList {
|
||||
};
|
||||
|
||||
|
||||
JitWriteProtection::JitWriteProtection(bool enable) {
|
||||
#ifdef __aarch64__
|
||||
// Mimic pthread_jit_write_protect_np(), but save the previous state
|
||||
u64 val = enable ? *(volatile u64*)0xfffffc118 : *(volatile u64*)0xfffffc110;
|
||||
u64 prev;
|
||||
asm volatile("mrs %0, s3_6_c15_c1_5" : "=r" (prev) : : );
|
||||
if (prev != val) {
|
||||
_prev = prev;
|
||||
_restore = true;
|
||||
asm volatile("msr s3_6_c15_c1_5, %0\n"
|
||||
"isb"
|
||||
: "+r" (val) : : "memory");
|
||||
} else {
|
||||
_restore = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
JitWriteProtection::~JitWriteProtection() {
|
||||
#ifdef __aarch64__
|
||||
if (_restore) {
|
||||
u64 prev = _prev;
|
||||
asm volatile("msr s3_6_c15_c1_5, %0\n"
|
||||
"isb"
|
||||
: "+r" (prev) : : "memory");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
const size_t OS::page_size = sysconf(_SC_PAGESIZE);
|
||||
const size_t OS::page_mask = OS::page_size - 1;
|
||||
|
||||
static mach_timebase_info_data_t timebase = {0, 0};
|
||||
|
||||
u64 OS::nanotime() {
|
||||
@@ -90,10 +124,15 @@ u64 OS::nanotime() {
|
||||
return (u64)mach_absolute_time() * timebase.numer / timebase.denom;
|
||||
}
|
||||
|
||||
u64 OS::millis() {
|
||||
u64 OS::micros() {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
return (u64)tv.tv_sec * 1000000 + tv.tv_usec;
|
||||
}
|
||||
|
||||
void OS::sleep(u64 nanos) {
|
||||
struct timespec ts = {(time_t)(nanos / 1000000000), (long)(nanos % 1000000000)};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
u64 OS::processStartTime() {
|
||||
@@ -135,6 +174,11 @@ int OS::threadId() {
|
||||
return (int)port;
|
||||
}
|
||||
|
||||
const char* OS::schedPolicy(int thread_id) {
|
||||
// Not used on macOS
|
||||
return "SCHED_OTHER";
|
||||
}
|
||||
|
||||
bool OS::threadName(int thread_id, char* name_buf, size_t name_len) {
|
||||
pthread_t thread = pthread_from_mach_thread_np(thread_id);
|
||||
return thread && pthread_getname_np(thread, name_buf, name_len) == 0 && name_buf[0] != 0;
|
||||
@@ -157,8 +201,9 @@ bool OS::isJavaLibraryVisible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
SigAction OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
struct sigaction sa;
|
||||
struct sigaction oldsa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
if (handler != NULL) {
|
||||
@@ -169,16 +214,37 @@ void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
}
|
||||
|
||||
sigaction(signo, &sa, NULL);
|
||||
sigaction(signo, &sa, &oldsa);
|
||||
return oldsa.sa_sigaction;
|
||||
}
|
||||
|
||||
SigAction OS::replaceCrashHandler(SigAction action) {
|
||||
struct sigaction sa;
|
||||
sigaction(SIGBUS, NULL, &sa);
|
||||
SigAction old_action = sa.sa_sigaction;
|
||||
sa.sa_sigaction = action;
|
||||
sigaction(SIGBUS, &sa, NULL);
|
||||
return old_action;
|
||||
}
|
||||
|
||||
bool OS::sendSignalToThread(int thread_id, int signo) {
|
||||
int result;
|
||||
asm volatile("syscall"
|
||||
: "=a" (result)
|
||||
: "a" (0x2000148), "D" (thread_id), "S" (signo)
|
||||
: "rcx", "r11", "memory");
|
||||
return result == 0;
|
||||
#ifdef __aarch64__
|
||||
register long x0 asm("x0") = thread_id;
|
||||
register long x1 asm("x1") = signo;
|
||||
register long x16 asm("x16") = 328;
|
||||
asm volatile("svc #0x80"
|
||||
: "+r" (x0)
|
||||
: "r" (x1), "r" (x16)
|
||||
: "memory");
|
||||
return x0 == 0;
|
||||
#else
|
||||
int result;
|
||||
asm volatile("syscall"
|
||||
: "=a" (result)
|
||||
: "a" (0x2000148), "D" (thread_id), "S" (signo)
|
||||
: "rcx", "r11", "memory");
|
||||
return result == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* OS::safeAlloc(size_t size) {
|
||||
@@ -195,26 +261,6 @@ void OS::safeFree(void* addr, size_t size) {
|
||||
munmap(addr, size);
|
||||
}
|
||||
|
||||
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
|
||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||||
if (source != NULL) {
|
||||
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, interval), interval, 0);
|
||||
dispatch_source_set_event_handler_f(source, callback);
|
||||
dispatch_set_context(source, arg);
|
||||
dispatch_resume(source);
|
||||
}
|
||||
return (Timer*)source;
|
||||
}
|
||||
|
||||
void OS::stopTimer(Timer* timer) {
|
||||
dispatch_source_t source = (dispatch_source_t)timer;
|
||||
if (source != NULL) {
|
||||
dispatch_source_cancel(source);
|
||||
dispatch_release(source);
|
||||
}
|
||||
}
|
||||
|
||||
bool OS::getCpuDescription(char* buf, size_t size) {
|
||||
return sysctlbyname("machdep.cpu.brand_string", buf, &size, NULL, 0) == 0;
|
||||
}
|
||||
@@ -255,4 +301,26 @@ u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
return user + system + idle;
|
||||
}
|
||||
|
||||
void OS::copyFile(int src_fd, int dst_fd, off_t offset, size_t size) {
|
||||
char* buf = (char*)mmap(NULL, size + offset, PROT_READ, MAP_PRIVATE, src_fd, 0);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (size > 0) {
|
||||
ssize_t bytes = write(dst_fd, buf + offset, size < 262144 ? size : 262144);
|
||||
if (bytes <= 0) {
|
||||
break;
|
||||
}
|
||||
offset += (size_t)bytes;
|
||||
size -= (size_t)bytes;
|
||||
}
|
||||
|
||||
munmap(buf, offset);
|
||||
}
|
||||
|
||||
void OS::freePageCache(int fd, off_t start_offset) {
|
||||
// Not supported on macOS
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
#define _PERFEVENTS_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "arch.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class PerfEvent;
|
||||
class PerfEventType;
|
||||
class StackContext;
|
||||
|
||||
class PerfEvents : public Engine {
|
||||
private:
|
||||
@@ -32,28 +34,27 @@ class PerfEvents : public Engine {
|
||||
static long _interval;
|
||||
static Ring _ring;
|
||||
static CStack _cstack;
|
||||
static bool _print_extended_warning;
|
||||
static bool _use_mmap_page;
|
||||
|
||||
static u64 readCounter(siginfo_t* siginfo, void* ucontext);
|
||||
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 "perf";
|
||||
}
|
||||
|
||||
const char* units();
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs);
|
||||
const char* title();
|
||||
const char* units();
|
||||
|
||||
static int walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx);
|
||||
static void resetBuffer(int tid);
|
||||
|
||||
static bool supported();
|
||||
static const char* getEventName(int event_id);
|
||||
|
||||
static bool createForThread(int tid);
|
||||
static int createForThread(int tid);
|
||||
static void destroyForThread(int tid);
|
||||
};
|
||||
|
||||
|
||||
@@ -25,18 +25,24 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include "arch.h"
|
||||
#include "j9StackTraces.h"
|
||||
#include "log.h"
|
||||
#include "os.h"
|
||||
#include "perfEvents.h"
|
||||
#include "fdtransferClient.h"
|
||||
#include "profiler.h"
|
||||
#include "spinLock.h"
|
||||
#include "stackFrame.h"
|
||||
#include "stackWalker.h"
|
||||
#include "symbols.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
// Ancient fcntl.h does not define F_SETOWN_EX constants and structures
|
||||
@@ -59,7 +65,18 @@ enum {
|
||||
};
|
||||
|
||||
|
||||
static const unsigned long PERF_PAGE_SIZE = sysconf(_SC_PAGESIZE);
|
||||
static int fetchInt(const char* file_name) {
|
||||
int fd = open(file_name, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char num[16] = "0";
|
||||
ssize_t r = read(fd, num, sizeof(num) - 1);
|
||||
(void) r;
|
||||
close(fd);
|
||||
return atoi(num);
|
||||
}
|
||||
|
||||
// Get perf_event_attr.config numeric value of the given tracepoint name
|
||||
// by reading /sys/kernel/debug/tracing/events/<name>/id file
|
||||
@@ -71,16 +88,94 @@ static int findTracepointId(const char* name) {
|
||||
|
||||
*strchr(buf, ':') = '/'; // make path from event name
|
||||
|
||||
return fetchInt(buf);
|
||||
}
|
||||
|
||||
// Get perf_event_attr.type for the given event source
|
||||
// by reading /sys/bus/event_source/devices/<name>/type
|
||||
static int findDeviceType(const char* name) {
|
||||
char buf[256];
|
||||
if ((size_t)snprintf(buf, sizeof(buf), "/sys/bus/event_source/devices/%s/type", name) >= sizeof(buf)) {
|
||||
return 0;
|
||||
}
|
||||
return fetchInt(buf);
|
||||
}
|
||||
|
||||
// Convert pmu/event-name/ to pmu/param1=N,param2=M/
|
||||
static void resolvePmuEventName(const char* device, char* event, size_t size) {
|
||||
char buf[256];
|
||||
if ((size_t)snprintf(buf, sizeof(buf), "/sys/bus/event_source/devices/%s/events/%s", device, event) >= sizeof(buf)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int fd = open(buf, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t r = read(fd, event, size);
|
||||
if (r > 0 && (r == size || event[r - 1] == '\n')) {
|
||||
event[r - 1] = 0;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Set a PMU parameter (such as umask) to the corresponding config field
|
||||
static bool setPmuConfig(const char* device, const char* param, __u64* config, __u64 val) {
|
||||
char buf[256];
|
||||
if ((size_t)snprintf(buf, sizeof(buf), "/sys/bus/event_source/devices/%s/format/%s", device, param) >= sizeof(buf)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd = open(buf, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t r = read(fd, buf, sizeof(buf));
|
||||
close(fd);
|
||||
|
||||
if (r > 0 && r < sizeof(buf)) {
|
||||
if (strncmp(buf, "config:", 7) == 0) {
|
||||
config[0] |= val << atoi(buf + 7);
|
||||
return true;
|
||||
} else if (strncmp(buf, "config1:", 8) == 0) {
|
||||
config[1] |= val << atoi(buf + 8);
|
||||
return true;
|
||||
} else if (strncmp(buf, "config2:", 8) == 0) {
|
||||
config[2] |= val << atoi(buf + 8);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void** _pthread_entry = NULL;
|
||||
|
||||
// Intercept thread creation/termination by patching libjvm's GOT entry for pthread_setspecific().
|
||||
// HotSpot puts VMThread into TLS on thread start, and resets on thread end.
|
||||
static int pthread_setspecific_hook(pthread_key_t key, const void* value) {
|
||||
if (key != VMThread::key()) {
|
||||
return pthread_setspecific(key, value);
|
||||
}
|
||||
if (pthread_getspecific(key) == value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char id[16] = "0";
|
||||
ssize_t r = read(fd, id, sizeof(id) - 1);
|
||||
(void) r;
|
||||
close(fd);
|
||||
return atoi(id);
|
||||
if (value != NULL) {
|
||||
int result = pthread_setspecific(key, value);
|
||||
PerfEvents::createForThread(OS::threadId());
|
||||
return result;
|
||||
} else {
|
||||
PerfEvents::destroyForThread(OS::threadId());
|
||||
return pthread_setspecific(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void** lookupThreadEntry() {
|
||||
CodeCache* lib = Profiler::instance()->findJvmLibrary("libj9thr");
|
||||
return lib != NULL ? lib->findGlobalOffsetEntry((void*)&pthread_setspecific) : NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,13 +189,25 @@ struct PerfEventType {
|
||||
long default_interval;
|
||||
__u32 type;
|
||||
__u64 config;
|
||||
__u32 bp_type;
|
||||
__u32 bp_len;
|
||||
__u64 config1;
|
||||
__u64 config2;
|
||||
int counter_arg;
|
||||
|
||||
enum {
|
||||
IDX_PREDEFINED = 12,
|
||||
IDX_RAW,
|
||||
IDX_PMU,
|
||||
IDX_BREAKPOINT,
|
||||
IDX_TRACEPOINT,
|
||||
IDX_KPROBE,
|
||||
IDX_UPROBE,
|
||||
};
|
||||
|
||||
static PerfEventType AVAILABLE_EVENTS[];
|
||||
static FunctionWithCounter KNOWN_FUNCTIONS[];
|
||||
|
||||
static char probe_func[256];
|
||||
|
||||
// Find which argument of a known function serves as a profiling counter,
|
||||
// e.g. the first argument of malloc() is allocation size
|
||||
static int findCounterArg(const char* name) {
|
||||
@@ -112,22 +219,22 @@ struct PerfEventType {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PerfEventType* findByType(__u32 type) {
|
||||
for (PerfEventType* event = AVAILABLE_EVENTS; ; event++) {
|
||||
if (event->type == type) {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Breakpoint format: func[+offset][/len][:rwx]
|
||||
// Breakpoint format: func[+offset][/len][:rwx][{arg}]
|
||||
static PerfEventType* getBreakpoint(const char* name, __u32 bp_type, __u32 bp_len) {
|
||||
char buf[256];
|
||||
strncpy(buf, name, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
|
||||
// Parse counter arg [{arg}]
|
||||
int counter_arg = 0;
|
||||
char* c = strrchr(buf, '{');
|
||||
if (c != NULL && c[1] >= '1' && c[1] <= '9') {
|
||||
*c++ = 0;
|
||||
counter_arg = atoi(c);
|
||||
}
|
||||
|
||||
// Parse access type [:rwx]
|
||||
char* c = strrchr(buf, ':');
|
||||
c = strrchr(buf, ':');
|
||||
if (c != NULL && c != name && c[-1] != ':') {
|
||||
*c++ = 0;
|
||||
if (strcmp(c, "r") == 0) {
|
||||
@@ -164,7 +271,11 @@ struct PerfEventType {
|
||||
} else {
|
||||
addr = (__u64)(uintptr_t)dlsym(RTLD_DEFAULT, buf);
|
||||
if (addr == 0) {
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.resolveSymbol(buf);
|
||||
addr = (__u64)(uintptr_t)Profiler::instance()->resolveSymbol(buf);
|
||||
}
|
||||
if (c == NULL) {
|
||||
// If offset is not specified explicitly, add the default breakpoint offset
|
||||
offset = BREAKPOINT_OFFSET;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,21 +283,113 @@ struct PerfEventType {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PerfEventType* breakpoint = findByType(PERF_TYPE_BREAKPOINT);
|
||||
breakpoint->config = addr + offset;
|
||||
breakpoint->bp_type = bp_type;
|
||||
breakpoint->bp_len = bp_len;
|
||||
breakpoint->counter_arg = bp_type == HW_BREAKPOINT_X ? findCounterArg(buf) : 0;
|
||||
PerfEventType* breakpoint = &AVAILABLE_EVENTS[IDX_BREAKPOINT];
|
||||
breakpoint->config = bp_type;
|
||||
breakpoint->config1 = addr + offset;
|
||||
breakpoint->config2 = bp_len;
|
||||
breakpoint->counter_arg = bp_type == HW_BREAKPOINT_X && counter_arg == 0 ? findCounterArg(buf) : counter_arg;
|
||||
return breakpoint;
|
||||
}
|
||||
|
||||
static PerfEventType* getTracepoint(int tracepoint_id) {
|
||||
PerfEventType* tracepoint = findByType(PERF_TYPE_TRACEPOINT);
|
||||
PerfEventType* tracepoint = &AVAILABLE_EVENTS[IDX_TRACEPOINT];
|
||||
tracepoint->config = tracepoint_id;
|
||||
return tracepoint;
|
||||
}
|
||||
|
||||
static PerfEventType* getProbe(PerfEventType* probe, const char* type, const char* name, __u64 ret) {
|
||||
strncpy(probe_func, name, sizeof(probe_func) - 1);
|
||||
probe_func[sizeof(probe_func) - 1] = 0;
|
||||
|
||||
if (probe->type == 0 && (probe->type = findDeviceType(type)) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long long offset = 0;
|
||||
char* c = strrchr(probe_func, '+');
|
||||
if (c != NULL) {
|
||||
*c++ = 0;
|
||||
offset = strtoll(c, NULL, 0);
|
||||
}
|
||||
|
||||
probe->config = ret;
|
||||
probe->config1 = (__u64)(uintptr_t)probe_func;
|
||||
probe->config2 = offset;
|
||||
return probe;
|
||||
}
|
||||
|
||||
static PerfEventType* getRawEvent(__u64 config) {
|
||||
PerfEventType* raw = &AVAILABLE_EVENTS[IDX_RAW];
|
||||
raw->config = config;
|
||||
return raw;
|
||||
}
|
||||
|
||||
static PerfEventType* getPmuEvent(const char* name) {
|
||||
char buf[256];
|
||||
strncpy(buf, name, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
|
||||
char* descriptor = strchr(buf, '/');
|
||||
*descriptor++ = 0;
|
||||
descriptor[strlen(descriptor) - 1] = 0;
|
||||
|
||||
PerfEventType* raw = &AVAILABLE_EVENTS[IDX_PMU];
|
||||
if ((raw->type = findDeviceType(buf)) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// pmu/rNNN/
|
||||
if (descriptor[0] == 'r' && descriptor[1] >= '0') {
|
||||
char* end;
|
||||
raw->config = strtoull(descriptor + 1, &end, 16);
|
||||
if (*end == 0) {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve event name to the list of parameters
|
||||
resolvePmuEventName(buf, descriptor, sizeof(buf) - (descriptor - buf));
|
||||
|
||||
raw->config = 0;
|
||||
raw->config1 = 0;
|
||||
raw->config2 = 0;
|
||||
|
||||
// Parse parameters
|
||||
while (descriptor != NULL && descriptor[0]) {
|
||||
char* p = descriptor;
|
||||
if ((descriptor = strchr(p, ',')) != NULL || (descriptor = strchr(p, ':')) != NULL) {
|
||||
*descriptor++ = 0;
|
||||
}
|
||||
|
||||
__u64 val = 1;
|
||||
char* eq = strchr(p, '=');
|
||||
if (eq != NULL) {
|
||||
*eq++ = 0;
|
||||
val = strtoull(eq, NULL, 0);
|
||||
}
|
||||
|
||||
if (strcmp(p, "config") == 0) {
|
||||
raw->config = val;
|
||||
} else if (strcmp(p, "config1") == 0) {
|
||||
raw->config1 = val;
|
||||
} else if (strcmp(p, "config2") == 0) {
|
||||
raw->config2 = val;
|
||||
} else if (!setPmuConfig(buf, p, &raw->config, val)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
static PerfEventType* forName(const char* name) {
|
||||
// Look through the table of predefined perf events
|
||||
for (int i = 0; i < IDX_PREDEFINED; i++) {
|
||||
if (strcmp(name, AVAILABLE_EVENTS[i].name) == 0) {
|
||||
return &AVAILABLE_EVENTS[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Hardware breakpoint
|
||||
if (strncmp(name, "mem:", 4) == 0) {
|
||||
return getBreakpoint(name + 4, HW_BREAKPOINT_RW, 1);
|
||||
@@ -198,16 +401,38 @@ struct PerfEventType {
|
||||
return tracepoint_id > 0 ? getTracepoint(tracepoint_id) : NULL;
|
||||
}
|
||||
|
||||
// Look through the table of predefined perf events
|
||||
for (PerfEventType* event = AVAILABLE_EVENTS; event->name != NULL; event++) {
|
||||
if (strcmp(name, event->name) == 0) {
|
||||
return event;
|
||||
// kprobe or uprobe
|
||||
if (strncmp(name, "kprobe:", 7) == 0) {
|
||||
return getProbe(&AVAILABLE_EVENTS[IDX_KPROBE], "kprobe", name + 7, 0);
|
||||
}
|
||||
if (strncmp(name, "uprobe:", 7) == 0) {
|
||||
return getProbe(&AVAILABLE_EVENTS[IDX_UPROBE], "uprobe", name + 7, 0);
|
||||
}
|
||||
if (strncmp(name, "kretprobe:", 10) == 0) {
|
||||
return getProbe(&AVAILABLE_EVENTS[IDX_KPROBE], "kprobe", name + 10, 1);
|
||||
}
|
||||
if (strncmp(name, "uretprobe:", 10) == 0) {
|
||||
return getProbe(&AVAILABLE_EVENTS[IDX_UPROBE], "uprobe", name + 10, 1);
|
||||
}
|
||||
|
||||
// Raw PMU register: rNNN
|
||||
if (name[0] == 'r' && name[1] >= '0') {
|
||||
char* end;
|
||||
__u64 reg = strtoull(name + 1, &end, 16);
|
||||
if (*end == 0) {
|
||||
return getRawEvent(reg);
|
||||
}
|
||||
}
|
||||
|
||||
// Raw perf event descriptor: pmu/event-descriptor/
|
||||
const char* s = strchr(name, '/');
|
||||
if (s > name && s[1] != 0 && s[strlen(s) - 1] == '/') {
|
||||
return getPmuEvent(name);
|
||||
}
|
||||
|
||||
// Kernel tracepoints defined in debugfs
|
||||
const char* c = strchr(name, ':');
|
||||
if (c != NULL && c[1] != ':') {
|
||||
s = strchr(name, ':');
|
||||
if (s != NULL && s[1] != ':') {
|
||||
int tracepoint_id = findTracepointId(name);
|
||||
if (tracepoint_id > 0) {
|
||||
return getTracepoint(tracepoint_id);
|
||||
@@ -232,7 +457,7 @@ PerfEventType PerfEventType::AVAILABLE_EVENTS[] = {
|
||||
{"instructions", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS},
|
||||
{"cache-references", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES},
|
||||
{"cache-misses", 1000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES},
|
||||
{"branches", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
|
||||
{"branch-instructions", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS},
|
||||
{"branch-misses", 1000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES},
|
||||
{"bus-cycles", 1000000, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES},
|
||||
|
||||
@@ -240,15 +465,20 @@ PerfEventType PerfEventType::AVAILABLE_EVENTS[] = {
|
||||
{"LLC-load-misses", 1000, PERF_TYPE_HW_CACHE, LOAD_MISS(PERF_COUNT_HW_CACHE_LL)},
|
||||
{"dTLB-load-misses", 1000, PERF_TYPE_HW_CACHE, LOAD_MISS(PERF_COUNT_HW_CACHE_DTLB)},
|
||||
|
||||
{"mem:breakpoint", 1, PERF_TYPE_BREAKPOINT, 0},
|
||||
{"trace:tracepoint", 1, PERF_TYPE_TRACEPOINT, 0},
|
||||
{"rNNN", 1000, PERF_TYPE_RAW, 0}, /* IDX_RAW */
|
||||
{"pmu/event-descriptor/", 1000, PERF_TYPE_RAW, 0}, /* IDX_PMU */
|
||||
|
||||
{NULL}
|
||||
{"mem:breakpoint", 1, PERF_TYPE_BREAKPOINT, 0}, /* IDX_BREAKPOINT */
|
||||
{"trace:tracepoint", 1, PERF_TYPE_TRACEPOINT, 0}, /* IDX_TRACEPOINT */
|
||||
|
||||
{"kprobe:func", 1, 0, 0}, /* IDX_KPROBE */
|
||||
{"uprobe:path", 1, 0, 0}, /* IDX_UPROBE */
|
||||
};
|
||||
|
||||
FunctionWithCounter PerfEventType::KNOWN_FUNCTIONS[] = {
|
||||
{"malloc", 1},
|
||||
{"mmap", 2},
|
||||
{"munmap", 2},
|
||||
{"read", 3},
|
||||
{"write", 3},
|
||||
{"send", 3},
|
||||
@@ -258,6 +488,8 @@ FunctionWithCounter PerfEventType::KNOWN_FUNCTIONS[] = {
|
||||
{NULL}
|
||||
};
|
||||
|
||||
char PerfEventType::probe_func[256];
|
||||
|
||||
|
||||
class RingBuffer {
|
||||
private:
|
||||
@@ -266,21 +498,21 @@ class RingBuffer {
|
||||
|
||||
public:
|
||||
RingBuffer(struct perf_event_mmap_page* page) {
|
||||
_start = (const char*)page + PERF_PAGE_SIZE;
|
||||
_start = (const char*)page + OS::page_size;
|
||||
}
|
||||
|
||||
struct perf_event_header* seek(u64 offset) {
|
||||
_offset = (unsigned long)offset & (PERF_PAGE_SIZE - 1);
|
||||
_offset = (unsigned long)offset & OS::page_mask;
|
||||
return (struct perf_event_header*)(_start + _offset);
|
||||
}
|
||||
|
||||
u64 next() {
|
||||
_offset = (_offset + sizeof(u64)) & (PERF_PAGE_SIZE - 1);
|
||||
_offset = (_offset + sizeof(u64)) & OS::page_mask;
|
||||
return *(u64*)(_start + _offset);
|
||||
}
|
||||
|
||||
u64 peek(unsigned long words) {
|
||||
unsigned long peek_offset = (_offset + words * sizeof(u64)) & (PERF_PAGE_SIZE - 1);
|
||||
unsigned long peek_offset = (_offset + words * sizeof(u64)) & OS::page_mask;
|
||||
return *(u64*)(_start + peek_offset);
|
||||
}
|
||||
};
|
||||
@@ -301,17 +533,23 @@ PerfEventType* PerfEvents::_event_type = NULL;
|
||||
long PerfEvents::_interval;
|
||||
Ring PerfEvents::_ring;
|
||||
CStack PerfEvents::_cstack;
|
||||
bool PerfEvents::_print_extended_warning;
|
||||
bool PerfEvents::_use_mmap_page;
|
||||
|
||||
bool PerfEvents::createForThread(int tid) {
|
||||
int PerfEvents::createForThread(int tid) {
|
||||
if (tid >= _max_events) {
|
||||
fprintf(stderr, "WARNING: tid[%d] > pid_max[%d]. Restart profiler after changing pid_max\n", tid, _max_events);
|
||||
return false;
|
||||
Log::warn("tid[%d] > pid_max[%d]. Restart profiler after changing pid_max", tid, _max_events);
|
||||
return -1;
|
||||
}
|
||||
|
||||
PerfEventType* event_type = _event_type;
|
||||
if (event_type == NULL) {
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Mark _events[tid] early to prevent duplicates. Real fd will be put later.
|
||||
if (!__sync_bool_compare_and_swap(&_events[tid]._fd, 0, -1)) {
|
||||
// Lost race. The event is created either from PerfEvents::start() or from pthread hook.
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct perf_event_attr attr = {0};
|
||||
@@ -319,12 +557,12 @@ bool PerfEvents::createForThread(int tid) {
|
||||
attr.type = event_type->type;
|
||||
|
||||
if (attr.type == PERF_TYPE_BREAKPOINT) {
|
||||
attr.bp_addr = event_type->config;
|
||||
attr.bp_type = event_type->bp_type;
|
||||
attr.bp_len = event_type->bp_len;
|
||||
attr.bp_type = event_type->config;
|
||||
} else {
|
||||
attr.config = event_type->config;
|
||||
}
|
||||
attr.config1 = event_type->config1;
|
||||
attr.config2 = event_type->config2;
|
||||
|
||||
// Hardware events may not always support zero skid
|
||||
if (attr.type == PERF_TYPE_SOFTWARE) {
|
||||
@@ -342,6 +580,10 @@ bool PerfEvents::createForThread(int tid) {
|
||||
attr.exclude_user = 1;
|
||||
}
|
||||
|
||||
if (_cstack == CSTACK_FP || _cstack == CSTACK_DWARF) {
|
||||
attr.exclude_callchain_user = 1;
|
||||
}
|
||||
|
||||
#ifdef PERF_ATTR_SIZE_VER5
|
||||
if (_cstack == CSTACK_LBR) {
|
||||
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK | PERF_SAMPLE_REGS_USER;
|
||||
@@ -353,31 +595,27 @@ bool PerfEvents::createForThread(int tid) {
|
||||
#warning "Compiling without LBR support. Kernel headers 4.1+ required"
|
||||
#endif
|
||||
|
||||
int fd = syscall(__NR_perf_event_open, &attr, tid, -1, -1, 0);
|
||||
int fd;
|
||||
if (FdTransferClient::hasPeer()) {
|
||||
fd = FdTransferClient::requestPerfFd(&tid, &attr);
|
||||
} else {
|
||||
fd = syscall(__NR_perf_event_open, &attr, tid, -1, -1, 0);
|
||||
}
|
||||
|
||||
if (fd == -1) {
|
||||
int err = errno;
|
||||
perror("perf_event_open failed");
|
||||
if (err == EACCES && _print_extended_warning) {
|
||||
fprintf(stderr, "Due to permission restrictions, you cannot collect kernel events.\n"
|
||||
"Try with --all-user option, or 'echo 1 > /proc/sys/kernel/perf_event_paranoid'\n");
|
||||
_print_extended_warning = false;
|
||||
}
|
||||
return false;
|
||||
Log::warn("perf_event_open for TID %d failed: %s", tid, strerror(errno));
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!__sync_bool_compare_and_swap(&_events[tid]._fd, 0, fd)) {
|
||||
// Lost race. The event is created either from start() or from onThreadStart()
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* page = mmap(NULL, 2 * PERF_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
void* page = _use_mmap_page ? mmap(NULL, 2 * OS::page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0) : NULL;
|
||||
if (page == MAP_FAILED) {
|
||||
perror("perf_event mmap failed");
|
||||
Log::warn("perf_event mmap failed: %s", strerror(errno));
|
||||
page = NULL;
|
||||
}
|
||||
|
||||
_events[tid].reset();
|
||||
_events[tid]._fd = fd;
|
||||
_events[tid]._page = (struct perf_event_mmap_page*)page;
|
||||
|
||||
struct f_owner_ex ex;
|
||||
@@ -391,7 +629,7 @@ bool PerfEvents::createForThread(int tid) {
|
||||
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
|
||||
ioctl(fd, PERF_EVENT_IOC_REFRESH, 1);
|
||||
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PerfEvents::destroyForThread(int tid) {
|
||||
@@ -401,18 +639,31 @@ void PerfEvents::destroyForThread(int tid) {
|
||||
|
||||
PerfEvent* event = &_events[tid];
|
||||
int fd = event->_fd;
|
||||
if (fd != 0 && __sync_bool_compare_and_swap(&event->_fd, fd, 0)) {
|
||||
if (fd > 0 && __sync_bool_compare_and_swap(&event->_fd, fd, 0)) {
|
||||
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
|
||||
close(fd);
|
||||
}
|
||||
if (event->_page != NULL) {
|
||||
event->lock();
|
||||
munmap(event->_page, 2 * PERF_PAGE_SIZE);
|
||||
munmap(event->_page, 2 * OS::page_size);
|
||||
event->_page = NULL;
|
||||
event->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
u64 PerfEvents::readCounter(siginfo_t* siginfo, void* ucontext) {
|
||||
switch (_event_type->counter_arg) {
|
||||
case 1: return StackFrame(ucontext).arg0();
|
||||
case 2: return StackFrame(ucontext).arg1();
|
||||
case 3: return StackFrame(ucontext).arg2();
|
||||
case 4: return StackFrame(ucontext).arg3();
|
||||
default: {
|
||||
u64 counter;
|
||||
return read(siginfo->si_fd, &counter, sizeof(counter)) == sizeof(counter) ? counter : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (siginfo->si_code <= 0) {
|
||||
// Looks like an external signal; don't treat as a profiling event
|
||||
@@ -420,41 +671,61 @@ void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
}
|
||||
|
||||
if (_enabled) {
|
||||
u64 counter;
|
||||
switch (_event_type->counter_arg) {
|
||||
case 1: counter = StackFrame(ucontext).arg0(); break;
|
||||
case 2: counter = StackFrame(ucontext).arg1(); break;
|
||||
case 3: counter = StackFrame(ucontext).arg2(); break;
|
||||
case 4: counter = StackFrame(ucontext).arg3(); break;
|
||||
default:
|
||||
if (read(siginfo->si_fd, &counter, sizeof(counter)) != sizeof(counter)) {
|
||||
counter = 1;
|
||||
}
|
||||
}
|
||||
|
||||
u64 counter = readCounter(siginfo, ucontext);
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, counter, 0, &event);
|
||||
Profiler::instance()->recordSample(ucontext, counter, 0, &event);
|
||||
} else {
|
||||
resetBuffer(OS::threadId());
|
||||
}
|
||||
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_RESET, 0);
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_REFRESH, 1);
|
||||
}
|
||||
|
||||
const char* PerfEvents::units() {
|
||||
if (_event_type == NULL || _event_type->name == EVENT_CPU) {
|
||||
return "ns";
|
||||
} else if (_event_type->type == PERF_TYPE_BREAKPOINT || _event_type->type == PERF_TYPE_TRACEPOINT) {
|
||||
return "events";
|
||||
void PerfEvents::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
if (siginfo->si_code <= 0) {
|
||||
// Looks like an external signal; don't treat as a profiling event
|
||||
return;
|
||||
}
|
||||
|
||||
const char* dash = strrchr(_event_type->name, '-');
|
||||
return dash != NULL ? dash + 1 : _event_type->name;
|
||||
if (_enabled) {
|
||||
u64 counter = readCounter(siginfo, ucontext);
|
||||
J9StackTraceNotification notif;
|
||||
StackContext java_ctx;
|
||||
notif.num_frames = _cstack == CSTACK_NO ? 0 : walk(OS::threadId(), ucontext, notif.addr, MAX_J9_NATIVE_FRAMES, &java_ctx);
|
||||
J9StackTraces::checkpoint(counter, ¬if);
|
||||
} else {
|
||||
resetBuffer(OS::threadId());
|
||||
}
|
||||
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_RESET, 0);
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_REFRESH, 1);
|
||||
}
|
||||
|
||||
const char* PerfEvents::title() {
|
||||
if (_event_type == NULL || _event_type->name == EVENT_CPU) {
|
||||
return "CPU profile";
|
||||
} else if (_event_type->type == PERF_TYPE_SOFTWARE || _event_type->type == PERF_TYPE_HARDWARE || _event_type->type == PERF_TYPE_HW_CACHE) {
|
||||
return _event_type->name;
|
||||
} else {
|
||||
return "Flame Graph";
|
||||
}
|
||||
}
|
||||
|
||||
const char* PerfEvents::units() {
|
||||
return _event_type == NULL || _event_type->name == EVENT_CPU ? "ns" : "total";
|
||||
}
|
||||
|
||||
Error PerfEvents::check(Arguments& args) {
|
||||
PerfEventType* event_type = PerfEventType::forName(args._event_desc);
|
||||
PerfEventType* event_type = PerfEventType::forName(args._event);
|
||||
if (event_type == NULL) {
|
||||
return Error("Unsupported event type");
|
||||
} else if (event_type->counter_arg > 4) {
|
||||
return Error("Only arguments 1-4 can be counted");
|
||||
}
|
||||
|
||||
if (_pthread_entry == NULL && (_pthread_entry = lookupThreadEntry()) == NULL) {
|
||||
return Error("Could not set pthread hook");
|
||||
}
|
||||
|
||||
struct perf_event_attr attr = {0};
|
||||
@@ -462,12 +733,12 @@ Error PerfEvents::check(Arguments& args) {
|
||||
attr.type = event_type->type;
|
||||
|
||||
if (attr.type == PERF_TYPE_BREAKPOINT) {
|
||||
attr.bp_addr = event_type->config;
|
||||
attr.bp_type = event_type->bp_type;
|
||||
attr.bp_len = event_type->bp_len;
|
||||
attr.bp_type = event_type->config;
|
||||
} else {
|
||||
attr.config = event_type->config;
|
||||
}
|
||||
attr.config1 = event_type->config1;
|
||||
attr.config2 = event_type->config2;
|
||||
|
||||
attr.sample_period = event_type->default_interval;
|
||||
attr.sample_type = PERF_SAMPLE_CALLCHAIN;
|
||||
@@ -478,10 +749,14 @@ Error PerfEvents::check(Arguments& args) {
|
||||
} else if (args._ring == RING_KERNEL) {
|
||||
attr.exclude_user = 1;
|
||||
} else if (!Symbols::haveKernelSymbols()) {
|
||||
Profiler::_instance.updateSymbols(true);
|
||||
Profiler::instance()->updateSymbols(true);
|
||||
attr.exclude_kernel = Symbols::haveKernelSymbols() ? 0 : 1;
|
||||
}
|
||||
|
||||
if (_cstack == CSTACK_FP || _cstack == CSTACK_DWARF) {
|
||||
attr.exclude_callchain_user = 1;
|
||||
}
|
||||
|
||||
#ifdef PERF_ATTR_SIZE_VER5
|
||||
if (args._cstack == CSTACK_LBR) {
|
||||
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK | PERF_SAMPLE_REGS_USER;
|
||||
@@ -501,9 +776,15 @@ Error PerfEvents::check(Arguments& args) {
|
||||
}
|
||||
|
||||
Error PerfEvents::start(Arguments& args) {
|
||||
_event_type = PerfEventType::forName(args._event_desc);
|
||||
_event_type = PerfEventType::forName(args._event);
|
||||
if (_event_type == NULL) {
|
||||
return Error("Unsupported event type");
|
||||
} else if (_event_type->counter_arg > 4) {
|
||||
return Error("Only arguments 1-4 can be counted");
|
||||
}
|
||||
|
||||
if (_pthread_entry == NULL && (_pthread_entry = lookupThreadEntry()) == NULL) {
|
||||
return Error("Could not set pthread hook");
|
||||
}
|
||||
|
||||
if (args._interval < 0) {
|
||||
@@ -513,13 +794,13 @@ Error PerfEvents::start(Arguments& args) {
|
||||
|
||||
_ring = args._ring;
|
||||
if (_ring != RING_USER && !Symbols::haveKernelSymbols()) {
|
||||
fprintf(stderr, "WARNING: Kernel symbols are unavailable due to restrictions. Try\n"
|
||||
" echo 0 > /proc/sys/kernel/kptr_restrict\n"
|
||||
" echo 1 > /proc/sys/kernel/perf_event_paranoid\n");
|
||||
Log::warn("Kernel symbols are unavailable due to restrictions. Try\n"
|
||||
" sysctl kernel.kptr_restrict=0\n"
|
||||
" sysctl kernel.perf_event_paranoid=1");
|
||||
_ring = RING_USER;
|
||||
}
|
||||
_cstack = args._cstack;
|
||||
_print_extended_warning = _ring != RING_USER;
|
||||
_use_mmap_page = _cstack != CSTACK_NO && (_ring != RING_USER || _cstack == CSTACK_DEFAULT || _cstack == CSTACK_LBR);
|
||||
|
||||
int max_events = OS::getMaxThreadId();
|
||||
if (max_events != _max_events) {
|
||||
@@ -528,34 +809,52 @@ Error PerfEvents::start(Arguments& args) {
|
||||
_max_events = max_events;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Enable thread events before traversing currently running threads
|
||||
Profiler::_instance.switchThreadEvents(JVMTI_ENABLE);
|
||||
// Enable pthread hook before traversing currently running threads
|
||||
__atomic_store_n(_pthread_entry, (void*)pthread_setspecific_hook, __ATOMIC_RELEASE);
|
||||
|
||||
// Create perf_events for all existing threads
|
||||
int err;
|
||||
bool created = false;
|
||||
ThreadList* thread_list = OS::listThreads();
|
||||
for (int tid; (tid = thread_list->next()) != -1; ) {
|
||||
created |= createForThread(tid);
|
||||
if ((err = createForThread(tid)) == 0) {
|
||||
created = true;
|
||||
}
|
||||
}
|
||||
delete thread_list;
|
||||
|
||||
if (!created) {
|
||||
Profiler::_instance.switchThreadEvents(JVMTI_DISABLE);
|
||||
return Error("Perf events unavailable. See stderr of the target process.");
|
||||
__atomic_store_n(_pthread_entry, (void*)pthread_setspecific, __ATOMIC_RELEASE);
|
||||
J9StackTraces::stop();
|
||||
if (err == EACCES || err == EPERM) {
|
||||
return Error("No access to perf events. Try --fdtransfer or --all-user option or 'sysctl kernel.perf_event_paranoid=1'");
|
||||
} else {
|
||||
return Error("Perf events unavailable");
|
||||
}
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void PerfEvents::stop() {
|
||||
__atomic_store_n(_pthread_entry, (void*)pthread_setspecific, __ATOMIC_RELEASE);
|
||||
for (int i = 0; i < _max_events; i++) {
|
||||
destroyForThread(i);
|
||||
}
|
||||
J9StackTraces::stop();
|
||||
}
|
||||
|
||||
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs) {
|
||||
int PerfEvents::walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx) {
|
||||
PerfEvent* event = &_events[tid];
|
||||
if (!event->tryLock()) {
|
||||
return 0; // the event is being destroyed
|
||||
@@ -579,8 +878,9 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
u64 ip = ring.next();
|
||||
if (ip < PERF_CONTEXT_MAX) {
|
||||
const void* iptr = (const void*)ip;
|
||||
if (java_methods->contains(iptr) || runtime_stubs->contains(iptr) || depth >= max_depth) {
|
||||
if (CodeHeap::contains(iptr) || depth >= max_depth) {
|
||||
// Stop at the first Java frame
|
||||
java_ctx->pc = iptr;
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = iptr;
|
||||
@@ -592,7 +892,8 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
|
||||
// Last userspace PC is stored right after branch stack
|
||||
const void* pc = (const void*)ring.peek(bnr * 3 + 2);
|
||||
if (java_methods->contains(pc) || runtime_stubs->contains(pc) || depth >= max_depth) {
|
||||
if (CodeHeap::contains(pc) || depth >= max_depth) {
|
||||
java_ctx->pc = pc;
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = pc;
|
||||
@@ -602,12 +903,14 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
const void* to = (const void*)ring.next();
|
||||
ring.next();
|
||||
|
||||
if (java_methods->contains(to) || runtime_stubs->contains(to) || depth >= max_depth) {
|
||||
if (CodeHeap::contains(to) || depth >= max_depth) {
|
||||
java_ctx->pc = to;
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = to;
|
||||
|
||||
if (java_methods->contains(from) || runtime_stubs->contains(from) || depth >= max_depth) {
|
||||
if (CodeHeap::contains(from) || depth >= max_depth) {
|
||||
java_ctx->pc = from;
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = from;
|
||||
@@ -624,9 +927,32 @@ stack_complete:
|
||||
}
|
||||
|
||||
event->unlock();
|
||||
|
||||
if (_cstack == CSTACK_FP) {
|
||||
depth += StackWalker::walkFP(ucontext, callchain + depth, max_depth - depth, java_ctx);
|
||||
} else if (_cstack == CSTACK_DWARF) {
|
||||
depth += StackWalker::walkDwarf(ucontext, callchain + depth, max_depth - depth, java_ctx);
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
void PerfEvents::resetBuffer(int tid) {
|
||||
PerfEvent* event = &_events[tid];
|
||||
if (!event->tryLock()) {
|
||||
return; // the event is being destroyed
|
||||
}
|
||||
|
||||
struct perf_event_mmap_page* page = event->_page;
|
||||
if (page != NULL) {
|
||||
u64 head = page->data_head;
|
||||
rmb();
|
||||
page->data_tail = head;
|
||||
}
|
||||
|
||||
event->unlock();
|
||||
}
|
||||
|
||||
bool PerfEvents::supported() {
|
||||
// The official way of knowing if perf_event_open() support is enabled
|
||||
// is checking for the existence of the file /proc/sys/kernel/perf_event_paranoid
|
||||
|
||||
@@ -24,14 +24,25 @@ PerfEvent* PerfEvents::_events;
|
||||
PerfEventType* PerfEvents::_event_type;
|
||||
long PerfEvents::_interval;
|
||||
Ring PerfEvents::_ring;
|
||||
bool PerfEvents::_print_extended_warning;
|
||||
CStack PerfEvents::_cstack;
|
||||
bool PerfEvents::_use_mmap_page;
|
||||
|
||||
u64 PerfEvents::readCounter(siginfo_t* siginfo, void* ucontext) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
}
|
||||
|
||||
void PerfEvents::signalHandlerJ9(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
}
|
||||
|
||||
const char* PerfEvents::title() {
|
||||
return Engine::title();
|
||||
}
|
||||
|
||||
const char* PerfEvents::units() {
|
||||
return "ns";
|
||||
return Engine::units();
|
||||
}
|
||||
|
||||
Error PerfEvents::check(Arguments& args) {
|
||||
@@ -45,11 +56,13 @@ Error PerfEvents::start(Arguments& args) {
|
||||
void PerfEvents::stop() {
|
||||
}
|
||||
|
||||
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs) {
|
||||
int PerfEvents::walk(int tid, void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PerfEvents::resetBuffer(int tid) {
|
||||
}
|
||||
|
||||
bool PerfEvents::supported() {
|
||||
return false;
|
||||
}
|
||||
@@ -58,8 +71,8 @@ const char* PerfEvents::getEventName(int event_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PerfEvents::createForThread(int tid) {
|
||||
return false;
|
||||
int PerfEvents::createForThread(int tid) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void PerfEvents::destroyForThread(int tid) {
|
||||
|
||||
1311
src/profiler.cpp
1311
src/profiler.cpp
File diff suppressed because it is too large
Load Diff
132
src/profiler.h
132
src/profiler.h
@@ -28,6 +28,7 @@
|
||||
#include "engine.h"
|
||||
#include "event.h"
|
||||
#include "flightRecorder.h"
|
||||
#include "log.h"
|
||||
#include "mutex.h"
|
||||
#include "spinLock.h"
|
||||
#include "threadFilter.h"
|
||||
@@ -41,18 +42,9 @@ const char FULL_VERSION_STRING[] =
|
||||
|
||||
const int MAX_NATIVE_FRAMES = 128;
|
||||
const int RESERVED_FRAMES = 4;
|
||||
const int MAX_NATIVE_LIBS = 2048;
|
||||
const int CONCURRENCY_LEVEL = 16;
|
||||
|
||||
|
||||
enum AddressType {
|
||||
ADDR_UNKNOWN,
|
||||
ADDR_JIT,
|
||||
ADDR_STUB,
|
||||
ADDR_NATIVE
|
||||
};
|
||||
|
||||
|
||||
union CallTraceBuffer {
|
||||
ASGCT_CallFrame _asgct_frames[1];
|
||||
jvmtiFrameInfo _jvmti_frames[1];
|
||||
@@ -60,8 +52,11 @@ union CallTraceBuffer {
|
||||
|
||||
|
||||
class FrameName;
|
||||
class NMethod;
|
||||
class StackContext;
|
||||
|
||||
enum State {
|
||||
NEW,
|
||||
IDLE,
|
||||
RUNNING,
|
||||
TERMINATED
|
||||
@@ -83,8 +78,12 @@ class Profiler {
|
||||
CallTraceStorage _call_trace_storage;
|
||||
FlightRecorder _jfr;
|
||||
Engine* _engine;
|
||||
int _events;
|
||||
Engine* _alloc_engine;
|
||||
int _event_mask;
|
||||
|
||||
time_t _start_time;
|
||||
volatile bool _timer_is_running;
|
||||
pthread_t _timer_thread;
|
||||
|
||||
u64 _total_samples;
|
||||
u64 _failures[ASGCT_FAILURE_TYPES];
|
||||
@@ -95,38 +94,23 @@ class Profiler {
|
||||
int _safe_mode;
|
||||
CStack _cstack;
|
||||
bool _add_thread_frame;
|
||||
bool _add_sched_frame;
|
||||
bool _update_thread_names;
|
||||
volatile bool _thread_events_state;
|
||||
|
||||
SpinLock _jit_lock;
|
||||
SpinLock _stubs_lock;
|
||||
CodeCache _java_methods;
|
||||
NativeCodeCache _runtime_stubs;
|
||||
NativeCodeCache* _native_libs[MAX_NATIVE_LIBS];
|
||||
volatile int _native_lib_count;
|
||||
CodeCache _runtime_stubs;
|
||||
CodeCacheArray _native_libs;
|
||||
|
||||
// Support for intercepting NativeLibrary.load() / NativeLibraries.load()
|
||||
JNINativeMethod _load_method;
|
||||
void* _original_NativeLibrary_load;
|
||||
void* _trapped_NativeLibrary_load;
|
||||
static jboolean JNICALL NativeLibraryLoadTrap(JNIEnv* env, jobject self, jstring name, jboolean builtin);
|
||||
static jboolean JNICALL NativeLibrariesLoadTrap(JNIEnv* env, jobject self, jobject lib, jstring name, jboolean builtin, jboolean jni);
|
||||
void bindNativeLibraryLoad(JNIEnv* env, bool enable);
|
||||
|
||||
// Support for intercepting Thread.setNativeName()
|
||||
void* _original_Thread_setNativeName;
|
||||
static void JNICALL ThreadSetNativeNameTrap(JNIEnv* env, jobject self, jstring name);
|
||||
void bindThreadSetNativeName(JNIEnv* env, bool enable);
|
||||
|
||||
void switchNativeMethodTraps(bool enable);
|
||||
// dlopen() hook support
|
||||
void** _dlopen_entry;
|
||||
static void* dlopen_hook(const char* filename, int flags);
|
||||
void switchLibraryTrap(bool enable);
|
||||
|
||||
Error installTraps(const char* begin, const char* end);
|
||||
void uninstallTraps();
|
||||
static void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
void trapHandlerImpl(void* ucontext);
|
||||
|
||||
void addJavaMethod(const void* address, int length, jmethodID method);
|
||||
void removeJavaMethod(const void* address, jmethodID method);
|
||||
void addRuntimeStub(const void* address, int length, const char* name);
|
||||
|
||||
void onThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
@@ -134,12 +118,13 @@ class Profiler {
|
||||
|
||||
const char* asgctError(int code);
|
||||
u32 getLockIndex(int tid);
|
||||
int getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int tid);
|
||||
int getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max_depth);
|
||||
int getJavaTraceJvmti(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int max_depth);
|
||||
int makeEventFrame(ASGCT_CallFrame* frames, jint event_type, uintptr_t id);
|
||||
bool fillTopFrame(const void* pc, ASGCT_CallFrame* frame);
|
||||
AddressType getAddressType(instruction_t* pc);
|
||||
bool isAddressInCode(uintptr_t addr);
|
||||
int getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int event_type, int tid, StackContext* java_ctx);
|
||||
int getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max_depth, StackContext* java_ctx);
|
||||
int getJavaTraceJvmti(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int start_depth, int max_depth);
|
||||
int getJavaTraceInternal(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int max_depth);
|
||||
int convertFrames(jvmtiFrameInfo* jvmti_frames, ASGCT_CallFrame* frames, int num_frames);
|
||||
void fillFrameTypes(ASGCT_CallFrame* frames, int num_frames, NMethod* nmethod);
|
||||
void setThreadInfo(int tid, const char* name, jlong java_thread_id);
|
||||
void updateThreadName(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
void updateJavaThreadNames();
|
||||
@@ -147,81 +132,104 @@ class Profiler {
|
||||
bool excludeTrace(FrameName* fn, CallTrace* trace);
|
||||
void mangle(const char* name, char* buf, size_t size);
|
||||
Engine* selectEngine(const char* event_name);
|
||||
Engine* selectAllocEngine(long alloc_interval);
|
||||
Engine* activeEngine();
|
||||
Error checkJvmCapabilities();
|
||||
|
||||
public:
|
||||
static Profiler _instance;
|
||||
time_t addTimeout(time_t start, int timeout);
|
||||
void startTimer(int timeout);
|
||||
void stopTimer();
|
||||
void timerLoop(int timeout);
|
||||
static void* timerThreadEntry(void* arg);
|
||||
|
||||
void lockAll();
|
||||
void unlockAll();
|
||||
|
||||
void dumpCollapsed(std::ostream& out, Arguments& args);
|
||||
void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree);
|
||||
void dumpText(std::ostream& out, Arguments& args);
|
||||
|
||||
static Profiler* const _instance;
|
||||
|
||||
public:
|
||||
Profiler() :
|
||||
_state(IDLE),
|
||||
_begin_trap(),
|
||||
_end_trap(),
|
||||
_state(NEW),
|
||||
_begin_trap(2),
|
||||
_end_trap(3),
|
||||
_thread_filter(),
|
||||
_call_trace_storage(),
|
||||
_jfr(),
|
||||
_start_time(0),
|
||||
_timer_is_running(false),
|
||||
_max_stack_depth(0),
|
||||
_safe_mode(0),
|
||||
_thread_events_state(JVMTI_DISABLE),
|
||||
_jit_lock(),
|
||||
_stubs_lock(),
|
||||
_java_methods(),
|
||||
_runtime_stubs("[stubs]"),
|
||||
_native_lib_count(0),
|
||||
_original_NativeLibrary_load(NULL) {
|
||||
_native_libs(),
|
||||
_dlopen_entry(NULL) {
|
||||
|
||||
for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
|
||||
_calltrace_buffer[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static Profiler* instance() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
u64 total_samples() { return _total_samples; }
|
||||
time_t uptime() { return time(NULL) - _start_time; }
|
||||
|
||||
Dictionary* classMap() { return &_class_map; }
|
||||
ThreadFilter* threadFilter() { return &_thread_filter; }
|
||||
|
||||
void run(Arguments& args);
|
||||
void runInternal(Arguments& args, std::ostream& out);
|
||||
Error run(Arguments& args);
|
||||
Error runInternal(Arguments& args, std::ostream& out);
|
||||
Error restart(Arguments& args);
|
||||
void shutdown(Arguments& args);
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args, bool reset);
|
||||
Error stop();
|
||||
Error flushJfr();
|
||||
Error dump(std::ostream& out, Arguments& args);
|
||||
void switchThreadEvents(jvmtiEventMode mode);
|
||||
void dumpCollapsed(std::ostream& out, Arguments& args);
|
||||
void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree);
|
||||
void dumpFlat(std::ostream& out, Arguments& args);
|
||||
int convertNativeTrace(int native_frames, const void** callchain, ASGCT_CallFrame* frames);
|
||||
void recordSample(void* ucontext, u64 counter, jint event_type, Event* event);
|
||||
void recordExternalSample(u64 counter, Event* event, int tid, int num_frames, ASGCT_CallFrame* frames);
|
||||
void writeLog(LogLevel level, const char* message);
|
||||
void writeLog(LogLevel level, const char* message, size_t len);
|
||||
|
||||
void updateSymbols(bool kernel_symbols);
|
||||
const void* resolveSymbol(const char* name);
|
||||
NativeCodeCache* findNativeLibrary(const void* address);
|
||||
const char* getLibraryName(const char* native_symbol);
|
||||
CodeCache* findJvmLibrary(const char* lib_name);
|
||||
CodeCache* findNativeLibrary(const void* address);
|
||||
const char* findNativeMethod(const void* address);
|
||||
|
||||
void trapHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void segvHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void setupSignalHandlers();
|
||||
|
||||
// CompiledMethodLoad is also needed to enable DebugNonSafepoints info by default
|
||||
static void JNICALL CompiledMethodLoad(jvmtiEnv* jvmti, jmethodID method,
|
||||
jint code_size, const void* code_addr,
|
||||
jint map_length, const jvmtiAddrLocationMap* map,
|
||||
const void* compile_info) {
|
||||
_instance.addJavaMethod(code_addr, code_size, method);
|
||||
}
|
||||
|
||||
static void JNICALL CompiledMethodUnload(jvmtiEnv* jvmti, jmethodID method,
|
||||
const void* code_addr) {
|
||||
_instance.removeJavaMethod(code_addr, method);
|
||||
instance()->addJavaMethod(code_addr, code_size, method);
|
||||
}
|
||||
|
||||
static void JNICALL DynamicCodeGenerated(jvmtiEnv* jvmti, const char* name,
|
||||
const void* address, jint length) {
|
||||
_instance.addRuntimeStub(address, length, name);
|
||||
instance()->addRuntimeStub(address, length, name);
|
||||
}
|
||||
|
||||
static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
|
||||
_instance.onThreadStart(jvmti, jni, thread);
|
||||
instance()->onThreadStart(jvmti, jni, thread);
|
||||
}
|
||||
|
||||
static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
|
||||
_instance.onThreadEnd(jvmti, jni, thread);
|
||||
instance()->onThreadEnd(jvmti, jni, thread);
|
||||
}
|
||||
|
||||
friend class Recording;
|
||||
|
||||
56
src/safeAccess.h
Normal file
56
src/safeAccess.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 _SAFEACCESS_H
|
||||
#define _SAFEACCESS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
#ifdef __clang__
|
||||
# define NOINLINE __attribute__((noinline))
|
||||
#else
|
||||
# define NOINLINE __attribute__((noinline,noclone))
|
||||
#endif
|
||||
|
||||
|
||||
class SafeAccess {
|
||||
public:
|
||||
NOINLINE __attribute__((aligned(16)))
|
||||
static void* load(void** ptr) {
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
static uintptr_t skipFaultInstruction(uintptr_t pc) {
|
||||
if (pc - (uintptr_t)load < 16) {
|
||||
#if defined(__x86_64__)
|
||||
return *(u16*)pc == 0x8b48 ? 3 : 0; // mov rax, [reg]
|
||||
#elif defined(__i386__)
|
||||
return *(u8*)pc == 0x8b ? 2 : 0; // mov eax, [reg]
|
||||
#elif defined(__arm__) || defined(__thumb__)
|
||||
return (*(instruction_t*)pc & 0x0e50f000) == 0x04100000 ? 4 : 0; // ldr r0, [reg]
|
||||
#elif defined(__aarch64__)
|
||||
return (*(instruction_t*)pc & 0xffc0001f) == 0xf9400000 ? 4 : 0; // ldr x0, [reg]
|
||||
#else
|
||||
return sizeof(instruction_t);
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _SAFEACCESS_H
|
||||
@@ -51,9 +51,19 @@ class SpinLock {
|
||||
__sync_fetch_and_sub(&_lock, 1);
|
||||
}
|
||||
|
||||
bool tryLockShared() {
|
||||
int value;
|
||||
while ((value = _lock) <= 0) {
|
||||
if (__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void lockShared() {
|
||||
int value;
|
||||
while ((value = _lock) == 1 || !__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
|
||||
while ((value = _lock) > 0 || !__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
|
||||
spinPause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class StackFrame {
|
||||
uintptr_t& sp();
|
||||
uintptr_t& fp();
|
||||
|
||||
uintptr_t retval();
|
||||
uintptr_t& retval();
|
||||
uintptr_t arg0();
|
||||
uintptr_t arg1();
|
||||
uintptr_t arg2();
|
||||
@@ -63,7 +63,8 @@ class StackFrame {
|
||||
|
||||
void ret();
|
||||
|
||||
bool pop(bool trust_frame_pointer);
|
||||
bool popStub(instruction_t* entry, const char* name);
|
||||
bool popMethod(instruction_t* entry);
|
||||
|
||||
bool checkInterruptedSyscall();
|
||||
|
||||
@@ -71,10 +72,6 @@ class StackFrame {
|
||||
// 0 = do not use stack snooping heuristics.
|
||||
static int callerLookupSlots();
|
||||
|
||||
// Check if PC looks like a valid return address (i.e. the previous instruction is a CALL).
|
||||
// It's safe to return false to skip return address heuristics.
|
||||
static bool isReturnAddress(instruction_t* pc);
|
||||
|
||||
// Check if PC points to a syscall instruction
|
||||
static bool isSyscall(instruction_t* pc);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 Andrei Pangin
|
||||
* Copyright 2017 BellSoft LLC
|
||||
* 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.
|
||||
@@ -13,85 +12,91 @@
|
||||
* 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.
|
||||
*
|
||||
* Author: Dmitry Samersoff
|
||||
*/
|
||||
|
||||
#if defined(__aarch64__)
|
||||
#ifdef __aarch64__
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/syscall.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
#define REG_FP 29
|
||||
#define REG_LR 30
|
||||
#ifdef __APPLE__
|
||||
# define REG(l, m) _ucontext->uc_mcontext->__ss.__##m
|
||||
#else
|
||||
# define REG(l, m) _ucontext->uc_mcontext.l
|
||||
#endif
|
||||
|
||||
|
||||
uintptr_t& StackFrame::pc() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.pc;
|
||||
return (uintptr_t&)REG(pc, pc);
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::sp() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.sp;
|
||||
return (uintptr_t&)REG(sp, sp);
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::fp() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.regs[REG_FP];
|
||||
return (uintptr_t&)REG(regs[29], fp);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::retval() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[0];
|
||||
uintptr_t& StackFrame::retval() {
|
||||
return (uintptr_t&)REG(regs[0], x[0]);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[0];
|
||||
return (uintptr_t)REG(regs[0], x[0]);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg1() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[1];
|
||||
return (uintptr_t)REG(regs[1], x[1]);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg2() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[2];
|
||||
return (uintptr_t)REG(regs[2], x[2]);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg3() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[3];
|
||||
return (uintptr_t)REG(regs[3], x[3]);
|
||||
}
|
||||
|
||||
void StackFrame::ret() {
|
||||
_ucontext->uc_mcontext.pc = _ucontext->uc_mcontext.regs[REG_LR];
|
||||
pc() = REG(regs[30], lr);
|
||||
}
|
||||
|
||||
bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
if (fp() == sp()) {
|
||||
// Expected frame layout:
|
||||
// sp 000000nnnnnnnnnn [stack]
|
||||
// sp+8 000000nnnnnnnnnn [link]
|
||||
fp() = stackAt(0);
|
||||
pc() = stackAt(1);
|
||||
sp() += 16;
|
||||
} else {
|
||||
// Hope LR holds correct value
|
||||
pc() = _ucontext->uc_mcontext.regs[REG_LR];
|
||||
}
|
||||
return true;
|
||||
|
||||
bool StackFrame::popStub(instruction_t* entry, const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::popMethod(instruction_t* entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::checkInterruptedSyscall() {
|
||||
#ifdef __APPLE__
|
||||
// We are not interested in syscalls that do not check error code, e.g. semaphore_wait_trap
|
||||
if (*(instruction_t*)pc() == 0xd65f03c0) {
|
||||
return true;
|
||||
}
|
||||
// If carry flag is set, the error code is in low byte of x0
|
||||
if (REG(pstate, cpsr) & (1 << 29)) {
|
||||
return (retval() & 0xff) == EINTR || (retval() & 0xff) == ETIMEDOUT;
|
||||
} else {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
}
|
||||
#else
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
#endif
|
||||
}
|
||||
|
||||
int StackFrame::callerLookupSlots() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StackFrame::isReturnAddress(instruction_t* pc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
// svc #0
|
||||
return *pc == 0xd4000001;
|
||||
// svc #0 or svc #80
|
||||
return (*pc & 0xffffefff) == 0xd4000001;
|
||||
}
|
||||
|
||||
#endif // defined(__aarch64__)
|
||||
#endif // __aarch64__
|
||||
|
||||
@@ -32,8 +32,8 @@ uintptr_t& StackFrame::fp() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.arm_fp;
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::retval() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.arm_r0;
|
||||
uintptr_t& StackFrame::retval() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.arm_r0;
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
@@ -56,7 +56,11 @@ void StackFrame::ret() {
|
||||
_ucontext->uc_mcontext.arm_pc = _ucontext->uc_mcontext.arm_lr;
|
||||
}
|
||||
|
||||
bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
bool StackFrame::popStub(instruction_t* entry, const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::popMethod(instruction_t* entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,10 +72,6 @@ int StackFrame::callerLookupSlots() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StackFrame::isReturnAddress(instruction_t* pc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
// swi #0
|
||||
return *pc == 0xef000000;
|
||||
|
||||
@@ -32,8 +32,8 @@ uintptr_t& StackFrame::fp() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_EBP];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::retval() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.gregs[REG_EAX];
|
||||
uintptr_t& StackFrame::retval() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.gregs[REG_EAX];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
@@ -57,18 +57,11 @@ void StackFrame::ret() {
|
||||
sp() += 4;
|
||||
}
|
||||
|
||||
bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
if (trust_frame_pointer && withinCurrentStack(fp())) {
|
||||
sp() = fp() + 8;
|
||||
fp() = stackAt(-2);
|
||||
pc() = stackAt(-1);
|
||||
return true;
|
||||
} else if (fp() == sp() || withinCurrentStack(stackAt(0))) {
|
||||
fp() = stackAt(0);
|
||||
pc() = stackAt(1);
|
||||
sp() += 8;
|
||||
return true;
|
||||
}
|
||||
bool StackFrame::popStub(instruction_t* entry, const char* name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::popMethod(instruction_t* entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -80,17 +73,6 @@ int StackFrame::callerLookupSlots() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
bool StackFrame::isReturnAddress(instruction_t* pc) {
|
||||
if (pc[-5] == 0xe8) {
|
||||
// call rel32
|
||||
return true;
|
||||
} else if (pc[-2] == 0xff && ((pc[-1] & 0xf0) == 0xd0 || (pc[-1] & 0xf0) == 0x10)) {
|
||||
// call reg or call [reg]
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
// int 0x80
|
||||
return pc[0] == 0xcd && pc[1] == 0x80;
|
||||
|
||||
137
src/stackFrame_ppc64.cpp
Normal file
137
src/stackFrame_ppc64.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Authors: Andrei Pangin and Gunter Haug
|
||||
*/
|
||||
|
||||
#if defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
uintptr_t& StackFrame::pc() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.regs->nip;
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::sp() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.regs->gpr[1];
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::fp() {
|
||||
return *((uintptr_t*)_ucontext->uc_mcontext.regs->gpr[1]);
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::retval() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.regs->gpr[3];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[3];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg1() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[4];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg2() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[5];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg3() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs->gpr[6];
|
||||
}
|
||||
|
||||
void StackFrame::ret() {
|
||||
_ucontext->uc_mcontext.regs->nip = _ucontext->uc_mcontext.regs->link;
|
||||
}
|
||||
|
||||
static inline bool inC1EpilogueCrit(uintptr_t pc) {
|
||||
if (!(pc & 0xfff)) {
|
||||
// Make sure we are not at the page boundary, so that reading [pc - 1] is safe
|
||||
return false;
|
||||
}
|
||||
// C1 epilogue and critical section (posX)
|
||||
// 3821**** add r1,r1,xx
|
||||
// pos3 xxxxxxxx
|
||||
// pos2 1000e1eb ld r31,16(r1)
|
||||
// pos1 a603e87f mtlr r31
|
||||
// xxxxxxxx
|
||||
// 2000804e blr
|
||||
instruction_t* inst = (instruction_t*)pc;
|
||||
if (inst[ 1] == 0xebe10010 && inst[2] == 0x7fe803a6 ||
|
||||
inst[ 0] == 0xebe10010 && inst[1] == 0x7fe803a6 ||
|
||||
inst[-1] == 0xebe10010 && inst[0] == 0x7fe803a6) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // not in critical section
|
||||
}
|
||||
|
||||
static inline bool inC2PrologueCrit(uintptr_t pc) {
|
||||
// C2 prologue and critical section
|
||||
// f821**** stdu r1, (xx)r1
|
||||
// pos1 fa950010 std r20,16(r21)
|
||||
instruction_t* inst = (instruction_t*)pc;
|
||||
if (inst[0] == 0xfa950010 && (inst[-1] & 0xffff0000) == 0xf8210000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // not in critical section
|
||||
}
|
||||
|
||||
|
||||
bool StackFrame::popStub(instruction_t* entry, const char* name) {
|
||||
pc() = _ucontext->uc_mcontext.regs->link;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackFrame::popMethod(instruction_t* entry) {
|
||||
// On PPC there is a valid back link to the previous frame at all times. The callee stores
|
||||
// the return address in the caller's frame before it constructs its own frame. After it
|
||||
// has destroyed its frame it restores the link register and returns. A problematic sequence
|
||||
// is the prologue/epilogue of a compiled method before/after frame construction/destruction.
|
||||
// Therefore popping the frame would not help here, as it is not yet/anymore present, rather
|
||||
// more adjusting the pc to the callers pc does the trick. There are two exceptions to this,
|
||||
// One in the prologue of C2 compiled methods and one in the epilogue of C1 compiled methods.
|
||||
if (inC1EpilogueCrit(pc())) {
|
||||
// lr not yet set: use the value stored in the frame
|
||||
pc() = stackAt(2);
|
||||
} else if (inC2PrologueCrit(pc())) {
|
||||
// frame constructed but lr not yet stored in it: just do it here
|
||||
*(((unsigned long *) _ucontext->uc_mcontext.regs->gpr[21]) + 2) = (unsigned long) _ucontext->uc_mcontext.regs->gpr[20];
|
||||
} else {
|
||||
// most probably caller's framer is still on top but pc is already in callee: use caller's pc
|
||||
pc() = _ucontext->uc_mcontext.regs->link;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackFrame::checkInterruptedSyscall() {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
}
|
||||
|
||||
int StackFrame::callerLookupSlots() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
// sc/svc
|
||||
return (*pc & 0x1f) == 17;
|
||||
}
|
||||
|
||||
#endif // defined(__PPC64__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
@@ -17,47 +17,48 @@
|
||||
#ifdef __x86_64__
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/syscall.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
# define REG(l, m) _ucontext->uc_mcontext->__ss.m
|
||||
# define REG(l, m) _ucontext->uc_mcontext->__ss.__##m
|
||||
#else
|
||||
# define REG(l, m) _ucontext->uc_mcontext.gregs[l]
|
||||
# define REG(l, m) _ucontext->uc_mcontext.gregs[REG_##l]
|
||||
#endif
|
||||
|
||||
|
||||
uintptr_t& StackFrame::pc() {
|
||||
return (uintptr_t&)REG(REG_RIP, __rip);
|
||||
return (uintptr_t&)REG(RIP, rip);
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::sp() {
|
||||
return (uintptr_t&)REG(REG_RSP, __rsp);
|
||||
return (uintptr_t&)REG(RSP, rsp);
|
||||
}
|
||||
|
||||
uintptr_t& StackFrame::fp() {
|
||||
return (uintptr_t&)REG(REG_RBP, __rbp);
|
||||
return (uintptr_t&)REG(RBP, rbp);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::retval() {
|
||||
return (uintptr_t)REG(REG_RAX, __rax);
|
||||
uintptr_t& StackFrame::retval() {
|
||||
return (uintptr_t&)REG(RAX, rax);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
return (uintptr_t)REG(REG_RDI, __rdi);
|
||||
return (uintptr_t)REG(RDI, rdi);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg1() {
|
||||
return (uintptr_t)REG(REG_RSI, __rsi);
|
||||
return (uintptr_t)REG(RSI, rsi);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg2() {
|
||||
return (uintptr_t)REG(REG_RDX, __rdx);
|
||||
return (uintptr_t)REG(RDX, rdx);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg3() {
|
||||
return (uintptr_t)REG(REG_RCX, __rcx);
|
||||
return (uintptr_t)REG(RCX, rcx);
|
||||
}
|
||||
|
||||
void StackFrame::ret() {
|
||||
@@ -66,41 +67,59 @@ void StackFrame::ret() {
|
||||
}
|
||||
|
||||
|
||||
static inline bool isFramePrologueEpilogue(uintptr_t pc) {
|
||||
if (pc & 0xfff) {
|
||||
// Make sure we are not at the page boundary, so that reading [pc - 1] is safe
|
||||
unsigned int opcode = *(unsigned int*)(pc - 1);
|
||||
if (opcode == 0xec834855) {
|
||||
// push rbp
|
||||
// sub rsp, $const
|
||||
bool StackFrame::popStub(instruction_t* entry, const char* name) {
|
||||
instruction_t* ip = (instruction_t*)pc();
|
||||
if (ip == entry || *ip == 0xc3
|
||||
|| strncmp(name, "itable", 6) == 0
|
||||
|| strncmp(name, "vtable", 6) == 0
|
||||
|| strcmp(name, "InlineCacheBuffer") == 0)
|
||||
{
|
||||
pc() = stackAt(0);
|
||||
sp() += 8;
|
||||
return true;
|
||||
} else if (entry != NULL && *(unsigned int*)entry == 0xec8b4855) {
|
||||
// The stub begins with
|
||||
// push rbp
|
||||
// mov rbp, rsp
|
||||
if (ip == entry + 1) {
|
||||
pc() = stackAt(1);
|
||||
sp() += 16;
|
||||
return true;
|
||||
} else if (opcode == 0xec8b4855) {
|
||||
// push rbp
|
||||
// mov rbp, rsp
|
||||
} else if (withinCurrentStack(fp())) {
|
||||
sp() = fp() + 16;
|
||||
fp() = stackAt(-2);
|
||||
pc() = stackAt(-1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (*(unsigned char*)pc == 0x5d && *(unsigned short*)(pc + 1) == 0x0585) {
|
||||
// pop rbp
|
||||
// test [polling_page], eax
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
if (trust_frame_pointer && withinCurrentStack(fp())) {
|
||||
sp() = fp() + 16;
|
||||
fp() = stackAt(-2);
|
||||
pc() = stackAt(-1);
|
||||
bool StackFrame::popMethod(instruction_t* entry) {
|
||||
instruction_t* ip = (instruction_t*)pc();
|
||||
if (ip <= entry || *ip == 0xc3 || *ip == 0x55 // ret or push rbp
|
||||
|| (((uintptr_t)ip & 0xfff) && ip[-1] == 0x5d) // after pop rbp
|
||||
|| (ip[0] == 0x41 && ip[1] == 0x85 && ip[2] == 0x02 && ip[3] == 0xc3)) // poll return
|
||||
{
|
||||
pc() = stackAt(0);
|
||||
sp() += 8;
|
||||
return true;
|
||||
} else if (fp() == sp() || withinCurrentStack(stackAt(0)) || isFramePrologueEpilogue(pc())) {
|
||||
} else if (*ip == 0x5d) {
|
||||
// pop rbp
|
||||
fp() = stackAt(0);
|
||||
pc() = stackAt(1);
|
||||
sp() += 16;
|
||||
return true;
|
||||
} else if (ip <= entry + 15 && ((uintptr_t)ip & 0xfff) && ip[-1] == 0x55) {
|
||||
// push rbp
|
||||
pc() = stackAt(1);
|
||||
sp() += 16;
|
||||
return true;
|
||||
} else if (ip <= entry + 7 && ip[0] == 0x48 && ip[1] == 0x89 && ip[2] == 0x6c && ip[3] == 0x24) {
|
||||
// mov [rsp + #off], rbp
|
||||
sp() += ip[4] + 16;
|
||||
pc() = stackAt(-1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -113,7 +132,7 @@ bool StackFrame::checkInterruptedSyscall() {
|
||||
}
|
||||
// If CF is set, the error code is in low byte of eax,
|
||||
// some other syscalls (ulock_wait) do not set CF when interrupted
|
||||
if (REG(REG_EFL, __rflags) & 1) {
|
||||
if (REG(EFL, rflags) & 1) {
|
||||
return (retval() & 0xff) == EINTR || (retval() & 0xff) == ETIMEDOUT;
|
||||
} else {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
@@ -122,7 +141,7 @@ bool StackFrame::checkInterruptedSyscall() {
|
||||
if (retval() == (uintptr_t)-EINTR) {
|
||||
// Workaround for JDK-8237858: restart the interrupted poll() manually.
|
||||
// Check if the previous instruction is mov eax, SYS_poll with infinite timeout
|
||||
if (arg2() == (uintptr_t)-1) {
|
||||
if ((int)arg2() == -1) {
|
||||
uintptr_t pc = this->pc();
|
||||
if ((pc & 0xfff) >= 7 && *(unsigned char*)(pc - 7) == 0xb8 && *(int*)(pc - 6) == SYS_poll) {
|
||||
this->pc() = pc - 7;
|
||||
@@ -138,17 +157,6 @@ int StackFrame::callerLookupSlots() {
|
||||
return 7;
|
||||
}
|
||||
|
||||
bool StackFrame::isReturnAddress(instruction_t* pc) {
|
||||
if (pc[-5] == 0xe8) {
|
||||
// call rel32
|
||||
return true;
|
||||
} else if (pc[-2] == 0xff && ((pc[-1] & 0xf0) == 0xd0 || (pc[-1] & 0xf0) == 0x10)) {
|
||||
// call reg or call [reg]
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
return pc[0] == 0x0f && pc[1] == 0x05;
|
||||
}
|
||||
|
||||
154
src/stackWalker.cpp
Normal file
154
src/stackWalker.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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 "stackWalker.h"
|
||||
#include "dwarf.h"
|
||||
#include "profiler.h"
|
||||
#include "safeAccess.h"
|
||||
#include "stackFrame.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
const intptr_t MIN_VALID_PC = 0x1000;
|
||||
const intptr_t MAX_WALK_SIZE = 0x100000;
|
||||
const intptr_t MAX_FRAME_SIZE = 0x40000;
|
||||
|
||||
|
||||
int StackWalker::walkFP(void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx) {
|
||||
const void* pc;
|
||||
uintptr_t fp;
|
||||
uintptr_t sp;
|
||||
uintptr_t bottom = (uintptr_t)&sp + MAX_WALK_SIZE;
|
||||
|
||||
if (ucontext == NULL) {
|
||||
pc = __builtin_return_address(0);
|
||||
fp = (uintptr_t)__builtin_frame_address(1);
|
||||
sp = (uintptr_t)__builtin_frame_address(0);
|
||||
} else {
|
||||
StackFrame frame(ucontext);
|
||||
pc = (const void*)frame.pc();
|
||||
fp = frame.fp();
|
||||
sp = frame.sp();
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
|
||||
// Walk until the bottom of the stack or until the first Java frame
|
||||
while (depth < max_depth) {
|
||||
if (CodeHeap::contains(pc)) {
|
||||
java_ctx->set(pc, sp, fp);
|
||||
break;
|
||||
}
|
||||
|
||||
callchain[depth++] = pc;
|
||||
|
||||
// Check if the next frame is below on the current stack
|
||||
if (fp < sp || fp >= sp + MAX_FRAME_SIZE || fp >= bottom) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Frame pointer must be word aligned
|
||||
if ((fp & (sizeof(uintptr_t) - 1)) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
pc = stripPointer(SafeAccess::load((void**)fp + FRAME_PC_SLOT));
|
||||
if (pc < (const void*)MIN_VALID_PC || pc > (const void*)-MIN_VALID_PC) {
|
||||
break;
|
||||
}
|
||||
|
||||
sp = fp + (FRAME_PC_SLOT + 1) * sizeof(void*);
|
||||
fp = *(uintptr_t*)fp;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
int StackWalker::walkDwarf(void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx) {
|
||||
const void* pc;
|
||||
uintptr_t fp;
|
||||
uintptr_t sp;
|
||||
uintptr_t prev_sp;
|
||||
uintptr_t bottom = (uintptr_t)&sp + MAX_WALK_SIZE;
|
||||
|
||||
if (ucontext == NULL) {
|
||||
pc = __builtin_return_address(0);
|
||||
fp = (uintptr_t)__builtin_frame_address(1);
|
||||
sp = (uintptr_t)__builtin_frame_address(0);
|
||||
} else {
|
||||
StackFrame frame(ucontext);
|
||||
pc = (const void*)frame.pc();
|
||||
fp = frame.fp();
|
||||
sp = frame.sp();
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
Profiler* profiler = Profiler::instance();
|
||||
|
||||
// Walk until the bottom of the stack or until the first Java frame
|
||||
while (depth < max_depth) {
|
||||
if (CodeHeap::contains(pc)) {
|
||||
java_ctx->set(pc, sp, fp);
|
||||
break;
|
||||
}
|
||||
|
||||
callchain[depth++] = pc;
|
||||
prev_sp = sp;
|
||||
|
||||
FrameDesc* f;
|
||||
CodeCache* cc = profiler->findNativeLibrary(pc);
|
||||
if (cc == NULL || (f = cc->findFrameDesc(pc)) == NULL) {
|
||||
f = &FrameDesc::default_frame;
|
||||
}
|
||||
|
||||
u8 cfa_reg = (u8)f->cfa;
|
||||
int cfa_off = f->cfa >> 8;
|
||||
if (cfa_reg == DW_REG_SP) {
|
||||
sp = sp + cfa_off;
|
||||
} else if (cfa_reg == DW_REG_FP) {
|
||||
sp = fp + cfa_off;
|
||||
} else if (cfa_reg == DW_REG_PLT) {
|
||||
sp += ((uintptr_t)pc & 15) >= 11 ? cfa_off * 2 : cfa_off;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the next frame is below on the current stack
|
||||
if (sp < prev_sp || sp >= prev_sp + MAX_FRAME_SIZE || sp >= bottom) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stack pointer must be word aligned
|
||||
if ((sp & (sizeof(uintptr_t) - 1)) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (f->fp_off & DW_PC_OFFSET) {
|
||||
pc = (const char*)pc + (f->fp_off >> 1);
|
||||
} else {
|
||||
if (f->fp_off != DW_SAME_FP && f->fp_off < MAX_FRAME_SIZE && f->fp_off > -MAX_FRAME_SIZE) {
|
||||
fp = (uintptr_t)SafeAccess::load((void**)(sp + f->fp_off));
|
||||
}
|
||||
pc = stripPointer(SafeAccess::load((void**)sp - 1));
|
||||
}
|
||||
|
||||
if (pc < (const void*)MIN_VALID_PC || pc > (const void*)-MIN_VALID_PC) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
41
src/stackWalker.h
Normal file
41
src/stackWalker.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 _STACKWALKER_H
|
||||
#define _STACKWALKER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
struct StackContext {
|
||||
const void* pc;
|
||||
uintptr_t sp;
|
||||
uintptr_t fp;
|
||||
|
||||
void set(const void* pc, uintptr_t sp, uintptr_t fp) {
|
||||
this->pc = pc;
|
||||
this->sp = sp;
|
||||
this->fp = fp;
|
||||
}
|
||||
};
|
||||
|
||||
class StackWalker {
|
||||
public:
|
||||
static int walkFP(void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx);
|
||||
static int walkDwarf(void* ucontext, const void** callchain, int max_depth, StackContext* java_ctx);
|
||||
};
|
||||
|
||||
#endif // _STACKWALKER_H
|
||||
@@ -29,8 +29,10 @@ class Symbols {
|
||||
static bool _have_kernel_symbols;
|
||||
|
||||
public:
|
||||
static void parseKernelSymbols(NativeCodeCache* cc);
|
||||
static void parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols);
|
||||
static void parseKernelSymbols(CodeCache* cc);
|
||||
static void parseLibraries(CodeCacheArray* array, bool kernel_symbols);
|
||||
|
||||
static void makePatchable(CodeCache* cc);
|
||||
|
||||
static bool haveKernelSymbols() {
|
||||
return _have_kernel_symbols;
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "symbols.h"
|
||||
#include "arch.h"
|
||||
#include "dwarf.h"
|
||||
#include "fdtransferClient.h"
|
||||
#include "log.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class SymbolDesc {
|
||||
@@ -76,7 +76,8 @@ class MemoryMapDesc {
|
||||
}
|
||||
|
||||
const char* file() { return _file; }
|
||||
bool isExecutable() { return _perm[0] == 'r' && _perm[2] == 'x'; }
|
||||
bool isReadable() { return _perm[0] == 'r'; }
|
||||
bool isExecutable() { return _perm[2] == 'x'; }
|
||||
const char* addr() { return (const char*)strtoul(_addr, NULL, 16); }
|
||||
const char* end() { return (const char*)strtoul(_end, NULL, 16); }
|
||||
unsigned long offs() { return strtoul(_offs, NULL, 16); }
|
||||
@@ -88,30 +89,34 @@ class MemoryMapDesc {
|
||||
const unsigned char ELFCLASS_SUPPORTED = ELFCLASS64;
|
||||
typedef Elf64_Ehdr ElfHeader;
|
||||
typedef Elf64_Shdr ElfSection;
|
||||
typedef Elf64_Phdr ElfProgramHeader;
|
||||
typedef Elf64_Nhdr ElfNote;
|
||||
typedef Elf64_Sym ElfSymbol;
|
||||
typedef Elf64_Rel ElfRelocation;
|
||||
typedef Elf64_Dyn ElfDyn;
|
||||
#define ELF_R_SYM ELF64_R_SYM
|
||||
#else
|
||||
const unsigned char ELFCLASS_SUPPORTED = ELFCLASS32;
|
||||
typedef Elf32_Ehdr ElfHeader;
|
||||
typedef Elf32_Shdr ElfSection;
|
||||
typedef Elf32_Phdr ElfProgramHeader;
|
||||
typedef Elf32_Nhdr ElfNote;
|
||||
typedef Elf32_Sym ElfSymbol;
|
||||
typedef Elf32_Rel ElfRelocation;
|
||||
typedef Elf32_Dyn ElfDyn;
|
||||
#define ELF_R_SYM ELF32_R_SYM
|
||||
#endif // __LP64__
|
||||
|
||||
|
||||
class ElfParser {
|
||||
private:
|
||||
NativeCodeCache* _cc;
|
||||
CodeCache* _cc;
|
||||
const char* _base;
|
||||
const char* _file_name;
|
||||
ElfHeader* _header;
|
||||
const char* _sections;
|
||||
|
||||
ElfParser(NativeCodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) {
|
||||
ElfParser(CodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) {
|
||||
_cc = cc;
|
||||
_base = base;
|
||||
_file_name = file_name;
|
||||
@@ -119,7 +124,7 @@ class ElfParser {
|
||||
_sections = (const char*)addr + _header->e_shoff;
|
||||
}
|
||||
|
||||
bool valid_header() {
|
||||
bool validHeader() {
|
||||
unsigned char* ident = _header->e_ident;
|
||||
return ident[0] == 0x7f && ident[1] == 'E' && ident[2] == 'L' && ident[3] == 'F'
|
||||
&& ident[4] == ELFCLASS_SUPPORTED && ident[5] == ELFDATA2LSB && ident[6] == EV_CURRENT
|
||||
@@ -134,8 +139,15 @@ class ElfParser {
|
||||
return (const char*)_header + section->sh_offset;
|
||||
}
|
||||
|
||||
ElfSection* findSection(uint32_t type, const char* name);
|
||||
const char* at(ElfProgramHeader* pheader) {
|
||||
return _header->e_type == ET_EXEC ? (const char*)pheader->p_vaddr : (const char*)_header + pheader->p_vaddr;
|
||||
}
|
||||
|
||||
ElfSection* findSection(uint32_t type, const char* name);
|
||||
ElfProgramHeader* findProgramHeader(uint32_t type);
|
||||
|
||||
void parseDynamicSection();
|
||||
void parseDwarfInfo();
|
||||
void loadSymbols(bool use_debug);
|
||||
bool loadSymbolsUsingBuildId();
|
||||
bool loadSymbolsUsingDebugLink();
|
||||
@@ -143,8 +155,9 @@ class ElfParser {
|
||||
void addRelocationSymbols(ElfSection* reltab, const char* plt);
|
||||
|
||||
public:
|
||||
static bool parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug);
|
||||
static void parseMem(NativeCodeCache* cc, const char* base);
|
||||
static void parseProgramHeaders(CodeCache* cc, const char* base);
|
||||
static bool parseFile(CodeCache* cc, const char* base, const char* file_name, bool use_debug);
|
||||
static void parseMem(CodeCache* cc, const char* base);
|
||||
};
|
||||
|
||||
|
||||
@@ -163,7 +176,20 @@ ElfSection* ElfParser::findSection(uint32_t type, const char* name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ElfParser::parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug) {
|
||||
ElfProgramHeader* ElfParser::findProgramHeader(uint32_t type) {
|
||||
const char* pheaders = (const char*)_header + _header->e_phoff;
|
||||
|
||||
for (int i = 0; i < _header->e_phnum; i++) {
|
||||
ElfProgramHeader* pheader = (ElfProgramHeader*)(pheaders + i * _header->e_phentsize);
|
||||
if (pheader->p_type == type) {
|
||||
return pheader;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ElfParser::parseFile(CodeCache* cc, const char* base, const char* file_name, bool use_debug) {
|
||||
int fd = open(file_name, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
@@ -174,30 +200,91 @@ bool ElfParser::parseFile(NativeCodeCache* cc, const char* base, const char* fil
|
||||
close(fd);
|
||||
|
||||
if (addr == MAP_FAILED) {
|
||||
if (strcmp(file_name, "/") == 0) {
|
||||
// https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018
|
||||
fprintf(stderr, "Could not parse symbols due to the OS bug\n");
|
||||
} else {
|
||||
fprintf(stderr, "Could not parse symbols from %s: %s\n", file_name, strerror(errno));
|
||||
}
|
||||
Log::warn("Could not parse symbols from %s: %s", file_name, strerror(errno));
|
||||
} else {
|
||||
ElfParser elf(cc, base, addr, file_name);
|
||||
elf.loadSymbols(use_debug);
|
||||
if (elf.validHeader()) {
|
||||
elf.loadSymbols(use_debug);
|
||||
}
|
||||
munmap(addr, length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ElfParser::parseMem(NativeCodeCache* cc, const char* base) {
|
||||
void ElfParser::parseMem(CodeCache* cc, const char* base) {
|
||||
ElfParser elf(cc, base, base);
|
||||
elf.loadSymbols(false);
|
||||
if (elf.validHeader()) {
|
||||
elf.loadSymbols(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ElfParser::parseProgramHeaders(CodeCache* cc, const char* base) {
|
||||
ElfParser elf(cc, base, base);
|
||||
if (elf.validHeader()) {
|
||||
cc->setTextBase(base);
|
||||
elf.parseDynamicSection();
|
||||
elf.parseDwarfInfo();
|
||||
}
|
||||
}
|
||||
|
||||
void ElfParser::parseDynamicSection() {
|
||||
ElfProgramHeader* dynamic = findProgramHeader(PT_DYNAMIC);
|
||||
if (dynamic != NULL) {
|
||||
void** got_start = NULL;
|
||||
size_t pltrelsz = 0;
|
||||
size_t relsz = 0;
|
||||
size_t relent = 0;
|
||||
size_t relcount = 0;
|
||||
|
||||
const char* dyn_start = at(dynamic);
|
||||
const char* dyn_end = dyn_start + dynamic->p_memsz;
|
||||
for (ElfDyn* dyn = (ElfDyn*)dyn_start; dyn < (ElfDyn*)dyn_end; dyn++) {
|
||||
switch (dyn->d_tag) {
|
||||
case DT_PLTGOT:
|
||||
got_start = (void**)dyn->d_un.d_ptr + 3;
|
||||
break;
|
||||
case DT_PLTRELSZ:
|
||||
pltrelsz = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_RELASZ:
|
||||
case DT_RELSZ:
|
||||
relsz = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_RELAENT:
|
||||
case DT_RELENT:
|
||||
relent = dyn->d_un.d_val;
|
||||
break;
|
||||
case DT_RELACOUNT:
|
||||
case DT_RELCOUNT:
|
||||
relcount = dyn->d_un.d_val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (got_start != NULL && relent != 0) {
|
||||
if (pltrelsz != 0) {
|
||||
// The number of entries in .got.plt section matches the number of entries in .rela.plt
|
||||
_cc->setGlobalOffsetTable(got_start, pltrelsz / relent);
|
||||
} else if (relsz != 0) {
|
||||
// RELRO technique: .got.plt has been merged into .got and made read-only.
|
||||
// Count the number of entries in .rela.dyn, excluding R_X86_64_RELATIVE.
|
||||
_cc->setGlobalOffsetTable(got_start, relsz / relent - relcount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ElfParser::parseDwarfInfo() {
|
||||
if (!DWARF_SUPPORTED) return;
|
||||
|
||||
ElfProgramHeader* eh_frame_hdr = findProgramHeader(PT_GNU_EH_FRAME);
|
||||
if (eh_frame_hdr != NULL) {
|
||||
DwarfParser dwarf(_cc->name(), _base, at(eh_frame_hdr));
|
||||
_cc->setDwarfTable(dwarf.table(), dwarf.count());
|
||||
}
|
||||
}
|
||||
|
||||
void ElfParser::loadSymbols(bool use_debug) {
|
||||
if (!valid_header()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for debug symbols in the original .so
|
||||
ElfSection* section = findSection(SHT_SYMTAB, ".symtab");
|
||||
if (section != NULL) {
|
||||
@@ -219,8 +306,8 @@ void ElfParser::loadSymbols(bool use_debug) {
|
||||
}
|
||||
|
||||
loaded:
|
||||
// Synthesize names for PLT stubs
|
||||
if (use_debug) {
|
||||
// Synthesize names for PLT stubs
|
||||
ElfSection* plt = findSection(SHT_PROGBITS, ".plt");
|
||||
ElfSection* reltab = findSection(SHT_RELA, ".rela.plt");
|
||||
if (reltab == NULL) {
|
||||
@@ -307,7 +394,10 @@ void ElfParser::loadSymbolTable(ElfSection* symtab) {
|
||||
for (; symbols < symbols_end; symbols += symtab->sh_entsize) {
|
||||
ElfSymbol* sym = (ElfSymbol*)symbols;
|
||||
if (sym->st_name != 0 && sym->st_value != 0) {
|
||||
_cc->add(_base + sym->st_value, (int)sym->st_size, strings + sym->st_name);
|
||||
// Skip special AArch64 mapping symbols: $x and $d
|
||||
if (sym->st_size != 0 || sym->st_info != 0 || strings[sym->st_name] != '$') {
|
||||
_cc->add(_base + sym->st_value, (int)sym->st_size, strings + sym->st_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,13 +434,32 @@ Mutex Symbols::_parse_lock;
|
||||
std::set<const void*> Symbols::_parsed_libraries;
|
||||
bool Symbols::_have_kernel_symbols = false;
|
||||
|
||||
void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
|
||||
std::ifstream maps("/proc/kallsyms");
|
||||
std::string str;
|
||||
void Symbols::parseKernelSymbols(CodeCache* cc) {
|
||||
int fd;
|
||||
if (FdTransferClient::hasPeer()) {
|
||||
fd = FdTransferClient::requestKallsymsFd();
|
||||
} else {
|
||||
fd = open("/proc/kallsyms", O_RDONLY);
|
||||
}
|
||||
|
||||
while (std::getline(maps, str)) {
|
||||
str += "_[k]";
|
||||
SymbolDesc symbol(str.c_str());
|
||||
if (fd == -1) {
|
||||
Log::warn("open(\"/proc/kallsyms\"): %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* f = fdopen(fd, "r");
|
||||
if (f == NULL) {
|
||||
Log::warn("fdopen(): %s", strerror(errno));
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
char str[256];
|
||||
while (fgets(str, sizeof(str) - 8, f) != NULL) {
|
||||
size_t len = strlen(str) - 1; // trim the '\n'
|
||||
strcpy(str + len, "_[k]");
|
||||
|
||||
SymbolDesc symbol(str);
|
||||
char type = symbol.type();
|
||||
if (type == 'T' || type == 't' || type == 'W' || type == 'w') {
|
||||
const char* addr = symbol.addr();
|
||||
@@ -360,48 +469,83 @@ void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void Symbols::parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols) {
|
||||
void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
MutexLocker ml(_parse_lock);
|
||||
|
||||
if (kernel_symbols && !haveKernelSymbols()) {
|
||||
NativeCodeCache* cc = new NativeCodeCache("[kernel]");
|
||||
CodeCache* cc = new CodeCache("[kernel]");
|
||||
parseKernelSymbols(cc);
|
||||
|
||||
if (haveKernelSymbols()) {
|
||||
cc->sort();
|
||||
array[count] = cc;
|
||||
atomicInc(count);
|
||||
array->add(cc);
|
||||
} else {
|
||||
delete cc;
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
std::string str;
|
||||
FILE* f = fopen("/proc/self/maps", "r");
|
||||
if (f == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (count < size && std::getline(maps, str)) {
|
||||
MemoryMapDesc map(str.c_str());
|
||||
if (map.isExecutable() && map.file() != NULL && map.file()[0] != 0) {
|
||||
const char* image_base = map.addr();
|
||||
const char* last_readable_base = NULL;
|
||||
const char* image_end = NULL;
|
||||
char* str = NULL;
|
||||
size_t str_size = 0;
|
||||
ssize_t len;
|
||||
|
||||
while ((len = getline(&str, &str_size, f)) > 0) {
|
||||
str[len - 1] = 0;
|
||||
|
||||
MemoryMapDesc map(str);
|
||||
if (!map.isReadable() || map.file() == NULL || map.file()[0] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* image_base = map.addr();
|
||||
if (image_base != image_end) last_readable_base = image_base;
|
||||
image_end = map.end();
|
||||
|
||||
if (map.isExecutable()) {
|
||||
if (!_parsed_libraries.insert(image_base).second) {
|
||||
continue; // the library was already parsed
|
||||
}
|
||||
|
||||
NativeCodeCache* cc = new NativeCodeCache(map.file(), image_base, map.end());
|
||||
int count = array->count();
|
||||
if (count >= MAX_NATIVE_LIBS) {
|
||||
break;
|
||||
}
|
||||
|
||||
CodeCache* cc = new CodeCache(map.file(), count, image_base, image_end);
|
||||
|
||||
if (map.inode() != 0) {
|
||||
ElfParser::parseFile(cc, image_base - map.offs(), map.file(), true);
|
||||
// Be careful: executable file is not always ELF, e.g. classes.jsa
|
||||
if ((image_base -= map.offs()) >= last_readable_base) {
|
||||
ElfParser::parseProgramHeaders(cc, image_base);
|
||||
}
|
||||
ElfParser::parseFile(cc, image_base, map.file(), true);
|
||||
} else if (strcmp(map.file(), "[vdso]") == 0) {
|
||||
ElfParser::parseMem(cc, image_base);
|
||||
}
|
||||
|
||||
cc->sort();
|
||||
array[count] = cc;
|
||||
atomicInc(count);
|
||||
array->add(cc);
|
||||
}
|
||||
}
|
||||
|
||||
free(str);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void Symbols::makePatchable(CodeCache* cc) {
|
||||
uintptr_t got_start = (uintptr_t)cc->gotStart() & ~OS::page_mask;
|
||||
uintptr_t got_size = ((uintptr_t)cc->gotEnd() - got_start + OS::page_mask) & ~OS::page_mask;
|
||||
mprotect((void*)got_start, got_size, PROT_READ | PROT_WRITE);
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 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,113 +16,95 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include "symbols.h"
|
||||
#include "arch.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
class MachOParser {
|
||||
private:
|
||||
NativeCodeCache* _cc;
|
||||
CodeCache* _cc;
|
||||
const mach_header* _image_base;
|
||||
|
||||
static const char* add(const void* base, uint32_t offset) {
|
||||
static const char* add(const void* base, uint64_t offset) {
|
||||
return (const char*)base + offset;
|
||||
}
|
||||
|
||||
void loadSymbols(mach_header_64* header, symtab_command* symtab) {
|
||||
nlist_64* symbol_table = (nlist_64*)add(header, symtab->symoff);
|
||||
const char* str_table = add(header, symtab->stroff);
|
||||
void findGlobalOffsetTable(const segment_command_64* sc) {
|
||||
const section_64* section = (const section_64*)add(sc, sizeof(segment_command_64));
|
||||
for (uint32_t i = 0; i < sc->nsects; i++) {
|
||||
if (strcmp(section->sectname, "__la_symbol_ptr") == 0) {
|
||||
_cc->setGlobalOffsetTable((void**)add(_image_base, section->addr), section->size / sizeof(void*));
|
||||
break;
|
||||
}
|
||||
section++;
|
||||
}
|
||||
}
|
||||
|
||||
void loadSymbols(const symtab_command* symtab, const char* text_base, const char* link_base) {
|
||||
const nlist_64* sym = (const nlist_64*)add(link_base, symtab->symoff);
|
||||
const char* str_table = add(link_base, symtab->stroff);
|
||||
|
||||
for (uint32_t i = 0; i < symtab->nsyms; i++) {
|
||||
nlist_64 sym = symbol_table[i];
|
||||
if ((sym.n_type & 0xee) == 0x0e && sym.n_value != 0) {
|
||||
const char* addr = add(_image_base, sym.n_value);
|
||||
const char* name = str_table + sym.n_un.n_strx;
|
||||
if ((sym->n_type & 0xee) == 0x0e && sym->n_value != 0) {
|
||||
const char* addr = text_base + sym->n_value;
|
||||
const char* name = str_table + sym->n_un.n_strx;
|
||||
if (name[0] == '_') name++;
|
||||
_cc->add(addr, 0, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parseMachO(mach_header_64* header) {
|
||||
load_command* lc = (load_command*)(header + 1);
|
||||
|
||||
for (uint32_t i = 0; i < header->ncmds; i++) {
|
||||
if (lc->cmd == LC_SYMTAB) {
|
||||
loadSymbols(header, (symtab_command*)lc);
|
||||
break;
|
||||
}
|
||||
lc = (load_command*)add(lc, lc->cmdsize);
|
||||
}
|
||||
}
|
||||
|
||||
void parseFatObject(fat_header* header) {
|
||||
int narch = header->nfat_arch;
|
||||
fat_arch* arch = (fat_arch*)(header + 1);
|
||||
|
||||
for (uint32_t i = 0; i < narch; i++) {
|
||||
if (arch[i].cputype == _image_base->cputype &&
|
||||
arch[i].cpusubtype == _image_base->cpusubtype) {
|
||||
parseMachO((mach_header_64*)add(header, arch[i].offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The same as parseFatObject, but fields are big-endian
|
||||
void parseFatObjectBE(fat_header* header) {
|
||||
int narch = htonl(header->nfat_arch);
|
||||
fat_arch* arch = (fat_arch*)(header + 1);
|
||||
|
||||
for (uint32_t i = 0; i < narch; i++) {
|
||||
if (htonl(arch[i].cputype) == _image_base->cputype &&
|
||||
htonl(arch[i].cpusubtype) == _image_base->cpusubtype) {
|
||||
parseMachO((mach_header_64*)add(header, htonl(arch[i].offset)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse(mach_header* header) {
|
||||
uint32_t magic = header->magic;
|
||||
if (magic == MH_MAGIC_64) {
|
||||
parseMachO((mach_header_64*)header);
|
||||
} else if (magic == FAT_MAGIC) {
|
||||
parseFatObject((fat_header*)header);
|
||||
} else if (magic == FAT_CIGAM) {
|
||||
parseFatObjectBE((fat_header*)header);
|
||||
sym++;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
MachOParser(NativeCodeCache* cc, const mach_header* image_base) : _cc(cc), _image_base(image_base) {
|
||||
MachOParser(CodeCache* cc, const mach_header* image_base) : _cc(cc), _image_base(image_base) {
|
||||
}
|
||||
|
||||
static void parseFile(NativeCodeCache* cc, const mach_header* image_base, const char* file_name) {
|
||||
int fd = open(file_name, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return;
|
||||
bool parse() {
|
||||
if (_image_base->magic != MH_MAGIC_64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t length = (size_t)lseek(fd, 0, SEEK_END);
|
||||
void* addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
const mach_header_64* header = (const mach_header_64*)_image_base;
|
||||
const load_command* lc = (const load_command*)(header + 1);
|
||||
|
||||
if (addr == MAP_FAILED) {
|
||||
fprintf(stderr, "Could not parse symbols from %s: %s\n", file_name, strerror(errno));
|
||||
} else {
|
||||
MachOParser parser(cc, image_base);
|
||||
parser.parse((mach_header*)addr);
|
||||
munmap(addr, length);
|
||||
const char* UNDEFINED = (const char*)-1;
|
||||
const char* text_base = UNDEFINED;
|
||||
const char* link_base = UNDEFINED;
|
||||
|
||||
for (uint32_t i = 0; i < header->ncmds; i++) {
|
||||
if (lc->cmd == LC_SEGMENT_64) {
|
||||
const segment_command_64* sc = (const segment_command_64*)lc;
|
||||
if ((sc->initprot & 4) != 0) {
|
||||
if (text_base == UNDEFINED || strcmp(sc->segname, "__TEXT") == 0) {
|
||||
text_base = (const char*)_image_base - sc->vmaddr;
|
||||
_cc->setTextBase(text_base);
|
||||
_cc->updateBounds(_image_base, add(_image_base, sc->vmsize));
|
||||
}
|
||||
} else if ((sc->initprot & 7) == 1) {
|
||||
if (link_base == UNDEFINED || strcmp(sc->segname, "__LINKEDIT") == 0) {
|
||||
link_base = text_base + sc->vmaddr - sc->fileoff;
|
||||
}
|
||||
} else if ((sc->initprot & 2) != 0) {
|
||||
if (strcmp(sc->segname, "__DATA") == 0) {
|
||||
findGlobalOffsetTable(sc);
|
||||
}
|
||||
}
|
||||
} else if (lc->cmd == LC_SYMTAB) {
|
||||
if (text_base == UNDEFINED || link_base == UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
loadSymbols((const symtab_command*)lc, text_base, link_base);
|
||||
break;
|
||||
}
|
||||
lc = (const load_command*)add(lc, lc->cmdsize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,28 +113,46 @@ Mutex Symbols::_parse_lock;
|
||||
std::set<const void*> Symbols::_parsed_libraries;
|
||||
bool Symbols::_have_kernel_symbols = false;
|
||||
|
||||
void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
|
||||
void Symbols::parseKernelSymbols(CodeCache* cc) {
|
||||
}
|
||||
|
||||
void Symbols::parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols) {
|
||||
void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
|
||||
MutexLocker ml(_parse_lock);
|
||||
uint32_t images = _dyld_image_count();
|
||||
|
||||
for (uint32_t i = 0; i < images && count < size; i++) {
|
||||
for (uint32_t i = 0; i < images; i++) {
|
||||
const mach_header* image_base = _dyld_get_image_header(i);
|
||||
if (!_parsed_libraries.insert(image_base).second) {
|
||||
if (image_base == NULL || !_parsed_libraries.insert(image_base).second) {
|
||||
continue; // the library was already parsed
|
||||
}
|
||||
|
||||
int count = array->count();
|
||||
if (count >= MAX_NATIVE_LIBS) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* path = _dyld_get_image_name(i);
|
||||
|
||||
NativeCodeCache* cc = new NativeCodeCache(path);
|
||||
MachOParser::parseFile(cc, image_base, path);
|
||||
// Protect the library from unloading while parsing symbols
|
||||
void* handle = dlopen(path, RTLD_LAZY | RTLD_NOLOAD);
|
||||
if (handle == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CodeCache* cc = new CodeCache(path, count);
|
||||
MachOParser parser(cc, image_base);
|
||||
if (!parser.parse()) {
|
||||
Log::warn("Could not parse symbols from %s", path);
|
||||
}
|
||||
dlclose(handle);
|
||||
|
||||
cc->sort();
|
||||
array[count] = cc;
|
||||
atomicInc(count);
|
||||
array->add(cc);
|
||||
}
|
||||
}
|
||||
|
||||
void Symbols::makePatchable(CodeCache* cc) {
|
||||
// Global Offset Table is always writable
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user