mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a20d771635 | ||
|
|
3a44bb6ba6 | ||
|
|
f73ac36c9c | ||
|
|
c94b1685cf | ||
|
|
90d4420d3f | ||
|
|
a96501a26a | ||
|
|
39f84be219 | ||
|
|
4af327e2c1 | ||
|
|
61919df2ff | ||
|
|
26880ecb22 | ||
|
|
af02f6b0fb | ||
|
|
c11d4ca487 | ||
|
|
b2dfe9b5b0 | ||
|
|
5585a77355 | ||
|
|
b5a67c2b95 | ||
|
|
9aea04a56a | ||
|
|
a48f77b380 | ||
|
|
8c5f6c1357 | ||
|
|
88730d4388 | ||
|
|
d132777a60 | ||
|
|
04dac10d41 | ||
|
|
5290b81190 | ||
|
|
93e1f963ef | ||
|
|
a18af69f8b | ||
|
|
60cac04c24 | ||
|
|
3d7e8efd3b | ||
|
|
d26d69e550 | ||
|
|
8160e49c14 | ||
|
|
731ac31064 | ||
|
|
013ceee55d | ||
|
|
f7ef0e97b2 | ||
|
|
c01fe588ce | ||
|
|
e498ad27d2 | ||
|
|
edb31a0f79 | ||
|
|
13394b7125 | ||
|
|
d227a83e42 | ||
|
|
7e8ad02ccb | ||
|
|
450f251732 | ||
|
|
53ca190457 | ||
|
|
683144a907 | ||
|
|
02b65627cd | ||
|
|
48e4fd5035 | ||
|
|
642a1ac7fb | ||
|
|
114e711fd6 | ||
|
|
f833f41b46 | ||
|
|
a82163b703 | ||
|
|
6b49cfa9be | ||
|
|
6c26e5ae69 | ||
|
|
1634380a16 | ||
|
|
1a6e582ad7 | ||
|
|
4b5a17b336 | ||
|
|
8392e568f4 | ||
|
|
d7d56c762b | ||
|
|
a4c6d42677 | ||
|
|
b7e907884b | ||
|
|
5b69492dba | ||
|
|
5a789bda42 | ||
|
|
a010f387b3 | ||
|
|
61d5cdcd68 | ||
|
|
2b14ee69ef | ||
|
|
048b54621d | ||
|
|
94d406c531 | ||
|
|
800580bb30 | ||
|
|
8cecd2df9b | ||
|
|
d86883043a | ||
|
|
d0772ba62c | ||
|
|
d6d4a3c2a3 | ||
|
|
49f9050bf5 | ||
|
|
67b77b9645 | ||
|
|
971fc85d1c | ||
|
|
50b9fe4d85 | ||
|
|
f9db1099f9 | ||
|
|
adce201837 | ||
|
|
a905d50e00 | ||
|
|
f006e00443 | ||
|
|
5ef449c2ed | ||
|
|
d9ca3e42a8 | ||
|
|
269bef2867 | ||
|
|
e62cb2cfd1 | ||
|
|
7135840f70 | ||
|
|
31ddc2f562 | ||
|
|
a5beee66ff | ||
|
|
c15439348f | ||
|
|
17fe36e43e | ||
|
|
5312a793ec | ||
|
|
4d43db91e1 | ||
|
|
0020af54a3 | ||
|
|
f67d392ad8 | ||
|
|
ff70da1736 | ||
|
|
07438daa70 | ||
|
|
e1e8aa068a | ||
|
|
a2691f919e | ||
|
|
f496a167fe | ||
|
|
d9a1252550 | ||
|
|
f3ca611267 | ||
|
|
119da0fcb2 | ||
|
|
6bb7f749c9 | ||
|
|
9593745098 | ||
|
|
e891ecd9da | ||
|
|
b8493976b6 | ||
|
|
54b85dc718 | ||
|
|
675a28fdc2 | ||
|
|
1a4437999b | ||
|
|
ee2438e25f | ||
|
|
cd062fead9 | ||
|
|
156389f11a | ||
|
|
8373224395 | ||
|
|
fe6c4ddeda | ||
|
|
98ac0c58d6 | ||
|
|
b57106b858 | ||
|
|
c204e28348 | ||
|
|
fc17386ec0 | ||
|
|
49d86abc6c | ||
|
|
9fc97fc681 | ||
|
|
9b24fdef99 | ||
|
|
e37059d409 | ||
|
|
869058b56b | ||
|
|
11b0f4598e | ||
|
|
cc9cee7bec | ||
|
|
5ae46d2312 | ||
|
|
776b5597bf | ||
|
|
e282b76880 | ||
|
|
e47d7f408f | ||
|
|
5044869ecd | ||
|
|
9516f54311 | ||
|
|
9bd414411f | ||
|
|
0334c5900e | ||
|
|
f502979135 | ||
|
|
d89fc3fbdc | ||
|
|
9279531cf8 | ||
|
|
9de1a63542 | ||
|
|
21af257716 | ||
|
|
28ed6f490e | ||
|
|
dc4f01dd14 | ||
|
|
1e3a4b77ee | ||
|
|
4cec7a3bb0 | ||
|
|
2557363892 | ||
|
|
78035134f4 | ||
|
|
0ef1122a3b | ||
|
|
8bb57de1d1 | ||
|
|
11d74b73af | ||
|
|
a759960bb0 | ||
|
|
7edcd2660a | ||
|
|
a97a5cae13 | ||
|
|
d2e7e2718c | ||
|
|
e1c3100c60 | ||
|
|
93c63d50d5 | ||
|
|
7e6db636d8 | ||
|
|
78a83a31b2 | ||
|
|
adcf89234b | ||
|
|
b7e9e6b955 | ||
|
|
19e16dc973 | ||
|
|
84602f8660 | ||
|
|
f5850e6f3b | ||
|
|
c14f9a9feb | ||
|
|
bbad5d835b | ||
|
|
f03fdae8df | ||
|
|
2159c7fd33 | ||
|
|
b66f920422 | ||
|
|
45e53b83f9 | ||
|
|
d265d142e6 | ||
|
|
5f94f6ee50 | ||
|
|
e7150f1b5e | ||
|
|
e89d41de54 | ||
|
|
7049adb202 | ||
|
|
99926e5c74 | ||
|
|
37777104fd | ||
|
|
a518d93ec8 | ||
|
|
936a9fea8d | ||
|
|
47c576e552 | ||
|
|
df05305642 | ||
|
|
d7a7a04684 | ||
|
|
9c04d76392 | ||
|
|
9df5518cd7 | ||
|
|
f77f2d1afb | ||
|
|
cef015d25f | ||
|
|
861598538a | ||
|
|
34da66f6fd | ||
|
|
cc4126911e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
||||
/build/
|
||||
/nbproject/
|
||||
/out/
|
||||
/.idea/
|
||||
/test/*.class
|
||||
.vscode
|
||||
*.iml
|
||||
|
||||
111
CHANGELOG.md
111
CHANGELOG.md
@@ -1,5 +1,116 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0-b1] - Early access
|
||||
|
||||
### Features
|
||||
- Profile multiple events together (cpu + alloc + lock)
|
||||
- HTML 5 Flame Graphs: faster rendering, smaller size
|
||||
- JFR v2 output format, compatible with FlightRecorder API
|
||||
- Automatically turn profiling on/off at `--begin`/`--end` functions
|
||||
- Time-to-safepoint profiling
|
||||
|
||||
### Improvements
|
||||
- Unlimited frame buffer. Removed `-b` option and 64K stack traces limit
|
||||
- Record CPU load in JFR format
|
||||
|
||||
### Changes
|
||||
- Removed non-ASL code. No more CDDL license
|
||||
|
||||
## [1.8.3] - 2021-01-06
|
||||
|
||||
### Improvements
|
||||
- libasyncProfiler.dylib symlink on macOS
|
||||
|
||||
### Bug fixes
|
||||
- Fixed possible deadlock on non-HotSpot JVMs
|
||||
- Gracefully stop profiler when terminating JVM
|
||||
- Fixed GetStackTrace problem after RedefineClasses
|
||||
|
||||
## [1.8.2] - 2020-11-02
|
||||
|
||||
### Improvements
|
||||
- AArch64 build is now provided out of the box
|
||||
- Compatibility with JDK 15 and JDK 16
|
||||
|
||||
### Bug fixes
|
||||
- More careful native stack walking in wall-clock mode
|
||||
- `resume` command is not compatible with JFR format
|
||||
- Wrong allocation sizes on JDK 8u262
|
||||
|
||||
## [1.8.1] - 2020-09-05
|
||||
|
||||
### Improvements
|
||||
- Possibility to specify application name instead of `pid` (contributed by @yuzawa-san)
|
||||
|
||||
### Bug fixes
|
||||
- Fixed long attach time and slow class loading on JDK 8
|
||||
- `UnsatisfiedLinkError` during Java method profiling
|
||||
- Avoid reading `/proc/kallsyms` when `--all-user` is specified
|
||||
|
||||
## [1.8] - 2020-08-10
|
||||
|
||||
### Features
|
||||
- Converters between different output formats:
|
||||
- JFR -> nflx (FlameScope)
|
||||
- Collapsed stacks -> HTML 5 Flame Graph
|
||||
|
||||
### Improvements
|
||||
- `profiler.sh` no longer requires bash (contributed by @cfstras)
|
||||
- Fixed long attach time and slow class loading on JDK 8
|
||||
- Fixed deadlocks in wall-clock profiling mode
|
||||
- Per-thread reverse Flame Graph and Call Tree
|
||||
- ARM build now works with ARM and THUMB flavors of JDK
|
||||
|
||||
### Changes
|
||||
- Release package is extracted into a separate folder
|
||||
|
||||
## [1.7.1] - 2020-05-14
|
||||
|
||||
### Features
|
||||
- LBR call stack support (available since Haswell)
|
||||
|
||||
### Improvements
|
||||
- `--filter` to profile only specified thread IDs in wall-clock mode
|
||||
- `--safe-mode` to disable selected stack recovery techniques
|
||||
|
||||
## [1.7] - 2020-03-17
|
||||
|
||||
### Features
|
||||
- Profile invocations of arbitrary Java methods
|
||||
- Filter stack traces by the given name pattern
|
||||
- Java API to filter monitored threads
|
||||
- `--cstack`/`--no-cstack` option
|
||||
|
||||
### Improvements
|
||||
- Thread names and Java thread IDs in JFR output
|
||||
- Wall clock profiler distinguishes RUNNABLE vs. SLEEPING threads
|
||||
- Stable profiling interval in wall clock mode
|
||||
- C++ function names as events, e.g. `-e VMThread::execute`
|
||||
- `check` command to test event availability
|
||||
- Allow shading of AsyncProfiler API
|
||||
- Enable CPU profiling on WSL
|
||||
- Enable allocation profiling on Zing
|
||||
- Reduce the amount of `unknown_Java` samples
|
||||
|
||||
## [1.6] - 2019-09-09
|
||||
|
||||
### Features
|
||||
- Pause/resume profiling
|
||||
- Allocation profiling support for JDK 12, 13 (contributed by @rraptorr)
|
||||
|
||||
### Improvements
|
||||
- Include all AsyncGetCallTrace failures in the profile
|
||||
- Parse symbols of JNI libraries loaded in runtime
|
||||
- The agent autodetects output format by the file extension
|
||||
- Output file name patterns: `%p` and `%t`
|
||||
- `-g` option to print method signatures
|
||||
- `-j` can increase the maximum Java stack depth
|
||||
- Allocaton sampling rate can be adjusted with `-i`
|
||||
- Improved reliability on macOS
|
||||
|
||||
### Changes
|
||||
- `-f` file names are now relative to the current shell directory
|
||||
|
||||
## [1.5] - 2019-01-08
|
||||
|
||||
### Features
|
||||
|
||||
95
Makefile
95
Makefile
@@ -1,63 +1,112 @@
|
||||
PROFILER_VERSION=1.5
|
||||
PROFILER_VERSION=2.0-b1
|
||||
JATTACH_VERSION=1.5
|
||||
LIB_PROFILER=libasyncProfiler.so
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
|
||||
LIB_PROFILER=libasyncProfiler.$(SOEXT)
|
||||
LIB_PROFILER_SO=libasyncProfiler.so
|
||||
JATTACH=jattach
|
||||
PROFILER_JAR=async-profiler.jar
|
||||
CC=gcc
|
||||
CFLAGS=-O2
|
||||
CPP=g++
|
||||
CPPFLAGS=-O2
|
||||
API_JAR=async-profiler.jar
|
||||
CONVERTER_JAR=converter.jar
|
||||
|
||||
CFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
CXXFLAGS=-O3 -fno-omit-frame-pointer -fvisibility=hidden
|
||||
INCLUDES=-I$(JAVA_HOME)/include
|
||||
LIBS=-ldl -lpthread
|
||||
|
||||
JAVAC=$(JAVA_HOME)/bin/javac
|
||||
JAR=$(JAVA_HOME)/bin/jar
|
||||
|
||||
SOURCES := $(wildcard src/*.cpp)
|
||||
HEADERS := $(wildcard src/*.h)
|
||||
API_SOURCES := $(wildcard src/api/one/profiler/*.java)
|
||||
CONVERTER_SOURCES := $(shell find src/converter -name '*.java')
|
||||
|
||||
ifeq ($(JAVA_HOME),)
|
||||
export JAVA_HOME:=$(shell java -cp . JavaHome)
|
||||
endif
|
||||
|
||||
OS:=$(shell uname -s)
|
||||
ifeq ($(OS), Darwin)
|
||||
CPPFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
INCLUDES += -I$(JAVA_HOME)/include/darwin
|
||||
RELEASE_TAG:=$(PROFILER_VERSION)-macos-x64
|
||||
SOEXT=dylib
|
||||
OS_TAG=macos
|
||||
else
|
||||
LIBS += -lrt
|
||||
INCLUDES += -I$(JAVA_HOME)/include/linux
|
||||
RELEASE_TAG:=$(PROFILER_VERSION)-linux-x64
|
||||
SOEXT=so
|
||||
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
|
||||
OS_TAG=linux-musl
|
||||
else
|
||||
OS_TAG=linux
|
||||
endif
|
||||
endif
|
||||
|
||||
ARCH:=$(shell uname -m)
|
||||
ifeq ($(ARCH),x86_64)
|
||||
ARCH_TAG=x64
|
||||
else
|
||||
ifeq ($(findstring arm,$(ARCH)),arm)
|
||||
ARCH_TAG=arm
|
||||
else
|
||||
ifeq ($(findstring aarch64,$(ARCH)),aarch64)
|
||||
ARCH_TAG=aarch64
|
||||
else
|
||||
ARCH_TAG=x86
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
.PHONY: all release test clean
|
||||
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(PROFILER_JAR)
|
||||
all: build build/$(LIB_PROFILER) build/$(JATTACH) build/$(API_JAR) build/$(CONVERTER_JAR)
|
||||
|
||||
release: build async-profiler-$(RELEASE_TAG).tar.gz
|
||||
release: build $(PACKAGE_NAME).tar.gz
|
||||
|
||||
async-profiler-$(RELEASE_TAG).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(PROFILER_JAR) profiler.sh LICENSE *.md
|
||||
tar cvzf $@ $^
|
||||
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
profiler.sh LICENSE *.md
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
|
||||
chmod -R 755 $(PACKAGE_DIR)
|
||||
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
|
||||
tar cvzf $@ -C $(PACKAGE_DIR)/.. $(PACKAGE_NAME)
|
||||
rm -r $(PACKAGE_DIR)
|
||||
|
||||
%.$(SOEXT): %.so
|
||||
-ln -s $(<F) $@
|
||||
|
||||
build:
|
||||
mkdir -p build
|
||||
|
||||
build/$(LIB_PROFILER): src/*.cpp src/*.h
|
||||
$(CPP) $(CPPFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ src/*.cpp $(LIBS)
|
||||
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
|
||||
|
||||
build/$(JATTACH): src/jattach/jattach.c
|
||||
$(CC) $(CFLAGS) -DJATTACH_VERSION=\"$(JATTACH_VERSION)\" -o $@ $^
|
||||
|
||||
build/$(PROFILER_JAR): src/java/one/profiler/*.java
|
||||
mkdir -p build/classes
|
||||
$(JAVAC) -source 6 -target 6 -d build/classes $^
|
||||
$(JAR) cvf $@ -C build/classes .
|
||||
rm -rf build/classes
|
||||
build/$(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 .
|
||||
$(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 .
|
||||
$(RM) -r build/converter
|
||||
|
||||
test: all
|
||||
test/smoke-test.sh
|
||||
test/thread-smoke-test.sh
|
||||
test/alloc-smoke-test.sh
|
||||
test/load-library-test.sh
|
||||
echo "All tests passed"
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
$(RM) -r build
|
||||
|
||||
409
README.md
409
README.md
@@ -1,24 +1,38 @@
|
||||
# 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
|
||||
and to track memory allocations. The profiler works with
|
||||
OpenJDK, Oracle JDK and other Java runtimes based on HotSpot JVM.
|
||||
OpenJDK, Oracle JDK and other Java runtimes based on the HotSpot JVM.
|
||||
|
||||
async-profiler can trace the following kinds of events:
|
||||
- CPU cycles
|
||||
- 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.
|
||||
|
||||
## Download
|
||||
|
||||
Latest release:
|
||||
Latest release (1.8.3):
|
||||
|
||||
- Linux x64: [async-profiler-1.5-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-linux-x64.tar.gz)
|
||||
- Linux ARM: [async-profiler-1.5-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-linux-arm.tar.gz)
|
||||
- macOS x64: [async-profiler-1.5-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.5/async-profiler-1.5-macos-x64.tar.gz)
|
||||
- Linux x64 (glibc): [async-profiler-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)
|
||||
|
||||
[Previous releases](https://github.com/jvm-profiling-tools/async-profiler/releases)
|
||||
|
||||
@@ -29,396 +43,11 @@ Latest release:
|
||||
|
||||
Note: macOS profiling is limited to user space code only.
|
||||
|
||||
## CPU profiling
|
||||
|
||||
In this mode profiler collects stack trace samples that include **Java** methods,
|
||||
**native** calls, **JVM** code and **kernel** functions.
|
||||
|
||||
The general approach is receiving call stacks generated by `perf_events`
|
||||
and matching them up with call stacks generated by `AsyncGetCallTrace`,
|
||||
in order to produce an accurate profile of both Java and native code.
|
||||
Additionally, async-profiler provides a workaround to recover stack traces
|
||||
in some [corner cases](https://bugs.openjdk.java.net/browse/JDK-8178287)
|
||||
where `AsyncGetCallTrace` fails.
|
||||
|
||||
This approach has the following advantages compared to using `perf_events`
|
||||
directly with a Java agent that translates addresses to Java method names:
|
||||
|
||||
* 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.
|
||||
|
||||
## 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;
|
||||
- when an object is allocated on a slow path outside TLAB.
|
||||
|
||||
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.
|
||||
|
||||
Unlike Java Mission Control which uses similar approach, async-profiler
|
||||
does not require Java Flight Recorder or any other JDK commercial feature.
|
||||
It is completely based on open source technologies and it works with OpenJDK.
|
||||
|
||||
The minimum supported JDK version is 7u40 where the TLAB callbacks appeared.
|
||||
|
||||
Heap profiler requires HotSpot debug symbols. Oracle JDK already has them
|
||||
embedded in `libjvm.so`, but in OpenJDK builds they are typically shipped
|
||||
in a separate package. For example, to install OpenJDK debug symbols on
|
||||
Debian / Ubuntu, run
|
||||
```
|
||||
# apt-get install openjdk-8-dbg
|
||||
```
|
||||
On Gentoo the ``icedtea`` OpenJDK package can be built with the per-package setting
|
||||
``FEATURES="nostrip"`` to retain symbols.
|
||||
|
||||
### 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.svg 8983`
|
||||
|
||||
## Building
|
||||
|
||||
Build status: [](https://travis-ci.org/jvm-profiling-tools/async-profiler)
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
# echo 1 > /proc/sys/kernel/perf_event_paranoid
|
||||
# echo 0 > /proc/sys/kernel/kptr_restrict
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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,svg,file=profile.svg ...
|
||||
```
|
||||
|
||||
Agent library is configured through the JVMTI argument interface. The format of the arguments string is described [in the source code](https://github.com/jvm-profiling-tools/async-profiler/blob/af94b0e55178c46e17c573a65c498d25b58b641b/src/arguments.cpp#L26). The `profiler.sh` script actually
|
||||
converts command line arguments to the that format.
|
||||
|
||||
For instance, `-e alloc` is converted to `event=alloc`, `-f profile.svg` is converted to `file=profile.svg` and so on. But some arguments are processed directly by `profiler.sh` script. E.g. `-d 5` results in 3 actions: 1) attaching profiler agent with start command, sleeping for 5 seconds, and then attaching the agent again with stop command.
|
||||
|
||||
## Flame Graph visualization
|
||||
|
||||
async-profiler provides out-of-the-box [Flame Graph](https://github.com/BrendanGregg/FlameGraph) support.
|
||||
Specify `-o svg` argument to dump profiling results as an interactive SVG
|
||||
immediately viewable in all mainstream browsers.
|
||||
Also, SVG output format will be chosen automatically if the target
|
||||
filename ends with `.svg`.
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ ./profiler.sh -d 30 -f /tmp/flamegraph.svg 8983
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
* `stop` - stops profiling and prints the report.
|
||||
|
||||
* `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`, `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`
|
||||
|
||||
* `-j N` - sets the Java stack profiling depth. This option will be ignored if N is greater
|
||||
than default MAX_STACK_FRAMES.
|
||||
Example: `./profiler.sh -j 30 8983`
|
||||
|
||||
* `-b N` - sets the frame buffer size, in the number of Java
|
||||
method ids that should fit in the buffer. If you receive messages about an
|
||||
insufficient frame buffer size, increase this value from the default.
|
||||
Example: `./profiler.sh -b 5000000 8983`
|
||||
|
||||
* `-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.
|
||||
|
||||
* `-a` - annotate Java method names by adding `_[j]` suffix.
|
||||
|
||||
* `-o fmt[,fmt...]` - specifies what information to dump when profiling ends.
|
||||
This is a comma-separated list of the following options:
|
||||
- `summary` - dump basic profiling statistics;
|
||||
- `traces[=N]` - dump call traces (at most N samples);
|
||||
- `flat[=N]` - dump flat profile (top N hot methods);
|
||||
- `jfr` - dump events in Java Flight Recorder format readable by Java Mission Control.
|
||||
This *does not* require JDK commercial features to be enabled.
|
||||
- `collapsed[=C]` - dump collapsed call traces in the format used by
|
||||
[FlameGraph](https://github.com/brendangregg/FlameGraph) script. This is
|
||||
a collection of call stacks, where each line is a semicolon separated list
|
||||
of frames followed by a counter.
|
||||
- `svg[=C]` - produce Flame Graph in SVG format.
|
||||
- `tree[=C]` - produce call tree in HTML format.
|
||||
--reverse option will generate backtrace view.
|
||||
|
||||
`C` is a counter type:
|
||||
- `samples` - the counter is a number of samples for the given trace;
|
||||
- `total` - the counter is a total value of collected metric, e.g. total allocation size.
|
||||
|
||||
The default format is `summary,traces=200,flat=200`.
|
||||
|
||||
* `--title TITLE`, `--width PX`, `--height PX`, `--minwidth PX`, `--reverse` - FlameGraph parameters.
|
||||
Example: `./profiler.sh -f profile.svg --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
|
||||
* `-f FILENAME` - the file name to dump the profile information to.
|
||||
Example: `./profiler.sh -o collapsed -f /tmp/traces.txt 8983`
|
||||
|
||||
* `--all-user` - include only user-mode events. This option is helpful when kernel profiling
|
||||
is restricted by `perf_event_paranoid` settings.
|
||||
`--all-kernel` is its counterpart option for including only kernel-mode events.
|
||||
|
||||
* `-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, async-profiler should be run by a privileged user -
|
||||
it will automatically switch to the proper pid/mount namespace and change
|
||||
user credentials to match the target process.
|
||||
|
||||
By default, Docker container restricts the access to `perf_event_open`
|
||||
syscall. You'll need to modify [seccomp profile](https://docs.docker.com/engine/security/seccomp/)
|
||||
or disable it altogether with `--security-opt=seccomp:unconfined` option.
|
||||
|
||||
Alternatively, if changing Docker configuration is not possible,
|
||||
you may fall back to `-e itimer` profiling mode, see [Troubleshooting](#troubleshooting).
|
||||
|
||||
## Restrictions/Limitations
|
||||
|
||||
* 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 e.g. without `-XX:+DebugNonSafepoints` there is a high chance that simple inlined methods will not appear in the profile. When agent is attached at runtime `CompiledMethodLoad` JVMTI event
|
||||
enables debug info, but only for methods compiled after the event is turned on.
|
||||
|
||||
## 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).
|
||||
|
||||
```
|
||||
Perf events unavailble. See stderr of the target process.
|
||||
```
|
||||
`perf_event_open()` syscall has failed. The error message is printed to the error stream
|
||||
of the target JVM.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
[frame_buffer_overflow]
|
||||
```
|
||||
This message in the output means there was not enough space to store all call traces.
|
||||
Consider increasing frame buffer size with `-b` option.
|
||||
|
||||
358
docs/cddl1.txt
358
docs/cddl1.txt
@@ -1,358 +0,0 @@
|
||||
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
|
||||
|
||||
1. Definitions.
|
||||
|
||||
1.1. "Contributor" means each individual or entity that
|
||||
creates or contributes to the creation of Modifications.
|
||||
|
||||
1.2. "Contributor Version" means the combination of the
|
||||
Original Software, prior Modifications used by a
|
||||
Contributor (if any), and the Modifications made by that
|
||||
particular Contributor.
|
||||
|
||||
1.3. "Covered Software" means (a) the Original Software, or
|
||||
(b) Modifications, or (c) the combination of files
|
||||
containing Original Software with files containing
|
||||
Modifications, in each case including portions thereof.
|
||||
|
||||
1.4. "Executable" means the Covered Software in any form
|
||||
other than Source Code.
|
||||
|
||||
1.5. "Initial Developer" means the individual or entity
|
||||
that first makes Original Software available under this
|
||||
License.
|
||||
|
||||
1.6. "Larger Work" means a work which combines Covered
|
||||
Software or portions thereof with code not governed by the
|
||||
terms of this License.
|
||||
|
||||
1.7. "License" means this document.
|
||||
|
||||
1.8. "Licensable" means having the right to grant, to the
|
||||
maximum extent possible, whether at the time of the initial
|
||||
grant or subsequently acquired, any and all of the rights
|
||||
conveyed herein.
|
||||
|
||||
1.9. "Modifications" means the Source Code and Executable
|
||||
form of any of the following:
|
||||
A. Any file that results from an addition to,
|
||||
deletion from or modification of the contents of a
|
||||
file containing Original Software or previous
|
||||
Modifications;
|
||||
B. Any new file that contains any part of the
|
||||
Original Software or previous Modification; or
|
||||
C. Any new file that is contributed or otherwise made
|
||||
available under the terms of this License.
|
||||
|
||||
1.10. "Original Software" means the Source Code and
|
||||
Executable form of computer software code that is
|
||||
originally released under this License.
|
||||
|
||||
1.11. "Patent Claims" means any patent claim(s), now owned
|
||||
or hereafter acquired, including without limitation,
|
||||
method, process, and apparatus claims, in any patent
|
||||
Licensable by grantor.
|
||||
|
||||
1.12. "Source Code" means (a) the common form of computer
|
||||
software code in which modifications are made and (b)
|
||||
associated documentation included in or with such code.
|
||||
|
||||
1.13. "You" (or "Your") means an individual or a legal
|
||||
entity exercising rights under, and complying with all of
|
||||
the terms of, this License. For legal entities, "You"
|
||||
includes any entity which controls, is controlled by, or is
|
||||
under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or
|
||||
indirect, to cause the direction or management of such
|
||||
entity, whether by contract or otherwise, or (b) ownership
|
||||
of more than fifty percent (50%) of the outstanding shares
|
||||
or beneficial ownership of such entity.
|
||||
|
||||
2. License Grants.
|
||||
|
||||
2.1. The Initial Developer Grant.
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, the
|
||||
Initial Developer hereby grants You a world-wide,
|
||||
royalty-free, non-exclusive license:
|
||||
(a) under intellectual property rights (other than
|
||||
patent or trademark) Licensable by Initial Developer,
|
||||
to use, reproduce, modify, display, perform,
|
||||
sublicense and distribute the Original Software (or
|
||||
portions thereof), with or without Modifications,
|
||||
and/or as part of a Larger Work; and
|
||||
(b) under Patent Claims infringed by the making,
|
||||
using or selling of Original Software, to make, have
|
||||
made, use, practice, sell, and offer for sale, and/or
|
||||
otherwise dispose of the Original Software (or
|
||||
portions thereof).
|
||||
(c) The licenses granted in Sections 2.1(a) and (b)
|
||||
are effective on the date Initial Developer first
|
||||
distributes or otherwise makes the Original Software
|
||||
available to a third party under the terms of this
|
||||
License.
|
||||
(d) Notwithstanding Section 2.1(b) above, no patent
|
||||
license is granted: (1) for code that You delete from
|
||||
the Original Software, or (2) for infringements
|
||||
caused by: (i) the modification of the Original
|
||||
Software, or (ii) the combination of the Original
|
||||
Software with other software or devices.
|
||||
|
||||
2.2. Contributor Grant.
|
||||
Conditioned upon Your compliance with Section 3.1 below and
|
||||
subject to third party intellectual property claims, each
|
||||
Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
(a) under intellectual property rights (other than
|
||||
patent or trademark) Licensable by Contributor to
|
||||
use, reproduce, modify, display, perform, sublicense
|
||||
and distribute the Modifications created by such
|
||||
Contributor (or portions thereof), either on an
|
||||
unmodified basis, with other Modifications, as
|
||||
Covered Software and/or as part of a Larger Work; and
|
||||
(b) under Patent Claims infringed by the making,
|
||||
using, or selling of Modifications made by that
|
||||
Contributor either alone and/or in combination with
|
||||
its Contributor Version (or portions of such
|
||||
combination), to make, use, sell, offer for sale,
|
||||
have made, and/or otherwise dispose of: (1)
|
||||
Modifications made by that Contributor (or portions
|
||||
thereof); and (2) the combination of Modifications
|
||||
made by that Contributor with its Contributor Version
|
||||
(or portions of such combination).
|
||||
(c) The licenses granted in Sections 2.2(a) and
|
||||
2.2(b) are effective on the date Contributor first
|
||||
distributes or otherwise makes the Modifications
|
||||
available to a third party.
|
||||
(d) Notwithstanding Section 2.2(b) above, no patent
|
||||
license is granted: (1) for any code that Contributor
|
||||
has deleted from the Contributor Version; (2) for
|
||||
infringements caused by: (i) third party
|
||||
modifications of Contributor Version, or (ii) the
|
||||
combination of Modifications made by that Contributor
|
||||
with other software (except as part of the
|
||||
Contributor Version) or other devices; or (3) under
|
||||
Patent Claims infringed by Covered Software in the
|
||||
absence of Modifications made by that Contributor.
|
||||
|
||||
3. Distribution Obligations.
|
||||
|
||||
3.1. Availability of Source Code.
|
||||
Any Covered Software that You distribute or otherwise make
|
||||
available in Executable form must also be made available in
|
||||
Source Code form and that Source Code form must be
|
||||
distributed only under the terms of this License. You must
|
||||
include a copy of this License with every copy of the
|
||||
Source Code form of the Covered Software You distribute or
|
||||
otherwise make available. You must inform recipients of any
|
||||
such Covered Software in Executable form as to how they can
|
||||
obtain such Covered Software in Source Code form in a
|
||||
reasonable manner on or through a medium customarily used
|
||||
for software exchange.
|
||||
|
||||
3.2. Modifications.
|
||||
The Modifications that You create or to which You
|
||||
contribute are governed by the terms of this License. You
|
||||
represent that You believe Your Modifications are Your
|
||||
original creation(s) and/or You have sufficient rights to
|
||||
grant the rights conveyed by this License.
|
||||
|
||||
3.3. Required Notices.
|
||||
You must include a notice in each of Your Modifications
|
||||
that identifies You as the Contributor of the Modification.
|
||||
You may not remove or alter any copyright, patent or
|
||||
trademark notices contained within the Covered Software, or
|
||||
any notices of licensing or any descriptive text giving
|
||||
attribution to any Contributor or the Initial Developer.
|
||||
|
||||
3.4. Application of Additional Terms.
|
||||
You may not offer or impose any terms on any Covered
|
||||
Software in Source Code form that alters or restricts the
|
||||
applicable version of this License or the recipients'
|
||||
rights hereunder. You may choose to offer, and to charge a
|
||||
fee for, warranty, support, indemnity or liability
|
||||
obligations to one or more recipients of Covered Software.
|
||||
However, you may do so only on Your own behalf, and not on
|
||||
behalf of the Initial Developer or any Contributor. You
|
||||
must make it absolutely clear that any such warranty,
|
||||
support, indemnity or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify the Initial
|
||||
Developer and every Contributor for any liability incurred
|
||||
by the Initial Developer or such Contributor as a result of
|
||||
warranty, support, indemnity or liability terms You offer.
|
||||
|
||||
3.5. Distribution of Executable Versions.
|
||||
You may distribute the Executable form of the Covered
|
||||
Software under the terms of this License or under the terms
|
||||
of a license of Your choice, which may contain terms
|
||||
different from this License, provided that You are in
|
||||
compliance with the terms of this License and that the
|
||||
license for the Executable form does not attempt to limit
|
||||
or alter the recipient's rights in the Source Code form
|
||||
from the rights set forth in this License. If You
|
||||
distribute the Covered Software in Executable form under a
|
||||
different license, You must make it absolutely clear that
|
||||
any terms which differ from this License are offered by You
|
||||
alone, not by the Initial Developer or Contributor. You
|
||||
hereby agree to indemnify the Initial Developer and every
|
||||
Contributor for any liability incurred by the Initial
|
||||
Developer or such Contributor as a result of any such terms
|
||||
You offer.
|
||||
|
||||
3.6. Larger Works.
|
||||
You may create a Larger Work by combining Covered Software
|
||||
with other code not governed by the terms of this License
|
||||
and distribute the Larger Work as a single product. In such
|
||||
a case, You must make sure the requirements of this License
|
||||
are fulfilled for the Covered Software.
|
||||
|
||||
4. Versions of the License.
|
||||
|
||||
4.1. New Versions.
|
||||
Sun Microsystems, Inc. is the initial license steward and
|
||||
may publish revised and/or new versions of this License
|
||||
from time to time. Each version will be given a
|
||||
distinguishing version number. Except as provided in
|
||||
Section 4.3, no one other than the license steward has the
|
||||
right to modify this License.
|
||||
|
||||
4.2. Effect of New Versions.
|
||||
You may always continue to use, distribute or otherwise
|
||||
make the Covered Software available under the terms of the
|
||||
version of the License under which You originally received
|
||||
the Covered Software. If the Initial Developer includes a
|
||||
notice in the Original Software prohibiting it from being
|
||||
distributed or otherwise made available under any
|
||||
subsequent version of the License, You must distribute and
|
||||
make the Covered Software available under the terms of the
|
||||
version of the License under which You originally received
|
||||
the Covered Software. Otherwise, You may also choose to
|
||||
use, distribute or otherwise make the Covered Software
|
||||
available under the terms of any subsequent version of the
|
||||
License published by the license steward.
|
||||
|
||||
4.3. Modified Versions.
|
||||
When You are an Initial Developer and You want to create a
|
||||
new license for Your Original Software, You may create and
|
||||
use a modified version of this License if You: (a) rename
|
||||
the license and remove any references to the name of the
|
||||
license steward (except to note that the license differs
|
||||
from this License); and (b) otherwise make it clear that
|
||||
the license contains terms which differ from this License.
|
||||
|
||||
5. DISCLAIMER OF WARRANTY.
|
||||
|
||||
COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS"
|
||||
BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
|
||||
INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED
|
||||
SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR
|
||||
PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY
|
||||
COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE
|
||||
INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF
|
||||
ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF
|
||||
WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
|
||||
ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
|
||||
DISCLAIMER.
|
||||
|
||||
6. TERMINATION.
|
||||
|
||||
6.1. This License and the rights granted hereunder will
|
||||
terminate automatically if You fail to comply with terms
|
||||
herein and fail to cure such breach within 30 days of
|
||||
becoming aware of the breach. Provisions which, by their
|
||||
nature, must remain in effect beyond the termination of
|
||||
this License shall survive.
|
||||
|
||||
6.2. If You assert a patent infringement claim (excluding
|
||||
declaratory judgment actions) against Initial Developer or
|
||||
a Contributor (the Initial Developer or Contributor against
|
||||
whom You assert such claim is referred to as "Participant")
|
||||
alleging that the Participant Software (meaning the
|
||||
Contributor Version where the Participant is a Contributor
|
||||
or the Original Software where the Participant is the
|
||||
Initial Developer) directly or indirectly infringes any
|
||||
patent, then any and all rights granted directly or
|
||||
indirectly to You by such Participant, the Initial
|
||||
Developer (if the Initial Developer is not the Participant)
|
||||
and all Contributors under Sections 2.1 and/or 2.2 of this
|
||||
License shall, upon 60 days notice from Participant
|
||||
terminate prospectively and automatically at the expiration
|
||||
of such 60 day notice period, unless if within such 60 day
|
||||
period You withdraw Your claim with respect to the
|
||||
Participant Software against such Participant either
|
||||
unilaterally or pursuant to a written agreement with
|
||||
Participant.
|
||||
|
||||
6.3. In the event of termination under Sections 6.1 or 6.2
|
||||
above, all end user licenses that have been validly granted
|
||||
by You or any distributor hereunder prior to termination
|
||||
(excluding licenses granted to You by any distributor)
|
||||
shall survive termination.
|
||||
|
||||
7. LIMITATION OF LIABILITY.
|
||||
|
||||
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
|
||||
(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE
|
||||
INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF
|
||||
COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE
|
||||
LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
|
||||
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT
|
||||
LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK
|
||||
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
|
||||
COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
|
||||
INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
|
||||
LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL
|
||||
INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
|
||||
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO
|
||||
NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT
|
||||
APPLY TO YOU.
|
||||
|
||||
8. U.S. GOVERNMENT END USERS.
|
||||
|
||||
The Covered Software is a "commercial item," as that term is
|
||||
defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial
|
||||
computer software" (as that term is defined at 48 C.F.R. ¤
|
||||
252.227-7014(a)(1)) and "commercial computer software
|
||||
documentation" as such terms are used in 48 C.F.R. 12.212 (Sept.
|
||||
1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1
|
||||
through 227.7202-4 (June 1995), all U.S. Government End Users
|
||||
acquire Covered Software with only those rights set forth herein.
|
||||
This U.S. Government Rights clause is in lieu of, and supersedes,
|
||||
any other FAR, DFAR, or other clause or provision that addresses
|
||||
Government rights in computer software under this License.
|
||||
|
||||
9. MISCELLANEOUS.
|
||||
|
||||
This License represents the complete agreement concerning subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the
|
||||
extent necessary to make it enforceable. This License shall be
|
||||
governed by the law of the jurisdiction specified in a notice
|
||||
contained within the Original Software (except to the extent
|
||||
applicable law, if any, provides otherwise), excluding such
|
||||
jurisdiction's conflict-of-law provisions. Any litigation
|
||||
relating to this License shall be subject to the jurisdiction of
|
||||
the courts located in the jurisdiction and venue specified in a
|
||||
notice contained within the Original Software, with the losing
|
||||
party responsible for costs, including, without limitation, court
|
||||
costs and reasonable attorneys' fees and expenses. The
|
||||
application of the United Nations Convention on Contracts for the
|
||||
International Sale of Goods is expressly excluded. Any law or
|
||||
regulation which provides that the language of a contract shall
|
||||
be construed against the drafter shall not apply to this License.
|
||||
You agree that You alone are responsible for compliance with the
|
||||
United States export administration regulations (and the export
|
||||
control laws and regulation of any other countries) when You use,
|
||||
distribute or otherwise make available any Covered Software.
|
||||
|
||||
10. RESPONSIBILITY FOR CLAIMS.
|
||||
|
||||
As between Initial Developer and the Contributors, each party is
|
||||
responsible for claims and damages arising, directly or
|
||||
indirectly, out of its utilization of rights under this License
|
||||
and You agree to work with Initial Developer and Contributors to
|
||||
distribute such responsibility on an equitable basis. Nothing
|
||||
herein is intended or shall be deemed to constitute any admission
|
||||
of liability.
|
||||
102
pom.xml
Normal file
102
pom.xml
Normal file
@@ -0,0 +1,102 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>tools.profiler</groupId>
|
||||
<artifactId>async-profiler</artifactId>
|
||||
<version>1.8.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>async-profiler</name>
|
||||
<url>https://profiler.tools</url>
|
||||
<description>Low overhead sampling profiler for Java</description>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<url>https://github.com/jvm-profiling-tools/async-profiler</url>
|
||||
<connection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:jvm-profiling-tools/async-profiler.git</developerConnection>
|
||||
</scm>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>apangin</id>
|
||||
<name>Andrei Pangin</name>
|
||||
<email>noreply@pangin.pro</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/api</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>ossrh</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
</project>
|
||||
183
profiler.sh
183
profiler.sh
@@ -1,10 +1,14 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [action] [options] <pid>"
|
||||
echo "Actions:"
|
||||
echo " start start profiling and return immediately"
|
||||
echo " resume resume profiling without resetting collected data"
|
||||
echo " stop stop profiling"
|
||||
echo " jstack get a thread dump"
|
||||
echo " check check if the specified profiling event is available"
|
||||
echo " status print profiling status"
|
||||
echo " list list profiling events supported by the target JVM"
|
||||
echo " collect collect profile for the specified period of time"
|
||||
@@ -15,57 +19,64 @@ usage() {
|
||||
echo " -f filename dump output to <filename>"
|
||||
echo " -i interval sampling interval in nanoseconds"
|
||||
echo " -j jstackdepth maximum Java stack depth"
|
||||
echo " -b bufsize frame buffer size"
|
||||
echo " -t profile different threads separately"
|
||||
echo " -s simple class names instead of FQN"
|
||||
echo " -g print method signatures"
|
||||
echo " -a annotate Java method names"
|
||||
echo " -o fmt[,fmt...] output format: summary|traces|flat|collapsed|svg|tree|jfr"
|
||||
echo " -o fmt output format: flat|collapsed|html|tree|jfr"
|
||||
echo " -I include output only stack traces containing the specified pattern"
|
||||
echo " -X exclude exclude stack traces with the specified pattern"
|
||||
echo " -v, --version display version string"
|
||||
echo ""
|
||||
echo " --title string SVG title"
|
||||
echo " --width px SVG width"
|
||||
echo " --height px SVG frame height"
|
||||
echo " --minwidth px skip frames smaller than px"
|
||||
echo " --title string FlameGraph title"
|
||||
echo " --minwidth pct skip frames smaller than pct%"
|
||||
echo " --reverse generate stack-reversed FlameGraph / Call tree"
|
||||
echo ""
|
||||
echo " --all-kernel only include kernel-mode events"
|
||||
echo " --all-user only include user-mode events"
|
||||
echo " --cstack mode how to traverse C stack: fp|lbr|no"
|
||||
echo " --begin function begin profiling when function is executed"
|
||||
echo " --end function end profiling when function is executed"
|
||||
echo ""
|
||||
echo "<pid> is a numeric process ID of the target JVM"
|
||||
echo " or 'jps' keyword to find running JVM automatically"
|
||||
echo " or the application's name as it would appear in the jps tool"
|
||||
echo ""
|
||||
echo "Example: $0 -d 30 -f profile.svg 3456"
|
||||
echo " $0 start -i 999000 jps"
|
||||
echo " $0 stop -o summary,flat jps"
|
||||
echo " $0 stop -o flat jps"
|
||||
echo " $0 -d 5 -e alloc MyAppName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
mirror_output() {
|
||||
# Mirror output from temporary file to local terminal
|
||||
if [[ $USE_TMP ]]; then
|
||||
if [[ -f $FILE ]]; then
|
||||
cat $FILE
|
||||
rm $FILE
|
||||
if [ "$USE_TMP" = true ]; then
|
||||
if [ -f "$FILE" ]; then
|
||||
cat "$FILE"
|
||||
rm "$FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_if_terminated() {
|
||||
if ! kill -0 $PID 2> /dev/null; then
|
||||
if ! kill -0 "$PID" 2> /dev/null; then
|
||||
mirror_output
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
jattach() {
|
||||
$JATTACH $PID load "$PROFILER" true "$1" > /dev/null
|
||||
set +e
|
||||
"$JATTACH" "$PID" load "$PROFILER" true "$1" > /dev/null
|
||||
RET=$?
|
||||
set -e
|
||||
|
||||
# Check if jattach failed
|
||||
if [ $RET -ne 0 ]; then
|
||||
if [ $RET -eq 255 ]; then
|
||||
echo "Failed to inject profiler into $PID"
|
||||
if [ "$UNAME_S" == "Darwin" ]; then
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
otool -L "$PROFILER"
|
||||
else
|
||||
ldd "$PROFILER"
|
||||
@@ -77,18 +88,8 @@ jattach() {
|
||||
mirror_output
|
||||
}
|
||||
|
||||
function abspath() {
|
||||
if [ "$UNAME_S" == "Darwin" ]; then
|
||||
perl -MCwd -e 'print Cwd::abs_path shift' $1
|
||||
else
|
||||
readlink -f $1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
OPTIND=1
|
||||
UNAME_S=$(uname -s)
|
||||
SCRIPT_DIR=$(dirname $(abspath $0))
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" > /dev/null 2>&1; pwd -P)"
|
||||
JATTACH=$SCRIPT_DIR/build/jattach
|
||||
PROFILER=$SCRIPT_DIR/build/libasyncProfiler.so
|
||||
ACTION="collect"
|
||||
@@ -96,27 +97,29 @@ EVENT="cpu"
|
||||
DURATION="60"
|
||||
FILE=""
|
||||
USE_TMP="true"
|
||||
INTERVAL=""
|
||||
JSTACKDEPTH=""
|
||||
FRAMEBUF=""
|
||||
THREADS=""
|
||||
RING=""
|
||||
OUTPUT=""
|
||||
FORMAT=""
|
||||
PARAMS=""
|
||||
PID=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-h|"-?")
|
||||
usage
|
||||
;;
|
||||
start|stop|status|list|collect)
|
||||
start|resume|stop|check|status|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
jstack)
|
||||
ACTION="start"
|
||||
EVENT="jstack"
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
-e)
|
||||
EVENT="$2"
|
||||
EVENT="$(echo "$2" | sed 's/,/,event=/g')"
|
||||
shift
|
||||
;;
|
||||
-d)
|
||||
@@ -125,27 +128,26 @@ while [[ $# -gt 0 ]]; do
|
||||
;;
|
||||
-f)
|
||||
FILE="$2"
|
||||
unset USE_TMP
|
||||
USE_TMP=false
|
||||
shift
|
||||
;;
|
||||
-i)
|
||||
INTERVAL=",interval=$2"
|
||||
PARAMS="$PARAMS,interval=$2"
|
||||
shift
|
||||
;;
|
||||
-j)
|
||||
JSTACKDEPTH=",jstackdepth=$2"
|
||||
shift
|
||||
;;
|
||||
-b)
|
||||
FRAMEBUF=",framebuf=$2"
|
||||
PARAMS="$PARAMS,jstackdepth=$2"
|
||||
shift
|
||||
;;
|
||||
-t)
|
||||
THREADS=",threads"
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-s)
|
||||
FORMAT="$FORMAT,simple"
|
||||
;;
|
||||
-g)
|
||||
FORMAT="$FORMAT,sig"
|
||||
;;
|
||||
-a)
|
||||
FORMAT="$FORMAT,ann"
|
||||
;;
|
||||
@@ -153,27 +155,49 @@ while [[ $# -gt 0 ]]; do
|
||||
OUTPUT="$2"
|
||||
shift
|
||||
;;
|
||||
-I|--include)
|
||||
FORMAT="$FORMAT,include=$2"
|
||||
shift
|
||||
;;
|
||||
-X|--exclude)
|
||||
FORMAT="$FORMAT,exclude=$2"
|
||||
shift
|
||||
;;
|
||||
--filter)
|
||||
FILTER="$(echo "$2" | sed 's/,/;/g')"
|
||||
FORMAT="$FORMAT,filter=$FILTER"
|
||||
shift
|
||||
;;
|
||||
--title)
|
||||
# escape XML special characters and comma
|
||||
TITLE=${2//&/&}
|
||||
TITLE=${TITLE//</<}
|
||||
TITLE=${TITLE//>/>}
|
||||
TITLE=${TITLE//,/,}
|
||||
TITLE="$(echo "$2" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/,/\,/g')"
|
||||
FORMAT="$FORMAT,title=$TITLE"
|
||||
shift
|
||||
;;
|
||||
--width|--height|--minwidth)
|
||||
FORMAT="$FORMAT,${1:2}=$2"
|
||||
FORMAT="$FORMAT,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--reverse)
|
||||
FORMAT="$FORMAT,reverse"
|
||||
;;
|
||||
--all-kernel)
|
||||
RING=",allkernel"
|
||||
PARAMS="$PARAMS,allkernel"
|
||||
;;
|
||||
--all-user)
|
||||
RING=",alluser"
|
||||
PARAMS="$PARAMS,alluser"
|
||||
;;
|
||||
--cstack|--call-graph)
|
||||
PARAMS="$PARAMS,cstack=$2"
|
||||
shift
|
||||
;;
|
||||
--begin|--end)
|
||||
PARAMS="$PARAMS,${1#--}=$2"
|
||||
shift
|
||||
;;
|
||||
--safe-mode)
|
||||
PARAMS="$PARAMS,safemode=$2"
|
||||
shift
|
||||
;;
|
||||
[0-9]*)
|
||||
PID="$1"
|
||||
@@ -182,43 +206,53 @@ while [[ $# -gt 0 ]]; do
|
||||
# A shortcut for getting PID of a running Java application
|
||||
# -XX:+PerfDisableSharedMem prevents jps from appearing in its own list
|
||||
PID=$(pgrep -n java || jps -q -J-XX:+PerfDisableSharedMem)
|
||||
if [ "$PID" = "" ]; then
|
||||
echo "No Java process could be found!"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
-*)
|
||||
echo "Unrecognized option: $1"
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
if [ $# -eq 1 ]; then
|
||||
# the last argument is the application name as it would appear in the jps tool
|
||||
PID=$(jps -J-XX:+PerfDisableSharedMem | grep " $1$" | head -n 1 | cut -d ' ' -f 1)
|
||||
if [ "$PID" = "" ]; then
|
||||
echo "No Java process '$1' could be found!"
|
||||
fi
|
||||
else
|
||||
echo "Unrecognized option: $1"
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ "$PID" == "" && "$ACTION" != "version" ]]; then
|
||||
if [ "$PID" = "" ] && [ "$ACTION" != "version" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
# If no -f argument is given, use temporary file to transfer output to caller terminal.
|
||||
# Let the target process create the file in case this script is run by superuser.
|
||||
if [[ $USE_TMP ]]; then
|
||||
if [ "$USE_TMP" = true ]; then
|
||||
FILE=/tmp/async-profiler.$$.$PID
|
||||
fi
|
||||
|
||||
# select default output format
|
||||
if [[ "$OUTPUT" == "" ]]; then
|
||||
if [[ $FILE == *.svg ]]; then
|
||||
OUTPUT="svg"
|
||||
elif [[ $FILE == *.html ]]; then
|
||||
OUTPUT="tree"
|
||||
elif [[ $FILE == *.jfr ]]; then
|
||||
OUTPUT="jfr"
|
||||
elif [[ $FILE == *.collapsed ]] || [[ $FILE == *.folded ]]; then
|
||||
OUTPUT="collapsed"
|
||||
else
|
||||
OUTPUT="summary,traces=200,flat=200"
|
||||
fi
|
||||
else
|
||||
case "$FILE" in
|
||||
/*)
|
||||
# Path is absolute
|
||||
;;
|
||||
*)
|
||||
# Output file is written by the target process. Make the path absolute to avoid confusion.
|
||||
FILE=$PWD/$FILE
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case $ACTION in
|
||||
start)
|
||||
jattach "start,event=$EVENT,file=$FILE$INTERVAL$JSTACKDEPTH$FRAMEBUF$THREADS$RING,$OUTPUT$FORMAT"
|
||||
start|resume|check)
|
||||
jattach "$ACTION,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
;;
|
||||
stop)
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
@@ -230,18 +264,19 @@ case $ACTION in
|
||||
jattach "list,file=$FILE"
|
||||
;;
|
||||
collect)
|
||||
jattach "start,event=$EVENT,file=$FILE$INTERVAL$JSTACKDEPTH$FRAMEBUF$THREADS$RING,$OUTPUT$FORMAT"
|
||||
while (( DURATION-- > 0 )); do
|
||||
jattach "start,event=$EVENT,file=$FILE,$OUTPUT$FORMAT$PARAMS"
|
||||
while [ "$DURATION" -gt 0 ]; do
|
||||
DURATION=$(( DURATION-1 ))
|
||||
check_if_terminated
|
||||
sleep 1
|
||||
done
|
||||
jattach "stop,file=$FILE,$OUTPUT$FORMAT"
|
||||
;;
|
||||
version)
|
||||
if [[ "$PID" == "" ]]; then
|
||||
java "-agentpath:$PROFILER=version" -version 2> /dev/null
|
||||
if [ "$PID" = "" ]; then
|
||||
java "-agentpath:$PROFILER=version=full" -version 2> /dev/null
|
||||
else
|
||||
jattach "version,file=$FILE"
|
||||
jattach "version=full,file=$FILE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "allocTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
@@ -23,103 +21,123 @@
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
// JDK 7-9
|
||||
Trap AllocTracer::_in_new_tlab("_ZN11AllocTracer33send_allocation_in_new_tlab_event");
|
||||
Trap AllocTracer::_outside_tlab("_ZN11AllocTracer34send_allocation_outside_tlab_event");
|
||||
// JDK 10+
|
||||
Trap AllocTracer::_in_new_tlab2("_ZN11AllocTracer27send_allocation_in_new_tlab");
|
||||
Trap AllocTracer::_outside_tlab2("_ZN11AllocTracer28send_allocation_outside_tlab");
|
||||
int AllocTracer::_trap_kind;
|
||||
Trap AllocTracer::_in_new_tlab;
|
||||
Trap AllocTracer::_outside_tlab;
|
||||
|
||||
|
||||
// Resolve the address of the intercepted function
|
||||
bool Trap::resolve(NativeCodeCache* libjvm) {
|
||||
if (_entry != NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_entry = (instruction_t*)libjvm->findSymbolByPrefix(_func_name);
|
||||
if (_entry != NULL) {
|
||||
// Make the entry point writable, so we can rewrite instructions
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
uintptr_t page_start = (uintptr_t)_entry & -page_size;
|
||||
mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert breakpoint at the very first instruction
|
||||
void Trap::install() {
|
||||
if (_entry != NULL) {
|
||||
_saved_insn = *_entry;
|
||||
*_entry = BREAKPOINT;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear breakpoint - restore the original instruction
|
||||
void Trap::uninstall() {
|
||||
if (_entry != NULL) {
|
||||
*_entry = _saved_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
u64 AllocTracer::_interval;
|
||||
volatile u64 AllocTracer::_allocated_bytes;
|
||||
|
||||
|
||||
// Called whenever our breakpoint trap is hit
|
||||
void AllocTracer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
int event_type;
|
||||
uintptr_t total_size;
|
||||
uintptr_t instance_size;
|
||||
|
||||
// PC points either to BREAKPOINT instruction or to the next one
|
||||
if (frame.pc() - (uintptr_t)_in_new_tlab._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg1(), false);
|
||||
} else if (frame.pc() - (uintptr_t)_outside_tlab._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg1(), true);
|
||||
} else if (frame.pc() - (uintptr_t)_in_new_tlab2._entry <= sizeof(instruction_t)) {
|
||||
if (_in_new_tlab.covers(frame.pc())) {
|
||||
// send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread)
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg2(), false);
|
||||
} else if (frame.pc() - (uintptr_t)_outside_tlab2._entry <= sizeof(instruction_t)) {
|
||||
// send_allocation_in_new_tlab_event(KlassHandle klass, size_t tlab_size, size_t alloc_size)
|
||||
event_type = BCI_ALLOC;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = _trap_kind == 1 ? frame.arg3() : frame.arg2();
|
||||
} else if (_outside_tlab.covers(frame.pc())) {
|
||||
// send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread)
|
||||
recordAllocation(ucontext, frame.arg0(), frame.arg2(), true);
|
||||
// send_allocation_outside_tlab_event(KlassHandle klass, size_t alloc_size);
|
||||
event_type = BCI_ALLOC_OUTSIDE_TLAB;
|
||||
total_size = _trap_kind == 1 ? frame.arg2() : frame.arg1();
|
||||
instance_size = 0;
|
||||
} else {
|
||||
// Not our trap; nothing to do
|
||||
// Not our trap
|
||||
return;
|
||||
}
|
||||
|
||||
// Leave the trapped function by simulating "ret" instruction
|
||||
uintptr_t klass = frame.arg0();
|
||||
frame.ret();
|
||||
|
||||
if (_enabled) {
|
||||
// TODO: _enabled also uses traps
|
||||
recordAllocation(ucontext, event_type, klass, total_size, instance_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AllocTracer::recordAllocation(void* ucontext, uintptr_t rklass, uintptr_t rsize, bool outside_tlab) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
if (outside_tlab) {
|
||||
// Invert the last bit to distinguish jmethodID from the allocation in new TLAB
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL_OUTSIDE_TLAB, (jmethodID)((uintptr_t)symbol ^ 1));
|
||||
} else {
|
||||
Profiler::_instance.recordSample(ucontext, rsize, BCI_SYMBOL, (jmethodID)symbol);
|
||||
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;
|
||||
event._instance_size = instance_size;
|
||||
|
||||
if (VMStructs::hasClassNames()) {
|
||||
VMSymbol* symbol = VMKlass::fromHandle(rklass)->name();
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(symbol->body(), symbol->length());
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(ucontext, total_size, event_type, &event);
|
||||
}
|
||||
|
||||
Error AllocTracer::check(Arguments& args) {
|
||||
if (_in_new_tlab.entry() != 0 && _outside_tlab.entry() != 0) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
NativeCodeCache* libjvm = VMStructs::libjvm();
|
||||
const void* ne;
|
||||
const void* oe;
|
||||
|
||||
if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer27send_allocation_in_new_tlab")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer28send_allocation_outside_tlab")) != NULL) {
|
||||
_trap_kind = 1; // JDK 10+
|
||||
} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_eventE11KlassHandleP8HeapWord")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_eventE11KlassHandleP8HeapWord")) != NULL) {
|
||||
_trap_kind = 1; // JDK 8u262+
|
||||
} else if ((ne = libjvm->findSymbolByPrefix("_ZN11AllocTracer33send_allocation_in_new_tlab_event")) != NULL &&
|
||||
(oe = libjvm->findSymbolByPrefix("_ZN11AllocTracer34send_allocation_outside_tlab_event")) != NULL) {
|
||||
_trap_kind = 2; // JDK 7-9
|
||||
} else {
|
||||
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");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error AllocTracer::start(Arguments& args) {
|
||||
if (!VMStructs::available()) {
|
||||
return Error("VMStructs unavailable. Unsupported JVM?");
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
NativeCodeCache* libjvm = Profiler::_instance.jvmLibrary();
|
||||
if (!(_in_new_tlab.resolve(libjvm) || _in_new_tlab2.resolve(libjvm)) ||
|
||||
!(_outside_tlab.resolve(libjvm) || _outside_tlab2.resolve(libjvm))) {
|
||||
return Error("No AllocTracer symbols found. Are JDK debug symbols installed?");
|
||||
}
|
||||
_interval = args._interval;
|
||||
_allocated_bytes = 0;
|
||||
|
||||
OS::installSignalHandler(SIGTRAP, signalHandler);
|
||||
|
||||
_in_new_tlab.install();
|
||||
_outside_tlab.install();
|
||||
_in_new_tlab2.install();
|
||||
_outside_tlab2.install();
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
@@ -127,6 +145,4 @@ Error AllocTracer::start(Arguments& args) {
|
||||
void AllocTracer::stop() {
|
||||
_in_new_tlab.uninstall();
|
||||
_outside_tlab.uninstall();
|
||||
_in_new_tlab2.uninstall();
|
||||
_outside_tlab2.uninstall();
|
||||
}
|
||||
|
||||
@@ -19,41 +19,23 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include "arch.h"
|
||||
#include "codeCache.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
// Describes OpenJDK function being intercepted
|
||||
class Trap {
|
||||
private:
|
||||
const char* _func_name;
|
||||
instruction_t* _entry;
|
||||
instruction_t _saved_insn;
|
||||
|
||||
public:
|
||||
Trap(const char* func_name) : _func_name(func_name), _entry(NULL) {
|
||||
}
|
||||
|
||||
bool resolve(NativeCodeCache* libjvm);
|
||||
void install();
|
||||
void uninstall();
|
||||
|
||||
friend class AllocTracer;
|
||||
};
|
||||
#include "trap.h"
|
||||
|
||||
|
||||
class AllocTracer : public Engine {
|
||||
private:
|
||||
// JDK 7-9
|
||||
static int _trap_kind;
|
||||
static Trap _in_new_tlab;
|
||||
static Trap _outside_tlab;
|
||||
// JDK 10+
|
||||
static Trap _in_new_tlab2;
|
||||
static Trap _outside_tlab2;
|
||||
|
||||
static u64 _interval;
|
||||
static volatile u64 _allocated_bytes;
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void recordAllocation(void* ucontext, uintptr_t rklass, uintptr_t rsize, bool outside_tlab);
|
||||
|
||||
static void recordAllocation(void* ucontext, int event_type, uintptr_t rklass,
|
||||
uintptr_t total_size, uintptr_t instance_size);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
@@ -64,6 +46,11 @@ class AllocTracer : public Engine {
|
||||
return "bytes";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package one.profiler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Java API for in-process profiling. Serves as a wrapper around
|
||||
* async-profiler native library. This class is a singleton.
|
||||
@@ -25,10 +27,7 @@ package one.profiler;
|
||||
public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
private static AsyncProfiler instance;
|
||||
|
||||
private final String version;
|
||||
|
||||
private AsyncProfiler() {
|
||||
this.version = version0();
|
||||
}
|
||||
|
||||
public static AsyncProfiler getInstance() {
|
||||
@@ -59,7 +58,20 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
*/
|
||||
@Override
|
||||
public void start(String event, long interval) throws IllegalStateException {
|
||||
start0(event, interval);
|
||||
start0(event, interval, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start or resume profiling without resetting collected data.
|
||||
* Note that event and interval may change since the previous profiling session.
|
||||
*
|
||||
* @param event Profiling event, see {@link Events}
|
||||
* @param interval Sampling interval, e.g. nanoseconds for Events.CPU
|
||||
* @throws IllegalStateException If profiler is already running
|
||||
*/
|
||||
@Override
|
||||
public void resume(String event, long interval) throws IllegalStateException {
|
||||
start0(event, interval, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +99,11 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
*/
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return version;
|
||||
try {
|
||||
return execute0("version");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,10 +113,10 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
* @param command Profiling command
|
||||
* @return The command result
|
||||
* @throws IllegalArgumentException If failed to parse the command
|
||||
* @throws java.io.IOException If failed to create output file
|
||||
* @throws IOException If failed to create output file
|
||||
*/
|
||||
@Override
|
||||
public String execute(String command) throws IllegalArgumentException, java.io.IOException {
|
||||
public String execute(String command) throws IllegalArgumentException, IOException {
|
||||
return execute0(command);
|
||||
}
|
||||
|
||||
@@ -112,18 +128,11 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
*/
|
||||
@Override
|
||||
public String dumpCollapsed(Counter counter) {
|
||||
return dumpCollapsed0(counter.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump collected stack traces
|
||||
*
|
||||
* @param maxTraces Maximum number of stack traces to dump. 0 means no limit
|
||||
* @return Textual representation of the profile
|
||||
*/
|
||||
@Override
|
||||
public String dumpTraces(int maxTraces) {
|
||||
return dumpTraces0(maxTraces);
|
||||
try {
|
||||
return execute0("collapsed,counter=" + counter.name().toLowerCase());
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,14 +143,49 @@ public class AsyncProfiler implements AsyncProfilerMXBean {
|
||||
*/
|
||||
@Override
|
||||
public String dumpFlat(int maxMethods) {
|
||||
return dumpFlat0(maxMethods);
|
||||
try {
|
||||
return execute0("flat=" + maxMethods);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private native void start0(String event, long interval) throws IllegalStateException;
|
||||
/**
|
||||
* Add the given thread to the set of profiled threads.
|
||||
* 'filter' option must be enabled to use this method.
|
||||
*
|
||||
* @param thread Thread to include in profiling
|
||||
*/
|
||||
public void addThread(Thread thread) {
|
||||
filterThread(thread, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given thread from the set of profiled threads.
|
||||
* 'filter' option must be enabled to use this method.
|
||||
*
|
||||
* @param thread Thread to exclude from profiling
|
||||
*/
|
||||
public void removeThread(Thread thread) {
|
||||
filterThread(thread, false);
|
||||
}
|
||||
|
||||
private void filterThread(Thread thread, boolean enable) {
|
||||
if (thread == null) {
|
||||
filterThread0(null, enable);
|
||||
} else {
|
||||
// Need to take lock to avoid race condition with a thread state change
|
||||
synchronized (thread) {
|
||||
Thread.State state = thread.getState();
|
||||
if (state != Thread.State.NEW && state != Thread.State.TERMINATED) {
|
||||
filterThread0(thread, enable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private native void start0(String event, long interval, boolean reset) throws IllegalStateException;
|
||||
private native void stop0() throws IllegalStateException;
|
||||
private native String execute0(String command) throws IllegalArgumentException, java.io.IOException;
|
||||
private native String dumpCollapsed0(int counter);
|
||||
private native String dumpTraces0(int maxTraces);
|
||||
private native String dumpFlat0(int maxMethods);
|
||||
private native String version0();
|
||||
private native String execute0(String command) throws IllegalArgumentException, IOException;
|
||||
private native void filterThread0(Thread thread, boolean enable);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ package one.profiler;
|
||||
*/
|
||||
public interface AsyncProfilerMXBean {
|
||||
void start(String event, long interval) throws IllegalStateException;
|
||||
void resume(String event, long interval) throws IllegalStateException;
|
||||
void stop() throws IllegalStateException;
|
||||
|
||||
long getSamples();
|
||||
@@ -37,6 +38,5 @@ public interface AsyncProfilerMXBean {
|
||||
String execute(String command) throws IllegalArgumentException, java.io.IOException;
|
||||
|
||||
String dumpCollapsed(Counter counter);
|
||||
String dumpTraces(int maxTraces);
|
||||
String dumpFlat(int maxMethods);
|
||||
}
|
||||
@@ -20,8 +20,9 @@ package one.profiler;
|
||||
* Predefined event names to use in {@link AsyncProfiler#start(String, long)}
|
||||
*/
|
||||
public class Events {
|
||||
public static final String CPU = "cpu";
|
||||
public static final String ALLOC = "alloc";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String WALL = "wall";
|
||||
public static final String CPU = "cpu";
|
||||
public static final String ALLOC = "alloc";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String WALL = "wall";
|
||||
public static final String ITIMER = "itimer";
|
||||
}
|
||||
30
src/arch.h
30
src/arch.h
@@ -18,9 +18,16 @@
|
||||
#define _ARCH_H
|
||||
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
|
||||
static inline u64 atomicInc(u64& var, u64 increment = 1) {
|
||||
static inline u64 atomicInc(volatile u64& var, u64 increment = 1) {
|
||||
return __sync_fetch_and_add(&var, increment);
|
||||
}
|
||||
|
||||
static inline int atomicInc(volatile int& var, int increment = 1) {
|
||||
return __sync_fetch_and_add(&var, increment);
|
||||
}
|
||||
|
||||
@@ -30,6 +37,11 @@ static inline u64 atomicInc(u64& var, u64 increment = 1) {
|
||||
typedef unsigned char instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xcc;
|
||||
|
||||
const int SYSCALL_SIZE = 2;
|
||||
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")
|
||||
@@ -38,6 +50,12 @@ const instruction_t BREAKPOINT = 0xcc;
|
||||
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xe7f001f0;
|
||||
const instruction_t BREAKPOINT_THUMB = 0xde01de01;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
const int PLT_HEADER_SIZE = 20;
|
||||
const int PLT_ENTRY_SIZE = 12;
|
||||
const int PERF_REG_PC = 15; // PERF_REG_ARM_PC
|
||||
|
||||
#define spinPause() asm volatile("yield")
|
||||
#define rmb() asm volatile("dmb ish" : : : "memory")
|
||||
@@ -48,16 +66,18 @@ const instruction_t BREAKPOINT = 0xe7f001f0;
|
||||
typedef unsigned int instruction_t;
|
||||
const instruction_t BREAKPOINT = 0xd4200000;
|
||||
|
||||
const int SYSCALL_SIZE = sizeof(instruction_t);
|
||||
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 rmb() asm volatile("dmb ish" : : : "memory")
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
|
||||
#else
|
||||
#warning "Compiling on unsupported arch"
|
||||
|
||||
#define spinPause()
|
||||
#define rmb() __sync_synchronize()
|
||||
#define flushCache(addr) __builtin___clear_cache((char*)(addr), (char*)(addr) + sizeof(instruction_t))
|
||||
#error "Compiling on unsupported arch"
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -15,160 +15,346 @@
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include "arguments.h"
|
||||
|
||||
|
||||
// Predefined value that denotes successful operation
|
||||
const Error Error::OK(NULL);
|
||||
|
||||
// Extra buffer space for expanding file pattern
|
||||
const size_t EXTRA_BUF_SIZE = 512;
|
||||
|
||||
// Statically compute hash code of a string containing up to 12 [a-z] letters
|
||||
#define HASH(s) (HASH12(s " "))
|
||||
|
||||
#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
|
||||
|
||||
// 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 CASE2(s1, s2) } else if (arg_hash == HASH(s1) || arg_hash == HASH(s2)) {
|
||||
|
||||
|
||||
// Parses agent arguments.
|
||||
// The format of the string is:
|
||||
// arg[,arg...]
|
||||
// where arg is one of the following options:
|
||||
// start - start profiling
|
||||
// stop - stop profiling
|
||||
// status - print profiling status (inactive / running for X seconds)
|
||||
// list - show the list of available profiling events
|
||||
// version - display the agent version
|
||||
// event=EVENT - which event to trace (cpu, alloc, lock, cache-misses etc.)
|
||||
// collapsed[=C] - dump collapsed stacks (the format used by FlameGraph script)
|
||||
// svg[=C] - produce Flame Graph in SVG format
|
||||
// tree[=C] - produce call tree in HTML format
|
||||
// C is counter type: 'samples' or 'total'
|
||||
// jfr - dump events in Java Flight Recorder format
|
||||
// summary - dump profiling summary (number of collected samples of each type)
|
||||
// traces[=N] - dump top N call traces
|
||||
// flat[=N] - dump top N methods (aka flat profile)
|
||||
// interval=N - sampling interval in ns (default: 10'000'000, i.e. 10 ms)
|
||||
// jstackdepth=N - maximum Java stack depth (default: MAX_STACK_FRAMES)
|
||||
// framebuf=N - size of the buffer for stack frames (default: 1'000'000)
|
||||
// threads - profile different threads separately
|
||||
// allkernel - include only kernel-mode events
|
||||
// alluser - include only user-mode events
|
||||
// simple[=bool] - simple class names instead of FQN
|
||||
// ann[=bool] - annotate Java method names
|
||||
// title=TITLE - FlameGraph title
|
||||
// width=PX - FlameGraph image width
|
||||
// height=PX - FlameGraph frame height
|
||||
// minwidth=PX - FlameGraph minimum frame width
|
||||
// reverse - generate stack-reversed FlameGraph / Call tree
|
||||
// file=FILENAME - output file name for dumping
|
||||
// start - start profiling
|
||||
// resume - start or resume profiling without resetting collected data
|
||||
// stop - stop profiling
|
||||
// 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
|
||||
//
|
||||
// It is possible to specify multiple dump options at the same time
|
||||
|
||||
Error Arguments::parse(const char* args) {
|
||||
if (args == NULL) {
|
||||
return Error::OK;
|
||||
} else if (strlen(args) >= sizeof(_buf)) {
|
||||
return Error("Argument list too long");
|
||||
}
|
||||
|
||||
size_t len = strlen(args);
|
||||
free(_buf);
|
||||
_buf = (char*)malloc(len + EXTRA_BUF_SIZE);
|
||||
if (_buf == NULL) {
|
||||
return Error("Not enough memory to parse arguments");
|
||||
}
|
||||
strcpy(_buf, args);
|
||||
|
||||
for (char* arg = strtok(_buf, ","); arg != NULL; arg = strtok(NULL, ",")) {
|
||||
char* value = strchr(arg, '=');
|
||||
if (value != NULL) *value++ = 0;
|
||||
|
||||
if (strcmp(arg, "start") == 0) {
|
||||
_action = ACTION_START;
|
||||
} else if (strcmp(arg, "stop") == 0) {
|
||||
_action = ACTION_STOP;
|
||||
} else if (strcmp(arg, "status") == 0) {
|
||||
_action = ACTION_STATUS;
|
||||
} else if (strcmp(arg, "list") == 0) {
|
||||
_action = ACTION_LIST;
|
||||
} else if (strcmp(arg, "version") == 0) {
|
||||
_action = ACTION_VERSION;
|
||||
} else if (strcmp(arg, "event") == 0) {
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("event must not be empty");
|
||||
}
|
||||
_event = value;
|
||||
} else if (strcmp(arg, "trace") == 0) {
|
||||
_trace = true;
|
||||
} else if (strcmp(arg, "collapsed") == 0 || strcmp(arg, "folded") == 0) {
|
||||
_dump_collapsed = true;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
} else if (strcmp(arg, "flamegraph") == 0 || strcmp(arg, "svg") == 0) {
|
||||
_dump_flamegraph = true;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
} else if (strcmp(arg, "tree") == 0) {
|
||||
_dump_tree = true;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
} else if (strcmp(arg, "jfr") == 0) {
|
||||
_dump_jfr = true;
|
||||
} else if (strcmp(arg, "summary") == 0) {
|
||||
_dump_summary = true;
|
||||
} else if (strcmp(arg, "traces") == 0) {
|
||||
_dump_traces = value == NULL ? INT_MAX : atoi(value);
|
||||
} else if (strcmp(arg, "flat") == 0) {
|
||||
_dump_flat = value == NULL ? INT_MAX : atoi(value);
|
||||
} else if (strcmp(arg, "interval") == 0) {
|
||||
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
|
||||
return Error("interval must be > 0");
|
||||
}
|
||||
} else if (strcmp(arg, "jstackdepth") == 0) {
|
||||
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
|
||||
return Error("jstackdepth must be > 0");
|
||||
}
|
||||
} else if (strcmp(arg, "framebuf") == 0) {
|
||||
if (value == NULL || (_framebuf = atoi(value)) <= 0) {
|
||||
return Error("framebuf must be > 0");
|
||||
}
|
||||
} else if (strcmp(arg, "threads") == 0) {
|
||||
_threads = true;
|
||||
} else if (strcmp(arg, "allkernel") == 0) {
|
||||
_ring = RING_KERNEL;
|
||||
} else if (strcmp(arg, "alluser") == 0) {
|
||||
_ring = RING_USER;
|
||||
} else if (strcmp(arg, "simple") == 0) {
|
||||
_simple = value == NULL || strcmp(value, "true") == 0;
|
||||
} else if (strcmp(arg, "ann") == 0) {
|
||||
_annotate = value == NULL || strcmp(value, "true") == 0;
|
||||
} else if (strcmp(arg, "title") == 0 && value != NULL) {
|
||||
_title = value;
|
||||
} else if (strcmp(arg, "width") == 0 && value != NULL) {
|
||||
_width = atoi(value);
|
||||
} else if (strcmp(arg, "height") == 0 && value != NULL) {
|
||||
_height = atoi(value);
|
||||
} else if (strcmp(arg, "minwidth") == 0 && value != NULL) {
|
||||
_minwidth = atof(value);
|
||||
} else if (strcmp(arg, "reverse") == 0) {
|
||||
_reverse = true;
|
||||
} else if (strcmp(arg, "file") == 0) {
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("file must not be empty");
|
||||
}
|
||||
_file = value;
|
||||
SWITCH (arg) {
|
||||
// Actions
|
||||
CASE("start")
|
||||
_action = ACTION_START;
|
||||
|
||||
CASE("resume")
|
||||
_action = ACTION_RESUME;
|
||||
|
||||
CASE("stop")
|
||||
_action = ACTION_STOP;
|
||||
|
||||
CASE("check")
|
||||
_action = ACTION_CHECK;
|
||||
|
||||
CASE("status")
|
||||
_action = ACTION_STATUS;
|
||||
|
||||
CASE("list")
|
||||
_action = ACTION_LIST;
|
||||
|
||||
CASE("version")
|
||||
_action = value == NULL ? ACTION_VERSION : ACTION_FULL_VERSION;
|
||||
|
||||
// Output formats
|
||||
CASE2("collapsed", "folded")
|
||||
_output = OUTPUT_COLLAPSED;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE2("flamegraph", "html")
|
||||
_output = OUTPUT_FLAMEGRAPH;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE("tree")
|
||||
_output = OUTPUT_TREE;
|
||||
_counter = value == NULL || strcmp(value, "samples") == 0 ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
CASE("jfr")
|
||||
_output = OUTPUT_JFR;
|
||||
|
||||
CASE("flat")
|
||||
_output = OUTPUT_FLAT;
|
||||
_dump_flat = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
// Basic options
|
||||
CASE("event")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("event must not be empty");
|
||||
}
|
||||
|
||||
if (!addEvent(value)) {
|
||||
return Error("multiple incompatible events");
|
||||
}
|
||||
|
||||
CASE("interval")
|
||||
if (value == NULL || (_interval = parseUnits(value)) <= 0) {
|
||||
return Error("Invalid interval");
|
||||
}
|
||||
|
||||
CASE("jstackdepth")
|
||||
if (value == NULL || (_jstackdepth = atoi(value)) <= 0) {
|
||||
return Error("jstackdepth must be > 0");
|
||||
}
|
||||
|
||||
CASE("safemode")
|
||||
_safe_mode = value == NULL ? INT_MAX : atoi(value);
|
||||
|
||||
CASE("file")
|
||||
if (value == NULL || value[0] == 0) {
|
||||
return Error("file must not be empty");
|
||||
}
|
||||
_file = value;
|
||||
|
||||
// Filters
|
||||
CASE("filter")
|
||||
_filter = value == NULL ? "" : value;
|
||||
|
||||
CASE("include")
|
||||
if (value != NULL) appendToEmbeddedList(_include, value);
|
||||
|
||||
CASE("exclude")
|
||||
if (value != NULL) appendToEmbeddedList(_exclude, value);
|
||||
|
||||
CASE("threads")
|
||||
_threads = true;
|
||||
|
||||
CASE("allkernel")
|
||||
_ring = RING_KERNEL;
|
||||
|
||||
CASE("alluser")
|
||||
_ring = RING_USER;
|
||||
|
||||
CASE("cstack")
|
||||
if (value != NULL) {
|
||||
if (value[0] == 'n') {
|
||||
_cstack = CSTACK_NO;
|
||||
} else if (value[0] == 'l') {
|
||||
_cstack = CSTACK_LBR;
|
||||
} else {
|
||||
_cstack = CSTACK_FP;
|
||||
}
|
||||
}
|
||||
|
||||
// Output style modifiers
|
||||
CASE("simple")
|
||||
_style |= STYLE_SIMPLE;
|
||||
|
||||
CASE("dot")
|
||||
_style |= STYLE_DOTTED;
|
||||
|
||||
CASE("sig")
|
||||
_style |= STYLE_SIGNATURES;
|
||||
|
||||
CASE("ann")
|
||||
_style |= STYLE_ANNOTATE;
|
||||
|
||||
CASE("begin")
|
||||
_begin = value;
|
||||
|
||||
CASE("end")
|
||||
_end = value;
|
||||
|
||||
// FlameGraph options
|
||||
CASE("title")
|
||||
if (value != NULL) _title = value;
|
||||
|
||||
CASE("minwidth")
|
||||
if (value != NULL) _minwidth = atof(value);
|
||||
|
||||
CASE("reverse")
|
||||
_reverse = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (dumpRequested() && (_action == ACTION_NONE || _action == ACTION_STOP)) {
|
||||
if (_file != NULL && strchr(_file, '%') != NULL) {
|
||||
_file = expandFilePattern(_buf + len + 1, EXTRA_BUF_SIZE - 1, _file);
|
||||
}
|
||||
|
||||
if (_file != NULL && _output == OUTPUT_NONE) {
|
||||
_output = detectOutputFormat(_file);
|
||||
_dump_flat = 200;
|
||||
}
|
||||
|
||||
if (_output != OUTPUT_NONE && (_action == ACTION_NONE || _action == ACTION_STOP)) {
|
||||
_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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The linked list of string offsets is embedded right into _buf array
|
||||
void Arguments::appendToEmbeddedList(int& list, char* value) {
|
||||
((int*)value)[-1] = list;
|
||||
list = (int)(value - _buf);
|
||||
}
|
||||
|
||||
// Should match statically computed HASH(arg)
|
||||
long long Arguments::hash(const char* arg) {
|
||||
long long h = 0;
|
||||
for (int shift = 0; *arg != 0; shift += 5) {
|
||||
h |= (*arg++ & 31LL) << shift;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// Expands %p to the process id
|
||||
// %t to the timestamp
|
||||
const char* Arguments::expandFilePattern(char* dest, size_t max_size, const char* pattern) {
|
||||
char* ptr = dest;
|
||||
char* end = dest + max_size - 1;
|
||||
|
||||
while (ptr < end && *pattern != 0) {
|
||||
char c = *pattern++;
|
||||
if (c == '%') {
|
||||
c = *pattern++;
|
||||
if (c == 0) {
|
||||
break;
|
||||
} else if (c == 'p') {
|
||||
ptr += snprintf(ptr, end - ptr, "%d", getpid());
|
||||
continue;
|
||||
} else if (c == 't') {
|
||||
time_t timestamp = time(NULL);
|
||||
struct tm t;
|
||||
localtime_r(×tamp, &t);
|
||||
ptr += snprintf(ptr, end - ptr, "%d%02d%02d-%02d%02d%02d",
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
|
||||
t.tm_hour, t.tm_min, t.tm_sec);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*ptr++ = c;
|
||||
}
|
||||
|
||||
*ptr = 0;
|
||||
return dest;
|
||||
}
|
||||
|
||||
Output Arguments::detectOutputFormat(const char* file) {
|
||||
const char* ext = strrchr(file, '.');
|
||||
if (ext != NULL) {
|
||||
if (strcmp(ext, ".html") == 0) {
|
||||
return OUTPUT_FLAMEGRAPH;
|
||||
} else if (strcmp(ext, ".jfr") == 0) {
|
||||
return OUTPUT_JFR;
|
||||
} else if (strcmp(ext, ".collapsed") == 0 || strcmp(ext, ".folded") == 0) {
|
||||
return OUTPUT_COLLAPSED;
|
||||
}
|
||||
}
|
||||
return OUTPUT_FLAT;
|
||||
}
|
||||
|
||||
long Arguments::parseUnits(const char* str) {
|
||||
char* end;
|
||||
long result = strtol(str, &end, 0);
|
||||
|
||||
if (*end) {
|
||||
switch (*end) {
|
||||
case 'K': case 'k':
|
||||
case 'U': case 'u': // microseconds
|
||||
return result * 1000;
|
||||
case 'M': case 'm': // million, megabytes or milliseconds
|
||||
return result * 1000000;
|
||||
case 'G': case 'g':
|
||||
case 'S': case 's': // seconds
|
||||
return result * 1000000000;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
return result;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Arguments::~Arguments() {
|
||||
if (!_shared) free(_buf);
|
||||
}
|
||||
|
||||
void Arguments::save(Arguments& other) {
|
||||
if (!_shared) free(_buf);
|
||||
*this = other;
|
||||
other._shared = true;
|
||||
}
|
||||
|
||||
115
src/arguments.h
115
src/arguments.h
@@ -21,21 +21,25 @@
|
||||
|
||||
|
||||
const long DEFAULT_INTERVAL = 10000000; // 10 ms
|
||||
const int DEFAULT_FRAMEBUF = 1000000;
|
||||
const int DEFAULT_JSTACKDEPTH = 2048;
|
||||
|
||||
const char* const EVENT_CPU = "cpu";
|
||||
const char* const EVENT_ALLOC = "alloc";
|
||||
const char* const EVENT_LOCK = "lock";
|
||||
const char* const EVENT_WALL = "wall";
|
||||
const char* const EVENT_ITIMER = "itimer";
|
||||
const char* const EVENT_JSTACK = "jstack";
|
||||
|
||||
enum Action {
|
||||
ACTION_NONE,
|
||||
ACTION_START,
|
||||
ACTION_RESUME,
|
||||
ACTION_STOP,
|
||||
ACTION_CHECK,
|
||||
ACTION_STATUS,
|
||||
ACTION_LIST,
|
||||
ACTION_VERSION,
|
||||
ACTION_FULL_VERSION,
|
||||
ACTION_DUMP
|
||||
};
|
||||
|
||||
@@ -50,6 +54,35 @@ 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
|
||||
};
|
||||
|
||||
enum CStack {
|
||||
CSTACK_DEFAULT,
|
||||
CSTACK_NO,
|
||||
CSTACK_FP,
|
||||
CSTACK_LBR
|
||||
};
|
||||
|
||||
enum Output {
|
||||
OUTPUT_NONE,
|
||||
OUTPUT_FLAT,
|
||||
OUTPUT_COLLAPSED,
|
||||
OUTPUT_FLAMEGRAPH,
|
||||
OUTPUT_TREE,
|
||||
OUTPUT_JFR
|
||||
};
|
||||
|
||||
|
||||
class Error {
|
||||
private:
|
||||
@@ -73,69 +106,77 @@ class Error {
|
||||
|
||||
class Arguments {
|
||||
private:
|
||||
char _buf[1024];
|
||||
char* _buf;
|
||||
bool _shared;
|
||||
|
||||
long parseUnits(const char* str);
|
||||
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);
|
||||
|
||||
public:
|
||||
Action _action;
|
||||
Counter _counter;
|
||||
Ring _ring;
|
||||
const char* _event;
|
||||
int _events;
|
||||
const char* _event_desc;
|
||||
long _interval;
|
||||
int _jstackdepth;
|
||||
int _framebuf;
|
||||
int _safe_mode;
|
||||
const char* _file;
|
||||
const char* _filter;
|
||||
int _include;
|
||||
int _exclude;
|
||||
bool _threads;
|
||||
bool _simple;
|
||||
bool _annotate;
|
||||
char* _file;
|
||||
bool _trace;
|
||||
bool _dump_collapsed;
|
||||
bool _dump_flamegraph;
|
||||
bool _dump_tree;
|
||||
bool _dump_jfr;
|
||||
bool _dump_summary;
|
||||
int _dump_traces;
|
||||
int _style;
|
||||
CStack _cstack;
|
||||
Output _output;
|
||||
int _dump_flat;
|
||||
const char* _begin;
|
||||
const char* _end;
|
||||
// FlameGraph parameters
|
||||
const char* _title;
|
||||
int _width;
|
||||
int _height;
|
||||
double _minwidth;
|
||||
bool _reverse;
|
||||
|
||||
Arguments() :
|
||||
_buf(NULL),
|
||||
_shared(false),
|
||||
_action(ACTION_NONE),
|
||||
_counter(COUNTER_SAMPLES),
|
||||
_ring(RING_ANY),
|
||||
_event(EVENT_CPU),
|
||||
_events(0),
|
||||
_event_desc(NULL),
|
||||
_interval(0),
|
||||
_jstackdepth(0),
|
||||
_framebuf(DEFAULT_FRAMEBUF),
|
||||
_threads(false),
|
||||
_simple(false),
|
||||
_annotate(false),
|
||||
_jstackdepth(DEFAULT_JSTACKDEPTH),
|
||||
_safe_mode(0),
|
||||
_file(NULL),
|
||||
_trace(false),
|
||||
_dump_collapsed(false),
|
||||
_dump_flamegraph(false),
|
||||
_dump_tree(false),
|
||||
_dump_jfr(false),
|
||||
_dump_summary(false),
|
||||
_dump_traces(0),
|
||||
_dump_flat(0),
|
||||
_filter(NULL),
|
||||
_include(0),
|
||||
_exclude(0),
|
||||
_threads(false),
|
||||
_style(0),
|
||||
_cstack(CSTACK_DEFAULT),
|
||||
_output(OUTPUT_NONE),
|
||||
_begin(NULL),
|
||||
_end(NULL),
|
||||
_title("Flame Graph"),
|
||||
_width(1200),
|
||||
_height(16),
|
||||
_minwidth(1),
|
||||
_minwidth(0),
|
||||
_reverse(false) {
|
||||
}
|
||||
|
||||
bool dumpRequested() {
|
||||
return _dump_collapsed || _dump_flamegraph || _dump_tree || _dump_jfr || _dump_summary || _dump_traces > 0 || _dump_flat > 0;
|
||||
}
|
||||
~Arguments();
|
||||
|
||||
void save(Arguments& other);
|
||||
|
||||
Error parse(const char* args);
|
||||
|
||||
bool addEvent(const char* event);
|
||||
|
||||
friend class FrameName;
|
||||
friend class Recording;
|
||||
};
|
||||
|
||||
#endif // _ARGUMENTS_H
|
||||
|
||||
246
src/callTraceStorage.cpp
Normal file
246
src/callTraceStorage.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <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;
|
||||
|
||||
|
||||
class LongHashTable {
|
||||
private:
|
||||
LongHashTable* _prev;
|
||||
void* _padding0;
|
||||
u32 _capacity;
|
||||
u32 _padding1[15];
|
||||
volatile u32 _size;
|
||||
u32 _padding2[15];
|
||||
|
||||
static size_t getSize(u32 capacity) {
|
||||
size_t size = sizeof(LongHashTable) + (sizeof(u64) + sizeof(CallTraceSample)) * capacity;
|
||||
return (size + PAGE_ALIGNMENT) & ~PAGE_ALIGNMENT;
|
||||
}
|
||||
|
||||
public:
|
||||
static LongHashTable* allocate(LongHashTable* prev, u32 capacity) {
|
||||
LongHashTable* table = (LongHashTable*)OS::safeAlloc(getSize(capacity));
|
||||
if (table != NULL) {
|
||||
table->_prev = prev;
|
||||
table->_capacity = capacity;
|
||||
table->_size = 0;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
LongHashTable* destroy() {
|
||||
LongHashTable* prev = _prev;
|
||||
OS::safeFree(this, getSize(_capacity));
|
||||
return prev;
|
||||
}
|
||||
|
||||
LongHashTable* prev() {
|
||||
return _prev;
|
||||
}
|
||||
|
||||
u32 capacity() {
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
u32 size() {
|
||||
return _size;
|
||||
}
|
||||
|
||||
u32 incSize() {
|
||||
return __sync_add_and_fetch(&_size, 1);
|
||||
}
|
||||
|
||||
u64* keys() {
|
||||
return (u64*)(this + 1);
|
||||
}
|
||||
|
||||
CallTraceSample* values() {
|
||||
return (CallTraceSample*)(keys() + _capacity);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(keys(), 0, (sizeof(u64) + sizeof(CallTraceSample)) * _capacity);
|
||||
_size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
CallTraceStorage::CallTraceStorage() : _allocator(CALL_TRACE_CHUNK) {
|
||||
_current_table = LongHashTable::allocate(NULL, INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
CallTraceStorage::~CallTraceStorage() {
|
||||
while (_current_table != NULL) {
|
||||
_current_table = _current_table->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::clear() {
|
||||
while (_current_table->prev() != NULL) {
|
||||
_current_table = _current_table->destroy();
|
||||
}
|
||||
_current_table->clear();
|
||||
_allocator.clear();
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectTraces(std::map<u32, CallTrace*>& map) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
map[capacity - (INITIAL_CAPACITY - 1) + slot] = values[slot].trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CallTraceStorage::collectSamples(std::vector<CallTraceSample*>& samples) {
|
||||
for (LongHashTable* table = _current_table; table != NULL; table = table->prev()) {
|
||||
u64* keys = table->keys();
|
||||
CallTraceSample* values = table->values();
|
||||
u32 capacity = table->capacity();
|
||||
|
||||
for (u32 slot = 0; slot < capacity; slot++) {
|
||||
if (keys[slot] != 0) {
|
||||
samples.push_back(&values[slot]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptation of MurmurHash64A by Austin Appleby
|
||||
u64 CallTraceStorage::calcHash(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const u64 M = 0xc6a4a7935bd1e995ULL;
|
||||
const int R = 47;
|
||||
|
||||
int len = num_frames * sizeof(ASGCT_CallFrame);
|
||||
u64 h = len * M;
|
||||
|
||||
const u64* data = (const u64*)frames;
|
||||
const u64* end = data + len / 8;
|
||||
|
||||
while (data != end) {
|
||||
u64 k = *data++;
|
||||
k *= M;
|
||||
k ^= k >> R;
|
||||
k *= M;
|
||||
h ^= k;
|
||||
h *= M;
|
||||
}
|
||||
|
||||
if (len & 4) {
|
||||
h ^= *(u32*)data;
|
||||
h *= M;
|
||||
}
|
||||
|
||||
h ^= h >> R;
|
||||
h *= M;
|
||||
h ^= h >> R;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
CallTrace* CallTraceStorage::storeCallTrace(int num_frames, ASGCT_CallFrame* frames) {
|
||||
const size_t header_size = sizeof(CallTrace) - sizeof(ASGCT_CallFrame);
|
||||
CallTrace* buf = (CallTrace*)_allocator.alloc(header_size + num_frames * sizeof(ASGCT_CallFrame));
|
||||
if (buf != NULL) {
|
||||
buf->num_frames = num_frames;
|
||||
// Do not use memcpy inside signal handler
|
||||
for (int i = 0; i < num_frames; i++) {
|
||||
buf->frames[i] = frames[i];
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
CallTrace* CallTraceStorage::findCallTrace(LongHashTable* table, u64 hash) {
|
||||
u64* keys = table->keys();
|
||||
u32 capacity = table->capacity();
|
||||
u32 slot = hash & (capacity - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
if (keys[slot] == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (++step >= capacity) {
|
||||
return NULL;
|
||||
}
|
||||
slot = (slot + step) & (capacity - 1);
|
||||
}
|
||||
|
||||
return table->values()[slot].trace;
|
||||
}
|
||||
|
||||
u32 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, u64 counter) {
|
||||
u64 hash = calcHash(num_frames, frames);
|
||||
|
||||
LongHashTable* table = _current_table;
|
||||
u64* keys = table->keys();
|
||||
u32 capacity = table->capacity();
|
||||
u32 slot = hash & (capacity - 1);
|
||||
u32 step = 0;
|
||||
|
||||
while (keys[slot] != hash) {
|
||||
if (keys[slot] == 0) {
|
||||
if (!__sync_bool_compare_and_swap(&keys[slot], 0, hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Increment the table size, and if the load factor exceeds 0.75, reserve a new table
|
||||
if (table->incSize() == capacity * 3 / 4) {
|
||||
LongHashTable* new_table = LongHashTable::allocate(table, capacity * 2);
|
||||
if (new_table != NULL) {
|
||||
__sync_bool_compare_and_swap(&_current_table, table, new_table);
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate from a previous table to save space
|
||||
CallTrace* trace = table->prev() == NULL ? NULL : findCallTrace(table->prev(), hash);
|
||||
if (trace == NULL) {
|
||||
trace = storeCallTrace(num_frames, frames);
|
||||
}
|
||||
table->values()[slot].trace = trace;
|
||||
break;
|
||||
}
|
||||
|
||||
if (++step >= capacity) {
|
||||
// Very unlikely case of a table overflow
|
||||
return 0;
|
||||
}
|
||||
// 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);
|
||||
|
||||
return capacity - (INITIAL_CAPACITY - 1) + slot;
|
||||
}
|
||||
60
src/callTraceStorage.h
Normal file
60
src/callTraceStorage.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _CALLTRACESTORAGE_H
|
||||
#define _CALLTRACESTORAGE_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "arch.h"
|
||||
#include "linearAllocator.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
class LongHashTable;
|
||||
|
||||
struct CallTrace {
|
||||
int num_frames;
|
||||
ASGCT_CallFrame frames[1];
|
||||
};
|
||||
|
||||
struct CallTraceSample {
|
||||
CallTrace* trace;
|
||||
u64 samples;
|
||||
u64 counter;
|
||||
};
|
||||
|
||||
class CallTraceStorage {
|
||||
private:
|
||||
LinearAllocator _allocator;
|
||||
LongHashTable* _current_table;
|
||||
|
||||
u64 calcHash(int num_frames, ASGCT_CallFrame* frames);
|
||||
CallTrace* storeCallTrace(int num_frames, ASGCT_CallFrame* frames);
|
||||
CallTrace* findCallTrace(LongHashTable* table, u64 hash);
|
||||
|
||||
public:
|
||||
CallTraceStorage();
|
||||
~CallTraceStorage();
|
||||
|
||||
void clear();
|
||||
void collectTraces(std::map<u32, CallTrace*>& map);
|
||||
void collectSamples(std::vector<CallTraceSample*>& samples);
|
||||
|
||||
u32 put(int num_frames, ASGCT_CallFrame* frames, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _CALLTRACESTORAGE
|
||||
@@ -22,21 +22,35 @@
|
||||
void CodeCache::expand() {
|
||||
CodeBlob* old_blobs = _blobs;
|
||||
CodeBlob* new_blobs = new CodeBlob[_capacity * 2];
|
||||
memcpy(new_blobs, old_blobs, _capacity * sizeof(CodeBlob));
|
||||
|
||||
int live = 0;
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (_blobs[i]._method != NULL) {
|
||||
new_blobs[live++] = _blobs[i];
|
||||
}
|
||||
}
|
||||
|
||||
_count = live;
|
||||
_capacity *= 2;
|
||||
_blobs = new_blobs;
|
||||
delete[] old_blobs;
|
||||
}
|
||||
|
||||
void CodeCache::add(const void* start, int length, jmethodID method) {
|
||||
void CodeCache::add(const void* start, int length, jmethodID method, bool update_bounds) {
|
||||
if (_count >= _capacity) {
|
||||
expand();
|
||||
}
|
||||
|
||||
const void* end = (const char*)start + length;
|
||||
_blobs[_count]._start = start;
|
||||
_blobs[_count]._end = (const char*)start + length;
|
||||
_blobs[_count]._end = end;
|
||||
_blobs[_count]._method = method;
|
||||
_count++;
|
||||
|
||||
if (update_bounds) {
|
||||
if (start < _min_address) _min_address = start;
|
||||
if (end > _max_address) _max_address = end;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::remove(const void* start, jmethodID method) {
|
||||
@@ -50,7 +64,8 @@ void CodeCache::remove(const void* start, jmethodID method) {
|
||||
|
||||
jmethodID CodeCache::find(const void* address) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (address >= _blobs[i]._start && address < _blobs[i]._end) {
|
||||
CodeBlob* cb = _blobs + i;
|
||||
if (address >= cb->_start && address < cb->_end && cb->_method != NULL) {
|
||||
return _blobs[i]._method;
|
||||
}
|
||||
}
|
||||
@@ -71,8 +86,13 @@ NativeCodeCache::~NativeCodeCache() {
|
||||
free(_name);
|
||||
}
|
||||
|
||||
void NativeCodeCache::add(const void* start, int length, const char* name) {
|
||||
CodeCache::add(start, length, (jmethodID)strdup(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() {
|
||||
@@ -80,8 +100,8 @@ void NativeCodeCache::sort() {
|
||||
|
||||
qsort(_blobs, _count, sizeof(CodeBlob), CodeBlob::comparator);
|
||||
|
||||
if (_min_address == NULL) _min_address = _blobs[0]._start;
|
||||
if (_max_address == NULL) _max_address = _blobs[_count - 1]._end;
|
||||
if (_min_address == NO_MIN_ADDRESS) _min_address = _blobs[0]._start;
|
||||
if (_max_address == NO_MAX_ADDRESS) _max_address = _blobs[_count - 1]._end;
|
||||
}
|
||||
|
||||
const char* NativeCodeCache::binarySearch(const void* address) {
|
||||
@@ -99,8 +119,9 @@ const char* NativeCodeCache::binarySearch(const void* address) {
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols with zero size can be valid functions: e.g. ASM entry points or kernel code
|
||||
if (low > 0 && _blobs[low - 1]._start == _blobs[low - 1]._end) {
|
||||
// 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 _name;
|
||||
@@ -117,7 +138,10 @@ const void* NativeCodeCache::findSymbol(const char* name) {
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix) {
|
||||
int prefix_len = strlen(prefix);
|
||||
return findSymbolByPrefix(prefix, strlen(prefix));
|
||||
}
|
||||
|
||||
const void* NativeCodeCache::findSymbolByPrefix(const char* prefix, int prefix_len) {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
const char* blob_name = (const char*)_blobs[i]._method;
|
||||
if (blob_name != NULL && strncmp(blob_name, prefix, prefix_len) == 0) {
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
#define NO_MIN_ADDRESS ((const void*)-1)
|
||||
#define NO_MAX_ADDRESS ((const void*)0)
|
||||
|
||||
const int INITIAL_CODE_CACHE_CAPACITY = 1000;
|
||||
|
||||
|
||||
@@ -50,6 +53,8 @@ class CodeCache {
|
||||
int _capacity;
|
||||
int _count;
|
||||
CodeBlob* _blobs;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
|
||||
void expand();
|
||||
|
||||
@@ -58,13 +63,19 @@ class CodeCache {
|
||||
_capacity = INITIAL_CODE_CACHE_CAPACITY;
|
||||
_count = 0;
|
||||
_blobs = new CodeBlob[_capacity];
|
||||
_min_address = NO_MIN_ADDRESS;
|
||||
_max_address = NO_MAX_ADDRESS;
|
||||
}
|
||||
|
||||
~CodeCache() {
|
||||
delete[] _blobs;
|
||||
}
|
||||
|
||||
void add(const void* start, int length, jmethodID method);
|
||||
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);
|
||||
};
|
||||
@@ -73,11 +84,11 @@ class CodeCache {
|
||||
class NativeCodeCache : public CodeCache {
|
||||
private:
|
||||
char* _name;
|
||||
const void* _min_address;
|
||||
const void* _max_address;
|
||||
|
||||
|
||||
public:
|
||||
NativeCodeCache(const char* name, const void* min_address = NULL, const void* max_address = NULL);
|
||||
NativeCodeCache(const char* name,
|
||||
const void* min_address = NO_MIN_ADDRESS,
|
||||
const void* max_address = NO_MAX_ADDRESS);
|
||||
|
||||
~NativeCodeCache();
|
||||
|
||||
@@ -85,15 +96,12 @@ class NativeCodeCache : public CodeCache {
|
||||
return _name;
|
||||
}
|
||||
|
||||
bool contains(const void* address) {
|
||||
return address >= _min_address && address < _max_address;
|
||||
}
|
||||
|
||||
void add(const void* start, int length, const char* name);
|
||||
void add(const void* start, int length, const char* name, bool update_bounds = false);
|
||||
void sort();
|
||||
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);
|
||||
};
|
||||
|
||||
#endif // _CODECACHE_H
|
||||
|
||||
442
src/converter/FlameGraph.java
Normal file
442
src/converter/FlameGraph.java
Normal file
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class FlameGraph {
|
||||
public String title = "Flame Graph";
|
||||
public boolean reverse;
|
||||
public double minwidth;
|
||||
public int skip;
|
||||
public String input;
|
||||
public String output;
|
||||
|
||||
private final Frame root = new Frame();
|
||||
private int depth;
|
||||
private long mintotal;
|
||||
|
||||
public FlameGraph(String... args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if (!arg.startsWith("--") && !arg.isEmpty()) {
|
||||
if (input == null) {
|
||||
input = arg;
|
||||
} else {
|
||||
output = arg;
|
||||
}
|
||||
} else if (arg.equals("--title")) {
|
||||
title = args[++i];
|
||||
} else if (arg.equals("--reverse")) {
|
||||
reverse = true;
|
||||
} else if (arg.equals("--minwidth")) {
|
||||
minwidth = Double.parseDouble(args[++i]);
|
||||
} else if (arg.equals("--skip")) {
|
||||
skip = Integer.parseInt(args[++i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parse() throws IOException {
|
||||
parse(new InputStreamReader(new FileInputStream(input), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public void parse(Reader in) throws IOException {
|
||||
try (BufferedReader br = new BufferedReader(in)) {
|
||||
for (String line; (line = br.readLine()) != null; ) {
|
||||
int space = line.lastIndexOf(' ');
|
||||
if (space <= 0) continue;
|
||||
|
||||
String[] trace = line.substring(0, space).split(";");
|
||||
long ticks = Long.parseLong(line.substring(space + 1));
|
||||
addSample(trace, ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addSample(String[] trace, long ticks) {
|
||||
Frame frame = root;
|
||||
if (reverse) {
|
||||
for (int i = trace.length; --i >= skip; ) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = skip; i < trace.length; i++) {
|
||||
frame.total += ticks;
|
||||
frame = frame.child(trace[i]);
|
||||
}
|
||||
}
|
||||
frame.total += ticks;
|
||||
frame.self += ticks;
|
||||
|
||||
depth = Math.max(depth, trace.length);
|
||||
}
|
||||
|
||||
public void dump() throws IOException {
|
||||
if (output == null) {
|
||||
dump(System.out);
|
||||
} else {
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
|
||||
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
|
||||
dump(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(PrintStream out) {
|
||||
out.print(applyReplacements(HEADER,
|
||||
"{title}", title,
|
||||
"{height}", (depth + 1) * 16,
|
||||
"{depth}", depth + 1,
|
||||
"{reverse}", reverse));
|
||||
|
||||
mintotal = (long) (root.total * minwidth / 100);
|
||||
printFrame(out, "all", root, 0, 0);
|
||||
|
||||
out.print(FOOTER);
|
||||
}
|
||||
|
||||
// Replace ${variables} in the given string with field values
|
||||
private String applyReplacements(String s, Object... params) {
|
||||
StringBuilder result = new StringBuilder(s.length() + 256);
|
||||
|
||||
int p = 0;
|
||||
for (int q; (q = s.indexOf('$', p)) >= 0; ) {
|
||||
result.append(s, p, q);
|
||||
p = s.indexOf('}', q + 2) + 1;
|
||||
String var = s.substring(q + 1, p);
|
||||
for (int i = 0; i < params.length; i += 2) {
|
||||
if (var.equals(params[i])) {
|
||||
result.append(params[i + 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.append(s, p, s.length());
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void printFrame(PrintStream out, String title, Frame frame, int level, long x) {
|
||||
int type = frameType(title);
|
||||
title = stripSuffix(title);
|
||||
if (title.indexOf('\'') >= 0) {
|
||||
title = title.replace("'", "\\'");
|
||||
}
|
||||
|
||||
out.println("f(" + level + "," + x + "," + frame.total + "," + type + ",'" + title + "')");
|
||||
|
||||
x += frame.self;
|
||||
for (Map.Entry<String, Frame> e : frame.entrySet()) {
|
||||
Frame child = e.getValue();
|
||||
if (child.total >= mintotal) {
|
||||
printFrame(out, e.getKey(), child, level + 1, x);
|
||||
}
|
||||
x += child.total;
|
||||
}
|
||||
}
|
||||
|
||||
private String stripSuffix(String title) {
|
||||
int len = title.length();
|
||||
if (len >= 4 && title.charAt(len - 1) == ']' && title.regionMatches(len - 4, "_[", 0, 2)) {
|
||||
return title.substring(0, len - 4);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
private int frameType(String title) {
|
||||
if (title.endsWith("_[j]")) {
|
||||
return 0;
|
||||
} else if (title.endsWith("_[i]")) {
|
||||
return 1;
|
||||
} else if (title.endsWith("_[k]")) {
|
||||
return 2;
|
||||
} else if (title.contains("::") || title.startsWith("-[") || title.startsWith("+[")) {
|
||||
return 3;
|
||||
} else if (title.indexOf('/') > 0 || title.indexOf('.') > 0 && Character.isUpperCase(title.charAt(0))) {
|
||||
return 0;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
System.out.println("Usage: java " + FlameGraph.class.getName() + " [options] input.collapsed [output.html]");
|
||||
System.out.println();
|
||||
System.out.println("Options:");
|
||||
System.out.println(" --title TITLE");
|
||||
System.out.println(" --reverse");
|
||||
System.out.println(" --minwidth PERCENT");
|
||||
System.out.println(" --skip FRAMES");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
fg.parse();
|
||||
fg.dump();
|
||||
}
|
||||
|
||||
static class Frame extends TreeMap<String, Frame> {
|
||||
long total;
|
||||
long self;
|
||||
|
||||
Frame child(String title) {
|
||||
Frame child = get(title);
|
||||
if (child == null) {
|
||||
put(title, child = new Frame());
|
||||
}
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String HEADER = "<!DOCTYPE html>\n" +
|
||||
"<html lang='en'>\n" +
|
||||
"<head>\n" +
|
||||
"<meta charset='utf-8'>\n" +
|
||||
"<style>\n" +
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n" +
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n" +
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n" +
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n" +
|
||||
"\tp {margin: 5px 0 5px 0}\n" +
|
||||
"\ta {color: #0366d6}\n" +
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n" +
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n" +
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n" +
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n" +
|
||||
"\t#reset {cursor: pointer}\n" +
|
||||
"</style>\n" +
|
||||
"</head>\n" +
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n" +
|
||||
"<h1>${title}</h1>\n" +
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n" +
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n" +
|
||||
"<canvas id='canvas' style='width: 100%; height: ${height}px'></canvas>\n" +
|
||||
"<div id='hl'><span></span></div>\n" +
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n" +
|
||||
"<p id='status'> </p>\n" +
|
||||
"<script>\n" +
|
||||
"\t// Copyright 2020 Andrei Pangin\n" +
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n" +
|
||||
"\t'use strict';\n" +
|
||||
"\tvar root, rootLevel, px, pattern;\n" +
|
||||
"\tvar reverse = ${reverse};\n" +
|
||||
"\tconst levels = Array(${depth});\n" +
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n" +
|
||||
"\t\tlevels[h] = [];\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tconst canvas = document.getElementById('canvas');\n" +
|
||||
"\tconst c = canvas.getContext('2d');\n" +
|
||||
"\tconst hl = document.getElementById('hl');\n" +
|
||||
"\tconst status = document.getElementById('status');\n" +
|
||||
"\n" +
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n" +
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n" +
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n" +
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n" +
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n" +
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n" +
|
||||
"\tc.font = document.body.style.font;\n" +
|
||||
"\n" +
|
||||
"\tconst palette = [\n" +
|
||||
"\t\t[0x50e150, 30, 30, 30],\n" +
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n" +
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n" +
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n" +
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n" +
|
||||
"\t];\n" +
|
||||
"\n" +
|
||||
"\tfunction getColor(p) {\n" +
|
||||
"\t\tconst v = Math.random();\n" +
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction f(level, left, width, type, title) {\n" +
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction samples(n) {\n" +
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction pct(a, b) {\n" +
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction findFrame(frames, x) {\n" +
|
||||
"\t\tlet left = 0;\n" +
|
||||
"\t\tlet right = frames.length - 1;\n" +
|
||||
"\n" +
|
||||
"\t\twhile (left <= right) {\n" +
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n" +
|
||||
"\t\t\tconst f = frames[mid];\n" +
|
||||
"\n" +
|
||||
"\t\t\tif (f.left > x) {\n" +
|
||||
"\t\t\t\tright = mid - 1;\n" +
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n" +
|
||||
"\t\t\t\tleft = mid + 1;\n" +
|
||||
"\t\t\t} else {\n" +
|
||||
"\t\t\t\treturn f;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n" +
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n" +
|
||||
"\n" +
|
||||
"\t\treturn null;\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction search(r) {\n" +
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n" +
|
||||
"\t\t\treturn;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n" +
|
||||
"\t\tconst matched = render(root, rootLevel);\n" +
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%';\n" +
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tfunction render(newRoot, newLevel) {\n" +
|
||||
"\t\tif (root) {\n" +
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n" +
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\troot = newRoot || levels[0][0];\n" +
|
||||
"\t\trootLevel = newLevel || 0;\n" +
|
||||
"\t\tpx = canvasWidth / root.width;\n" +
|
||||
"\n" +
|
||||
"\t\tconst x0 = root.left;\n" +
|
||||
"\t\tconst x1 = x0 + root.width;\n" +
|
||||
"\t\tconst marked = [];\n" +
|
||||
"\n" +
|
||||
"\t\tfunction mark(f) {\n" +
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfunction totalMarked() {\n" +
|
||||
"\t\t\tlet total = 0;\n" +
|
||||
"\t\t\tlet left = 0;\n" +
|
||||
"\t\t\tfor (let x in marked) {\n" +
|
||||
"\t\t\t\tif (+x >= left) {\n" +
|
||||
"\t\t\t\t\ttotal += marked[x];\n" +
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t\treturn total;\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n" +
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n" +
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n" +
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
|
||||
"\n" +
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n" +
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n" +
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n" +
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n" +
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\t\t\tif (alpha) {\n" +
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n" +
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n" +
|
||||
"\t\t\t\t}\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n" +
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n" +
|
||||
"\t\t\tconst frames = levels[h];\n" +
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n" +
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\n" +
|
||||
"\t\treturn totalMarked();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tcanvas.onmousemove = function() {\n" +
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n" +
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n" +
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n" +
|
||||
"\t\t\tif (f) {\n" +
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n" +
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n" +
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n" +
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n" +
|
||||
"\t\t\t\thl.style.display = 'block';\n" +
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%)';\n" +
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n" +
|
||||
"\t\t\t\tcanvas.onclick = function() {\n" +
|
||||
"\t\t\t\t\tif (f != root) {\n" +
|
||||
"\t\t\t\t\t\trender(f, h);\n" +
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n" +
|
||||
"\t\t\t\t\t}\n" +
|
||||
"\t\t\t\t};\n" +
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n" +
|
||||
"\t\t\t\treturn;\n" +
|
||||
"\t\t\t}\n" +
|
||||
"\t\t}\n" +
|
||||
"\t\tcanvas.onmouseout();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tcanvas.onmouseout = function() {\n" +
|
||||
"\t\thl.style.display = 'none';\n" +
|
||||
"\t\tstatus.textContent = '\\xa0';\n" +
|
||||
"\t\tcanvas.title = '';\n" +
|
||||
"\t\tcanvas.style.cursor = '';\n" +
|
||||
"\t\tcanvas.onclick = '';\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n" +
|
||||
"\t\treverse = !reverse;\n" +
|
||||
"\t\trender();\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('search').onclick = function() {\n" +
|
||||
"\t\tsearch(true);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n" +
|
||||
"\t\tsearch(false);\n" +
|
||||
"\t}\n" +
|
||||
"\n" +
|
||||
"\twindow.onkeydown = function() {\n" +
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n" +
|
||||
"\t\t\tevent.preventDefault();\n" +
|
||||
"\t\t\tsearch(true);\n" +
|
||||
"\t\t} else if (event.keyCode === 27) {\n" +
|
||||
"\t\t\tsearch(false);\n" +
|
||||
"\t\t}\n" +
|
||||
"\t}\n";
|
||||
|
||||
private static final String FOOTER = "render();\n" +
|
||||
"</script></body></html>\n";
|
||||
}
|
||||
1
src/converter/MANIFEST.MF
Normal file
1
src/converter/MANIFEST.MF
Normal file
@@ -0,0 +1 @@
|
||||
Main-Class: Main
|
||||
31
src/converter/Main.java
Normal file
31
src/converter/Main.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main entry point of jar.
|
||||
* Lists available converters.
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Usage: java -cp converter.jar <Converter> [options] <input> <output>");
|
||||
System.out.println();
|
||||
System.out.println("Available converters:");
|
||||
System.out.println(" FlameGraph input.collapsed output.html");
|
||||
System.out.println(" jfr2flame input.jfr output.html");
|
||||
System.out.println(" jfr2nflx input.jfr output.nflx");
|
||||
}
|
||||
}
|
||||
92
src/converter/jfr2flame.java
Normal file
92
src/converter/jfr2flame.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.StackTrace;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to HTML Flame Graph.
|
||||
*/
|
||||
public class jfr2flame {
|
||||
|
||||
private static final int FRAME_KERNEL = 5;
|
||||
|
||||
private final JfrReader jfr;
|
||||
private final Dictionary<String> methodNames = new Dictionary<>();
|
||||
|
||||
public jfr2flame(JfrReader jfr) {
|
||||
this.jfr = jfr;
|
||||
}
|
||||
|
||||
public void convert(final FlameGraph fg) {
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
String[] trace = new String[methods.length];
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
trace[trace.length - 1 - i] = getMethodName(methods[i], types[i]);
|
||||
}
|
||||
fg.addSample(trace, stackTrace.samples);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getMethodName(long methodId, int type) {
|
||||
String result = methodNames.get(methodId);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0) {
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = type == FRAME_KERNEL ? methodStr + "_[k]" : methodStr;
|
||||
} else {
|
||||
String classStr = new String(className, StandardCharsets.UTF_8);
|
||||
String methodStr = new String(methodName, StandardCharsets.UTF_8);
|
||||
result = classStr + '.' + methodStr + "_[j]";
|
||||
}
|
||||
|
||||
methodNames.put(methodId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
FlameGraph fg = new FlameGraph(args);
|
||||
if (fg.input == null) {
|
||||
System.out.println("Usage: java " + jfr2flame.class.getName() + " [options] input.jfr [output.html]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try (JfrReader jfr = new JfrReader(fg.input)) {
|
||||
new jfr2flame(jfr).convert(fg);
|
||||
}
|
||||
|
||||
fg.dump();
|
||||
}
|
||||
}
|
||||
161
src/converter/jfr2nflx.java
Normal file
161
src/converter/jfr2nflx.java
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Dictionary;
|
||||
import one.jfr.JfrReader;
|
||||
import one.jfr.MethodRef;
|
||||
import one.jfr.Sample;
|
||||
import one.jfr.StackTrace;
|
||||
import one.proto.Proto;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to nflxprofile format
|
||||
* as described in https://github.com/Netflix/nflxprofile/blob/master/nflxprofile.proto.
|
||||
* The result nflxprofile can be opened and analyzed with FlameScope.
|
||||
*/
|
||||
public class jfr2nflx {
|
||||
|
||||
private static final String[] FRAME_TYPE = {"jit", "jit", "inlined", "user", "user", "kernel"};
|
||||
private static final byte[] NO_STACK = "[no_stack]".getBytes();
|
||||
|
||||
private final JfrReader jfr;
|
||||
|
||||
public jfr2nflx(JfrReader jfr) {
|
||||
this.jfr = jfr;
|
||||
}
|
||||
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
int samples = jfr.samples.size();
|
||||
long durationTicks = samples == 0 ? 0 : jfr.samples.get(samples - 1).time - jfr.startTicks + 1;
|
||||
|
||||
final Proto profile = new Proto(200000)
|
||||
.field(1, 0.0)
|
||||
.field(2, Math.max(jfr.durationNanos / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(3, packSamples())
|
||||
.field(4, packDeltas())
|
||||
.field(6, "async-profiler")
|
||||
.field(8, new Proto(32).field(1, "has_node_stack").field(2, "true"))
|
||||
.field(8, new Proto(32).field(1, "has_samples_tid").field(2, "true"))
|
||||
.field(11, packTids());
|
||||
|
||||
final Proto nodes = new Proto(10000);
|
||||
final Proto node = new Proto(10000);
|
||||
|
||||
// Don't use lambda for faster startup
|
||||
jfr.stackTraces.forEach(new Dictionary.Visitor<StackTrace>() {
|
||||
@Override
|
||||
public void visit(long id, StackTrace stackTrace) {
|
||||
profile.field(5, nodes
|
||||
.field(1, (int) id)
|
||||
.field(2, packNode(node, stackTrace)));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
}
|
||||
});
|
||||
|
||||
out.write(profile.buffer(), 0, profile.size());
|
||||
|
||||
long endTime = System.nanoTime();
|
||||
System.out.println("Wrote " + profile.size() + " bytes in " + (endTime - startTime) / 1e9 + " s");
|
||||
}
|
||||
|
||||
private Proto packNode(Proto node, StackTrace stackTrace) {
|
||||
long[] methods = stackTrace.methods;
|
||||
byte[] types = stackTrace.types;
|
||||
int top = methods.length - 1;
|
||||
|
||||
node.field(1, top >= 0 ? getMethodName(methods[top]) : NO_STACK);
|
||||
node.field(2, 1);
|
||||
node.field(4, top >= 0 ? FRAME_TYPE[types[top]] : "user");
|
||||
|
||||
for (Proto frame = new Proto(100); --top >= 0; frame.reset()) {
|
||||
node.field(10, frame
|
||||
.field(1, getMethodName(methods[top]))
|
||||
.field(2, FRAME_TYPE[types[top]]));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private Proto packSamples() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (Sample sample : jfr.samples) {
|
||||
proto.writeInt(sample.stackTraceId);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private Proto packDeltas() {
|
||||
Proto proto = new Proto(10000);
|
||||
double ticksPerSec = jfr.ticksPerSec;
|
||||
long prevTime = jfr.startTicks;
|
||||
for (Sample sample : jfr.samples) {
|
||||
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
|
||||
prevTime = sample.time;
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private Proto packTids() {
|
||||
Proto proto = new Proto(10000);
|
||||
for (Sample sample : jfr.samples) {
|
||||
proto.writeInt(sample.tid);
|
||||
}
|
||||
return proto;
|
||||
}
|
||||
|
||||
private byte[] getMethodName(long methodId) {
|
||||
MethodRef method = jfr.methods.get(methodId);
|
||||
ClassRef cls = jfr.classes.get(method.cls);
|
||||
byte[] className = jfr.symbols.get(cls.name);
|
||||
byte[] methodName = jfr.symbols.get(method.name);
|
||||
|
||||
if (className == null || className.length == 0) {
|
||||
return methodName;
|
||||
} else {
|
||||
byte[] fullName = Arrays.copyOf(className, className.length + 1 + methodName.length);
|
||||
fullName[className.length] = '.';
|
||||
System.arraycopy(methodName, 0, fullName, className.length + 1, methodName.length);
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Usage: java " + jfr2nflx.class.getName() + " input.jfr output.nflx");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File dst = new File(args[1]);
|
||||
if (dst.isDirectory()) {
|
||||
dst = new File(dst, new File(args[0]).getName().replace(".jfr", ".nflx"));
|
||||
}
|
||||
|
||||
try (JfrReader jfr = new JfrReader(args[0]);
|
||||
FileOutputStream out = new FileOutputStream(dst)) {
|
||||
new jfr2nflx(jfr).dump(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/converter/one/jfr/ClassRef.java
Normal file
25
src/converter/one/jfr/ClassRef.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class ClassRef {
|
||||
public final long name;
|
||||
|
||||
public ClassRef(long name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
107
src/converter/one/jfr/Dictionary.java
Normal file
107
src/converter/one/jfr/Dictionary.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
/**
|
||||
* Fast and compact long->Object map.
|
||||
*/
|
||||
public class Dictionary<T> {
|
||||
private static final int INITIAL_CAPACITY = 16;
|
||||
|
||||
private long[] keys;
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
public Dictionary() {
|
||||
this.keys = new long[INITIAL_CAPACITY];
|
||||
this.values = new Object[INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void 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) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
keys[i] = key;
|
||||
values[i] = value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T get(long key) {
|
||||
int mask = keys.length - 1;
|
||||
int i = hashCode(key) & mask;
|
||||
while (keys[i] != key && keys[i] != 0) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
return (T) values[i];
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void forEach(Visitor<T> visitor) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != 0) {
|
||||
visitor.visit(keys[i], (T) values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int preallocate(int count) {
|
||||
int newSize = size + count;
|
||||
if (newSize * 2 > keys.length) {
|
||||
resize(Integer.highestOneBit(newSize * 4 - 1));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void resize(int newCapacity) {
|
||||
long[] newKeys = new long[newCapacity];
|
||||
Object[] newValues = new Object[newCapacity];
|
||||
int mask = newKeys.length - 1;
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] != 0) {
|
||||
for (int j = hashCode(keys[i]) & mask; ; j = (j + 1) & mask) {
|
||||
if (newKeys[j] == 0) {
|
||||
newKeys[j] = keys[i];
|
||||
newValues[j] = values[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = newKeys;
|
||||
values = newValues;
|
||||
}
|
||||
|
||||
private static int hashCode(long key) {
|
||||
return (int) (key ^ (key >>> 32));
|
||||
}
|
||||
|
||||
public interface Visitor<T> {
|
||||
void visit(long key, T value);
|
||||
}
|
||||
}
|
||||
23
src/converter/one/jfr/Element.java
Normal file
23
src/converter/one/jfr/Element.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
class Element {
|
||||
|
||||
void addChild(Element e) {
|
||||
}
|
||||
}
|
||||
49
src/converter/one/jfr/JfrClass.java
Normal file
49
src/converter/one/jfr/JfrClass.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class JfrClass extends Element {
|
||||
final int id;
|
||||
final String name;
|
||||
final List<JfrField> fields;
|
||||
|
||||
JfrClass(Map<String, String> attributes) {
|
||||
this.id = Integer.parseInt(attributes.get("id"));
|
||||
this.name = attributes.get("name");
|
||||
this.fields = new ArrayList<>(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addChild(Element e) {
|
||||
if (e instanceof JfrField) {
|
||||
fields.add((JfrField) e);
|
||||
}
|
||||
}
|
||||
|
||||
JfrField field(String name) {
|
||||
for (JfrField field : fields) {
|
||||
if (field.name.equals(name)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
31
src/converter/one/jfr/JfrField.java
Normal file
31
src/converter/one/jfr/JfrField.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class JfrField extends Element {
|
||||
final String name;
|
||||
final int type;
|
||||
final boolean constantPool;
|
||||
|
||||
JfrField(Map<String, String> attributes) {
|
||||
this.name = attributes.get("name");
|
||||
this.type = Integer.parseInt(attributes.get("class"));
|
||||
this.constantPool = "true".equals(attributes.get("constantPool"));
|
||||
}
|
||||
}
|
||||
384
src/converter/one/jfr/JfrReader.java
Normal file
384
src/converter/one/jfr/JfrReader.java
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses JFR output produced by async-profiler.
|
||||
*/
|
||||
public class JfrReader implements Closeable {
|
||||
private static final int CHUNK_HEADER_SIZE = 68;
|
||||
private static final int CPOOL_OFFSET = 16;
|
||||
private static final int META_OFFSET = 24;
|
||||
|
||||
private final FileChannel ch;
|
||||
private final ByteBuffer buf;
|
||||
|
||||
public final long startNanos;
|
||||
public final long durationNanos;
|
||||
public final long startTicks;
|
||||
public final long ticksPerSec;
|
||||
|
||||
public final Dictionary<JfrClass> types = new Dictionary<>();
|
||||
public final Map<String, JfrClass> typesByName = new HashMap<>();
|
||||
public final Dictionary<String> threads = new Dictionary<>();
|
||||
public final Dictionary<ClassRef> classes = new Dictionary<>();
|
||||
public final Dictionary<byte[]> symbols = new Dictionary<>();
|
||||
public final Dictionary<MethodRef> methods = new Dictionary<>();
|
||||
public final Dictionary<StackTrace> stackTraces = new Dictionary<>();
|
||||
public final Map<Integer, String> frameTypes = new HashMap<>();
|
||||
public final Map<Integer, String> threadStates = new HashMap<>();
|
||||
public final List<Sample> samples = new ArrayList<>();
|
||||
|
||||
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());
|
||||
|
||||
if (buf.getInt(0) != 0x464c5200) {
|
||||
throw new IOException("Not a valid 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
|
||||
public void close() throws IOException {
|
||||
ch.close();
|
||||
}
|
||||
|
||||
private void readMeta() {
|
||||
buf.position(buf.getInt(META_OFFSET + 4));
|
||||
getVarint();
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
|
||||
String[] strings = new String[getVarint()];
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
strings[i] = getString();
|
||||
}
|
||||
readElement(strings);
|
||||
}
|
||||
|
||||
private Element readElement(String[] strings) {
|
||||
String name = strings[getVarint()];
|
||||
|
||||
int attributeCount = getVarint();
|
||||
Map<String, String> attributes = new HashMap<>(attributeCount);
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
attributes.put(strings[getVarint()], strings[getVarint()]);
|
||||
}
|
||||
|
||||
Element e = createElement(name, attributes);
|
||||
int childCount = getVarint();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
e.addChild(readElement(strings));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
private Element createElement(String name, Map<String, String> attributes) {
|
||||
switch (name) {
|
||||
case "class": {
|
||||
JfrClass type = new JfrClass(attributes);
|
||||
if (!attributes.containsKey("superType")) {
|
||||
types.put(type.id, type);
|
||||
}
|
||||
typesByName.put(type.name, type);
|
||||
return type;
|
||||
}
|
||||
case "field":
|
||||
return new JfrField(attributes);
|
||||
default:
|
||||
return new Element();
|
||||
}
|
||||
}
|
||||
|
||||
private void readConstantPool() {
|
||||
int offset = buf.getInt(CPOOL_OFFSET + 4);
|
||||
while (true) {
|
||||
buf.position(offset);
|
||||
getVarint();
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
long delta = getVarlong();
|
||||
getVarint();
|
||||
|
||||
int poolCount = getVarint();
|
||||
for (int i = 0; i < poolCount; i++) {
|
||||
int type = getVarint();
|
||||
readConstants(types.get(type));
|
||||
}
|
||||
|
||||
if (delta == 0) {
|
||||
break;
|
||||
}
|
||||
offset += delta;
|
||||
}
|
||||
}
|
||||
|
||||
private void readConstants(JfrClass type) {
|
||||
switch (type.name) {
|
||||
case "jdk.types.ChunkHeader":
|
||||
buf.position(buf.position() + (CHUNK_HEADER_SIZE + 3));
|
||||
break;
|
||||
case "java.lang.Thread":
|
||||
readThreads(type.field("group") != null);
|
||||
break;
|
||||
case "java.lang.Class":
|
||||
readClasses(type.field("hidden") != null);
|
||||
break;
|
||||
case "jdk.types.Symbol":
|
||||
readSymbols();
|
||||
break;
|
||||
case "jdk.types.Method":
|
||||
readMethods();
|
||||
break;
|
||||
case "jdk.types.StackTrace":
|
||||
readStackTraces();
|
||||
break;
|
||||
case "jdk.types.FrameType":
|
||||
readMap(frameTypes);
|
||||
break;
|
||||
case "jdk.types.ThreadState":
|
||||
readMap(threadStates);
|
||||
break;
|
||||
default:
|
||||
readOtherConstants(type.fields);
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreads(boolean hasGroup) {
|
||||
int count = threads.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
String osName = getString();
|
||||
int osThreadId = getVarint();
|
||||
String javaName = getString();
|
||||
long javaThreadId = getVarlong();
|
||||
if (hasGroup) getVarlong();
|
||||
threads.put(id, javaName != null ? javaName : osName);
|
||||
}
|
||||
}
|
||||
|
||||
private void readClasses(boolean hasHidden) {
|
||||
int count = classes.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
long loader = getVarlong();
|
||||
long name = getVarlong();
|
||||
long pkg = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
if (hasHidden) getVarint();
|
||||
classes.put(id, new ClassRef(name));
|
||||
}
|
||||
}
|
||||
|
||||
private void readMethods() {
|
||||
int count = methods.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
long cls = getVarlong();
|
||||
long name = getVarlong();
|
||||
long sig = getVarlong();
|
||||
int modifiers = getVarint();
|
||||
int hidden = getVarint();
|
||||
methods.put(id, new MethodRef(cls, name, sig));
|
||||
}
|
||||
}
|
||||
|
||||
private void readStackTraces() {
|
||||
int count = stackTraces.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
int truncated = getVarint();
|
||||
StackTrace stackTrace = readStackTrace();
|
||||
stackTraces.put(id, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
private StackTrace readStackTrace() {
|
||||
int depth = getVarint();
|
||||
long[] methods = new long[depth];
|
||||
byte[] types = new byte[depth];
|
||||
for (int i = 0; i < depth; i++) {
|
||||
methods[i] = getVarlong();
|
||||
int line = getVarint();
|
||||
int bci = getVarint();
|
||||
types[i] = buf.get();
|
||||
}
|
||||
return new StackTrace(methods, types);
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
int count = symbols.preallocate(getVarint());
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = getVarlong();
|
||||
if (buf.get() != 3) {
|
||||
throw new IllegalArgumentException("Invalid symbol encoding");
|
||||
}
|
||||
symbols.put(id, getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private void readMap(Map<Integer, String> map) {
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
map.put(getVarint(), getString());
|
||||
}
|
||||
}
|
||||
|
||||
private void readOtherConstants(List<JfrField> fields) {
|
||||
int stringType = getTypeId("java.lang.String");
|
||||
|
||||
boolean[] numeric = new boolean[fields.size()];
|
||||
for (int i = 0; i < numeric.length; i++) {
|
||||
JfrField f = fields.get(i);
|
||||
numeric[i] = f.constantPool || f.type != stringType;
|
||||
}
|
||||
|
||||
int count = getVarint();
|
||||
for (int i = 0; i < count; i++) {
|
||||
getVarlong();
|
||||
readFields(numeric);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFields(boolean[] numeric) {
|
||||
for (boolean n : numeric) {
|
||||
if (n) {
|
||||
getVarlong();
|
||||
} else {
|
||||
getString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void 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 int getTypeId(String typeName) {
|
||||
JfrClass type = typesByName.get(typeName);
|
||||
return type != null ? type.id : -1;
|
||||
}
|
||||
|
||||
private int getVarint() {
|
||||
int result = 0;
|
||||
for (int shift = 0; ; shift += 7) {
|
||||
byte b = buf.get();
|
||||
result |= (b & 0x7f) << shift;
|
||||
if (b >= 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getVarlong() {
|
||||
long result = 0;
|
||||
for (int shift = 0; shift < 56; shift += 7) {
|
||||
byte b = buf.get();
|
||||
result |= (b & 0x7fL) << shift;
|
||||
if (b >= 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result | (buf.get() & 0xffL) << 56;
|
||||
}
|
||||
|
||||
private String getString() {
|
||||
switch (buf.get()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return "";
|
||||
case 3:
|
||||
return new String(getBytes(), StandardCharsets.UTF_8);
|
||||
case 4: {
|
||||
char[] chars = new char[getVarint()];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = (char) getVarint();
|
||||
}
|
||||
return new String(chars);
|
||||
}
|
||||
case 5:
|
||||
return new String(getBytes(), StandardCharsets.ISO_8859_1);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid string encoding");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getBytes() {
|
||||
byte[] bytes = new byte[getVarint()];
|
||||
buf.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
29
src/converter/one/jfr/MethodRef.java
Normal file
29
src/converter/one/jfr/MethodRef.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class MethodRef {
|
||||
public final long cls;
|
||||
public final long name;
|
||||
public final long sig;
|
||||
|
||||
public MethodRef(long cls, long name, long sig) {
|
||||
this.cls = cls;
|
||||
this.name = name;
|
||||
this.sig = sig;
|
||||
}
|
||||
}
|
||||
36
src/converter/one/jfr/Sample.java
Normal file
36
src/converter/one/jfr/Sample.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class Sample implements Comparable<Sample> {
|
||||
public final long time;
|
||||
public final int tid;
|
||||
public final int stackTraceId;
|
||||
public final int threadState;
|
||||
|
||||
public Sample(long time, int tid, int stackTraceId, int threadState) {
|
||||
this.time = time;
|
||||
this.tid = tid;
|
||||
this.stackTraceId = stackTraceId;
|
||||
this.threadState = threadState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Sample o) {
|
||||
return Long.compare(time, o.time);
|
||||
}
|
||||
}
|
||||
28
src/converter/one/jfr/StackTrace.java
Normal file
28
src/converter/one/jfr/StackTrace.java
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class StackTrace {
|
||||
public final long[] methods;
|
||||
public final byte[] types;
|
||||
public long samples;
|
||||
|
||||
public StackTrace(long[] methods, byte[] types) {
|
||||
this.methods = methods;
|
||||
this.types = types;
|
||||
}
|
||||
}
|
||||
127
src/converter/one/proto/Proto.java
Normal file
127
src/converter/one/proto/Proto.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package one.proto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Simplified implementation of Protobuf writer, capable of encoding
|
||||
* varints, doubles, ASCII strings and embedded messages
|
||||
*/
|
||||
public class Proto {
|
||||
private byte[] buf;
|
||||
private int pos;
|
||||
|
||||
public Proto(int capacity) {
|
||||
this.buf = new byte[capacity];
|
||||
}
|
||||
|
||||
public byte[] buffer() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
public Proto field(int index, int n) {
|
||||
tag(index, 0);
|
||||
writeInt(n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, double d) {
|
||||
tag(index, 1);
|
||||
writeDouble(d);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, String s) {
|
||||
tag(index, 2);
|
||||
writeString(s);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, byte[] bytes) {
|
||||
tag(index, 2);
|
||||
writeBytes(bytes, 0, bytes.length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Proto field(int index, Proto proto) {
|
||||
tag(index, 2);
|
||||
writeBytes(proto.buf, 0, proto.pos);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void writeInt(int n) {
|
||||
int length = n == 0 ? 1 : (38 - Integer.numberOfLeadingZeros(n)) / 7;
|
||||
ensureCapacity(length);
|
||||
|
||||
while (n > 0x7f) {
|
||||
buf[pos++] = (byte) (0x80 | (n & 0x7f));
|
||||
n >>>= 7;
|
||||
}
|
||||
buf[pos++] = (byte) n;
|
||||
}
|
||||
|
||||
public void writeDouble(double d) {
|
||||
ensureCapacity(8);
|
||||
long n = Double.doubleToRawLongBits(d);
|
||||
buf[pos] = (byte) n;
|
||||
buf[pos + 1] = (byte) (n >>> 8);
|
||||
buf[pos + 2] = (byte) (n >>> 16);
|
||||
buf[pos + 3] = (byte) (n >>> 24);
|
||||
buf[pos + 4] = (byte) (n >>> 32);
|
||||
buf[pos + 5] = (byte) (n >>> 40);
|
||||
buf[pos + 6] = (byte) (n >>> 48);
|
||||
buf[pos + 7] = (byte) (n >>> 56);
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
public void writeString(String s) {
|
||||
int length = s.length();
|
||||
writeInt(length);
|
||||
ensureCapacity(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
buf[pos++] = (byte) s.charAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeBytes(byte[] bytes, int offset, int length) {
|
||||
writeInt(length);
|
||||
ensureCapacity(length);
|
||||
System.arraycopy(bytes, offset, buf, pos, length);
|
||||
pos += length;
|
||||
}
|
||||
|
||||
private void tag(int index, int type) {
|
||||
ensureCapacity(1);
|
||||
buf[pos++] = (byte) (index << 3 | type);
|
||||
}
|
||||
|
||||
private void ensureCapacity(int length) {
|
||||
if (pos + length > buf.length) {
|
||||
buf = Arrays.copyOf(buf, Math.max(pos + length, buf.length * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/dictionary.cpp
Normal file
126
src/dictionary.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "dictionary.h"
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
static inline char* allocateKey(const char* key, size_t length) {
|
||||
char* result = (char*)malloc(length + 1);
|
||||
memcpy(result, key, length);
|
||||
result[length] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline bool keyEquals(const char* candidate, const char* key, size_t length) {
|
||||
return strncmp(candidate, key, length) == 0 && candidate[length] == 0;
|
||||
}
|
||||
|
||||
|
||||
Dictionary::Dictionary() {
|
||||
_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
}
|
||||
|
||||
Dictionary::~Dictionary() {
|
||||
clear(_table);
|
||||
free(_table);
|
||||
}
|
||||
|
||||
void Dictionary::clear() {
|
||||
clear(_table);
|
||||
memset(_table, 0, sizeof(DictTable));
|
||||
_table->base_index = _base_index = 1;
|
||||
}
|
||||
|
||||
void Dictionary::clear(DictTable* table) {
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
free(row->keys[j]);
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
clear(row->next);
|
||||
free(row->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Many popular symbols are quite short, e.g. "[B", "()V" etc.
|
||||
// FNV-1a is reasonably fast and sufficiently random.
|
||||
unsigned int Dictionary::hash(const char* key, size_t length) {
|
||||
unsigned int h = 2166136261U;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
h = (h ^ key[i]) * 16777619;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
unsigned int Dictionary::lookup(const char* key) {
|
||||
return lookup(key, strlen(key));
|
||||
}
|
||||
|
||||
unsigned int Dictionary::lookup(const char* key, size_t length) {
|
||||
DictTable* table = _table;
|
||||
unsigned int h = hash(key, length);
|
||||
|
||||
while (true) {
|
||||
DictRow* row = &table->rows[h % ROWS];
|
||||
for (int c = 0; c < CELLS; c++) {
|
||||
if (row->keys[c] == NULL) {
|
||||
char* new_key = allocateKey(key, length);
|
||||
if (__sync_bool_compare_and_swap(&row->keys[c], NULL, new_key)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
free(new_key);
|
||||
}
|
||||
if (keyEquals(row->keys[c], key, length)) {
|
||||
return table->index(h % ROWS, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (row->next == NULL) {
|
||||
DictTable* new_table = (DictTable*)calloc(1, sizeof(DictTable));
|
||||
new_table->base_index = __sync_add_and_fetch(&_base_index, TABLE_CAPACITY);
|
||||
if (!__sync_bool_compare_and_swap(&row->next, NULL, new_table)) {
|
||||
free(new_table);
|
||||
}
|
||||
}
|
||||
|
||||
table = row->next;
|
||||
h = (h >> ROW_BITS) | (h << (32 - ROW_BITS));
|
||||
}
|
||||
}
|
||||
|
||||
void Dictionary::collect(std::map<unsigned int, const char*>& map) {
|
||||
collect(map, _table);
|
||||
}
|
||||
|
||||
void Dictionary::collect(std::map<unsigned int, const char*>& map, DictTable* table) {
|
||||
for (int i = 0; i < ROWS; i++) {
|
||||
DictRow* row = &table->rows[i];
|
||||
for (int j = 0; j < CELLS; j++) {
|
||||
if (row->keys[j] != NULL) {
|
||||
map[table->index(i, j)] = row->keys[j];
|
||||
}
|
||||
}
|
||||
if (row->next != NULL) {
|
||||
collect(map, row->next);
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/dictionary.h
Normal file
70
src/dictionary.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DICTIONARY_H
|
||||
#define _DICTIONARY_H
|
||||
|
||||
#include <map>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
#define ROW_BITS 7
|
||||
#define ROWS (1 << ROW_BITS)
|
||||
#define CELLS 3
|
||||
#define TABLE_CAPACITY (ROWS * CELLS)
|
||||
|
||||
|
||||
struct DictTable;
|
||||
|
||||
struct DictRow {
|
||||
char* keys[CELLS];
|
||||
DictTable* next;
|
||||
};
|
||||
|
||||
struct DictTable {
|
||||
DictRow rows[ROWS];
|
||||
unsigned int base_index;
|
||||
|
||||
unsigned int index(int row, int col) {
|
||||
return base_index + (col << ROW_BITS) + row;
|
||||
}
|
||||
};
|
||||
|
||||
// Append-only concurrent hash table based on multi-level arrays
|
||||
class Dictionary {
|
||||
private:
|
||||
DictTable* _table;
|
||||
volatile unsigned int _base_index;
|
||||
|
||||
static void clear(DictTable* table);
|
||||
|
||||
static unsigned int hash(const char* key, size_t length);
|
||||
|
||||
static void collect(std::map<unsigned int, const char*>& map, DictTable* table);
|
||||
|
||||
public:
|
||||
Dictionary();
|
||||
~Dictionary();
|
||||
|
||||
void clear();
|
||||
|
||||
unsigned int lookup(const char* key);
|
||||
unsigned int lookup(const char* key, size_t length);
|
||||
|
||||
void collect(std::map<unsigned int, const char*>& map);
|
||||
};
|
||||
|
||||
#endif // _DICTIONARY_H
|
||||
@@ -18,22 +18,50 @@
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
volatile bool Engine::_enabled;
|
||||
|
||||
Error Engine::check(Arguments& args) {
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
CStack Engine::cstack() {
|
||||
return CSTACK_FP;
|
||||
}
|
||||
|
||||
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address) {
|
||||
StackFrame frame(ucontext);
|
||||
const void* pc = (const void*)frame.pc();
|
||||
uintptr_t fp = frame.fp();
|
||||
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*)0x1000;
|
||||
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 && !(pc >= jit_min_address && pc < jit_max_address)) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
37
src/engine.h
37
src/engine.h
@@ -18,21 +18,50 @@
|
||||
#define _ENGINE_H
|
||||
|
||||
#include "arguments.h"
|
||||
#include "codeCache.h"
|
||||
|
||||
|
||||
class Engine {
|
||||
protected:
|
||||
static volatile bool _enabled;
|
||||
|
||||
public:
|
||||
virtual const char* name() = 0;
|
||||
virtual const char* units() = 0;
|
||||
|
||||
virtual Error check(Arguments& args);
|
||||
virtual Error start(Arguments& args) = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual void onThreadStart() {}
|
||||
virtual void onThreadEnd() {}
|
||||
|
||||
virtual CStack cstack();
|
||||
virtual int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address);
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs);
|
||||
|
||||
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
|
||||
|
||||
55
src/event.h
Normal file
55
src/event.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _EVENT_H
|
||||
#define _EVENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class Event {
|
||||
public:
|
||||
u32 id() {
|
||||
return *(u32*)this;
|
||||
}
|
||||
};
|
||||
|
||||
class ExecutionEvent : public Event {
|
||||
public:
|
||||
ThreadState _thread_state;
|
||||
|
||||
ExecutionEvent() : _thread_state(THREAD_RUNNING) {
|
||||
}
|
||||
};
|
||||
|
||||
class AllocEvent : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _total_size;
|
||||
u64 _instance_size;
|
||||
};
|
||||
|
||||
class LockEvent : public Event {
|
||||
public:
|
||||
u32 _class_id;
|
||||
u64 _start_time;
|
||||
u64 _end_time;
|
||||
uintptr_t _address;
|
||||
long long _timeout;
|
||||
};
|
||||
|
||||
#endif // _EVENT_H
|
||||
@@ -1,375 +1,256 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* This is a specialized C++ port of the FlameGraph script available at
|
||||
* https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* Copyright 2016 Netflix, Inc.
|
||||
* Copyright 2011 Joyent, Inc. All rights reserved.
|
||||
* Copyright 2011 Brendan Gregg. All rights reserved.
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* CDDL HEADER START
|
||||
*
|
||||
* The contents of this file are subject to the terms of the
|
||||
* Common Development and Distribution License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
*
|
||||
* You can obtain a copy of the license at docs/cddl1.txt or
|
||||
* http://opensource.org/licenses/CDDL-1.0.
|
||||
* See the License for the specific language governing permissions
|
||||
* and limitations under the License.
|
||||
*
|
||||
* When distributing Covered Code, include this CDDL HEADER in each
|
||||
* file and include the License file at docs/cddl1.txt.
|
||||
* If applicable, add the following below this CDDL HEADER, with the
|
||||
* fields enclosed by brackets "[]" replaced with your own identifying
|
||||
* information: Portions Copyright [yyyy] [name of copyright owner]
|
||||
*
|
||||
* CDDL HEADER END
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "flameGraph.h"
|
||||
|
||||
|
||||
static const char SVG_HEADER[] =
|
||||
"<?xml version=\"1.0\" standalone=\"no\"?>\n"
|
||||
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
|
||||
"<svg version=\"1.1\" width=\"%d\" height=\"%d\" onload=\"init(evt)\" viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
|
||||
"<style type=\"text/css\">\n"
|
||||
"\ttext { font-family:Verdana; font-size:12px; fill:black; }\n"
|
||||
"\t.func_g:hover { stroke:black; stroke-width:0.5; cursor:pointer; }\n"
|
||||
static const char FLAMEGRAPH_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang='en'>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<style>\n"
|
||||
"\tbody {margin: 0; padding: 10px; background-color: #ffffff}\n"
|
||||
"\th1 {margin: 5px 0 0 0; font-size: 18px; font-weight: normal; text-align: center}\n"
|
||||
"\theader {margin: -24px 0 5px 0; line-height: 24px}\n"
|
||||
"\tbutton {font: 12px sans-serif; cursor: pointer}\n"
|
||||
"\tp {margin: 5px 0 5px 0}\n"
|
||||
"\ta {color: #0366d6}\n"
|
||||
"\t#hl {position: absolute; display: none; overflow: hidden; white-space: nowrap; pointer-events: none; background-color: #ffffe0; outline: 1px solid #ffc000; height: 15px}\n"
|
||||
"\t#hl span {padding: 0 3px 0 3px}\n"
|
||||
"\t#status {overflow: hidden; white-space: nowrap}\n"
|
||||
"\t#match {overflow: hidden; white-space: nowrap; display: none; float: right; text-align: right}\n"
|
||||
"\t#reset {cursor: pointer}\n"
|
||||
"</style>\n"
|
||||
"<script type=\"text/ecmascript\">\n"
|
||||
"<![CDATA[\n"
|
||||
"\tvar details, searchbtn, matchedtxt, svg;\n"
|
||||
"\tfunction init(evt) {\n"
|
||||
"\t\tdetails = document.getElementById(\"details\").firstChild;\n"
|
||||
"\t\tsearchbtn = document.getElementById(\"search\");\n"
|
||||
"\t\tmatchedtxt = document.getElementById(\"matched\");\n"
|
||||
"\t\tsvg = document.getElementsByTagName(\"svg\")[0];\n"
|
||||
"\t\tsearching = 0;\n"
|
||||
"</head>\n"
|
||||
"<body style='font: 12px Verdana, sans-serif'>\n"
|
||||
"<h1>%s</h1>\n"
|
||||
"<header style='text-align: left'><button id='reverse' title='Reverse'>🔻</button> <button id='search' title='Search'>🔍</button></header>\n"
|
||||
"<header style='text-align: right'>Produced by <a href='https://github.com/jvm-profiling-tools/async-profiler'>async-profiler</a></header>\n"
|
||||
"<canvas id='canvas' style='width: 100%%; height: %dpx'></canvas>\n"
|
||||
"<div id='hl'><span></span></div>\n"
|
||||
"<p id='match'>Matched: <span id='matchval'></span> <span id='reset' title='Clear'>❌</span></p>\n"
|
||||
"<p id='status'> </p>\n"
|
||||
"<script>\n"
|
||||
"\t// Copyright 2020 Andrei Pangin\n"
|
||||
"\t// Licensed under the Apache License, Version 2.0.\n"
|
||||
"\t'use strict';\n"
|
||||
"\tvar root, rootLevel, px, pattern;\n"
|
||||
"\tvar reverse = %s;\n"
|
||||
"\tconst levels = Array(%d);\n"
|
||||
"\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\tlevels[h] = [];\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// mouse-over for info\n"
|
||||
"\tfunction s(node) {\t\t// show\n"
|
||||
"\t\tinfo = g_to_text(node);\n"
|
||||
"\t\tdetails.nodeValue = \"Function: \" + info;\n"
|
||||
"\t}\n"
|
||||
"\tfunction c() {\t\t\t// clear\n"
|
||||
"\t\tdetails.nodeValue = ' ';\n"
|
||||
"\tconst canvas = document.getElementById('canvas');\n"
|
||||
"\tconst c = canvas.getContext('2d');\n"
|
||||
"\tconst hl = document.getElementById('hl');\n"
|
||||
"\tconst status = document.getElementById('status');\n"
|
||||
"\n"
|
||||
"\tconst canvasWidth = canvas.offsetWidth;\n"
|
||||
"\tconst canvasHeight = canvas.offsetHeight;\n"
|
||||
"\tcanvas.style.width = canvasWidth + 'px';\n"
|
||||
"\tcanvas.width = canvasWidth * (devicePixelRatio || 1);\n"
|
||||
"\tcanvas.height = canvasHeight * (devicePixelRatio || 1);\n"
|
||||
"\tif (devicePixelRatio) c.scale(devicePixelRatio, devicePixelRatio);\n"
|
||||
"\tc.font = document.body.style.font;\n"
|
||||
"\n"
|
||||
"\tconst palette = [\n"
|
||||
"\t\t[0x50e150, 30, 30, 30],\n"
|
||||
"\t\t[0x50bebe, 30, 30, 30],\n"
|
||||
"\t\t[0xe17d00, 30, 30, 0],\n"
|
||||
"\t\t[0xc8c83c, 30, 30, 10],\n"
|
||||
"\t\t[0xe15a5a, 30, 40, 40],\n"
|
||||
"\t];\n"
|
||||
"\n"
|
||||
"\tfunction getColor(p) {\n"
|
||||
"\t\tconst v = Math.random();\n"
|
||||
"\t\treturn '#' + (p[0] + ((p[1] * v) << 16 | (p[2] * v) << 8 | (p[3] * v))).toString(16);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// ctrl-F for search\n"
|
||||
"\twindow.addEventListener(\"keydown\",function (e) {\n"
|
||||
"\t\tif (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {\n"
|
||||
"\t\t\te.preventDefault();\n"
|
||||
"\t\t\tsearch_prompt();\n"
|
||||
"\tfunction f(level, left, width, type, title) {\n"
|
||||
"\t\tlevels[level].push({left: left, width: width, color: getColor(palette[type]), title: title});\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction samples(n) {\n"
|
||||
"\t\treturn n === 1 ? '1 sample' : n.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',') + ' samples';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction pct(a, b) {\n"
|
||||
"\t\treturn a >= b ? '100' : (100 * a / b).toFixed(2);\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction findFrame(frames, x) {\n"
|
||||
"\t\tlet left = 0;\n"
|
||||
"\t\tlet right = frames.length - 1;\n"
|
||||
"\n"
|
||||
"\t\twhile (left <= right) {\n"
|
||||
"\t\t\tconst mid = (left + right) >>> 1;\n"
|
||||
"\t\t\tconst f = frames[mid];\n"
|
||||
"\n"
|
||||
"\t\t\tif (f.left > x) {\n"
|
||||
"\t\t\t\tright = mid - 1;\n"
|
||||
"\t\t\t} else if (f.left + f.width <= x) {\n"
|
||||
"\t\t\t\tleft = mid + 1;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\treturn f;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t})\n"
|
||||
"\n"
|
||||
"\t// functions\n"
|
||||
"\tfunction find_child(parent, name, attr) {\n"
|
||||
"\t\tvar children = parent.childNodes;\n"
|
||||
"\t\tfor (var i=0; i<children.length;i++) {\n"
|
||||
"\t\t\tif (children[i].tagName == name)\n"
|
||||
"\t\t\t\treturn (attr != undefined) ? children[i].attributes[attr].value : children[i];\n"
|
||||
"\t\t}\n"
|
||||
"\t\treturn;\n"
|
||||
"\t}\n"
|
||||
"\tfunction orig_save(e, attr, val) {\n"
|
||||
"\t\tif (e.attributes[\"_orig_\"+attr] != undefined) return;\n"
|
||||
"\t\tif (e.attributes[attr] == undefined) return;\n"
|
||||
"\t\tif (val == undefined) val = e.attributes[attr].value;\n"
|
||||
"\t\te.setAttribute(\"_orig_\"+attr, val);\n"
|
||||
"\t}\n"
|
||||
"\tfunction orig_load(e, attr) {\n"
|
||||
"\t\tif (e.attributes[\"_orig_\"+attr] == undefined) return;\n"
|
||||
"\t\te.attributes[attr].value = e.attributes[\"_orig_\"+attr].value;\n"
|
||||
"\t\te.removeAttribute(\"_orig_\"+attr);\n"
|
||||
"\t}\n"
|
||||
"\tfunction g_to_text(e) {\n"
|
||||
"\t\tvar text = find_child(e, \"title\").firstChild.nodeValue;\n"
|
||||
"\t\treturn (text)\n"
|
||||
"\t}\n"
|
||||
"\tfunction g_to_func(e) {\n"
|
||||
"\t\tvar func = g_to_text(e);\n"
|
||||
"\t\t// if there's any manipulation we want to do to the function\n"
|
||||
"\t\t// name before it's searched, do it here before returning.\n"
|
||||
"\t\treturn (func);\n"
|
||||
"\t}\n"
|
||||
"\tfunction update_text(e) {\n"
|
||||
"\t\tvar r = find_child(e, \"rect\");\n"
|
||||
"\t\tvar t = find_child(e, \"text\");\n"
|
||||
"\t\tvar w = parseFloat(r.attributes[\"width\"].value) -3;\n"
|
||||
"\t\tvar txt = find_child(e, \"title\").textContent.replace(/\\([^(]*\\)$/,\"\");\n"
|
||||
"\t\tt.attributes[\"x\"].value = parseFloat(r.attributes[\"x\"].value) +3;\n"
|
||||
"\t\tif (frames[left] && (frames[left].left - x) * px < 0.5) return frames[left];\n"
|
||||
"\t\tif (frames[right] && (x - (frames[right].left + frames[right].width)) * px < 0.5) return frames[right];\n"
|
||||
"\n"
|
||||
"\t\t// Smaller than this size won't fit anything\n"
|
||||
"\t\tif (w < 2*12*0.59) {\n"
|
||||
"\t\t\tt.textContent = \"\";\n"
|
||||
"\t\treturn null;\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tfunction search(r) {\n"
|
||||
"\t\tif (r && (r = prompt('Enter regexp to search:', '')) === null) {\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tt.textContent = txt;\n"
|
||||
"\t\t// Fit in full text width\n"
|
||||
"\t\tif (/^ *$/.test(txt) || t.getSubStringLength(0, txt.length) < w)\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\t\tpattern = r ? RegExp(r) : undefined;\n"
|
||||
"\t\tconst matched = render(root, rootLevel);\n"
|
||||
"\t\tdocument.getElementById('matchval').textContent = pct(matched, root.width) + '%%';\n"
|
||||
"\t\tdocument.getElementById('match').style.display = r ? 'inherit' : 'none';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t\tfor (var x=txt.length-2; x>0; x--) {\n"
|
||||
"\t\t\tif (t.getSubStringLength(0, x+2) <= w) {\n"
|
||||
"\t\t\t\tt.textContent = txt.substring(0,x) + \"..\";\n"
|
||||
"\tfunction render(newRoot, newLevel) {\n"
|
||||
"\t\tif (root) {\n"
|
||||
"\t\t\tc.fillStyle = '#ffffff';\n"
|
||||
"\t\t\tc.fillRect(0, 0, canvasWidth, canvasHeight);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\troot = newRoot || levels[0][0];\n"
|
||||
"\t\trootLevel = newLevel || 0;\n"
|
||||
"\t\tpx = canvasWidth / root.width;\n"
|
||||
"\n"
|
||||
"\t\tconst x0 = root.left;\n"
|
||||
"\t\tconst x1 = x0 + root.width;\n"
|
||||
"\t\tconst marked = [];\n"
|
||||
"\n"
|
||||
"\t\tfunction mark(f) {\n"
|
||||
"\t\t\treturn marked[f.left] >= f.width || (marked[f.left] = f.width);\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction totalMarked() {\n"
|
||||
"\t\t\tlet total = 0;\n"
|
||||
"\t\t\tlet left = 0;\n"
|
||||
"\t\t\tfor (let x in marked) {\n"
|
||||
"\t\t\t\tif (+x >= left) {\n"
|
||||
"\t\t\t\t\ttotal += marked[x];\n"
|
||||
"\t\t\t\t\tleft = +x + marked[x];\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\treturn total;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfunction drawFrame(f, y, alpha) {\n"
|
||||
"\t\t\tif (f.left < x1 && f.left + f.width > x0) {\n"
|
||||
"\t\t\t\tc.fillStyle = pattern && f.title.match(pattern) && mark(f) ? '#ee00ee' : f.color;\n"
|
||||
"\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (f.width * px >= 21) {\n"
|
||||
"\t\t\t\t\tconst chars = Math.floor(f.width * px / 7);\n"
|
||||
"\t\t\t\t\tconst title = f.title.length <= chars ? f.title : f.title.substring(0, chars - 2) + '..';\n"
|
||||
"\t\t\t\t\tc.fillStyle = '#000000';\n"
|
||||
"\t\t\t\t\tc.fillText(title, Math.max(f.left - x0, 0) * px + 3, y + 12, f.width * px - 6);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\n"
|
||||
"\t\t\t\tif (alpha) {\n"
|
||||
"\t\t\t\t\tc.fillStyle = 'rgba(255, 255, 255, 0.5)';\n"
|
||||
"\t\t\t\t\tc.fillRect((f.left - x0) * px, y, f.width * px, 15);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tfor (let h = 0; h < levels.length; h++) {\n"
|
||||
"\t\t\tconst y = reverse ? h * 16 : canvasHeight - (h + 1) * 16;\n"
|
||||
"\t\t\tconst frames = levels[h];\n"
|
||||
"\t\t\tfor (let i = 0; i < frames.length; i++) {\n"
|
||||
"\t\t\t\tdrawFrame(frames[i], y, h < rootLevel);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\treturn totalMarked();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\tcanvas.onmousemove = function() {\n"
|
||||
"\t\tconst h = Math.floor((reverse ? event.offsetY : (canvasHeight - event.offsetY)) / 16);\n"
|
||||
"\t\tif (h >= 0 && h < levels.length) {\n"
|
||||
"\t\t\tconst f = findFrame(levels[h], event.offsetX / px + root.left);\n"
|
||||
"\t\t\tif (f) {\n"
|
||||
"\t\t\t\thl.style.left = (Math.max(f.left - root.left, 0) * px + canvas.offsetLeft) + 'px';\n"
|
||||
"\t\t\t\thl.style.width = (Math.min(f.width, root.width) * px) + 'px';\n"
|
||||
"\t\t\t\thl.style.top = ((reverse ? h * 16 : canvasHeight - (h + 1) * 16) + canvas.offsetTop) + 'px';\n"
|
||||
"\t\t\t\thl.firstChild.textContent = f.title;\n"
|
||||
"\t\t\t\thl.style.display = 'block';\n"
|
||||
"\t\t\t\tcanvas.title = f.title + '\\n(' + samples(f.width) + ', ' + pct(f.width, levels[0][0].width) + '%%)';\n"
|
||||
"\t\t\t\tcanvas.style.cursor = 'pointer';\n"
|
||||
"\t\t\t\tcanvas.onclick = function() {\n"
|
||||
"\t\t\t\t\tif (f != root) {\n"
|
||||
"\t\t\t\t\t\trender(f, h);\n"
|
||||
"\t\t\t\t\t\tcanvas.onmousemove();\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t};\n"
|
||||
"\t\t\t\tstatus.textContent = 'Function: ' + canvas.title;\n"
|
||||
"\t\t\t\treturn;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tt.textContent = \"\";\n"
|
||||
"\t\tcanvas.onmouseout();\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// zoom\n"
|
||||
"\tfunction zoom_reset(e) {\n"
|
||||
"\t\tif (e.attributes != undefined) {\n"
|
||||
"\t\t\torig_load(e, \"x\");\n"
|
||||
"\t\t\torig_load(e, \"width\");\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor(var i=0, c=e.childNodes; i<c.length; i++) {\n"
|
||||
"\t\t\tzoom_reset(c[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom_child(e, x, ratio) {\n"
|
||||
"\t\tif (e.attributes != undefined) {\n"
|
||||
"\t\t\tif (e.attributes[\"x\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"x\");\n"
|
||||
"\t\t\t\te.attributes[\"x\"].value = (parseFloat(e.attributes[\"x\"].value) - x - 10) * ratio + 10;\n"
|
||||
"\t\t\t\tif(e.tagName == \"text\") e.attributes[\"x\"].value = find_child(e.parentNode, \"rect\", \"x\") + 3;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (e.attributes[\"width\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"width\");\n"
|
||||
"\t\t\t\te.attributes[\"width\"].value = parseFloat(e.attributes[\"width\"].value) * ratio;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor(var i=0, c=e.childNodes; i<c.length; i++) {\n"
|
||||
"\t\t\tzoom_child(c[i], x-10, ratio);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom_parent(e) {\n"
|
||||
"\t\tif (e.attributes) {\n"
|
||||
"\t\t\tif (e.attributes[\"x\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"x\");\n"
|
||||
"\t\t\t\te.attributes[\"x\"].value = 10;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (e.attributes[\"width\"] != undefined) {\n"
|
||||
"\t\t\t\torig_save(e, \"width\");\n"
|
||||
"\t\t\t\te.attributes[\"width\"].value = parseInt(svg.width.baseVal.value) - (10*2);\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (e.childNodes == undefined) return;\n"
|
||||
"\t\tfor(var i=0, c=e.childNodes; i<c.length; i++) {\n"
|
||||
"\t\t\tzoom_parent(c[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction zoom(node) {\n"
|
||||
"\t\tvar attr = find_child(node, \"rect\").attributes;\n"
|
||||
"\t\tvar width = parseFloat(attr[\"width\"].value);\n"
|
||||
"\t\tvar xmin = parseFloat(attr[\"x\"].value);\n"
|
||||
"\t\tvar xmax = parseFloat(xmin + width);\n"
|
||||
"\t\tvar ymin = parseFloat(attr[\"y\"].value);\n"
|
||||
"\t\tvar ratio = (svg.width.baseVal.value - 2*10) / width;\n"
|
||||
"\n"
|
||||
"\t\t// XXX: Workaround for JavaScript float issues (fix me)\n"
|
||||
"\t\tvar fudge = 0.0001;\n"
|
||||
"\n"
|
||||
"\t\tvar unzoombtn = document.getElementById(\"unzoom\");\n"
|
||||
"\t\tunzoombtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"g\");\n"
|
||||
"\t\tfor(var i=0;i<el.length;i++){\n"
|
||||
"\t\t\tvar e = el[i];\n"
|
||||
"\t\t\tvar a = find_child(e, \"rect\").attributes;\n"
|
||||
"\t\t\tvar ex = parseFloat(a[\"x\"].value);\n"
|
||||
"\t\t\tvar ew = parseFloat(a[\"width\"].value);\n"
|
||||
"\t\t\t// Is it an ancestor\n"
|
||||
"\t\t\tif (%d == 0) {\n"
|
||||
"\t\t\t\tvar upstack = parseFloat(a[\"y\"].value) > ymin;\n"
|
||||
"\t\t\t} else {\n"
|
||||
"\t\t\t\tvar upstack = parseFloat(a[\"y\"].value) < ymin;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (upstack) {\n"
|
||||
"\t\t\t\t// Direct ancestor\n"
|
||||
"\t\t\t\tif (ex <= xmin && (ex+ew+fudge) >= xmax) {\n"
|
||||
"\t\t\t\t\te.style[\"opacity\"] = \"0.5\";\n"
|
||||
"\t\t\t\t\tzoom_parent(e);\n"
|
||||
"\t\t\t\t\te.onclick = function(e){unzoom(); zoom(this);};\n"
|
||||
"\t\t\t\t\tupdate_text(e);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\t// not in current path\n"
|
||||
"\t\t\t\telse\n"
|
||||
"\t\t\t\t\te.style[\"display\"] = \"none\";\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\t// Children maybe\n"
|
||||
"\t\t\telse {\n"
|
||||
"\t\t\t\t// no common path\n"
|
||||
"\t\t\t\tif (ex < xmin || ex + fudge >= xmax) {\n"
|
||||
"\t\t\t\t\te.style[\"display\"] = \"none\";\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\telse {\n"
|
||||
"\t\t\t\t\tzoom_child(e, xmin, ratio);\n"
|
||||
"\t\t\t\t\te.onclick = function(e){zoom(this);};\n"
|
||||
"\t\t\t\t\tupdate_text(e);\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"\tfunction unzoom() {\n"
|
||||
"\t\tvar unzoombtn = document.getElementById(\"unzoom\");\n"
|
||||
"\t\tunzoombtn.style[\"opacity\"] = \"0.0\";\n"
|
||||
"\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"g\");\n"
|
||||
"\t\tfor(i=0;i<el.length;i++) {\n"
|
||||
"\t\t\tel[i].style[\"display\"] = \"block\";\n"
|
||||
"\t\t\tel[i].style[\"opacity\"] = \"1\";\n"
|
||||
"\t\t\tzoom_reset(el[i]);\n"
|
||||
"\t\t\tupdate_text(el[i]);\n"
|
||||
"\t\t}\n"
|
||||
"\tcanvas.onmouseout = function() {\n"
|
||||
"\t\thl.style.display = 'none';\n"
|
||||
"\t\tstatus.textContent = '\\xa0';\n"
|
||||
"\t\tcanvas.title = '';\n"
|
||||
"\t\tcanvas.style.cursor = '';\n"
|
||||
"\t\tcanvas.onclick = '';\n"
|
||||
"\t}\n"
|
||||
"\n"
|
||||
"\t// search\n"
|
||||
"\tfunction reset_search() {\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"rect\");\n"
|
||||
"\t\tfor (var i=0; i < el.length; i++) {\n"
|
||||
"\t\t\torig_load(el[i], \"fill\")\n"
|
||||
"\t\t}\n"
|
||||
"\tdocument.getElementById('reverse').onclick = function() {\n"
|
||||
"\t\treverse = !reverse;\n"
|
||||
"\t\trender();\n"
|
||||
"\t}\n"
|
||||
"\tfunction search_prompt() {\n"
|
||||
"\t\tif (!searching) {\n"
|
||||
"\t\t\tvar term = prompt(\"Enter a search term (regexp \" +\n"
|
||||
"\t\t\t \"allowed, eg: ^ext4_)\", \"\");\n"
|
||||
"\t\t\tif (term != null) {\n"
|
||||
"\t\t\t\tsearch(term)\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t} else {\n"
|
||||
"\t\t\treset_search();\n"
|
||||
"\t\t\tsearching = 0;\n"
|
||||
"\t\t\tsearchbtn.style[\"opacity\"] = \"0.1\";\n"
|
||||
"\t\t\tsearchbtn.firstChild.nodeValue = \"Search\"\n"
|
||||
"\t\t\tmatchedtxt.style[\"opacity\"] = \"0.0\";\n"
|
||||
"\t\t\tmatchedtxt.firstChild.nodeValue = \"\"\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\tdocument.getElementById('search').onclick = function() {\n"
|
||||
"\t\tsearch(true);\n"
|
||||
"\t}\n"
|
||||
"\tfunction search(term) {\n"
|
||||
"\t\tvar re = new RegExp(term);\n"
|
||||
"\t\tvar el = document.getElementsByTagName(\"g\");\n"
|
||||
"\t\tvar matches = new Object();\n"
|
||||
"\t\tvar maxwidth = 0;\n"
|
||||
"\t\tfor (var i = 0; i < el.length; i++) {\n"
|
||||
"\t\t\tvar e = el[i];\n"
|
||||
"\t\t\tif (e.attributes[\"class\"].value != \"func_g\")\n"
|
||||
"\t\t\t\tcontinue;\n"
|
||||
"\t\t\tvar func = g_to_func(e);\n"
|
||||
"\t\t\tvar rect = find_child(e, \"rect\");\n"
|
||||
"\t\t\tif (rect == null) {\n"
|
||||
"\t\t\t\t// the rect might be wrapped in an anchor\n"
|
||||
"\t\t\t\t// if nameattr href is being used\n"
|
||||
"\t\t\t\tif (rect = find_child(e, \"a\")) {\n"
|
||||
"\t\t\t\t rect = find_child(r, \"rect\");\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t\tif (func == null || rect == null)\n"
|
||||
"\t\t\t\tcontinue;\n"
|
||||
"\n"
|
||||
"\t\t\t// Save max width. Only works as we have a root frame\n"
|
||||
"\t\t\tvar w = parseFloat(rect.attributes[\"width\"].value);\n"
|
||||
"\t\t\tif (w > maxwidth)\n"
|
||||
"\t\t\t\tmaxwidth = w;\n"
|
||||
"\n"
|
||||
"\t\t\tif (func.match(re)) {\n"
|
||||
"\t\t\t\t// highlight\n"
|
||||
"\t\t\t\tvar x = parseFloat(rect.attributes[\"x\"].value);\n"
|
||||
"\t\t\t\torig_save(rect, \"fill\");\n"
|
||||
"\t\t\t\trect.attributes[\"fill\"].value =\n"
|
||||
"\t\t\t\t \"rgb(230,0,230)\";\n"
|
||||
"\n"
|
||||
"\t\t\t\t// remember matches\n"
|
||||
"\t\t\t\tif (matches[x] == undefined) {\n"
|
||||
"\t\t\t\t\tmatches[x] = w;\n"
|
||||
"\t\t\t\t} else {\n"
|
||||
"\t\t\t\t\tif (w > matches[x]) {\n"
|
||||
"\t\t\t\t\t\t// overwrite with parent\n"
|
||||
"\t\t\t\t\t\tmatches[x] = w;\n"
|
||||
"\t\t\t\t\t}\n"
|
||||
"\t\t\t\t}\n"
|
||||
"\t\t\t\tsearching = 1;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\tif (!searching)\n"
|
||||
"\t\t\treturn;\n"
|
||||
"\n"
|
||||
"\t\tsearchbtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t\tsearchbtn.firstChild.nodeValue = \"Reset Search\"\n"
|
||||
"\n"
|
||||
"\t\t// calculate percent matched, excluding vertical overlap\n"
|
||||
"\t\tvar count = 0;\n"
|
||||
"\t\tvar lastx = -1;\n"
|
||||
"\t\tvar lastw = 0;\n"
|
||||
"\t\tvar keys = Array();\n"
|
||||
"\t\tfor (k in matches) {\n"
|
||||
"\t\t\tif (matches.hasOwnProperty(k))\n"
|
||||
"\t\t\t\tkeys.push(k);\n"
|
||||
"\t\t}\n"
|
||||
"\t\t// sort the matched frames by their x location\n"
|
||||
"\t\t// ascending, then width descending\n"
|
||||
"\t\tkeys.sort(function(a, b){\n"
|
||||
"\t\t\treturn a - b;\n"
|
||||
"\t\t});\n"
|
||||
"\t\t// Step through frames saving only the biggest bottom-up frames\n"
|
||||
"\t\t// thanks to the sort order. This relies on the tree property\n"
|
||||
"\t\t// where children are always smaller than their parents.\n"
|
||||
"\t\tvar fudge = 0.0001;\t// JavaScript floating point\n"
|
||||
"\t\tfor (var k in keys) {\n"
|
||||
"\t\t\tvar x = parseFloat(keys[k]);\n"
|
||||
"\t\t\tvar w = matches[keys[k]];\n"
|
||||
"\t\t\tif (x >= lastx + lastw - fudge) {\n"
|
||||
"\t\t\t\tcount += w;\n"
|
||||
"\t\t\t\tlastx = x;\n"
|
||||
"\t\t\t\tlastw = w;\n"
|
||||
"\t\t\t}\n"
|
||||
"\t\t}\n"
|
||||
"\t\t// display matched percent\n"
|
||||
"\t\tmatchedtxt.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t\tpct = 100 * count / maxwidth;\n"
|
||||
"\t\tif (pct == 100)\n"
|
||||
"\t\t\tpct = \"100\"\n"
|
||||
"\t\telse\n"
|
||||
"\t\t\tpct = pct.toFixed(1)\n"
|
||||
"\t\tmatchedtxt.firstChild.nodeValue = \"Matched: \" + pct + \"%%\";\n"
|
||||
"\tdocument.getElementById('reset').onclick = function() {\n"
|
||||
"\t\tsearch(false);\n"
|
||||
"\t}\n"
|
||||
"\tfunction searchover(e) {\n"
|
||||
"\t\tsearchbtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t}\n"
|
||||
"\tfunction searchout(e) {\n"
|
||||
"\t\tif (searching) {\n"
|
||||
"\t\t\tsearchbtn.style[\"opacity\"] = \"1.0\";\n"
|
||||
"\t\t} else {\n"
|
||||
"\t\t\tsearchbtn.style[\"opacity\"] = \"0.1\";\n"
|
||||
"\n"
|
||||
"\twindow.onkeydown = function() {\n"
|
||||
"\t\tif (event.ctrlKey && event.keyCode === 70) {\n"
|
||||
"\t\t\tevent.preventDefault();\n"
|
||||
"\t\t\tsearch(true);\n"
|
||||
"\t\t} else if (event.keyCode === 27) {\n"
|
||||
"\t\t\tsearch(false);\n"
|
||||
"\t\t}\n"
|
||||
"\t}\n"
|
||||
"]]>\n"
|
||||
"</script>\n"
|
||||
"<rect x=\"0\" y=\"0\" width=\"100%%\" height=\"100%%\" fill=\"rgb(240,240,220)\"/>\n"
|
||||
"<text x=\"%d\" y=\"%d\" text-anchor=\"middle\" style=\"font-size:17px\">%s</text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"details\"> </text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"unzoom\" onclick=\"unzoom()\" style=\"opacity:0.0;cursor:pointer\">Reset Zoom</text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"search\" onmouseover=\"searchover()\" onmouseout=\"searchout()\" onclick=\"search_prompt()\" style=\"opacity:0.1;cursor:pointer\">Search</text>\n"
|
||||
"<text x=\"%d\" y=\"%d\" id=\"matched\"> </text>\n";
|
||||
"\t}\n";
|
||||
|
||||
static const char TREE_HEADER[] =
|
||||
static const char FLAMEGRAPH_FOOTER[] =
|
||||
"render();\n"
|
||||
"</script></body></html>\n";
|
||||
|
||||
|
||||
static const char TREE_HEADER[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"en\">\n"
|
||||
"<head>\n"
|
||||
@@ -413,19 +294,19 @@ static const char TREE_HEADER[] =
|
||||
" font-weight: bold;\n"
|
||||
" background-color: #D9D9D9;\n"
|
||||
"}\n"
|
||||
".green {\n"
|
||||
".t0 {\n"
|
||||
" color: #32c832;\n"
|
||||
"}\n"
|
||||
".aqua {\n"
|
||||
".t1 {\n"
|
||||
" color: #32a5a5;\n"
|
||||
"}\n"
|
||||
".brown {\n"
|
||||
".t2 {\n"
|
||||
" color: #be5a00;\n"
|
||||
"}\n"
|
||||
".yellow {\n"
|
||||
".t3 {\n"
|
||||
" color: #afaf32;\n"
|
||||
"}\n"
|
||||
".red {\n"
|
||||
".t4 {\n"
|
||||
" color: #c83232;\n"
|
||||
"}\n"
|
||||
"ul.tree li > div {\n"
|
||||
@@ -532,27 +413,11 @@ class StringUtils {
|
||||
return len >= suffixlen && s.compare(len - suffixlen, suffixlen, suffix) == 0;
|
||||
}
|
||||
|
||||
static std::string trim(const std::string& s, size_t maxchars) {
|
||||
if (maxchars < 3) {
|
||||
return "";
|
||||
} else if (s.length() > maxchars) {
|
||||
return s.substr(0, maxchars - 2) + "..";
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
static void replace(std::string& s, char c, const char* replacement) {
|
||||
for (size_t i = 0; (i = s.find(c, i)) != std::string::npos; i++) {
|
||||
s.replace(i, 1, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
static void escape(std::string& s) {
|
||||
replace(s, '&', "&");
|
||||
replace(s, '<', "<");
|
||||
replace(s, '>', ">");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -583,183 +448,129 @@ class Format {
|
||||
};
|
||||
|
||||
|
||||
class Palette {
|
||||
private:
|
||||
const char* _name;
|
||||
int _base;
|
||||
int _r, _g, _b;
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Palette(const char* name, int base, int r, int g, int b) : _name(name), _base(base), _r(r), _g(g), _b(b) {
|
||||
std::string _name;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(const std::string& name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
}
|
||||
|
||||
const char* name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
int pickColor() const {
|
||||
double value = double(rand()) / RAND_MAX;
|
||||
return _base + (int(_r * value) << 16 | int(_g * value) << 8 | int(_b * value));
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void FlameGraph::dump(std::ostream& out, bool tree) {
|
||||
_scale = (_imagewidth - 20) / (double)_root._total;
|
||||
_pct = 100 / (double)_root._total;
|
||||
|
||||
u64 cutoff = (u64)ceil(_minwidth / _scale);
|
||||
_imageheight = _frameheight * _root.depth(cutoff) + 70;
|
||||
_mintotal = _minwidth == 0 && tree ? _root._total / 1000 : (u64)(_root._total * _minwidth / 100);
|
||||
int depth = _root.depth(_mintotal);
|
||||
|
||||
if (tree) {
|
||||
printTreeHeader(out);
|
||||
char buf[sizeof(TREE_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, TREE_HEADER,
|
||||
_reverse ? "Backtrace" : "Call tree",
|
||||
_counter == COUNTER_SAMPLES ? "samples" : "counter",
|
||||
Format().thousands(_root._total));
|
||||
out << buf;
|
||||
|
||||
printTreeFrame(out, _root, 0);
|
||||
printTreeFooter(out);
|
||||
|
||||
out << TREE_FOOTER;
|
||||
} else {
|
||||
printHeader(out);
|
||||
printFrame(out, "all", _root, 10, _reverse ? 35 : (_imageheight - _frameheight - 35));
|
||||
printFooter(out);
|
||||
char buf[sizeof(FLAMEGRAPH_HEADER) + 256];
|
||||
snprintf(buf, sizeof(buf) - 1, FLAMEGRAPH_HEADER, _title, depth * 16, _reverse ? "true" : "false", depth);
|
||||
out << buf;
|
||||
|
||||
printFrame(out, "all", _root, 0, 0);
|
||||
|
||||
out << FLAMEGRAPH_FOOTER;
|
||||
}
|
||||
}
|
||||
|
||||
void FlameGraph::printHeader(std::ostream& out) {
|
||||
char buf[sizeof(SVG_HEADER) + 256];
|
||||
int x0 = _imagewidth / 2;
|
||||
int x1 = 10;
|
||||
int x2 = _imagewidth - 110;
|
||||
int y0 = 24;
|
||||
int y1 = _imageheight - 17;
|
||||
void FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x) {
|
||||
std::string name_copy = name;
|
||||
int type = frameType(name_copy);
|
||||
StringUtils::replace(name_copy, '\'', "\\'");
|
||||
|
||||
sprintf(buf, SVG_HEADER,
|
||||
_imagewidth, _imageheight, _imagewidth, _imageheight, _reverse,
|
||||
x0, y0, _title, x1, y1, x1, y0, x2, y0, x2, y1);
|
||||
out << buf;
|
||||
}
|
||||
snprintf(_buf, sizeof(_buf) - 1, "f(%d,%llu,%llu,%d,'%s')\n", level, x, f._total, type, name_copy.c_str());
|
||||
out << _buf;
|
||||
|
||||
void FlameGraph::printFooter(std::ostream& out) {
|
||||
out << "</svg>\n";
|
||||
}
|
||||
|
||||
double FlameGraph::printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y) {
|
||||
double framewidth = f._total * _scale;
|
||||
|
||||
// Skip too narrow frames, they are not important
|
||||
if (framewidth >= _minwidth) {
|
||||
std::string full_title = name;
|
||||
int color = selectFramePalette(full_title).pickColor();
|
||||
std::string short_title = StringUtils::trim(full_title, size_t(framewidth / 7));
|
||||
StringUtils::escape(full_title);
|
||||
StringUtils::escape(short_title);
|
||||
|
||||
// Compensate rounding error in frame width
|
||||
double w = (round((x + framewidth) * 10) - round(x * 10)) / 10.0;
|
||||
|
||||
snprintf(_buf, sizeof(_buf),
|
||||
"<g class=\"func_g\" onmouseover=\"s(this)\" onmouseout=\"c()\" onclick=\"zoom(this)\">\n"
|
||||
"<title>%s (%s samples, %.2f%%)</title><rect x=\"%.1f\" y=\"%.1f\" width=\"%.1f\" height=\"%d\" fill=\"#%06x\" rx=\"2\" ry=\"2\"/>\n"
|
||||
"<text x=\"%.1f\" y=\"%.1f\">%s</text>\n"
|
||||
"</g>\n",
|
||||
full_title.c_str(), Format().thousands(f._total), f._total * _pct, x, y, w, _frameheight - 1, color,
|
||||
x + 3, y + 3 + _frameheight * 0.5, short_title.c_str());
|
||||
out << _buf;
|
||||
|
||||
x += f._self * _scale;
|
||||
y += _reverse ? _frameheight : -_frameheight;
|
||||
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
x += printFrame(out, it->first, it->second, x, y);
|
||||
x += f._self;
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
if (it->second._total >= _mintotal) {
|
||||
printFrame(out, it->first, it->second, level + 1, x);
|
||||
}
|
||||
x += it->second._total;
|
||||
}
|
||||
|
||||
return framewidth;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeHeader(std::ostream& out) {
|
||||
char buf[sizeof(TREE_HEADER) + 256];
|
||||
const char* title = _reverse ? "Backtrace" : "Call tree";
|
||||
const char* counter = _counter == COUNTER_SAMPLES ? "samples" : "counter";
|
||||
sprintf(buf, TREE_HEADER, title, counter, Format().thousands(_root._total));
|
||||
out << buf;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeFooter(std::ostream& out) {
|
||||
out << TREE_FOOTER;
|
||||
}
|
||||
|
||||
bool FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int depth) {
|
||||
double framewidth = f._total * _scale;
|
||||
if (framewidth < _minwidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FlameGraph::printTreeFrame(std::ostream& out, const Trie& f, int level) {
|
||||
std::vector<Node> subnodes;
|
||||
for (std::map<std::string, Trie>::const_iterator it = f._children.begin(); it != f._children.end(); ++it) {
|
||||
subnodes.push_back(Node(it->first, it->second));
|
||||
}
|
||||
std::sort(subnodes.begin(), subnodes.end());
|
||||
|
||||
double pct = 100.0 / _root._total;
|
||||
for (size_t i = 0; i < subnodes.size(); i++) {
|
||||
std::string full_title = subnodes[i]._name;
|
||||
std::string name = subnodes[i]._name;
|
||||
const Trie* trie = subnodes[i]._trie;
|
||||
const char* color = selectFramePalette(full_title).name();
|
||||
StringUtils::escape(full_title);
|
||||
|
||||
int type = frameType(name);
|
||||
StringUtils::replace(name, '&', "&");
|
||||
StringUtils::replace(name, '<', "<");
|
||||
StringUtils::replace(name, '>', ">");
|
||||
|
||||
if (_reverse) {
|
||||
snprintf(_buf, sizeof(_buf),
|
||||
"<li><div>[%d] %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
|
||||
depth,
|
||||
trie->_total * _pct, Format().thousands(trie->_total),
|
||||
color, full_title.c_str());
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<li><div>[%d] %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
|
||||
level,
|
||||
trie->_total * pct, Format().thousands(trie->_total),
|
||||
type, name.c_str());
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf),
|
||||
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"%s\"> %s</span>\n",
|
||||
depth,
|
||||
trie->_total * _pct, Format().thousands(trie->_total),
|
||||
trie->_self * _pct, Format().thousands(trie->_self),
|
||||
color, full_title.c_str());
|
||||
snprintf(_buf, sizeof(_buf) - 1,
|
||||
"<li><div>[%d] %.2f%% %s self: %.2f%% %s</div><span class=\"t%d\"> %s</span>\n",
|
||||
level,
|
||||
trie->_total * pct, Format().thousands(trie->_total),
|
||||
trie->_self * pct, Format().thousands(trie->_self),
|
||||
type, name.c_str());
|
||||
}
|
||||
out << _buf;
|
||||
|
||||
if (trie->_children.size() > 0) {
|
||||
out << "<ul>\n";
|
||||
if (!printTreeFrame(out, *trie, depth + 1)) {
|
||||
if (trie->_total >= _mintotal) {
|
||||
printTreeFrame(out, *trie, level + 1);
|
||||
} else {
|
||||
out << "<li>...\n";
|
||||
}
|
||||
out << "</ul>\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Palette& FlameGraph::selectFramePalette(std::string& name) {
|
||||
static const Palette
|
||||
green ("green", 0x32c832, 60, 55, 60),
|
||||
aqua ("aqua", 0x32a5a5, 60, 55, 55),
|
||||
brown ("brown", 0xbe5a00, 65, 65, 0),
|
||||
yellow("yellow", 0xafaf32, 55, 55, 20),
|
||||
red ("red", 0xc83232, 55, 80, 80);
|
||||
|
||||
int FlameGraph::frameType(std::string& name) {
|
||||
if (StringUtils::endsWith(name, "_[j]", 4)) {
|
||||
// Java compiled frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return green;
|
||||
return 0;
|
||||
} else if (StringUtils::endsWith(name, "_[i]", 4)) {
|
||||
// Java inlined frame
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return aqua;
|
||||
return 1;
|
||||
} else if (StringUtils::endsWith(name, "_[k]", 4)) {
|
||||
// Kernel function
|
||||
name = name.substr(0, name.length() - 4);
|
||||
return brown;
|
||||
return 2;
|
||||
} else if (name.find("::") != std::string::npos || name.compare(0, 2, "-[") == 0 || name.compare(0, 2, "+[") == 0) {
|
||||
// C++ function or Objective C method
|
||||
return yellow;
|
||||
return 3;
|
||||
} else if ((int)name.find('/') > 0 || ((int)name.find('.') > 0 && name[0] >= 'A' && name[0] <= 'Z')) {
|
||||
// Java regular method
|
||||
return green;
|
||||
return 0;
|
||||
} else {
|
||||
// Other native code
|
||||
return red;
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,55 +57,30 @@ class Trie {
|
||||
}
|
||||
};
|
||||
|
||||
class Node {
|
||||
public:
|
||||
std::string _name;
|
||||
const Trie* _trie;
|
||||
|
||||
Node(std::string name, const Trie& trie) : _name(name), _trie(&trie) {
|
||||
}
|
||||
|
||||
bool operator<(const Node& other) const {
|
||||
return _trie->_total > other._trie->_total;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Palette;
|
||||
|
||||
|
||||
class FlameGraph {
|
||||
private:
|
||||
Trie _root;
|
||||
char _buf[4096];
|
||||
u64 _mintotal;
|
||||
|
||||
const char* _title;
|
||||
Counter _counter;
|
||||
int _imagewidth;
|
||||
int _imageheight;
|
||||
int _frameheight;
|
||||
double _minwidth;
|
||||
double _scale;
|
||||
double _pct;
|
||||
bool _reverse;
|
||||
|
||||
void printHeader(std::ostream& out);
|
||||
void printFooter(std::ostream& out);
|
||||
double printFrame(std::ostream& out, const std::string& name, const Trie& f, double x, double y);
|
||||
void printTreeHeader(std::ostream& out);
|
||||
void printTreeFooter(std::ostream& out);
|
||||
bool printTreeFrame(std::ostream& out, const Trie& f, int depth);
|
||||
const Palette& selectFramePalette(std::string& name);
|
||||
void printFrame(std::ostream& out, const std::string& name, const Trie& f, int level, u64 x);
|
||||
void printTreeFrame(std::ostream& out, const Trie& f, int level);
|
||||
int frameType(std::string& name);
|
||||
|
||||
public:
|
||||
FlameGraph(const char* title, Counter counter, int width, int height, double minwidth, bool reverse) :
|
||||
FlameGraph(const char* title, Counter counter, double minwidth, bool reverse) :
|
||||
_root(),
|
||||
_title(title),
|
||||
_counter(counter),
|
||||
_imagewidth(width),
|
||||
_frameheight(height),
|
||||
_minwidth(minwidth),
|
||||
_reverse(reverse) {
|
||||
_buf[sizeof(_buf) - 1] = 0;
|
||||
}
|
||||
|
||||
Trie* root() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,8 +17,9 @@
|
||||
#ifndef _FLIGHTRECORDER_H
|
||||
#define _FLIGHTRECORDER_H
|
||||
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
|
||||
#include "event.h"
|
||||
|
||||
class Recording;
|
||||
|
||||
@@ -30,10 +31,15 @@ class FlightRecorder {
|
||||
FlightRecorder() : _rec(NULL) {
|
||||
}
|
||||
|
||||
Error start(const char* file);
|
||||
Error start(Arguments& args, bool reset);
|
||||
void stop();
|
||||
|
||||
void recordExecutionSample(int lock_index, int tid, int call_trace_id);
|
||||
bool active() {
|
||||
return _rec != NULL;
|
||||
}
|
||||
|
||||
void recordEvent(int lock_index, int tid, u32 call_trace_id,
|
||||
int event_type, Event* event, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _FLIGHTRECORDER_H
|
||||
|
||||
@@ -19,26 +19,99 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "frameName.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
FrameName::FrameName(bool simple, bool annotate, bool dotted, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
Matcher::Matcher(const char* pattern) {
|
||||
if (pattern[0] == '*') {
|
||||
_type = MATCH_ENDS_WITH;
|
||||
_pattern = strdup(pattern + 1);
|
||||
} else {
|
||||
_type = MATCH_EQUALS;
|
||||
_pattern = strdup(pattern);
|
||||
}
|
||||
|
||||
_len = strlen(_pattern);
|
||||
if (_len > 0 && _pattern[_len - 1] == '*') {
|
||||
_type = _type == MATCH_EQUALS ? MATCH_STARTS_WITH : MATCH_CONTAINS;
|
||||
_pattern[--_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Matcher::~Matcher() {
|
||||
free(_pattern);
|
||||
}
|
||||
|
||||
Matcher::Matcher(const Matcher& m) {
|
||||
_type = m._type;
|
||||
_pattern = strdup(m._pattern);
|
||||
_len = m._len;
|
||||
}
|
||||
|
||||
Matcher& Matcher::operator=(const Matcher& m) {
|
||||
free(_pattern);
|
||||
|
||||
_type = m._type;
|
||||
_pattern = strdup(m._pattern);
|
||||
_len = m._len;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Matcher::matches(const char* s) {
|
||||
switch (_type) {
|
||||
case MATCH_EQUALS:
|
||||
return strcmp(s, _pattern) == 0;
|
||||
case MATCH_CONTAINS:
|
||||
return strstr(s, _pattern) != NULL;
|
||||
case MATCH_STARTS_WITH:
|
||||
return strncmp(s, _pattern, _len) == 0;
|
||||
case MATCH_ENDS_WITH:
|
||||
int slen = strlen(s);
|
||||
return slen >= _len && strcmp(s + slen - _len, _pattern) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
FrameName::FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names) :
|
||||
_cache(),
|
||||
_simple(simple),
|
||||
_annotate(annotate),
|
||||
_dotted(dotted),
|
||||
_class_names(),
|
||||
_include(),
|
||||
_exclude(),
|
||||
_style(style),
|
||||
_thread_names_lock(thread_names_lock),
|
||||
_thread_names(thread_names)
|
||||
{
|
||||
// Require printf to use standard C format regardless of system locale
|
||||
_saved_locale = uselocale(newlocale(LC_NUMERIC_MASK, "C", (locale_t)0));
|
||||
memset(_buf, 0, sizeof(_buf));
|
||||
|
||||
buildFilter(_include, args._buf, args._include);
|
||||
buildFilter(_exclude, args._buf, args._exclude);
|
||||
|
||||
Profiler::_instance.classMap()->collect(_class_names);
|
||||
}
|
||||
|
||||
FrameName::~FrameName() {
|
||||
freelocale(uselocale(_saved_locale));
|
||||
}
|
||||
|
||||
void FrameName::buildFilter(std::vector<Matcher>& vector, const char* base, int offset) {
|
||||
while (offset != 0) {
|
||||
vector.push_back(base + offset);
|
||||
offset = ((int*)(base + offset))[-1];
|
||||
}
|
||||
}
|
||||
|
||||
char* FrameName::truncate(char* name, int max_length) {
|
||||
if (strlen(name) > max_length && max_length >= 4) {
|
||||
strcpy(name + max_length - 4, "...)");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
const char* FrameName::cppDemangle(const char* name) {
|
||||
if (name != NULL && name[0] == '_' && name[1] == 'Z') {
|
||||
int status;
|
||||
@@ -56,31 +129,34 @@ char* FrameName::javaMethodName(jmethodID method) {
|
||||
jclass method_class;
|
||||
char* class_name = NULL;
|
||||
char* method_name = NULL;
|
||||
char* method_sig = NULL;
|
||||
char* result;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmtiError err;
|
||||
|
||||
if ((err = jvmti->GetMethodName(method, &method_name, NULL, NULL)) == 0 &&
|
||||
if ((err = jvmti->GetMethodName(method, &method_name, &method_sig, NULL)) == 0 &&
|
||||
(err = jvmti->GetMethodDeclaringClass(method, &method_class)) == 0 &&
|
||||
(err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
|
||||
// Trim 'L' and ';' off the class descriptor like 'Ljava/lang/Object;'
|
||||
result = javaClassName(class_name + 1, strlen(class_name) - 2, _simple, _dotted);
|
||||
result = javaClassName(class_name + 1, strlen(class_name) - 2, _style);
|
||||
strcat(result, ".");
|
||||
strcat(result, method_name);
|
||||
if (_annotate) strcat(result, "_[j]");
|
||||
if (_style & STYLE_SIGNATURES) strcat(result, truncate(method_sig, 255));
|
||||
if (_style & STYLE_ANNOTATE) strcat(result, "_[j]");
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf), "[jvmtiError %d]", err);
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[jvmtiError %d]", err);
|
||||
result = _buf;
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
jvmti->Deallocate((unsigned char*)method_sig);
|
||||
jvmti->Deallocate((unsigned char*)method_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char* FrameName::javaClassName(const char* symbol, int length, bool simple, bool dotted) {
|
||||
char* FrameName::javaClassName(const char* symbol, int length, int style) {
|
||||
char* result = _buf;
|
||||
|
||||
int array_dimension = 0;
|
||||
@@ -113,13 +189,13 @@ char* FrameName::javaClassName(const char* symbol, int length, bool simple, bool
|
||||
} while (--array_dimension > 0);
|
||||
}
|
||||
|
||||
if (simple) {
|
||||
if (style & STYLE_SIMPLE) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') result = s + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (dotted) {
|
||||
if (style & STYLE_DOTTED) {
|
||||
for (char* s = result; *s; s++) {
|
||||
if (*s == '/') *s = '.';
|
||||
}
|
||||
@@ -128,7 +204,7 @@ char* FrameName::javaClassName(const char* symbol, int length, bool simple, bool
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* FrameName::name(ASGCT_CallFrame& frame) {
|
||||
const char* FrameName::name(ASGCT_CallFrame& frame, bool for_matching) {
|
||||
if (frame.method_id == NULL) {
|
||||
return "[unknown]";
|
||||
}
|
||||
@@ -137,30 +213,37 @@ const char* FrameName::name(ASGCT_CallFrame& frame) {
|
||||
case BCI_NATIVE_FRAME:
|
||||
return cppDemangle((const char*)frame.method_id);
|
||||
|
||||
case BCI_SYMBOL: {
|
||||
VMSymbol* symbol = (VMSymbol*)frame.method_id;
|
||||
char* class_name = javaClassName(symbol->body(), symbol->length(), _simple, true);
|
||||
return strcat(class_name, _dotted ? "" : "_[i]");
|
||||
}
|
||||
|
||||
case BCI_SYMBOL_OUTSIDE_TLAB: {
|
||||
VMSymbol* symbol = (VMSymbol*)((uintptr_t)frame.method_id ^ 1);
|
||||
char* class_name = javaClassName(symbol->body(), symbol->length(), _simple, true);
|
||||
return strcat(class_name, _dotted ? " (out)" : "_[k]");
|
||||
case BCI_ALLOC:
|
||||
case BCI_ALLOC_OUTSIDE_TLAB:
|
||||
case BCI_LOCK:
|
||||
case BCI_PARK: {
|
||||
const char* symbol = _class_names[(uintptr_t)frame.method_id];
|
||||
char* class_name = javaClassName(symbol, strlen(symbol), _style | STYLE_DOTTED);
|
||||
if (!for_matching && !(_style & STYLE_DOTTED)) {
|
||||
strcat(class_name, frame.bci == BCI_ALLOC_OUTSIDE_TLAB ? "_[k]" : "_[i]");
|
||||
}
|
||||
return class_name;
|
||||
}
|
||||
|
||||
case BCI_THREAD_ID: {
|
||||
int tid = (int)(uintptr_t)frame.method_id;
|
||||
MutexLocker ml(_thread_names_lock);
|
||||
ThreadMap::iterator it = _thread_names.find(tid);
|
||||
if (it != _thread_names.end()) {
|
||||
snprintf(_buf, sizeof(_buf), "[%s tid=%d]", it->second.c_str(), tid);
|
||||
if (for_matching) {
|
||||
return it != _thread_names.end() ? it->second.c_str() : "";
|
||||
} else if (it != _thread_names.end()) {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[%s tid=%d]", it->second.c_str(), tid);
|
||||
} else {
|
||||
snprintf(_buf, sizeof(_buf), "[tid=%d]", tid);
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[tid=%d]", tid);
|
||||
}
|
||||
return _buf;
|
||||
}
|
||||
|
||||
case BCI_ERROR: {
|
||||
snprintf(_buf, sizeof(_buf) - 1, "[%s]", (const char*)frame.method_id);
|
||||
return _buf;
|
||||
}
|
||||
|
||||
default: {
|
||||
JMethodCache::iterator it = _cache.lower_bound(frame.method_id);
|
||||
if (it != _cache.end() && it->first == frame.method_id) {
|
||||
@@ -173,3 +256,22 @@ const char* FrameName::name(ASGCT_CallFrame& frame) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameName::include(const char* frame_name) {
|
||||
for (int i = 0; i < _include.size(); i++) {
|
||||
if (_include[i].matches(frame_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FrameName::exclude(const char* frame_name) {
|
||||
for (int i = 0; i < _exclude.size(); i++) {
|
||||
if (_exclude[i].matches(frame_name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
#include <jvmti.h>
|
||||
#include <locale.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "arguments.h"
|
||||
#include "mutex.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
@@ -31,27 +33,63 @@
|
||||
|
||||
typedef std::map<jmethodID, std::string> JMethodCache;
|
||||
typedef std::map<int, std::string> ThreadMap;
|
||||
typedef std::map<unsigned int, const char*> ClassMap;
|
||||
|
||||
|
||||
enum MatchType {
|
||||
MATCH_EQUALS,
|
||||
MATCH_CONTAINS,
|
||||
MATCH_STARTS_WITH,
|
||||
MATCH_ENDS_WITH
|
||||
};
|
||||
|
||||
|
||||
class Matcher {
|
||||
private:
|
||||
MatchType _type;
|
||||
char* _pattern;
|
||||
int _len;
|
||||
|
||||
public:
|
||||
Matcher(const char* pattern);
|
||||
~Matcher();
|
||||
|
||||
Matcher(const Matcher& m);
|
||||
Matcher& operator=(const Matcher& m);
|
||||
|
||||
bool matches(const char* s);
|
||||
};
|
||||
|
||||
|
||||
class FrameName {
|
||||
private:
|
||||
JMethodCache _cache;
|
||||
char _buf[520];
|
||||
bool _simple;
|
||||
bool _annotate;
|
||||
bool _dotted;
|
||||
ClassMap _class_names;
|
||||
std::vector<Matcher> _include;
|
||||
std::vector<Matcher> _exclude;
|
||||
char _buf[800]; // must be large enough for class name + method name + method signature
|
||||
int _style;
|
||||
Mutex& _thread_names_lock;
|
||||
ThreadMap& _thread_names;
|
||||
locale_t _saved_locale;
|
||||
|
||||
void buildFilter(std::vector<Matcher>& vector, const char* base, int offset);
|
||||
char* truncate(char* name, int max_length);
|
||||
const char* cppDemangle(const char* name);
|
||||
char* javaMethodName(jmethodID method);
|
||||
char* javaClassName(const char* symbol, int length, bool simple, bool dotted);
|
||||
char* javaClassName(const char* symbol, int length, int style);
|
||||
|
||||
public:
|
||||
FrameName(bool simple, bool annotate, bool dotted, Mutex& thread_names_lock, ThreadMap& thread_names);
|
||||
FrameName(Arguments& args, int style, Mutex& thread_names_lock, ThreadMap& thread_names);
|
||||
~FrameName();
|
||||
|
||||
const char* name(ASGCT_CallFrame& frame);
|
||||
const char* name(ASGCT_CallFrame& frame, bool for_matching = false);
|
||||
|
||||
bool hasIncludeList() { return !_include.empty(); }
|
||||
bool hasExcludeList() { return !_exclude.empty(); }
|
||||
|
||||
bool include(const char* frame_name);
|
||||
bool exclude(const char* frame_name);
|
||||
};
|
||||
|
||||
#endif // _FRAMENAME_H
|
||||
|
||||
593
src/instrument.cpp
Normal file
593
src/instrument.cpp
Normal file
@@ -0,0 +1,593 @@
|
||||
/*
|
||||
* Copyright 2019 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "arch.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmEntry.h"
|
||||
#include "instrument.h"
|
||||
|
||||
|
||||
// A class with a single native recordSample() method
|
||||
static const char INSTRUMENT_CLASS[] =
|
||||
"\xCA\xFE\xBA\xBE" // magic
|
||||
"\x00\x00\x00\x32" // version: 50
|
||||
"\x00\x07" // constant_pool_count: 7
|
||||
"\x07\x00\x02" // #1 = CONSTANT_Class: #2
|
||||
"\x01\x00\x17one/profiler/Instrument" // #2 = CONSTANT_Utf8: "one/profiler/Instrument"
|
||||
"\x07\x00\x04" // #3 = CONSTANT_Class: #4
|
||||
"\x01\x00\x10java/lang/Object" // #4 = CONSTANT_Utf8: "java/lang/Object"
|
||||
"\x01\x00\x0CrecordSample" // #5 = CONSTANT_Utf8: "recordSample"
|
||||
"\x01\x00\x03()V" // #6 = CONSTANT_Utf8: "()V"
|
||||
"\x00\x21" // access_flags: public super
|
||||
"\x00\x01" // this_class: #1
|
||||
"\x00\x03" // super_class: #3
|
||||
"\x00\x00" // interfaces_count: 0
|
||||
"\x00\x00" // fields_count: 0
|
||||
"\x00\x01" // methods_count: 1
|
||||
"\x01\x09" // access_flags: public static native
|
||||
"\x00\x05" // name_index: #5
|
||||
"\x00\x06" // descriptor_index: #6
|
||||
"\x00\x00" // attributes_count: 0
|
||||
"\x00"; // attributes_count: 0
|
||||
|
||||
|
||||
enum ConstantTag {
|
||||
CONSTANT_Utf8 = 1,
|
||||
CONSTANT_Integer = 3,
|
||||
CONSTANT_Float = 4,
|
||||
CONSTANT_Long = 5,
|
||||
CONSTANT_Double = 6,
|
||||
CONSTANT_Class = 7,
|
||||
CONSTANT_String = 8,
|
||||
CONSTANT_Fieldref = 9,
|
||||
CONSTANT_Methodref = 10,
|
||||
CONSTANT_InterfaceMethodref = 11,
|
||||
CONSTANT_NameAndType = 12,
|
||||
CONSTANT_MethodHandle = 15,
|
||||
CONSTANT_MethodType = 16,
|
||||
CONSTANT_Dynamic = 17,
|
||||
CONSTANT_InvokeDynamic = 18,
|
||||
CONSTANT_Module = 19,
|
||||
CONSTANT_Package = 20
|
||||
};
|
||||
|
||||
class Constant {
|
||||
private:
|
||||
u8 _tag;
|
||||
u8 _info[2];
|
||||
|
||||
public:
|
||||
u8 tag() {
|
||||
return _tag;
|
||||
}
|
||||
|
||||
int slots() {
|
||||
return _tag == CONSTANT_Long || _tag == CONSTANT_Double ? 2 : 1;
|
||||
}
|
||||
|
||||
u16 info() {
|
||||
return (u16)_info[0] << 8 | (u16)_info[1];
|
||||
}
|
||||
|
||||
int length() {
|
||||
switch (_tag) {
|
||||
case CONSTANT_Utf8:
|
||||
return 2 + info();
|
||||
case CONSTANT_Integer:
|
||||
case CONSTANT_Float:
|
||||
case CONSTANT_Fieldref:
|
||||
case CONSTANT_Methodref:
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
case CONSTANT_NameAndType:
|
||||
case CONSTANT_Dynamic:
|
||||
case CONSTANT_InvokeDynamic:
|
||||
return 4;
|
||||
case CONSTANT_Long:
|
||||
case CONSTANT_Double:
|
||||
return 8;
|
||||
case CONSTANT_Class:
|
||||
case CONSTANT_String:
|
||||
case CONSTANT_MethodType:
|
||||
case CONSTANT_Module:
|
||||
case CONSTANT_Package:
|
||||
return 2;
|
||||
case CONSTANT_MethodHandle:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool equals(const char* value, u16 len) {
|
||||
return _tag == CONSTANT_Utf8 && info() == len && memcmp(_info + 2, value, len) == 0;
|
||||
}
|
||||
|
||||
bool matches(const char* value, u16 len) {
|
||||
if (len > 0 && value[len - 1] == '*') {
|
||||
return _tag == CONSTANT_Utf8 && info() >= len - 1 && memcmp(_info + 2, value, len - 1) == 0;
|
||||
}
|
||||
return equals(value, len);
|
||||
}
|
||||
};
|
||||
|
||||
enum Scope {
|
||||
SCOPE_CLASS,
|
||||
SCOPE_FIELD,
|
||||
SCOPE_METHOD,
|
||||
SCOPE_REWRITE_METHOD,
|
||||
SCOPE_REWRITE_CODE
|
||||
};
|
||||
|
||||
enum PatchConstants {
|
||||
EXTRA_CONSTANTS = 6,
|
||||
EXTRA_BYTECODES = 4,
|
||||
EXTRA_STACKMAPS = 1
|
||||
};
|
||||
|
||||
|
||||
class BytecodeRewriter {
|
||||
private:
|
||||
const u8* _src;
|
||||
const u8* _src_limit;
|
||||
|
||||
u8* _dst;
|
||||
int _dst_len;
|
||||
int _dst_capacity;
|
||||
|
||||
Constant** _cpool;
|
||||
u16 _cpool_len;
|
||||
|
||||
const char* _target_class;
|
||||
u16 _target_class_len;
|
||||
const char* _target_method;
|
||||
u16 _target_method_len;
|
||||
const char* _target_signature;
|
||||
u16 _target_signature_len;
|
||||
|
||||
// Reader
|
||||
|
||||
const u8* get(int bytes) {
|
||||
const u8* result = _src;
|
||||
_src += bytes;
|
||||
return _src <= _src_limit ? result : NULL;
|
||||
}
|
||||
|
||||
u8 get8() {
|
||||
return *get(1);
|
||||
}
|
||||
|
||||
u16 get16() {
|
||||
return ntohs(*(u16*)get(2));
|
||||
}
|
||||
|
||||
u32 get32() {
|
||||
return ntohl(*(u32*)get(4));
|
||||
}
|
||||
|
||||
u64 get64() {
|
||||
return OS::ntoh64(*(u64*)get(8));
|
||||
}
|
||||
|
||||
Constant* getConstant() {
|
||||
Constant* c = (Constant*)get(1);
|
||||
get(c->length());
|
||||
return c;
|
||||
}
|
||||
|
||||
// Writer
|
||||
|
||||
u8* alloc(int bytes) {
|
||||
if (_dst_len + bytes > _dst_capacity) {
|
||||
grow(_dst_len + bytes + 2000);
|
||||
}
|
||||
u8* result = _dst + _dst_len;
|
||||
_dst_len += bytes;
|
||||
return result;
|
||||
}
|
||||
|
||||
void grow(int new_capacity) {
|
||||
u8* new_dst = NULL;
|
||||
VM::jvmti()->Allocate(new_capacity, &new_dst);
|
||||
memcpy(new_dst, _dst, _dst_len);
|
||||
VM::jvmti()->Deallocate(_dst);
|
||||
|
||||
_dst = new_dst;
|
||||
_dst_capacity = new_capacity;
|
||||
}
|
||||
|
||||
void put(const u8* src, int bytes) {
|
||||
memcpy(alloc(bytes), src, bytes);
|
||||
}
|
||||
|
||||
void put8(u8 v) {
|
||||
*alloc(1) = v;
|
||||
}
|
||||
|
||||
void put16(u16 v) {
|
||||
*(u16*)alloc(2) = htons(v);
|
||||
}
|
||||
|
||||
void put32(u32 v) {
|
||||
*(u32*)alloc(4) = htonl(v);
|
||||
}
|
||||
|
||||
void put64(u64 v) {
|
||||
*(u64*)alloc(8) = OS::hton64(v);
|
||||
}
|
||||
|
||||
void putConstant(const char* value) {
|
||||
u16 len = strlen(value);
|
||||
put8(CONSTANT_Utf8);
|
||||
put16(len);
|
||||
put((const u8*)value, len);
|
||||
}
|
||||
|
||||
void putConstant(u8 tag, u16 ref) {
|
||||
put8(tag);
|
||||
put16(ref);
|
||||
}
|
||||
|
||||
void putConstant(u8 tag, u16 ref1, u16 ref2) {
|
||||
put8(tag);
|
||||
put16(ref1);
|
||||
put16(ref2);
|
||||
}
|
||||
|
||||
// BytecodeRewriter
|
||||
|
||||
void rewriteCode();
|
||||
void rewriteBytecodeTable(int data_len);
|
||||
void rewriteStackMapTable();
|
||||
void rewriteAttributes(Scope scope);
|
||||
void rewriteMembers(Scope scope);
|
||||
bool rewriteClass();
|
||||
|
||||
public:
|
||||
BytecodeRewriter(const u8* class_data, int class_data_len, const char* target_class) :
|
||||
_src(class_data),
|
||||
_src_limit(class_data + class_data_len),
|
||||
_dst(NULL),
|
||||
_dst_len(0),
|
||||
_dst_capacity(class_data_len + 400),
|
||||
_cpool(NULL) {
|
||||
|
||||
_target_class = target_class;
|
||||
_target_class_len = strlen(_target_class);
|
||||
|
||||
_target_method = _target_class + _target_class_len + 1;
|
||||
_target_signature = strchr(_target_method, '(');
|
||||
|
||||
if (_target_signature == NULL) {
|
||||
_target_method_len = strlen(_target_method);
|
||||
} else {
|
||||
_target_method_len = _target_signature - _target_method;
|
||||
_target_signature_len = strlen(_target_signature);
|
||||
}
|
||||
}
|
||||
|
||||
~BytecodeRewriter() {
|
||||
delete[] _cpool;
|
||||
}
|
||||
|
||||
void rewrite(u8** new_class_data, int* new_class_data_len) {
|
||||
if (VM::jvmti()->Allocate(_dst_capacity, &_dst) == 0) {
|
||||
if (rewriteClass()) {
|
||||
*new_class_data = _dst;
|
||||
*new_class_data_len = _dst_len;
|
||||
} else {
|
||||
VM::jvmti()->Deallocate(_dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void BytecodeRewriter::rewriteCode() {
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length);
|
||||
|
||||
int code_begin = _dst_len;
|
||||
|
||||
u16 max_stack = get16();
|
||||
put16(max_stack);
|
||||
|
||||
u16 max_locals = get16();
|
||||
put16(max_locals);
|
||||
|
||||
u32 code_length = get32();
|
||||
put32(code_length + EXTRA_BYTECODES);
|
||||
|
||||
// invokestatic "one/profiler/Instrument.recordSample()V"
|
||||
// nop after invoke helps to prepend StackMapTable without rewriting
|
||||
put8(0xb8);
|
||||
put16(_cpool_len);
|
||||
put8(0);
|
||||
// The rest of the code is unchanged
|
||||
put(get(code_length), code_length);
|
||||
|
||||
u16 exception_table_length = get16();
|
||||
put16(exception_table_length);
|
||||
|
||||
for (int i = 0; i < exception_table_length; i++) {
|
||||
u16 start_pc = get16();
|
||||
u16 end_pc = get16();
|
||||
u16 handler_pc = get16();
|
||||
u16 catch_type = get16();
|
||||
put16(EXTRA_BYTECODES + start_pc);
|
||||
put16(EXTRA_BYTECODES + end_pc);
|
||||
put16(EXTRA_BYTECODES + handler_pc);
|
||||
put16(catch_type);
|
||||
}
|
||||
|
||||
rewriteAttributes(SCOPE_REWRITE_CODE);
|
||||
|
||||
// Patch attribute length
|
||||
*(u32*)(_dst + code_begin - 4) = htonl(_dst_len - code_begin);
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteBytecodeTable(int data_len) {
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length);
|
||||
|
||||
u16 table_length = get16();
|
||||
put16(table_length);
|
||||
|
||||
for (int i = 0; i < table_length; i++) {
|
||||
u16 start_pc = get16();
|
||||
put16(EXTRA_BYTECODES + start_pc);
|
||||
|
||||
put(get(data_len), data_len);
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteStackMapTable() {
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length + EXTRA_STACKMAPS);
|
||||
|
||||
u16 number_of_entries = get16();
|
||||
put16(number_of_entries + EXTRA_STACKMAPS);
|
||||
|
||||
// Prepend same_frame
|
||||
put8(EXTRA_BYTECODES - 1);
|
||||
put(get(attribute_length - 2), attribute_length - 2);
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteAttributes(Scope scope) {
|
||||
u16 attributes_count = get16();
|
||||
put16(attributes_count);
|
||||
|
||||
for (int i = 0; i < attributes_count; i++) {
|
||||
u16 attribute_name_index = get16();
|
||||
put16(attribute_name_index);
|
||||
|
||||
Constant* attribute_name = _cpool[attribute_name_index];
|
||||
if (scope == SCOPE_REWRITE_METHOD && attribute_name->equals("Code", 4)) {
|
||||
rewriteCode();
|
||||
continue;
|
||||
} else if (scope == SCOPE_REWRITE_CODE) {
|
||||
if (attribute_name->equals("LineNumberTable", 15)) {
|
||||
rewriteBytecodeTable(2);
|
||||
continue;
|
||||
} else if (attribute_name->equals("LocalVariableTable", 18) ||
|
||||
attribute_name->equals("LocalVariableTypeTable", 22)) {
|
||||
rewriteBytecodeTable(8);
|
||||
continue;
|
||||
} else if (attribute_name->equals("StackMapTable", 13)) {
|
||||
rewriteStackMapTable();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
u32 attribute_length = get32();
|
||||
put32(attribute_length);
|
||||
put(get(attribute_length), attribute_length);
|
||||
}
|
||||
}
|
||||
|
||||
void BytecodeRewriter::rewriteMembers(Scope scope) {
|
||||
u16 members_count = get16();
|
||||
put16(members_count);
|
||||
|
||||
for (int i = 0; i < members_count; i++) {
|
||||
u16 access_flags = get16();
|
||||
put16(access_flags);
|
||||
|
||||
u16 name_index = get16();
|
||||
put16(name_index);
|
||||
|
||||
u16 descriptor_index = get16();
|
||||
put16(descriptor_index);
|
||||
|
||||
bool need_rewrite = scope == SCOPE_METHOD
|
||||
&& _cpool[name_index]->matches(_target_method, _target_method_len)
|
||||
&& (_target_signature == NULL || _cpool[descriptor_index]->matches(_target_signature, _target_signature_len));
|
||||
|
||||
rewriteAttributes(need_rewrite ? SCOPE_REWRITE_METHOD : SCOPE_METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
bool BytecodeRewriter::rewriteClass() {
|
||||
u32 magic = get32();
|
||||
put32(magic);
|
||||
|
||||
u32 version = get32();
|
||||
put32(version);
|
||||
|
||||
_cpool_len = get16();
|
||||
put16(_cpool_len + EXTRA_CONSTANTS);
|
||||
|
||||
const u8* cpool_start = _src;
|
||||
|
||||
_cpool = new Constant*[_cpool_len];
|
||||
for (int i = 1; i < _cpool_len; i += _cpool[i]->slots()) {
|
||||
_cpool[i] = getConstant();
|
||||
}
|
||||
|
||||
const u8* cpool_end = _src;
|
||||
put(cpool_start, cpool_end - cpool_start);
|
||||
|
||||
putConstant(CONSTANT_Methodref, _cpool_len + 1, _cpool_len + 2);
|
||||
putConstant(CONSTANT_Class, _cpool_len + 3);
|
||||
putConstant(CONSTANT_NameAndType, _cpool_len + 4, _cpool_len + 5);
|
||||
putConstant("one/profiler/Instrument");
|
||||
putConstant("recordSample");
|
||||
putConstant("()V");
|
||||
|
||||
u16 access_flags = get16();
|
||||
put16(access_flags);
|
||||
|
||||
u16 this_class = get16();
|
||||
put16(this_class);
|
||||
|
||||
u16 class_name_index = _cpool[this_class]->info();
|
||||
if (!_cpool[class_name_index]->equals(_target_class, _target_class_len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u16 super_class = get16();
|
||||
put16(super_class);
|
||||
|
||||
u16 interfaces_count = get16();
|
||||
put16(interfaces_count);
|
||||
put(get(interfaces_count * 2), interfaces_count * 2);
|
||||
|
||||
rewriteMembers(SCOPE_FIELD);
|
||||
rewriteMembers(SCOPE_METHOD);
|
||||
rewriteAttributes(SCOPE_CLASS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
char* Instrument::_target_class = NULL;
|
||||
bool Instrument::_instrument_class_loaded = false;
|
||||
u64 Instrument::_interval;
|
||||
volatile u64 Instrument::_calls;
|
||||
volatile bool Instrument::_running;
|
||||
|
||||
Error Instrument::check(Arguments& args) {
|
||||
if (!_instrument_class_loaded) {
|
||||
JNIEnv* jni = VM::jni();
|
||||
const JNINativeMethod native_method = {(char*)"recordSample", (char*)"()V", (void*)recordSample};
|
||||
|
||||
jclass cls = jni->DefineClass(NULL, NULL, (const jbyte*)INSTRUMENT_CLASS, sizeof(INSTRUMENT_CLASS));
|
||||
if (cls == NULL || jni->RegisterNatives(cls, &native_method, 1) != 0) {
|
||||
jni->ExceptionClear();
|
||||
return Error("Could not load Instrument class");
|
||||
}
|
||||
|
||||
_instrument_class_loaded = true;
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error Instrument::start(Arguments& args) {
|
||||
Error error = check(args);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (args._interval < 0) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
|
||||
setupTargetClassAndMethod(args._event_desc);
|
||||
_interval = args._interval ? args._interval : 1;
|
||||
_calls = 0;
|
||||
_running = true;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
|
||||
retransformMatchedClasses(jvmti);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void Instrument::stop() {
|
||||
_running = false;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
retransformMatchedClasses(jvmti); // undo transformation
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
|
||||
}
|
||||
|
||||
void Instrument::setupTargetClassAndMethod(const char* event) {
|
||||
char* new_class = strdup(event);
|
||||
*strrchr(new_class, '.') = 0;
|
||||
|
||||
for (char* s = new_class; *s; s++) {
|
||||
if (*s == '.') *s = '/';
|
||||
}
|
||||
|
||||
char* old_class = _target_class;
|
||||
_target_class = new_class;
|
||||
free(old_class);
|
||||
}
|
||||
|
||||
void Instrument::retransformMatchedClasses(jvmtiEnv* jvmti) {
|
||||
jint class_count;
|
||||
jclass* classes;
|
||||
if (jvmti->GetLoadedClasses(&class_count, &classes) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
jint matched_count = 0;
|
||||
size_t len = strlen(_target_class);
|
||||
for (int i = 0; i < class_count; i++) {
|
||||
char* signature;
|
||||
if (jvmti->GetClassSignature(classes[i], &signature, NULL) == 0) {
|
||||
if (signature[0] == 'L' && strncmp(signature + 1, _target_class, len) == 0 && signature[len + 1] == ';') {
|
||||
classes[matched_count++] = classes[i];
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)signature);
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_count > 0) {
|
||||
jvmti->RetransformClasses(matched_count, classes);
|
||||
VM::jni()->ExceptionClear();
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)classes);
|
||||
}
|
||||
|
||||
void JNICALL Instrument::ClassFileLoadHook(jvmtiEnv* jvmti, JNIEnv* jni,
|
||||
jclass class_being_redefined, jobject loader,
|
||||
const char* name, jobject protection_domain,
|
||||
jint class_data_len, const u8* class_data,
|
||||
jint* new_class_data_len, u8** new_class_data) {
|
||||
// Do not retransform if the profiling has stopped
|
||||
if (!_running) return;
|
||||
|
||||
if (name == NULL || strcmp(name, _target_class) == 0) {
|
||||
BytecodeRewriter rewriter(class_data, class_data_len, _target_class);
|
||||
rewriter.rewrite(new_class_data, new_class_data_len);
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL Instrument::recordSample(JNIEnv* jni, jobject unused) {
|
||||
if (!_enabled) return;
|
||||
|
||||
if (_interval <= 1 || ((atomicInc(_calls) + 1) % _interval) == 0) {
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(NULL, _interval, BCI_INSTRUMENT, &event);
|
||||
}
|
||||
}
|
||||
62
src/instrument.h
Normal file
62
src/instrument.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2019 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _INSTRUMENT_H
|
||||
#define _INSTRUMENT_H
|
||||
|
||||
#include <jvmti.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class Instrument : public Engine {
|
||||
private:
|
||||
static char* _target_class;
|
||||
static bool _instrument_class_loaded;
|
||||
static u64 _interval;
|
||||
static volatile u64 _calls;
|
||||
static volatile bool _running;
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "instrument";
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "calls";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
void setupTargetClassAndMethod(const char* event);
|
||||
|
||||
void retransformMatchedClasses(jvmtiEnv* jvmti);
|
||||
|
||||
static void JNICALL ClassFileLoadHook(jvmtiEnv* jvmti, JNIEnv* jni,
|
||||
jclass class_being_redefined, jobject loader,
|
||||
const char* name, jobject protection_domain,
|
||||
jint class_data_len, const u8* class_data,
|
||||
jint* new_class_data_len, u8** new_class_data);
|
||||
|
||||
static void JNICALL recordSample(JNIEnv* jni, jobject unused);
|
||||
};
|
||||
|
||||
#endif // _INSTRUMENT_H
|
||||
@@ -24,7 +24,24 @@ long ITimer::_interval;
|
||||
|
||||
|
||||
void ITimer::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, NULL);
|
||||
if (!_enabled) return;
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
Error ITimer::check(Arguments& args) {
|
||||
OS::installSignalHandler(SIGPROF, NULL, SIG_IGN);
|
||||
|
||||
struct itimerval tv_on = {{1, 0}, {1, 0}};
|
||||
if (setitimer(ITIMER_PROF, &tv_on, NULL) != 0) {
|
||||
return Error("ITIMER_PROF is not supported on this system");
|
||||
}
|
||||
|
||||
struct itimerval tv_off = {{0, 0}, {0, 0}};
|
||||
setitimer(ITIMER_PROF, &tv_off, NULL);
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error ITimer::start(Arguments& args) {
|
||||
@@ -38,7 +55,10 @@ Error ITimer::start(Arguments& args) {
|
||||
long sec = _interval / 1000000000;
|
||||
long usec = (_interval % 1000000000) / 1000;
|
||||
struct itimerval tv = {{sec, usec}, {sec, usec}};
|
||||
setitimer(ITIMER_PROF, &tv, NULL);
|
||||
|
||||
if (setitimer(ITIMER_PROF, &tv, NULL) != 0) {
|
||||
return Error("ITIMER_PROF is not supported on this system");
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ class ITimer : public Engine {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
@@ -32,25 +32,14 @@
|
||||
#define MAX_PATH 1024
|
||||
#define TMP_PATH (MAX_PATH - 64)
|
||||
|
||||
static char temp_path_storage[TMP_PATH] = {0};
|
||||
static char tmp_path[TMP_PATH] = {0};
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
const char* get_temp_path() {
|
||||
return temp_path_storage;
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
// A process may have its own root path (when running in chroot environment)
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/root", pid);
|
||||
|
||||
// Append /tmp to the resolved root symlink
|
||||
ssize_t path_size = readlink(path, temp_path_storage, sizeof(temp_path_storage) - 10);
|
||||
strcpy(temp_path_storage + (path_size > 1 ? path_size : 0), "/tmp");
|
||||
|
||||
// Parse /proc/pid/status to find process credentials
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/proc/%d/status", pid);
|
||||
FILE* status_file = fopen(path, "r");
|
||||
if (status_file == NULL) {
|
||||
@@ -78,6 +67,17 @@ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
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];
|
||||
@@ -172,18 +172,6 @@ int alt_lookup_nspid(int pid) {
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
// macOS has a secure per-user temporary directory
|
||||
const char* get_temp_path() {
|
||||
if (temp_path_storage[0] == 0) {
|
||||
int path_size = confstr(_CS_DARWIN_USER_TEMP_DIR, temp_path_storage, sizeof(temp_path_storage));
|
||||
if (path_size == 0 || path_size > sizeof(temp_path_storage)) {
|
||||
strcpy(temp_path_storage, "/tmp");
|
||||
}
|
||||
}
|
||||
|
||||
return temp_path_storage;
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
@@ -199,6 +187,12 @@ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
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;
|
||||
@@ -214,10 +208,6 @@ int alt_lookup_nspid(int pid) {
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
const char* get_temp_path() {
|
||||
return "/tmp";
|
||||
}
|
||||
|
||||
int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
||||
struct kinfo_proc info;
|
||||
@@ -233,6 +223,11 @@ int get_process_info(int pid, uid_t* uid, gid_t* gid, int* nspid) {
|
||||
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;
|
||||
@@ -249,7 +244,7 @@ int alt_lookup_nspid(int pid) {
|
||||
// Check if remote JVM has already opened socket for Dynamic Attach
|
||||
static int check_socket(int pid) {
|
||||
char path[MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s/.java_pid%d", get_temp_path(), pid);
|
||||
snprintf(path, sizeof(path), "%s/.java_pid%d", tmp_path, pid);
|
||||
|
||||
struct stat stats;
|
||||
return stat(path, &stats) == 0 && S_ISSOCK(stats.st_mode);
|
||||
@@ -277,7 +272,7 @@ static int start_attach_mechanism(int pid, int nspid) {
|
||||
int fd = creat(path, 0660);
|
||||
if (fd == -1 || (close(fd) == 0 && !check_file_owner(path))) {
|
||||
// Failed to create attach trigger in current directory. Retry in /tmp
|
||||
snprintf(path, sizeof(path), "%s/.attach_pid%d", get_temp_path(), nspid);
|
||||
snprintf(path, sizeof(path), "%s/.attach_pid%d", tmp_path, nspid);
|
||||
fd = creat(path, 0660);
|
||||
if (fd == -1) {
|
||||
return 0;
|
||||
@@ -309,7 +304,7 @@ static int connect_socket(int pid) {
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
int bytes = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.java_pid%d", get_temp_path(), pid);
|
||||
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;
|
||||
}
|
||||
@@ -388,9 +383,18 @@ int main(int argc, char** argv) {
|
||||
nspid = alt_lookup_nspid(pid);
|
||||
}
|
||||
|
||||
// Make sure our /tmp and target /tmp is the same
|
||||
if (!enter_mount_ns(pid)) {
|
||||
printf("WARNING: couldn't enter target process mnt namespace\n");
|
||||
// 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.
|
||||
|
||||
129
src/javaApi.cpp
129
src/javaApi.cpp
@@ -18,28 +18,24 @@
|
||||
#include <sstream>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "javaApi.h"
|
||||
#include "arguments.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
static void throw_new(JNIEnv* env, const char* exception_class, const char* message) {
|
||||
jclass cls = env->FindClass(exception_class);
|
||||
if (cls != NULL) {
|
||||
env->ThrowNew(cls, message);
|
||||
}
|
||||
}
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval) {
|
||||
Java_one_profiler_AsyncProfiler_start0(JNIEnv* env, jobject unused, jstring event, jlong interval, jboolean reset) {
|
||||
Arguments args;
|
||||
args._event = env->GetStringUTFChars(event, NULL);
|
||||
const char* event_str = env->GetStringUTFChars(event, NULL);
|
||||
args.addEvent(event_str);
|
||||
args._interval = interval;
|
||||
Error error = Profiler::_instance.start(args);
|
||||
env->ReleaseStringUTFChars(event, args._event);
|
||||
Error error = Profiler::_instance.start(args, reset);
|
||||
env->ReleaseStringUTFChars(event, event_str);
|
||||
|
||||
if (error) {
|
||||
throw_new(env, "java/lang/IllegalStateException", error.message());
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +44,10 @@ Java_one_profiler_AsyncProfiler_stop0(JNIEnv* env, jobject unused) {
|
||||
Error error = Profiler::_instance.stop();
|
||||
|
||||
if (error) {
|
||||
throw_new(env, "java/lang/IllegalStateException", error.message());
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalStateException", error.message());
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
||||
return (jlong)Profiler::_instance.total_samples();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring command) {
|
||||
Arguments args;
|
||||
@@ -65,11 +56,11 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co
|
||||
env->ReleaseStringUTFChars(command, command_str);
|
||||
|
||||
if (error) {
|
||||
throw_new(env, "java/lang/IllegalArgumentException", error.message());
|
||||
JavaAPI::throwNew(env, "java/lang/IllegalArgumentException", error.message());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (args._file == NULL) {
|
||||
if (args._file == NULL || args._output == OUTPUT_JFR) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.runInternal(args, out);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
@@ -80,39 +71,87 @@ Java_one_profiler_AsyncProfiler_execute0(JNIEnv* env, jobject unused, jstring co
|
||||
out.close();
|
||||
return env->NewStringUTF("OK");
|
||||
} else {
|
||||
throw_new(env, "java/io/IOException", strerror(errno));
|
||||
JavaAPI::throwNew(env, "java/io/IOException", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_dumpCollapsed0(JNIEnv* env, jobject unused, jint counter) {
|
||||
Arguments args;
|
||||
args._counter = counter == COUNTER_SAMPLES ? COUNTER_SAMPLES : COUNTER_TOTAL;
|
||||
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.dumpCollapsed(out, args);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_one_profiler_AsyncProfiler_getSamples(JNIEnv* env, jobject unused) {
|
||||
return (jlong)Profiler::_instance.total_samples();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_dumpTraces0(JNIEnv* env, jobject unused, jint max_traces) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.dumpSummary(out);
|
||||
Profiler::_instance.dumpTraces(out, max_traces ? max_traces : MAX_CALLTRACES);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
extern "C" JNIEXPORT 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 {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
|
||||
if (enable) {
|
||||
thread_filter->add(thread_id);
|
||||
} else {
|
||||
thread_filter->remove(thread_id);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_dumpFlat0(JNIEnv* env, jobject unused, jint max_methods) {
|
||||
std::ostringstream out;
|
||||
Profiler::_instance.dumpSummary(out);
|
||||
Profiler::_instance.dumpFlat(out, max_methods ? max_methods : MAX_CALLTRACES);
|
||||
return env->NewStringUTF(out.str().c_str());
|
||||
|
||||
#define F(name, sig) {(char*)#name, (char*)sig, (void*)Java_one_profiler_AsyncProfiler_##name}
|
||||
|
||||
static const JNINativeMethod profiler_natives[] = {
|
||||
F(start0, "(Ljava/lang/String;JZ)V"),
|
||||
F(stop0, "()V"),
|
||||
F(execute0, "(Ljava/lang/String;)Ljava/lang/String;"),
|
||||
F(getSamples, "()J"),
|
||||
F(filterThread0, "(Ljava/lang/Thread;Z)V"),
|
||||
};
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_one_profiler_AsyncProfiler_version0(JNIEnv* env, jobject unused) {
|
||||
return env->NewStringUTF(PROFILER_VERSION);
|
||||
// Since AsyncProfiler class can be renamed or moved to another package (shaded),
|
||||
// we look for the actual class in the stack trace.
|
||||
void JavaAPI::registerNatives(jvmtiEnv* jvmti, JNIEnv* jni) {
|
||||
jvmtiFrameInfo frame[10];
|
||||
jint frame_count;
|
||||
if (jvmti->GetStackTrace(NULL, 0, sizeof(frame) / sizeof(frame[0]), frame, &frame_count) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
jclass System = jni->FindClass("java/lang/System");
|
||||
jmethodID load = jni->GetStaticMethodID(System, "load", "(Ljava/lang/String;)V");
|
||||
jmethodID loadLibrary = jni->GetStaticMethodID(System, "loadLibrary", "(Ljava/lang/String;)V");
|
||||
|
||||
// Look for System.load() or System.loadLibrary() method in the stack trace.
|
||||
// The next frame will belong to AsyncProfiler class.
|
||||
for (int i = 0; i < frame_count - 1; i++) {
|
||||
if (frame[i].method == load || frame[i].method == loadLibrary) {
|
||||
jclass profiler_class;
|
||||
if (jvmti->GetMethodDeclaringClass(frame[i + 1].method, &profiler_class) == 0) {
|
||||
for (int j = 0; j < sizeof(profiler_natives) / sizeof(JNINativeMethod); j++) {
|
||||
jni->RegisterNatives(profiler_class, &profiler_natives[j], 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jni->ExceptionClear();
|
||||
}
|
||||
|
||||
29
src/javaApi.h
Normal file
29
src/javaApi.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _JAVAAPI_H
|
||||
#define _JAVAAPI_H
|
||||
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
class JavaAPI {
|
||||
public:
|
||||
static void throwNew(JNIEnv* env, const char* exception_class, const char* message);
|
||||
static void registerNatives(jvmtiEnv* jvmti, JNIEnv* jni);
|
||||
};
|
||||
|
||||
#endif // _JAVAAPI_H
|
||||
213
src/jfrMetadata.cpp
Normal file
213
src/jfrMetadata.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "jfrMetadata.h"
|
||||
|
||||
|
||||
std::map<std::string, int> Element::_string_map;
|
||||
std::vector<std::string> Element::_strings;
|
||||
|
||||
JfrMetadata JfrMetadata::_root;
|
||||
|
||||
JfrMetadata::JfrMetadata() : Element("root") {
|
||||
*this
|
||||
<< (element("metadata")
|
||||
|
||||
<< type("boolean", T_BOOLEAN)
|
||||
<< type("char", T_CHAR)
|
||||
<< type("float", T_FLOAT)
|
||||
<< type("double", T_DOUBLE)
|
||||
<< type("byte", T_BYTE)
|
||||
<< type("short", T_SHORT)
|
||||
<< type("int", T_INT)
|
||||
<< type("long", T_LONG)
|
||||
|
||||
<< type("java.lang.String", T_STRING)
|
||||
|
||||
<< (type("java.lang.Class", T_CLASS, "Java Class")
|
||||
<< field("classLoader", T_CLASS_LOADER, "Class Loader", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL)
|
||||
<< field("package", T_PACKAGE, "Package", F_CPOOL)
|
||||
<< field("modifiers", T_INT, "Access Modifiers"))
|
||||
|
||||
<< (type("java.lang.Thread", T_THREAD, "Thread")
|
||||
<< field("osName", T_STRING, "OS Thread Name")
|
||||
<< field("osThreadId", T_LONG, "OS Thread Id")
|
||||
<< field("javaName", T_STRING, "Java Thread Name")
|
||||
<< field("javaThreadId", T_LONG, "Java Thread Id"))
|
||||
|
||||
<< (type("jdk.types.ClassLoader", T_CLASS_LOADER, "Java Class Loader")
|
||||
<< field("type", T_CLASS, "Type", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.FrameType", T_FRAME_TYPE, "Frame type", true)
|
||||
<< field("description", T_STRING, "Description"))
|
||||
|
||||
<< (type("jdk.types.ThreadState", T_THREAD_STATE, "Java Thread State", true)
|
||||
<< field("name", T_STRING, "Name"))
|
||||
|
||||
<< (type("jdk.types.StackTrace", T_STACK_TRACE, "Stacktrace")
|
||||
<< field("truncated", T_BOOLEAN, "Truncated")
|
||||
<< field("frames", T_STACK_FRAME, "Stack Frames", F_ARRAY))
|
||||
|
||||
<< (type("jdk.types.StackFrame", T_STACK_FRAME)
|
||||
<< field("method", T_METHOD, "Java Method", F_CPOOL)
|
||||
<< field("lineNumber", T_INT, "Line Number")
|
||||
<< field("bytecodeIndex", T_INT, "Bytecode Index")
|
||||
<< field("type", T_FRAME_TYPE, "Frame Type", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.Method", T_METHOD, "Java Method")
|
||||
<< field("type", T_CLASS, "Type", F_CPOOL)
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL)
|
||||
<< field("descriptor", T_SYMBOL, "Descriptor", F_CPOOL)
|
||||
<< field("modifiers", T_INT, "Access Modifiers")
|
||||
<< field("hidden", T_BOOLEAN, "Hidden"))
|
||||
|
||||
<< (type("jdk.types.Package", T_PACKAGE, "Package")
|
||||
<< field("name", T_SYMBOL, "Name", F_CPOOL))
|
||||
|
||||
<< (type("jdk.types.Symbol", T_SYMBOL, "Symbol", true)
|
||||
<< field("string", T_STRING, "String"))
|
||||
|
||||
<< (type("jdk.ExecutionSample", T_EXECUTION_SAMPLE, "Method Profiling Sample")
|
||||
<< category("Java Virtual Machine", "Profiling")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("sampledThread", T_THREAD, "Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("state", T_THREAD_STATE, "Thread State", F_CPOOL))
|
||||
|
||||
<< (type("jdk.ObjectAllocationInNewTLAB", T_ALLOC_IN_NEW_TLAB, "Allocation in new TLAB")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES)
|
||||
<< field("tlabSize", T_LONG, "TLAB Size", F_BYTES))
|
||||
|
||||
<< (type("jdk.ObjectAllocationOutsideTLAB", T_ALLOC_OUTSIDE_TLAB, "Allocation outside TLAB")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("objectClass", T_CLASS, "Object Class", F_CPOOL)
|
||||
<< field("allocationSize", T_LONG, "Allocation Size", F_BYTES))
|
||||
|
||||
<< (type("jdk.JavaMonitorEnter", T_MONITOR_ENTER, "Java Monitor Blocked")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("monitorClass", T_CLASS, "Monitor Class", F_CPOOL)
|
||||
<< field("address", T_LONG, "Monitor Address", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.ThreadPark", T_THREAD_PARK, "Java Thread Park")
|
||||
<< category("Java Application")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("stackTrace", T_STACK_TRACE, "Stack Trace", F_CPOOL)
|
||||
<< field("parkedClass", T_CLASS, "Class Parked On", F_CPOOL)
|
||||
<< field("timeout", T_LONG, "Park Timeout", F_DURATION_NANOS)
|
||||
<< field("address", T_LONG, "Address of Object Parked", F_ADDRESS))
|
||||
|
||||
<< (type("jdk.CPULoad", T_CPU_LOAD, "CPU Load")
|
||||
<< category("Operating System", "Processor")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("jvmUser", T_FLOAT, "JVM User", F_PERCENTAGE)
|
||||
<< field("jvmSystem", T_FLOAT, "JVM System", F_PERCENTAGE)
|
||||
<< field("machineTotal", T_FLOAT, "Machine Total", F_PERCENTAGE))
|
||||
|
||||
<< (type("jdk.ActiveRecording", T_ACTIVE_RECORDING, "Flight Recording")
|
||||
<< category("Flight Recorder")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("duration", T_LONG, "Duration", F_DURATION_TICKS)
|
||||
<< field("eventThread", T_THREAD, "Event Thread", F_CPOOL)
|
||||
<< field("id", T_LONG, "Id")
|
||||
<< field("name", T_STRING, "Name")
|
||||
<< field("destination", T_STRING, "Destination")
|
||||
<< field("maxAge", T_LONG, "Max Age", F_DURATION_MILLIS)
|
||||
<< field("maxSize", T_LONG, "Max Size", F_BYTES)
|
||||
<< field("recordingStart", T_LONG, "Start Time", F_TIME_MILLIS)
|
||||
<< field("recordingDuration", T_LONG, "Recording Duration", F_DURATION_MILLIS))
|
||||
|
||||
<< (type("jdk.ActiveSetting", T_ACTIVE_SETTING, "Recording 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("id", T_LONG, "Event Id")
|
||||
<< field("name", T_STRING, "Setting Name")
|
||||
<< field("value", T_STRING, "Setting Value"))
|
||||
|
||||
<< (type("jdk.OSInformation", T_OS_INFORMATION, "OS Information")
|
||||
<< category("Operating System")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("osVersion", T_STRING, "OS Version"))
|
||||
|
||||
<< (type("jdk.CPUInformation", T_CPU_INFORMATION, "CPU Information")
|
||||
<< category("Operating System", "Processor")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("cpu", T_STRING, "Type")
|
||||
<< field("description", T_STRING, "Description")
|
||||
<< field("sockets", T_INT, "Sockets", F_UNSIGNED)
|
||||
<< field("cores", T_INT, "Cores", F_UNSIGNED)
|
||||
<< field("hwThreads", T_INT, "Hardware Threads", F_UNSIGNED))
|
||||
|
||||
<< (type("jdk.JVMInformation", T_JVM_INFORMATION, "JVM Information")
|
||||
<< category("Java Virtual Machine")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("jvmName", T_STRING, "JVM Name")
|
||||
<< field("jvmVersion", T_STRING, "JVM Version")
|
||||
<< field("jvmArguments", T_STRING, "JVM Command Line Arguments")
|
||||
<< field("jvmFlags", T_STRING, "JVM Settings File Arguments")
|
||||
<< field("javaArguments", T_STRING, "Java Application Arguments")
|
||||
<< field("jvmStartTime", T_LONG, "JVM Start Time", F_TIME_MILLIS)
|
||||
<< field("pid", T_LONG, "Process Identifier"))
|
||||
|
||||
<< (type("jdk.InitialSystemProperty", T_INITIAL_SYSTEM_PROPERTY, "Initial System Property")
|
||||
<< category("Java Virtual Machine")
|
||||
<< field("startTime", T_LONG, "Start Time", F_TIME_TICKS)
|
||||
<< field("key", T_STRING, "Key")
|
||||
<< field("value", T_STRING, "Value"))
|
||||
|
||||
<< (type("jdk.jfr.Label", T_LABEL, NULL)
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.Category", T_CATEGORY, NULL)
|
||||
<< field("value", T_STRING, NULL, F_ARRAY))
|
||||
|
||||
<< (type("jdk.jfr.Timestamp", T_TIMESTAMP, "Timestamp")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.Timespan", T_TIMESPAN, "Timespan")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< (type("jdk.jfr.DataAmount", T_DATA_AMOUNT, "Data Amount")
|
||||
<< field("value", T_STRING))
|
||||
|
||||
<< type("jdk.jfr.MemoryAddress", T_MEMORY_ADDRESS, "Memory Address")
|
||||
|
||||
<< type("jdk.jfr.Unsigned", T_UNSIGNED, "Unsigned Value")
|
||||
|
||||
<< type("jdk.jfr.Percentage", T_PERCENTAGE, "Percentage"))
|
||||
|
||||
<< element("region").attribute("locale", "en_US").attribute("gmtOffset", "0");
|
||||
|
||||
// The map is used only during construction
|
||||
_string_map.clear();
|
||||
}
|
||||
231
src/jfrMetadata.h
Normal file
231
src/jfrMetadata.h
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _JFRMETADATA_H
|
||||
#define _JFRMETADATA_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
enum JfrType {
|
||||
T_METADATA = 0,
|
||||
T_CPOOL = 1,
|
||||
|
||||
T_BOOLEAN = 4,
|
||||
T_CHAR = 5,
|
||||
T_FLOAT = 6,
|
||||
T_DOUBLE = 7,
|
||||
T_BYTE = 8,
|
||||
T_SHORT = 9,
|
||||
T_INT = 10,
|
||||
T_LONG = 11,
|
||||
|
||||
T_STRING = 20,
|
||||
T_CLASS = 21,
|
||||
T_THREAD = 22,
|
||||
T_CLASS_LOADER = 23,
|
||||
T_FRAME_TYPE = 24,
|
||||
T_THREAD_STATE = 25,
|
||||
T_STACK_TRACE = 26,
|
||||
T_STACK_FRAME = 27,
|
||||
T_METHOD = 28,
|
||||
T_PACKAGE = 29,
|
||||
T_SYMBOL = 30,
|
||||
|
||||
T_EVENT = 100,
|
||||
T_EXECUTION_SAMPLE = 101,
|
||||
T_ALLOC_IN_NEW_TLAB = 102,
|
||||
T_ALLOC_OUTSIDE_TLAB = 103,
|
||||
T_MONITOR_ENTER = 104,
|
||||
T_THREAD_PARK = 105,
|
||||
T_CPU_LOAD = 106,
|
||||
T_ACTIVE_RECORDING = 107,
|
||||
T_ACTIVE_SETTING = 108,
|
||||
T_OS_INFORMATION = 109,
|
||||
T_CPU_INFORMATION = 110,
|
||||
T_JVM_INFORMATION = 111,
|
||||
T_INITIAL_SYSTEM_PROPERTY = 112,
|
||||
|
||||
T_ANNOTATION = 200,
|
||||
T_LABEL = 201,
|
||||
T_CATEGORY = 202,
|
||||
T_TIMESTAMP = 203,
|
||||
T_TIMESPAN = 204,
|
||||
T_DATA_AMOUNT = 205,
|
||||
T_MEMORY_ADDRESS = 206,
|
||||
T_UNSIGNED = 207,
|
||||
T_PERCENTAGE = 208,
|
||||
};
|
||||
|
||||
|
||||
class Attribute {
|
||||
public:
|
||||
int _key;
|
||||
int _value;
|
||||
|
||||
Attribute(int key, int value) : _key(key), _value(value) {
|
||||
}
|
||||
};
|
||||
|
||||
class Element {
|
||||
protected:
|
||||
static std::map<std::string, int> _string_map;
|
||||
static std::vector<std::string> _strings;
|
||||
|
||||
static int getId(const char* s) {
|
||||
std::string str(s);
|
||||
int id = _string_map[str];
|
||||
if (id == 0) {
|
||||
id = _string_map[str] = _string_map.size();
|
||||
_strings.push_back(str);
|
||||
}
|
||||
return id - 1;
|
||||
}
|
||||
|
||||
public:
|
||||
const int _name;
|
||||
std::vector<Attribute> _attributes;
|
||||
std::vector<const Element*> _children;
|
||||
|
||||
Element(const char* name) : _name(getId(name)), _attributes(), _children() {
|
||||
}
|
||||
|
||||
Element& attribute(const char* key, const char* value) {
|
||||
_attributes.push_back(Attribute(getId(key), getId(value)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Element& attribute(const char* key, JfrType value) {
|
||||
char value_str[16];
|
||||
sprintf(value_str, "%d", value);
|
||||
return attribute(key, value_str);
|
||||
}
|
||||
|
||||
Element& operator<<(const Element& child) {
|
||||
_children.push_back(&child);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class JfrMetadata : Element {
|
||||
private:
|
||||
static JfrMetadata _root;
|
||||
|
||||
enum FieldFlags {
|
||||
F_CPOOL = 0x1,
|
||||
F_ARRAY = 0x2,
|
||||
F_UNSIGNED = 0x4,
|
||||
F_BYTES = 0x8,
|
||||
F_TIME_TICKS = 0x10,
|
||||
F_TIME_MILLIS = 0x20,
|
||||
F_DURATION_TICKS = 0x40,
|
||||
F_DURATION_NANOS = 0x80,
|
||||
F_DURATION_MILLIS = 0x100,
|
||||
F_ADDRESS = 0x200,
|
||||
F_PERCENTAGE = 0x400,
|
||||
};
|
||||
|
||||
static Element& element(const char* name) {
|
||||
return *new Element(name);
|
||||
}
|
||||
|
||||
static Element& type(const char* name, JfrType id, const char* label = NULL, bool simple = false) {
|
||||
Element& e = element("class");
|
||||
e.attribute("name", name);
|
||||
e.attribute("id", id);
|
||||
if (simple) {
|
||||
e.attribute("simpleType", "true");
|
||||
} else if (id > T_ANNOTATION) {
|
||||
e.attribute("superType", "java.lang.annotation.Annotation");
|
||||
} else if (id > T_EVENT) {
|
||||
e.attribute("superType", "jdk.jfr.Event");
|
||||
}
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& field(const char* name, JfrType type, const char* label = NULL, int flags = 0) {
|
||||
Element& e = element("field");
|
||||
e.attribute("name", name);
|
||||
e.attribute("class", type);
|
||||
if (flags & F_CPOOL) {
|
||||
e.attribute("constantPool", "true");
|
||||
}
|
||||
if (flags & F_ARRAY) {
|
||||
e.attribute("dimension", "1");
|
||||
}
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
if (flags & F_UNSIGNED) {
|
||||
e << annotation(T_UNSIGNED);
|
||||
} else if (flags & F_BYTES) {
|
||||
e << annotation(T_UNSIGNED) << annotation(T_DATA_AMOUNT, "BYTES");
|
||||
} else if (flags & F_TIME_TICKS) {
|
||||
e << annotation(T_TIMESTAMP, "TICKS");
|
||||
} else if (flags & F_TIME_MILLIS) {
|
||||
e << annotation(T_TIMESTAMP, "MILLISECONDS_SINCE_EPOCH");
|
||||
} else if (flags & F_DURATION_TICKS) {
|
||||
e << annotation(T_TIMESPAN, "TICKS");
|
||||
} else if (flags & F_DURATION_NANOS) {
|
||||
e << annotation(T_TIMESPAN, "NANOSECONDS");
|
||||
} else if (flags & F_DURATION_MILLIS) {
|
||||
e << annotation(T_TIMESPAN, "MILLISECONDS");
|
||||
} else if (flags & F_ADDRESS) {
|
||||
e << annotation(T_UNSIGNED) << annotation(T_MEMORY_ADDRESS);
|
||||
} else if (flags & F_PERCENTAGE) {
|
||||
e << annotation(T_PERCENTAGE);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& annotation(JfrType type, const char* value = NULL) {
|
||||
Element& e = element("annotation");
|
||||
e.attribute("class", type);
|
||||
if (value != NULL) {
|
||||
e.attribute("value", value);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Element& category(const char* value0, const char* value1 = NULL) {
|
||||
Element& e = annotation(T_CATEGORY);
|
||||
e.attribute("value-0", value0);
|
||||
if (value1 != NULL) {
|
||||
e.attribute("value-1", value1);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
public:
|
||||
JfrMetadata();
|
||||
|
||||
static Element* root() {
|
||||
return &_root;
|
||||
}
|
||||
|
||||
static std::vector<std::string>& strings() {
|
||||
return _strings;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _JFRMETADATA_H
|
||||
71
src/jstack.cpp
Normal file
71
src/jstack.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include "jstack.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
// Wait at most this number of milliseconds to finish processing of pending signals
|
||||
const int MAX_WAIT_MILLIS = 2000;
|
||||
|
||||
|
||||
void JStack::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = Profiler::_instance.getThreadState(ucontext);
|
||||
Profiler::_instance.recordSample(ucontext, 1, 0, &event);
|
||||
}
|
||||
|
||||
Error JStack::start(Arguments& args) {
|
||||
OS::installSignalHandler(SIGVTALRM, signalHandler);
|
||||
|
||||
int self = OS::threadId();
|
||||
u64 required_samples = Profiler::_instance.total_samples();
|
||||
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
|
||||
bool thread_filter_enabled = thread_filter->enabled();
|
||||
|
||||
ThreadList* thread_list = OS::listThreads();
|
||||
int thread_id;
|
||||
while ((thread_id = thread_list->next()) != -1) {
|
||||
if (thread_id != self && (!thread_filter_enabled || thread_filter->accept(thread_id))) {
|
||||
if (OS::sendSignalToThread(thread_id, SIGVTALRM)) {
|
||||
required_samples++;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete thread_list;
|
||||
|
||||
// Get our own stack trace after all other threads
|
||||
if (!thread_filter_enabled || thread_filter->accept(self)) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(NULL, 1, 0, &event);
|
||||
required_samples++;
|
||||
}
|
||||
|
||||
// Wait until all asynchronous stack traces collected
|
||||
for (int i = 0; Profiler::_instance.total_samples() < required_samples && i < MAX_WAIT_MILLIS; i++) {
|
||||
struct timespec timeout = {0, 1000000};
|
||||
nanosleep(&timeout, NULL);
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void JStack::stop() {
|
||||
// Nothing to do
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2018 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,40 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _TRACER_H
|
||||
#define _TRACER_H
|
||||
#ifndef _JSTACK_H
|
||||
#define _JSTACK_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class Tracer {
|
||||
class JStack : public Engine {
|
||||
private:
|
||||
FILE* _out;
|
||||
int _pipefd[2];
|
||||
pthread_t _thread;
|
||||
volatile int _tracer_tid;
|
||||
bool _simple;
|
||||
bool _annotate;
|
||||
bool _running;
|
||||
|
||||
void tracerLoop();
|
||||
|
||||
static void* threadEntry(void* tracer) {
|
||||
((Tracer*)tracer)->tracerLoop();
|
||||
return NULL;
|
||||
}
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
Tracer() : _running(false) {
|
||||
const char* name() {
|
||||
return EVENT_JSTACK;
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "samples";
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
void recordExecutionSample(int tid, int call_trace_id, u64 counter);
|
||||
};
|
||||
|
||||
#endif // _TRACER_H
|
||||
#endif // _JSTACK_H
|
||||
106
src/linearAllocator.cpp
Normal file
106
src/linearAllocator.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "linearAllocator.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
LinearAllocator::LinearAllocator(size_t chunk_size) {
|
||||
_chunk_size = chunk_size;
|
||||
_reserve = _tail = allocateChunk(NULL);
|
||||
}
|
||||
|
||||
LinearAllocator::~LinearAllocator() {
|
||||
clear();
|
||||
freeChunk(_tail);
|
||||
}
|
||||
|
||||
void LinearAllocator::clear() {
|
||||
if (_reserve->prev == _tail) {
|
||||
freeChunk(_reserve);
|
||||
}
|
||||
while (_tail->prev != NULL) {
|
||||
Chunk* current = _tail;
|
||||
_tail = _tail->prev;
|
||||
freeChunk(current);
|
||||
}
|
||||
_reserve = _tail;
|
||||
_tail->offs = sizeof(Chunk);
|
||||
}
|
||||
|
||||
void* LinearAllocator::alloc(size_t size) {
|
||||
Chunk* chunk = _tail;
|
||||
|
||||
do {
|
||||
// Fast path: bump a pointer with CAS
|
||||
for (size_t offs = chunk->offs; offs + size <= _chunk_size; offs = chunk->offs) {
|
||||
if (__sync_bool_compare_and_swap(&chunk->offs, offs, offs + size)) {
|
||||
if (_chunk_size / 2 - offs < size) {
|
||||
// Stepped over a middle of the chunk - it's time to prepare a new one
|
||||
reserveChunk(chunk);
|
||||
}
|
||||
return (char*)chunk + offs;
|
||||
}
|
||||
}
|
||||
} while ((chunk = getNextChunk(chunk)) != NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::allocateChunk(Chunk* current) {
|
||||
Chunk* chunk = (Chunk*)OS::safeAlloc(_chunk_size);
|
||||
if (chunk != NULL) {
|
||||
chunk->prev = current;
|
||||
chunk->offs = sizeof(Chunk);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void LinearAllocator::freeChunk(Chunk* current) {
|
||||
OS::safeFree(current, _chunk_size);
|
||||
}
|
||||
|
||||
void LinearAllocator::reserveChunk(Chunk* current) {
|
||||
Chunk* reserve = allocateChunk(current);
|
||||
if (reserve != NULL && !__sync_bool_compare_and_swap(&_reserve, current, reserve)) {
|
||||
// Unlikely case that we are too late
|
||||
freeChunk(reserve);
|
||||
}
|
||||
}
|
||||
|
||||
Chunk* LinearAllocator::getNextChunk(Chunk* current) {
|
||||
Chunk* reserve = _reserve;
|
||||
|
||||
if (reserve == current) {
|
||||
// Unlikely case: no reserve yet.
|
||||
// It's probably being allocated right now, so let's compete
|
||||
reserve = allocateChunk(current);
|
||||
if (reserve == NULL) {
|
||||
// Not enough memory
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Chunk* prev_reserve = __sync_val_compare_and_swap(&_reserve, current, reserve);
|
||||
if (prev_reserve != current) {
|
||||
freeChunk(reserve);
|
||||
reserve = prev_reserve;
|
||||
}
|
||||
}
|
||||
|
||||
// Expected case: a new chunk is already reserved
|
||||
Chunk* tail = __sync_val_compare_and_swap(&_tail, current, reserve);
|
||||
return tail == current ? reserve : tail;
|
||||
}
|
||||
50
src/linearAllocator.h
Normal file
50
src/linearAllocator.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _LINEARALLOCATOR_H
|
||||
#define _LINEARALLOCATOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
struct Chunk {
|
||||
Chunk* prev;
|
||||
volatile size_t offs;
|
||||
// To avoid false sharing
|
||||
char _padding[56];
|
||||
};
|
||||
|
||||
class LinearAllocator {
|
||||
private:
|
||||
size_t _chunk_size;
|
||||
Chunk* _tail;
|
||||
Chunk* _reserve;
|
||||
|
||||
Chunk* allocateChunk(Chunk* current);
|
||||
void freeChunk(Chunk* current);
|
||||
void reserveChunk(Chunk* current);
|
||||
Chunk* getNextChunk(Chunk* current);
|
||||
|
||||
public:
|
||||
LinearAllocator(size_t chunk_size);
|
||||
~LinearAllocator();
|
||||
|
||||
void clear();
|
||||
|
||||
void* alloc(size_t size);
|
||||
};
|
||||
|
||||
#endif // _LINEARALLOCATOR_H
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include "lockTracer.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
@@ -23,20 +24,13 @@
|
||||
jlong LockTracer::_start_time = 0;
|
||||
jclass LockTracer::_LockSupport = NULL;
|
||||
jmethodID LockTracer::_getBlocker = NULL;
|
||||
UnsafeParkFunc LockTracer::_original_Unsafe_Park = NULL;
|
||||
bool LockTracer::_supports_lock_names = false;
|
||||
|
||||
Error LockTracer::start(Arguments& args) {
|
||||
// PermGen in JDK 7 makes difficult to get symbol name from jclass.
|
||||
// Also some JVMs do not support VMStructs at all.
|
||||
// Let's just record stack traces without lock names in these cases.
|
||||
_supports_lock_names = VMStructs::available() && !VMStructs::hasPermGen();
|
||||
|
||||
// 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);
|
||||
jvmti->GetTime(&_start_time);
|
||||
_start_time = OS::nanotime();
|
||||
|
||||
if (_getBlocker == NULL) {
|
||||
JNIEnv* env = VM::jni();
|
||||
@@ -44,13 +38,8 @@ Error LockTracer::start(Arguments& args) {
|
||||
_getBlocker = env->GetStaticMethodID(_LockSupport, "getBlocker", "(Ljava/lang/Thread;)Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
if (_original_Unsafe_Park == NULL) {
|
||||
NativeCodeCache* libjvm = Profiler::_instance.jvmLibrary();
|
||||
_original_Unsafe_Park = (UnsafeParkFunc)libjvm->findSymbol("Unsafe_Park");
|
||||
}
|
||||
|
||||
// Intercent Unsafe.park() for tracing contended ReentrantLocks
|
||||
if (_original_Unsafe_Park != NULL) {
|
||||
if (VMStructs::_unsafe_park != NULL) {
|
||||
bindUnsafePark(UnsafeParkTrap);
|
||||
}
|
||||
|
||||
@@ -64,81 +53,94 @@ void LockTracer::stop() {
|
||||
jvmti->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, NULL);
|
||||
|
||||
// Reset Unsafe.park() trap
|
||||
if (_original_Unsafe_Park != NULL) {
|
||||
bindUnsafePark(_original_Unsafe_Park);
|
||||
if (VMStructs::_unsafe_park != NULL) {
|
||||
bindUnsafePark(VMStructs::_unsafe_park);
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::MonitorContendedEnter(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
||||
jlong enter_time;
|
||||
jvmti->GetTime(&enter_time);
|
||||
jlong enter_time = OS::nanotime();
|
||||
jvmti->SetTag(thread, enter_time);
|
||||
}
|
||||
|
||||
void JNICALL LockTracer::MonitorContendedEntered(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object) {
|
||||
jlong enter_time, entered_time;
|
||||
jvmti->GetTime(&entered_time);
|
||||
jlong entered_time = OS::nanotime();
|
||||
jlong enter_time;
|
||||
jvmti->GetTag(thread, &enter_time);
|
||||
|
||||
// Time is meaningless if lock attempt has started before profiling
|
||||
if (enter_time >= _start_time) {
|
||||
recordContendedLock(env->GetObjectClass(object), entered_time - enter_time);
|
||||
if (_enabled && 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) {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jclass lock_class = getParkBlockerClass(jvmti, env);
|
||||
jobject park_blocker = _enabled ? getParkBlocker(jvmti, env) : NULL;
|
||||
jlong park_start_time, park_end_time;
|
||||
|
||||
if (lock_class != NULL) {
|
||||
jvmti->GetTime(&park_start_time);
|
||||
if (park_blocker != NULL) {
|
||||
park_start_time = OS::nanotime();
|
||||
}
|
||||
|
||||
_original_Unsafe_Park(env, instance, isAbsolute, time);
|
||||
|
||||
if (lock_class != NULL) {
|
||||
jvmti->GetTime(&park_end_time);
|
||||
recordContendedLock(lock_class, park_end_time - park_start_time);
|
||||
VMStructs::_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);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
jclass LockTracer::getParkBlockerClass(jvmtiEnv* jvmti, JNIEnv* env) {
|
||||
jobject LockTracer::getParkBlocker(jvmtiEnv* jvmti, JNIEnv* env) {
|
||||
jthread thread;
|
||||
if (jvmti->GetCurrentThread(&thread) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Call LockSupport.getBlocker(Thread.currentThread())
|
||||
jobject park_blocker = env->CallStaticObjectMethod(_LockSupport, _getBlocker, thread);
|
||||
if (park_blocker == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jclass lock_class = env->GetObjectClass(park_blocker);
|
||||
char* class_name;
|
||||
if (jvmti->GetClassSignature(lock_class, &class_name, NULL) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Do not count synchronizers other than ReentrantLock, ReentrantReadWriteLock and Semaphore
|
||||
if (strncmp(class_name, "Ljava/util/concurrent/locks/ReentrantLock", 41) != 0 &&
|
||||
strncmp(class_name, "Ljava/util/concurrent/locks/ReentrantReadWriteLock", 50) != 0 &&
|
||||
strncmp(class_name, "Ljava/util/concurrent/Semaphore", 31) != 0) {
|
||||
lock_class = NULL;
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)class_name);
|
||||
return lock_class;
|
||||
return env->CallStaticObjectMethod(_LockSupport, _getBlocker, thread);
|
||||
}
|
||||
|
||||
void LockTracer::recordContendedLock(jclass lock_class, jlong time) {
|
||||
if (_supports_lock_names) {
|
||||
VMSymbol* lock_name = (*(java_lang_Class**)lock_class)->klass()->name();
|
||||
Profiler::_instance.recordSample(NULL, time, BCI_SYMBOL, (jmethodID)lock_name);
|
||||
} else {
|
||||
Profiler::_instance.recordSample(NULL, time, BCI_SYMBOL, NULL);
|
||||
char* LockTracer::getLockName(jvmtiEnv* jvmti, JNIEnv* env, jobject lock) {
|
||||
char* class_name;
|
||||
if (jvmti->GetClassSignature(env->GetObjectClass(lock), &class_name, NULL) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
return class_name;
|
||||
}
|
||||
|
||||
bool LockTracer::isConcurrentLock(const char* lock_name) {
|
||||
// Do not count synchronizers other than ReentrantLock, ReentrantReadWriteLock and Semaphore
|
||||
return strncmp(lock_name, "Ljava/util/concurrent/locks/ReentrantLock", 41) == 0 ||
|
||||
strncmp(lock_name, "Ljava/util/concurrent/locks/ReentrantReadWriteLock", 50) == 0 ||
|
||||
strncmp(lock_name, "Ljava/util/concurrent/Semaphore", 31) == 0;
|
||||
}
|
||||
|
||||
void LockTracer::recordContendedLock(int event_type, u64 start_time, u64 end_time,
|
||||
const char* lock_name, jobject lock, jlong timeout) {
|
||||
LockEvent event;
|
||||
event._class_id = 0;
|
||||
event._start_time = start_time;
|
||||
event._end_time = end_time;
|
||||
event._address = *(uintptr_t*)lock;
|
||||
event._timeout = timeout;
|
||||
|
||||
if (lock_name != NULL) {
|
||||
if (lock_name[0] == 'L') {
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(lock_name + 1, strlen(lock_name) - 2);
|
||||
} else {
|
||||
event._class_id = Profiler::_instance.classMap()->lookup(lock_name);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(NULL, end_time - start_time, event_type, &event);
|
||||
}
|
||||
|
||||
void LockTracer::bindUnsafePark(UnsafeParkFunc entry) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define _LOCKTRACER_H
|
||||
|
||||
#include <jvmti.h>
|
||||
#include "arch.h"
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
@@ -28,11 +29,12 @@ class LockTracer : public Engine {
|
||||
static jlong _start_time;
|
||||
static jclass _LockSupport;
|
||||
static jmethodID _getBlocker;
|
||||
static UnsafeParkFunc _original_Unsafe_Park;
|
||||
static bool _supports_lock_names;
|
||||
|
||||
static jclass getParkBlockerClass(jvmtiEnv* jvmti, JNIEnv* env);
|
||||
static void recordContendedLock(jclass lock_class, jlong time);
|
||||
static jobject getParkBlocker(jvmtiEnv* jvmti, JNIEnv* env);
|
||||
static char* getLockName(jvmtiEnv* jvmti, JNIEnv* env, jobject lock);
|
||||
static bool isConcurrentLock(const char* lock_name);
|
||||
static void recordContendedLock(int event_type, u64 start_time, u64 end_time,
|
||||
const char* lock_name, jobject lock, jlong timeout);
|
||||
static void bindUnsafePark(UnsafeParkFunc entry);
|
||||
|
||||
public:
|
||||
@@ -44,6 +46,10 @@ class LockTracer : public Engine {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
|
||||
45
src/os.h
45
src/os.h
@@ -18,26 +18,65 @@
|
||||
#define _OS_H
|
||||
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
enum ThreadState {
|
||||
THREAD_INVALID,
|
||||
THREAD_RUNNING,
|
||||
THREAD_SLEEPING
|
||||
};
|
||||
|
||||
|
||||
class Timer {
|
||||
};
|
||||
|
||||
|
||||
class ThreadList {
|
||||
public:
|
||||
virtual ~ThreadList() {}
|
||||
virtual void rewind() = 0;
|
||||
virtual int next() = 0;
|
||||
virtual int size() = 0;
|
||||
};
|
||||
|
||||
|
||||
class OS {
|
||||
private:
|
||||
typedef void (*SigAction)(int, siginfo_t*, void*);
|
||||
typedef void (*SigHandler)(int);
|
||||
typedef void (*TimerCallback)(void*);
|
||||
|
||||
public:
|
||||
static u64 nanotime();
|
||||
static u64 millis();
|
||||
static u64 processStartTime();
|
||||
|
||||
static u64 hton64(u64 x);
|
||||
static u64 ntoh64(u64 x);
|
||||
|
||||
static int getMaxThreadId();
|
||||
static int processId();
|
||||
static int threadId();
|
||||
static bool isThreadRunning(int thread_id);
|
||||
static void installSignalHandler(int signo, void (*handler)(int, siginfo_t*, void*));
|
||||
static void sendSignalToThread(int thread_id, int signo);
|
||||
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 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);
|
||||
};
|
||||
|
||||
#endif // _OS_H
|
||||
|
||||
240
src/os_linux.cpp
240
src/os_linux.cpp
@@ -23,22 +23,54 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/times.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
#ifdef __LP64__
|
||||
# define MMAP_SYSCALL __NR_mmap
|
||||
#else
|
||||
# define MMAP_SYSCALL __NR_mmap2
|
||||
#endif
|
||||
|
||||
|
||||
class LinuxThreadList : public ThreadList {
|
||||
private:
|
||||
DIR* _dir;
|
||||
int _thread_count;
|
||||
|
||||
int getThreadCount() {
|
||||
char buf[512];
|
||||
int fd = open("/proc/self/stat", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thread_count = 0;
|
||||
if (read(fd, buf, sizeof(buf)) > 0) {
|
||||
char* s = strchr(buf, ')');
|
||||
if (s != NULL) {
|
||||
// Read 18th integer field after the command name
|
||||
for (int field = 0; *s != ' ' || ++field < 18; s++) ;
|
||||
thread_count = atoi(s + 1);
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return thread_count;
|
||||
}
|
||||
|
||||
public:
|
||||
LinuxThreadList() {
|
||||
_dir = opendir("/proc/self/task");
|
||||
_thread_count = -1;
|
||||
}
|
||||
|
||||
~LinuxThreadList() {
|
||||
@@ -47,6 +79,13 @@ class LinuxThreadList : public ThreadList {
|
||||
}
|
||||
}
|
||||
|
||||
void rewind() {
|
||||
if (_dir != NULL) {
|
||||
rewinddir(_dir);
|
||||
}
|
||||
_thread_count = -1;
|
||||
}
|
||||
|
||||
int next() {
|
||||
if (_dir != NULL) {
|
||||
struct dirent* entry;
|
||||
@@ -58,6 +97,13 @@ class LinuxThreadList : public ThreadList {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int size() {
|
||||
if (_thread_count < 0) {
|
||||
_thread_count = getThreadCount();
|
||||
}
|
||||
return _thread_count;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -73,49 +119,201 @@ u64 OS::millis() {
|
||||
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
}
|
||||
|
||||
u64 OS::processStartTime() {
|
||||
static u64 start_time = 0;
|
||||
|
||||
if (start_time == 0) {
|
||||
char buf[64];
|
||||
sprintf(buf, "/proc/%d", processId());
|
||||
|
||||
struct stat st;
|
||||
if (stat(buf, &st) == 0) {
|
||||
start_time = (u64)st.st_mtim.tv_sec * 1000 + st.st_mtim.tv_nsec / 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
return start_time;
|
||||
}
|
||||
|
||||
u64 OS::hton64(u64 x) {
|
||||
return htonl(1) == 1 ? x : bswap_64(x);
|
||||
}
|
||||
|
||||
u64 OS::ntoh64(u64 x) {
|
||||
return ntohl(1) == 1 ? x : bswap_64(x);
|
||||
}
|
||||
|
||||
int OS::getMaxThreadId() {
|
||||
char buf[16] = "65536";
|
||||
int fd = open("/proc/sys/kernel/pid_max", O_RDONLY);
|
||||
if (fd != -1) {
|
||||
ssize_t r = read(fd, buf, sizeof(buf) - 1);
|
||||
(void) r;
|
||||
close(fd);
|
||||
}
|
||||
return atoi(buf);
|
||||
}
|
||||
|
||||
int OS::processId() {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
return self_pid;
|
||||
}
|
||||
|
||||
int OS::threadId() {
|
||||
return syscall(__NR_gettid);
|
||||
}
|
||||
|
||||
bool OS::isThreadRunning(int thread_id) {
|
||||
char buf[512];
|
||||
sprintf(buf, "/proc/self/task/%d/stat", thread_id);
|
||||
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);
|
||||
int fd = open(buf, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool running = false;
|
||||
ssize_t r = read(fd, name_buf, name_len);
|
||||
close(fd);
|
||||
|
||||
if (r > 0) {
|
||||
name_buf[r - 1] = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ThreadState OS::threadState(int thread_id) {
|
||||
char buf[512];
|
||||
sprintf(buf, "/proc/self/task/%d/stat", thread_id);
|
||||
int fd = open(buf, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return THREAD_INVALID;
|
||||
}
|
||||
|
||||
ThreadState state = THREAD_INVALID;
|
||||
if (read(fd, buf, sizeof(buf)) > 0) {
|
||||
char* s = strchr(buf, ')');
|
||||
running = s != NULL && (s[2] == 'R' || s[2] == 'D');
|
||||
state = s != NULL && (s[2] == 'R' || s[2] == 'D') ? THREAD_RUNNING : THREAD_SLEEPING;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return running;
|
||||
}
|
||||
|
||||
void OS::installSignalHandler(int signo, void (*handler)(int, siginfo_t*, void*)) {
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_sigaction = handler;
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
|
||||
sigaction(signo, &sa, NULL);
|
||||
}
|
||||
|
||||
void OS::sendSignalToThread(int thread_id, int signo) {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
syscall(__NR_tgkill, self_pid, thread_id, signo);
|
||||
return state;
|
||||
}
|
||||
|
||||
ThreadList* OS::listThreads() {
|
||||
return new LinuxThreadList();
|
||||
}
|
||||
|
||||
bool OS::isJavaLibraryVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
if (handler != NULL) {
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_flags = 0;
|
||||
} else {
|
||||
sa.sa_sigaction = action;
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
}
|
||||
|
||||
sigaction(signo, &sa, NULL);
|
||||
}
|
||||
|
||||
bool OS::sendSignalToThread(int thread_id, int signo) {
|
||||
return syscall(__NR_tgkill, processId(), thread_id, signo) == 0;
|
||||
}
|
||||
|
||||
void* OS::safeAlloc(size_t size) {
|
||||
// Naked syscall can be used inside a signal handler.
|
||||
// Also, we don't want to catch our own calls when profiling mmap.
|
||||
intptr_t result = syscall(MMAP_SYSCALL, NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (result < 0 && result > -4096) {
|
||||
return NULL;
|
||||
}
|
||||
return (void*)result;
|
||||
}
|
||||
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t r = read(fd, buf, size);
|
||||
close(fd);
|
||||
if (r <= 0) {
|
||||
return false;
|
||||
}
|
||||
buf[r < size ? r : size - 1] = 0;
|
||||
|
||||
char* c;
|
||||
do {
|
||||
c = strchr(buf, '\n');
|
||||
} while (c != NULL && *(buf = c + 1) != '\n');
|
||||
|
||||
*buf = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 OS::getProcessCpuTime(u64* utime, u64* stime) {
|
||||
struct tms buf;
|
||||
clock_t real = times(&buf);
|
||||
*utime = buf.tms_utime;
|
||||
*stime = buf.tms_stime;
|
||||
return real;
|
||||
}
|
||||
|
||||
u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
int fd = open("/proc/stat", O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return (u64)-1;
|
||||
}
|
||||
|
||||
u64 real = (u64)-1;
|
||||
char buf[512];
|
||||
if (read(fd, buf, sizeof(buf)) >= 12) {
|
||||
u64 user, nice, system, idle;
|
||||
if (sscanf(buf + 4, "%llu %llu %llu %llu", &user, &nice, &system, &idle) == 4) {
|
||||
*utime = user + nice;
|
||||
*stime = system;
|
||||
real = user + nice + system + idle;
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return real;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
203
src/os_macos.cpp
203
src/os_macos.cpp
@@ -16,36 +16,68 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_interface.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach/processor_info.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/times.h>
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class MacThreadList : public ThreadList {
|
||||
private:
|
||||
task_t _task;
|
||||
thread_array_t _thread_array;
|
||||
unsigned int _thread_count;
|
||||
unsigned int _thread_index;
|
||||
|
||||
void ensureThreadArray() {
|
||||
if (_thread_array == NULL) {
|
||||
_thread_count = 0;
|
||||
_thread_index = 0;
|
||||
task_threads(_task, &_thread_array, &_thread_count);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
MacThreadList() : _thread_array(NULL), _thread_count(0), _thread_index(0) {
|
||||
task_threads(mach_task_self(), &_thread_array, &_thread_count);
|
||||
MacThreadList() {
|
||||
_task = mach_task_self();
|
||||
_thread_array = NULL;
|
||||
}
|
||||
|
||||
~MacThreadList() {
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)_thread_array, sizeof(thread_t) * _thread_count);
|
||||
rewind();
|
||||
}
|
||||
|
||||
void rewind() {
|
||||
if (_thread_array != NULL) {
|
||||
for (int i = 0; i < _thread_count; i++) {
|
||||
mach_port_deallocate(_task, _thread_array[i]);
|
||||
}
|
||||
vm_deallocate(_task, (vm_address_t)_thread_array, _thread_count * sizeof(thread_t));
|
||||
_thread_array = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int next() {
|
||||
ensureThreadArray();
|
||||
if (_thread_index < _thread_count) {
|
||||
return (int)_thread_array[_thread_index++];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int size() {
|
||||
ensureThreadArray();
|
||||
return _thread_count;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -64,38 +96,163 @@ u64 OS::millis() {
|
||||
return (u64)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
}
|
||||
|
||||
u64 OS::processStartTime() {
|
||||
static u64 start_time = 0;
|
||||
|
||||
if (start_time == 0) {
|
||||
struct proc_bsdinfo info;
|
||||
if (proc_pidinfo(processId(), PROC_PIDTBSDINFO, 0, &info, sizeof(info)) > 0) {
|
||||
start_time = (u64)info.pbi_start_tvsec * 1000 + info.pbi_start_tvusec / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
return start_time;
|
||||
}
|
||||
|
||||
u64 OS::hton64(u64 x) {
|
||||
return OSSwapHostToBigInt64(x);
|
||||
}
|
||||
|
||||
int OS::threadId() {
|
||||
return pthread_mach_thread_np(pthread_self());
|
||||
u64 OS::ntoh64(u64 x) {
|
||||
return OSSwapBigToHostInt64(x);
|
||||
}
|
||||
|
||||
bool OS::isThreadRunning(int thread_id) {
|
||||
int OS::getMaxThreadId() {
|
||||
return 0x7fffffff;
|
||||
}
|
||||
|
||||
int OS::processId() {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
return self_pid;
|
||||
}
|
||||
|
||||
int OS::threadId() {
|
||||
// Used to be pthread_mach_thread_np(pthread_self()),
|
||||
// but pthread_mach_thread_np is not async signal safe
|
||||
mach_port_t port = mach_thread_self();
|
||||
mach_port_deallocate(mach_task_self(), port);
|
||||
return (int)port;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ThreadState OS::threadState(int thread_id) {
|
||||
struct thread_basic_info info;
|
||||
mach_msg_type_number_t size = sizeof(info);
|
||||
if (thread_info((thread_act_t)thread_id, THREAD_BASIC_INFO, (thread_info_t)&info, &size) != 0) {
|
||||
return false;
|
||||
return THREAD_INVALID;
|
||||
}
|
||||
return info.run_state == TH_STATE_RUNNING;
|
||||
}
|
||||
|
||||
void OS::installSignalHandler(int signo, void (*handler)(int, siginfo_t*, void*)) {
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_sigaction = handler;
|
||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
|
||||
sigaction(signo, &sa, NULL);
|
||||
}
|
||||
|
||||
void OS::sendSignalToThread(int thread_id, int signo) {
|
||||
asm volatile("syscall" : : "a"(0x2000148), "D"(thread_id), "S"(signo));
|
||||
return info.run_state == TH_STATE_RUNNING ? THREAD_RUNNING : THREAD_SLEEPING;
|
||||
}
|
||||
|
||||
ThreadList* OS::listThreads() {
|
||||
return new MacThreadList();
|
||||
}
|
||||
|
||||
bool OS::isJavaLibraryVisible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
if (handler != NULL) {
|
||||
sa.sa_handler = handler;
|
||||
sa.sa_flags = 0;
|
||||
} else {
|
||||
sa.sa_sigaction = action;
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
}
|
||||
|
||||
sigaction(signo, &sa, NULL);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void* OS::safeAlloc(size_t size) {
|
||||
// mmap() is not guaranteed to be async signal safe, but in practice, it is.
|
||||
// There is no a reasonable alternative anyway.
|
||||
void* result = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (result == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
u64 OS::getProcessCpuTime(u64* utime, u64* stime) {
|
||||
struct tms buf;
|
||||
clock_t real = times(&buf);
|
||||
*utime = buf.tms_utime;
|
||||
*stime = buf.tms_stime;
|
||||
return real;
|
||||
}
|
||||
|
||||
u64 OS::getTotalCpuTime(u64* utime, u64* stime) {
|
||||
natural_t cpu_count;
|
||||
processor_info_array_t cpu_info_array;
|
||||
mach_msg_type_number_t cpu_info_count;
|
||||
|
||||
host_name_port_t host = mach_host_self();
|
||||
kern_return_t ret = host_processor_info(host, PROCESSOR_CPU_LOAD_INFO, &cpu_count, &cpu_info_array, &cpu_info_count);
|
||||
mach_port_deallocate(mach_task_self(), host);
|
||||
if (ret != 0) {
|
||||
return (u64)-1;
|
||||
}
|
||||
|
||||
processor_cpu_load_info_data_t* cpu_load = (processor_cpu_load_info_data_t*)cpu_info_array;
|
||||
u64 user = 0;
|
||||
u64 system = 0;
|
||||
u64 idle = 0;
|
||||
for (natural_t i = 0; i < cpu_count; i++) {
|
||||
user += cpu_load[i].cpu_ticks[CPU_STATE_USER] + cpu_load[i].cpu_ticks[CPU_STATE_NICE];
|
||||
system += cpu_load[i].cpu_ticks[CPU_STATE_SYSTEM];
|
||||
idle += cpu_load[i].cpu_ticks[CPU_STATE_IDLE];
|
||||
}
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)cpu_info_array, cpu_info_count * sizeof(int));
|
||||
|
||||
*utime = user;
|
||||
*stime = system;
|
||||
return user + system + idle;
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
@@ -31,10 +31,9 @@ class PerfEvents : public Engine {
|
||||
static PerfEventType* _event_type;
|
||||
static long _interval;
|
||||
static Ring _ring;
|
||||
static CStack _cstack;
|
||||
static bool _print_extended_warning;
|
||||
|
||||
static bool createForThread(int tid);
|
||||
static void destroyForThread(int tid);
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
@@ -44,17 +43,18 @@ class PerfEvents : public Engine {
|
||||
|
||||
const char* units();
|
||||
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
|
||||
void onThreadStart();
|
||||
void onThreadEnd();
|
||||
|
||||
int getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address);
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs);
|
||||
|
||||
static bool supported();
|
||||
static const char* getEventName(int event_id);
|
||||
|
||||
static bool createForThread(int tid);
|
||||
static void destroyForThread(int tid);
|
||||
};
|
||||
|
||||
#endif // _PERFEVENTS_H
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <errno.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"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "profiler.h"
|
||||
#include "spinLock.h"
|
||||
#include "stackFrame.h"
|
||||
#include "symbols.h"
|
||||
|
||||
|
||||
// Ancient fcntl.h does not define F_SETOWN_EX constants and structures
|
||||
@@ -59,17 +61,6 @@ enum {
|
||||
|
||||
static const unsigned long PERF_PAGE_SIZE = sysconf(_SC_PAGESIZE);
|
||||
|
||||
static int getMaxPID() {
|
||||
char buf[16] = "65536";
|
||||
int fd = open("/proc/sys/kernel/pid_max", O_RDONLY);
|
||||
if (fd != -1) {
|
||||
ssize_t r = read(fd, buf, sizeof(buf) - 1);
|
||||
(void) r;
|
||||
close(fd);
|
||||
}
|
||||
return atoi(buf);
|
||||
}
|
||||
|
||||
// Get perf_event_attr.config numeric value of the given tracepoint name
|
||||
// by reading /sys/kernel/debug/tracing/events/<name>/id file
|
||||
static int findTracepointId(const char* name) {
|
||||
@@ -137,7 +128,7 @@ struct PerfEventType {
|
||||
|
||||
// Parse access type [:rwx]
|
||||
char* c = strrchr(buf, ':');
|
||||
if (c != NULL) {
|
||||
if (c != NULL && c != name && c[-1] != ':') {
|
||||
*c++ = 0;
|
||||
if (strcmp(c, "r") == 0) {
|
||||
bp_type = HW_BREAKPOINT_R;
|
||||
@@ -173,13 +164,14 @@ struct PerfEventType {
|
||||
} else {
|
||||
addr = (__u64)(uintptr_t)dlsym(RTLD_DEFAULT, buf);
|
||||
if (addr == 0) {
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.findSymbol(buf);
|
||||
}
|
||||
if (addr == 0) {
|
||||
return NULL;
|
||||
addr = (__u64)(uintptr_t)Profiler::_instance.resolveSymbol(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PerfEventType* breakpoint = findByType(PERF_TYPE_BREAKPOINT);
|
||||
breakpoint->config = addr + offset;
|
||||
breakpoint->bp_type = bp_type;
|
||||
@@ -214,7 +206,8 @@ struct PerfEventType {
|
||||
}
|
||||
|
||||
// Kernel tracepoints defined in debugfs
|
||||
if (strchr(name, ':') != NULL) {
|
||||
const char* c = strchr(name, ':');
|
||||
if (c != NULL && c[1] != ':') {
|
||||
int tracepoint_id = findTracepointId(name);
|
||||
if (tracepoint_id > 0) {
|
||||
return getTracepoint(tracepoint_id);
|
||||
@@ -285,6 +278,11 @@ class RingBuffer {
|
||||
_offset = (_offset + sizeof(u64)) & (PERF_PAGE_SIZE - 1);
|
||||
return *(u64*)(_start + _offset);
|
||||
}
|
||||
|
||||
u64 peek(unsigned long words) {
|
||||
unsigned long peek_offset = (_offset + words * sizeof(u64)) & (PERF_PAGE_SIZE - 1);
|
||||
return *(u64*)(_start + peek_offset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -302,6 +300,7 @@ PerfEvent* PerfEvents::_events = NULL;
|
||||
PerfEventType* PerfEvents::_event_type = NULL;
|
||||
long PerfEvents::_interval;
|
||||
Ring PerfEvents::_ring;
|
||||
CStack PerfEvents::_cstack;
|
||||
bool PerfEvents::_print_extended_warning;
|
||||
|
||||
bool PerfEvents::createForThread(int tid) {
|
||||
@@ -336,7 +335,6 @@ bool PerfEvents::createForThread(int tid) {
|
||||
attr.sample_type = PERF_SAMPLE_CALLCHAIN;
|
||||
attr.disabled = 1;
|
||||
attr.wakeup_events = 1;
|
||||
attr.exclude_idle = 1;
|
||||
|
||||
if (_ring == RING_USER) {
|
||||
attr.exclude_kernel = 1;
|
||||
@@ -344,18 +342,35 @@ bool PerfEvents::createForThread(int tid) {
|
||||
attr.exclude_user = 1;
|
||||
}
|
||||
|
||||
#ifdef PERF_ATTR_SIZE_VER5
|
||||
if (_cstack == CSTACK_LBR) {
|
||||
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK | PERF_SAMPLE_REGS_USER;
|
||||
attr.branch_sample_type = PERF_SAMPLE_BRANCH_USER | PERF_SAMPLE_BRANCH_CALL_STACK;
|
||||
attr.sample_regs_user = 1ULL << PERF_REG_PC;
|
||||
attr.exclude_callchain_user = 1;
|
||||
}
|
||||
#else
|
||||
#warning "Compiling without LBR support. Kernel headers 4.1+ required"
|
||||
#endif
|
||||
|
||||
int 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");
|
||||
fprintf(stderr, "Try with --all-user option, or 'echo 1 > /proc/sys/kernel/perf_event_paranoid'\n");
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (page == MAP_FAILED) {
|
||||
perror("perf_event mmap failed");
|
||||
@@ -363,7 +378,6 @@ bool PerfEvents::createForThread(int tid) {
|
||||
}
|
||||
|
||||
_events[tid].reset();
|
||||
_events[tid]._fd = fd;
|
||||
_events[tid]._page = (struct perf_event_mmap_page*)page;
|
||||
|
||||
struct f_owner_ex ex;
|
||||
@@ -386,10 +400,10 @@ void PerfEvents::destroyForThread(int tid) {
|
||||
}
|
||||
|
||||
PerfEvent* event = &_events[tid];
|
||||
if (event->_fd != 0) {
|
||||
ioctl(event->_fd, PERF_EVENT_IOC_DISABLE, 0);
|
||||
close(event->_fd);
|
||||
event->_fd = 0;
|
||||
int fd = event->_fd;
|
||||
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();
|
||||
@@ -405,19 +419,23 @@ void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionEvent event;
|
||||
Profiler::_instance.recordSample(ucontext, counter, 0, &event);
|
||||
}
|
||||
|
||||
Profiler::_instance.recordSample(ucontext, counter, 0, NULL);
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_RESET, 0);
|
||||
ioctl(siginfo->si_fd, PERF_EVENT_IOC_REFRESH, 1);
|
||||
}
|
||||
@@ -433,8 +451,57 @@ const char* PerfEvents::units() {
|
||||
return dash != NULL ? dash + 1 : _event_type->name;
|
||||
}
|
||||
|
||||
Error PerfEvents::check(Arguments& args) {
|
||||
PerfEventType* event_type = PerfEventType::forName(args._event_desc);
|
||||
if (event_type == NULL) {
|
||||
return Error("Unsupported event type");
|
||||
}
|
||||
|
||||
struct perf_event_attr attr = {0};
|
||||
attr.size = sizeof(attr);
|
||||
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;
|
||||
} else {
|
||||
attr.config = event_type->config;
|
||||
}
|
||||
|
||||
attr.sample_period = event_type->default_interval;
|
||||
attr.sample_type = PERF_SAMPLE_CALLCHAIN;
|
||||
attr.disabled = 1;
|
||||
|
||||
if (args._ring == RING_USER) {
|
||||
attr.exclude_kernel = 1;
|
||||
} else if (args._ring == RING_KERNEL) {
|
||||
attr.exclude_user = 1;
|
||||
} else if (!Symbols::haveKernelSymbols()) {
|
||||
Profiler::_instance.updateSymbols(true);
|
||||
attr.exclude_kernel = Symbols::haveKernelSymbols() ? 0 : 1;
|
||||
}
|
||||
|
||||
#ifdef PERF_ATTR_SIZE_VER5
|
||||
if (args._cstack == CSTACK_LBR) {
|
||||
attr.sample_type |= PERF_SAMPLE_BRANCH_STACK | PERF_SAMPLE_REGS_USER;
|
||||
attr.branch_sample_type = PERF_SAMPLE_BRANCH_USER | PERF_SAMPLE_BRANCH_CALL_STACK;
|
||||
attr.sample_regs_user = 1ULL << PERF_REG_PC;
|
||||
attr.exclude_callchain_user = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
|
||||
if (fd == -1) {
|
||||
return Error(strerror(errno));
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PerfEvents::start(Arguments& args) {
|
||||
_event_type = PerfEventType::forName(args._event);
|
||||
_event_type = PerfEventType::forName(args._event_desc);
|
||||
if (_event_type == NULL) {
|
||||
return Error("Unsupported event type");
|
||||
}
|
||||
@@ -445,15 +512,22 @@ Error PerfEvents::start(Arguments& args) {
|
||||
_interval = args._interval ? args._interval : _event_type->default_interval;
|
||||
|
||||
_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");
|
||||
_ring = RING_USER;
|
||||
}
|
||||
_cstack = args._cstack;
|
||||
_print_extended_warning = _ring != RING_USER;
|
||||
|
||||
int max_events = getMaxPID();
|
||||
int max_events = OS::getMaxThreadId();
|
||||
if (max_events != _max_events) {
|
||||
free(_events);
|
||||
_events = (PerfEvent*)calloc(max_events, sizeof(PerfEvent));
|
||||
_max_events = max_events;
|
||||
}
|
||||
|
||||
|
||||
OS::installSignalHandler(SIGPROF, signalHandler);
|
||||
|
||||
// Enable thread events before traversing currently running threads
|
||||
@@ -480,16 +554,8 @@ void PerfEvents::stop() {
|
||||
}
|
||||
}
|
||||
|
||||
void PerfEvents::onThreadStart() {
|
||||
createForThread(OS::threadId());
|
||||
}
|
||||
|
||||
void PerfEvents::onThreadEnd() {
|
||||
destroyForThread(OS::threadId());
|
||||
}
|
||||
|
||||
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address) {
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs) {
|
||||
PerfEvent* event = &_events[tid];
|
||||
if (!event->tryLock()) {
|
||||
return 0; // the event is being destroyed
|
||||
@@ -509,22 +575,51 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
struct perf_event_header* hdr = ring.seek(tail);
|
||||
if (hdr->type == PERF_RECORD_SAMPLE) {
|
||||
u64 nr = ring.next();
|
||||
while (nr-- > 0 && depth < max_depth) {
|
||||
while (nr-- > 0) {
|
||||
u64 ip = ring.next();
|
||||
if (ip < PERF_CONTEXT_MAX) {
|
||||
const void* iptr = (const void*)ip;
|
||||
if (iptr >= jit_min_address && iptr < jit_max_address) {
|
||||
if (java_methods->contains(iptr) || runtime_stubs->contains(iptr) || depth >= max_depth) {
|
||||
// Stop at the first Java frame
|
||||
break;
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = iptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cstack == CSTACK_LBR) {
|
||||
u64 bnr = ring.next();
|
||||
|
||||
// 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) {
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = pc;
|
||||
|
||||
while (bnr-- > 0) {
|
||||
const void* from = (const void*)ring.next();
|
||||
const void* to = (const void*)ring.next();
|
||||
ring.next();
|
||||
|
||||
if (java_methods->contains(to) || runtime_stubs->contains(to) || depth >= max_depth) {
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = to;
|
||||
|
||||
if (java_methods->contains(from) || runtime_stubs->contains(from) || depth >= max_depth) {
|
||||
goto stack_complete;
|
||||
}
|
||||
callchain[depth++] = from;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
tail += hdr->size;
|
||||
}
|
||||
|
||||
stack_complete:
|
||||
page->data_tail = head;
|
||||
}
|
||||
|
||||
@@ -533,7 +628,10 @@ int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain,
|
||||
}
|
||||
|
||||
bool PerfEvents::supported() {
|
||||
return true;
|
||||
// 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
|
||||
struct stat statbuf;
|
||||
return stat("/proc/sys/kernel/perf_event_paranoid", &statbuf) == 0;
|
||||
}
|
||||
|
||||
const char* PerfEvents::getEventName(int event_id) {
|
||||
|
||||
@@ -27,14 +27,17 @@ Ring PerfEvents::_ring;
|
||||
bool PerfEvents::_print_extended_warning;
|
||||
|
||||
|
||||
bool PerfEvents::createForThread(int tid) { return false; }
|
||||
void PerfEvents::destroyForThread(int tid) {}
|
||||
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {}
|
||||
void PerfEvents::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
}
|
||||
|
||||
const char* PerfEvents::units() {
|
||||
return "ns";
|
||||
}
|
||||
|
||||
Error PerfEvents::check(Arguments& args) {
|
||||
return Error("PerfEvents are unsupported on macOS");
|
||||
}
|
||||
|
||||
Error PerfEvents::start(Arguments& args) {
|
||||
return Error("PerfEvents are unsupported on macOS");
|
||||
}
|
||||
@@ -42,14 +45,8 @@ Error PerfEvents::start(Arguments& args) {
|
||||
void PerfEvents::stop() {
|
||||
}
|
||||
|
||||
void PerfEvents::onThreadStart() {
|
||||
}
|
||||
|
||||
void PerfEvents::onThreadEnd() {
|
||||
}
|
||||
|
||||
int PerfEvents::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
const void* jit_min_address, const void* jit_max_address) {
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -61,4 +58,11 @@ const char* PerfEvents::getEventName(int event_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PerfEvents::createForThread(int tid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void PerfEvents::destroyForThread(int tid) {
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
1236
src/profiler.cpp
1236
src/profiler.cpp
File diff suppressed because it is too large
Load Diff
198
src/profiler.h
198
src/profiler.h
@@ -22,60 +22,44 @@
|
||||
#include <time.h>
|
||||
#include "arch.h"
|
||||
#include "arguments.h"
|
||||
#include "callTraceStorage.h"
|
||||
#include "codeCache.h"
|
||||
#include "dictionary.h"
|
||||
#include "engine.h"
|
||||
#include "event.h"
|
||||
#include "flightRecorder.h"
|
||||
#include "mutex.h"
|
||||
#include "spinLock.h"
|
||||
#include "codeCache.h"
|
||||
#include "tracer.h"
|
||||
#include "threadFilter.h"
|
||||
#include "trap.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
const char FULL_VERSION_STRING[] =
|
||||
"Async-profiler " PROFILER_VERSION " built on " __DATE__ "\n"
|
||||
"Copyright 2018 Andrei Pangin\n";
|
||||
"Copyright 2016-2021 Andrei Pangin\n";
|
||||
|
||||
const int MAX_CALLTRACES = 65536;
|
||||
const int MAX_STACK_FRAMES = 2048;
|
||||
const int MAX_NATIVE_FRAMES = 128;
|
||||
const int RESERVED_FRAMES = 4;
|
||||
const int MAX_NATIVE_LIBS = 2048;
|
||||
const int CONCURRENCY_LEVEL = 16;
|
||||
|
||||
|
||||
static inline int cmp64(u64 a, u64 b) {
|
||||
return a > b ? 1 : a == b ? 0 : -1;
|
||||
}
|
||||
enum AddressType {
|
||||
ADDR_UNKNOWN,
|
||||
ADDR_JIT,
|
||||
ADDR_STUB,
|
||||
ADDR_NATIVE
|
||||
};
|
||||
|
||||
|
||||
union CallTraceBuffer {
|
||||
ASGCT_CallFrame _asgct_frames[MAX_STACK_FRAMES];
|
||||
jvmtiFrameInfo _jvmti_frames[MAX_STACK_FRAMES];
|
||||
ASGCT_CallFrame _asgct_frames[1];
|
||||
jvmtiFrameInfo _jvmti_frames[1];
|
||||
};
|
||||
|
||||
|
||||
class CallTraceSample {
|
||||
public:
|
||||
u64 _samples;
|
||||
u64 _counter;
|
||||
int _start_frame; // Offset in frame buffer
|
||||
int _num_frames;
|
||||
|
||||
static int comparator(const void* s1, const void* s2) {
|
||||
return cmp64(((CallTraceSample*)s2)->_counter, ((CallTraceSample*)s1)->_counter);
|
||||
}
|
||||
};
|
||||
|
||||
class MethodSample {
|
||||
public:
|
||||
u64 _samples;
|
||||
u64 _counter;
|
||||
ASGCT_CallFrame _method;
|
||||
|
||||
static int comparator(const void* s1, const void* s2) {
|
||||
return cmp64(((MethodSample*)s2)->_counter, ((MethodSample*)s1)->_counter);
|
||||
}
|
||||
};
|
||||
|
||||
class FrameName;
|
||||
|
||||
enum State {
|
||||
IDLE,
|
||||
@@ -85,123 +69,136 @@ enum State {
|
||||
|
||||
class Profiler {
|
||||
private:
|
||||
|
||||
// See hotspot/src/share/vm/prims/forte.cpp
|
||||
enum {
|
||||
ticks_no_Java_frame = 0,
|
||||
ticks_no_class_load = -1,
|
||||
ticks_GC_active = -2,
|
||||
ticks_unknown_not_Java = -3,
|
||||
ticks_not_walkable_not_Java = -4,
|
||||
ticks_unknown_Java = -5,
|
||||
ticks_not_walkable_Java = -6,
|
||||
ticks_unknown_state = -7,
|
||||
ticks_thread_exit = -8,
|
||||
ticks_deopt = -9,
|
||||
ticks_safepoint = -10,
|
||||
ticks_skipped = -11,
|
||||
FAILURE_TYPES = 12
|
||||
};
|
||||
|
||||
Mutex _state_lock;
|
||||
State _state;
|
||||
Trap _begin_trap;
|
||||
Trap _end_trap;
|
||||
Mutex _thread_names_lock;
|
||||
// TODO: single map?
|
||||
std::map<int, std::string> _thread_names;
|
||||
Tracer _tracer;
|
||||
std::map<int, jlong> _thread_ids;
|
||||
Dictionary _class_map;
|
||||
Dictionary _symbol_map;
|
||||
ThreadFilter _thread_filter;
|
||||
CallTraceStorage _call_trace_storage;
|
||||
FlightRecorder _jfr;
|
||||
Engine* _engine;
|
||||
int _events;
|
||||
time_t _start_time;
|
||||
|
||||
u64 _total_samples;
|
||||
u64 _total_counter;
|
||||
u64 _failures[FAILURE_TYPES];
|
||||
u64 _hashes[MAX_CALLTRACES];
|
||||
CallTraceSample _traces[MAX_CALLTRACES];
|
||||
MethodSample _methods[MAX_CALLTRACES];
|
||||
u64 _failures[ASGCT_FAILURE_TYPES];
|
||||
|
||||
SpinLock _locks[CONCURRENCY_LEVEL];
|
||||
CallTraceBuffer _calltrace_buffer[CONCURRENCY_LEVEL];
|
||||
ASGCT_CallFrame* _frame_buffer;
|
||||
int _jstackdepth;
|
||||
int _frame_buffer_size;
|
||||
volatile int _frame_buffer_index;
|
||||
bool _frame_buffer_overflow;
|
||||
bool _threads;
|
||||
CallTraceBuffer* _calltrace_buffer[CONCURRENCY_LEVEL];
|
||||
int _max_stack_depth;
|
||||
int _safe_mode;
|
||||
CStack _cstack;
|
||||
bool _add_thread_frame;
|
||||
bool _update_thread_names;
|
||||
volatile bool _thread_events_state;
|
||||
|
||||
SpinLock _jit_lock;
|
||||
const void* _jit_min_address;
|
||||
const void* _jit_max_address;
|
||||
SpinLock _stubs_lock;
|
||||
CodeCache _java_methods;
|
||||
NativeCodeCache _runtime_stubs;
|
||||
NativeCodeCache* _native_libs[MAX_NATIVE_LIBS];
|
||||
int _native_lib_count;
|
||||
volatile int _native_lib_count;
|
||||
|
||||
void* (*_ThreadLocalStorage_thread)();
|
||||
jvmtiError (*_JvmtiEnv_GetStackTrace)(void* self, void* thread, jint start_depth, jint max_frame_count,
|
||||
jvmtiFrameInfo* frame_buffer, jint* count_ptr);
|
||||
// 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);
|
||||
|
||||
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 updateJitRange(const void* min_address, const void* max_address);
|
||||
|
||||
const char* findNativeMethod(const void* address);
|
||||
void onThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
void onThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
|
||||
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, jmethodID event);
|
||||
int makeEventFrame(ASGCT_CallFrame* frames, jint event_type, uintptr_t id);
|
||||
bool fillTopFrame(const void* pc, ASGCT_CallFrame* frame);
|
||||
bool addressInCode(const void* pc);
|
||||
u64 hashCallTrace(int num_frames, ASGCT_CallFrame* frames);
|
||||
int storeCallTrace(int num_frames, ASGCT_CallFrame* frames, u64 counter);
|
||||
void copyToFrameBuffer(int num_frames, ASGCT_CallFrame* frames, CallTraceSample* trace);
|
||||
u64 hashMethod(jmethodID method);
|
||||
void storeMethod(jmethodID method, jint bci, u64 counter);
|
||||
void resetSymbols();
|
||||
void initJvmtiFunctions(NativeCodeCache* libjvm);
|
||||
void setThreadName(int tid, const char* name);
|
||||
AddressType getAddressType(instruction_t* pc);
|
||||
void setThreadInfo(int tid, const char* name, jlong java_thread_id);
|
||||
void updateThreadName(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
void updateAllThreadNames();
|
||||
void updateJavaThreadNames();
|
||||
void updateNativeThreadNames();
|
||||
bool excludeTrace(FrameName* fn, CallTrace* trace);
|
||||
void mangle(const char* name, char* buf, size_t size);
|
||||
Engine* selectEngine(const char* event_name);
|
||||
Error checkJvmCapabilities();
|
||||
|
||||
public:
|
||||
static Profiler _instance;
|
||||
|
||||
Profiler() :
|
||||
_state(IDLE),
|
||||
_tracer(),
|
||||
_begin_trap(),
|
||||
_end_trap(),
|
||||
_thread_filter(),
|
||||
_call_trace_storage(),
|
||||
_jfr(),
|
||||
_frame_buffer(NULL),
|
||||
_start_time(0),
|
||||
_max_stack_depth(0),
|
||||
_safe_mode(0),
|
||||
_thread_events_state(JVMTI_DISABLE),
|
||||
_jit_lock(),
|
||||
_jit_min_address((const void*)-1),
|
||||
_jit_max_address((const void*)0),
|
||||
_stubs_lock(),
|
||||
_java_methods(),
|
||||
_runtime_stubs("[stubs]"),
|
||||
_native_lib_count(0),
|
||||
_ThreadLocalStorage_thread(NULL),
|
||||
_JvmtiEnv_GetStackTrace(NULL) {
|
||||
_original_NativeLibrary_load(NULL) {
|
||||
|
||||
for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
|
||||
_calltrace_buffer[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
u64 total_samples() { return _total_samples; }
|
||||
u64 total_counter() { return _total_counter; }
|
||||
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);
|
||||
void shutdown(Arguments& args);
|
||||
Error start(Arguments& args);
|
||||
Error check(Arguments& args);
|
||||
Error start(Arguments& args, bool reset);
|
||||
Error stop();
|
||||
void switchThreadEvents(jvmtiEventMode mode);
|
||||
void dumpSummary(std::ostream& out);
|
||||
void dump(std::ostream& out, Arguments& args);
|
||||
void dumpCollapsed(std::ostream& out, Arguments& args);
|
||||
void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree);
|
||||
void dumpTraces(std::ostream& out, int max_traces);
|
||||
void dumpFlat(std::ostream& out, int max_methods);
|
||||
void recordSample(void* ucontext, u64 counter, jint event_type, jmethodID event);
|
||||
NativeCodeCache* jvmLibrary();
|
||||
const void* findSymbol(const char* name);
|
||||
void dumpFlat(std::ostream& out, Arguments& args);
|
||||
ThreadState getThreadState(void* ucontext);
|
||||
void recordSample(void* ucontext, u64 counter, jint event_type, Event* event);
|
||||
|
||||
void updateSymbols(bool kernel_symbols);
|
||||
const void* resolveSymbol(const char* name);
|
||||
NativeCodeCache* findNativeLibrary(const void* address);
|
||||
const char* findNativeMethod(const void* address);
|
||||
|
||||
// CompiledMethodLoad is also needed to enable DebugNonSafepoints info by default
|
||||
static void JNICALL CompiledMethodLoad(jvmtiEnv* jvmti, jmethodID method,
|
||||
@@ -222,17 +219,14 @@ class Profiler {
|
||||
}
|
||||
|
||||
static void JNICALL ThreadStart(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
|
||||
_instance.updateThreadName(jvmti, jni, thread);
|
||||
_instance._engine->onThreadStart();
|
||||
_instance.onThreadStart(jvmti, jni, thread);
|
||||
}
|
||||
|
||||
static void JNICALL ThreadEnd(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
|
||||
_instance.updateThreadName(jvmti, jni, thread);
|
||||
_instance._engine->onThreadEnd();
|
||||
_instance.onThreadEnd(jvmti, jni, thread);
|
||||
}
|
||||
|
||||
friend class Recording;
|
||||
friend class Tracer;
|
||||
};
|
||||
|
||||
#endif // _PROFILER_H
|
||||
|
||||
@@ -30,7 +30,7 @@ class SpinLock {
|
||||
volatile int _lock;
|
||||
|
||||
public:
|
||||
SpinLock() : _lock(0) {
|
||||
SpinLock(int initial_state = 0) : _lock(initial_state) {
|
||||
}
|
||||
|
||||
void reset() {
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ucontext.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
class StackFrame {
|
||||
private:
|
||||
ucontext_t* _ucontext;
|
||||
|
||||
uintptr_t stackAt(int slot) {
|
||||
return ((uintptr_t*)sp())[slot];
|
||||
static bool withinCurrentStack(uintptr_t address) {
|
||||
// Check that the address is not too far from the stack pointer of current context
|
||||
void* real_sp;
|
||||
return address - (uintptr_t)&real_sp <= 0xffff;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -40,10 +43,19 @@ class StackFrame {
|
||||
fp() = saved_fp;
|
||||
}
|
||||
|
||||
bool validSP() {
|
||||
return withinCurrentStack(sp());
|
||||
}
|
||||
|
||||
uintptr_t stackAt(int slot) {
|
||||
return ((uintptr_t*)sp())[slot];
|
||||
}
|
||||
|
||||
uintptr_t& pc();
|
||||
uintptr_t& sp();
|
||||
uintptr_t& fp();
|
||||
|
||||
uintptr_t retval();
|
||||
uintptr_t arg0();
|
||||
uintptr_t arg1();
|
||||
uintptr_t arg2();
|
||||
@@ -52,6 +64,19 @@ class StackFrame {
|
||||
void ret();
|
||||
|
||||
bool pop(bool trust_frame_pointer);
|
||||
|
||||
bool checkInterruptedSyscall();
|
||||
|
||||
// Look that many stack slots for a return address candidate.
|
||||
// 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);
|
||||
};
|
||||
|
||||
#endif // _STACKFRAME_H
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#if defined(__aarch64__)
|
||||
|
||||
#include <errno.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
@@ -37,6 +38,10 @@ uintptr_t& StackFrame::fp() {
|
||||
return (uintptr_t&)_ucontext->uc_mcontext.regs[REG_FP];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::retval() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[0];
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.regs[0];
|
||||
}
|
||||
@@ -72,4 +77,21 @@ bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StackFrame::checkInterruptedSyscall() {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
}
|
||||
|
||||
int StackFrame::callerLookupSlots() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StackFrame::isReturnAddress(instruction_t* pc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
// svc #0
|
||||
return *pc == 0xd4000001;
|
||||
}
|
||||
|
||||
#endif // defined(__aarch64__)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#if defined(__arm__) || defined(__thumb__)
|
||||
|
||||
#include <errno.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
@@ -31,6 +32,10 @@ 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::arg0() {
|
||||
return (uintptr_t)_ucontext->uc_mcontext.arm_r0;
|
||||
}
|
||||
@@ -55,4 +60,21 @@ bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::checkInterruptedSyscall() {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
}
|
||||
|
||||
int StackFrame::callerLookupSlots() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool StackFrame::isReturnAddress(instruction_t* pc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::isSyscall(instruction_t* pc) {
|
||||
// swi #0
|
||||
return *pc == 0xef000000;
|
||||
}
|
||||
|
||||
#endif // defined(__arm__) || defined(__thumb__)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#ifdef __i386__
|
||||
|
||||
#include <errno.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
@@ -31,6 +32,10 @@ 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::arg0() {
|
||||
return stackAt(1);
|
||||
}
|
||||
@@ -52,31 +57,43 @@ void StackFrame::ret() {
|
||||
sp() += 4;
|
||||
}
|
||||
|
||||
|
||||
static inline bool withinCurrentStack(uintptr_t value) {
|
||||
// Check that value is not too far from stack pointer of current context
|
||||
void* real_sp;
|
||||
return value - (uintptr_t)&real_sp <= 0xffff;
|
||||
}
|
||||
|
||||
bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
if (!withinCurrentStack(sp())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
pc() = stackAt(0);
|
||||
sp() += 4;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StackFrame::checkInterruptedSyscall() {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif // __i386__
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#ifdef __x86_64__
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/syscall.h>
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
@@ -38,6 +40,10 @@ uintptr_t& StackFrame::fp() {
|
||||
return (uintptr_t&)REG(REG_RBP, __rbp);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::retval() {
|
||||
return (uintptr_t)REG(REG_RAX, __rax);
|
||||
}
|
||||
|
||||
uintptr_t StackFrame::arg0() {
|
||||
return (uintptr_t)REG(REG_RDI, __rdi);
|
||||
}
|
||||
@@ -60,12 +66,6 @@ void StackFrame::ret() {
|
||||
}
|
||||
|
||||
|
||||
static inline bool withinCurrentStack(uintptr_t value) {
|
||||
// Check that value is not too far from stack pointer of current context
|
||||
void* real_sp;
|
||||
return value - (uintptr_t)&real_sp <= 0xffff;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -91,23 +91,66 @@ static inline bool isFramePrologueEpilogue(uintptr_t pc) {
|
||||
}
|
||||
|
||||
bool StackFrame::pop(bool trust_frame_pointer) {
|
||||
if (!withinCurrentStack(sp())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trust_frame_pointer && withinCurrentStack(fp())) {
|
||||
sp() = fp() + 16;
|
||||
fp() = stackAt(-2);
|
||||
pc() = stackAt(-1);
|
||||
return true;
|
||||
} else if (fp() == sp() || withinCurrentStack(stackAt(0)) || isFramePrologueEpilogue(pc())) {
|
||||
fp() = stackAt(0);
|
||||
pc() = stackAt(1);
|
||||
sp() += 16;
|
||||
} else {
|
||||
pc() = stackAt(0);
|
||||
sp() += 8;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
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() == 0xc3) {
|
||||
return true;
|
||||
}
|
||||
// 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) {
|
||||
return (retval() & 0xff) == EINTR || (retval() & 0xff) == ETIMEDOUT;
|
||||
} else {
|
||||
return retval() == (uintptr_t)-EINTR;
|
||||
}
|
||||
#else
|
||||
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) {
|
||||
uintptr_t pc = this->pc();
|
||||
if ((pc & 0xfff) >= 7 && *(unsigned char*)(pc - 7) == 0xb8 && *(int*)(pc - 6) == SYS_poll) {
|
||||
this->pc() = pc - 7;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif // __x86_64__
|
||||
|
||||
@@ -17,15 +17,24 @@
|
||||
#ifndef _SYMBOLS_H
|
||||
#define _SYMBOLS_H
|
||||
|
||||
#include <set>
|
||||
#include "codeCache.h"
|
||||
#include "mutex.h"
|
||||
|
||||
|
||||
class Symbols {
|
||||
private:
|
||||
static void parseKernelSymbols(NativeCodeCache* cc);
|
||||
static Mutex _parse_lock;
|
||||
static std::set<const void*> _parsed_libraries;
|
||||
static bool _have_kernel_symbols;
|
||||
|
||||
public:
|
||||
static int parseMaps(NativeCodeCache** array, int size);
|
||||
static void parseKernelSymbols(NativeCodeCache* cc);
|
||||
static void parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols);
|
||||
|
||||
static bool haveKernelSymbols() {
|
||||
return _have_kernel_symbols;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _SYMBOLS_H
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
@@ -29,6 +31,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "symbols.h"
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
class SymbolDesc {
|
||||
@@ -87,12 +90,16 @@ typedef Elf64_Ehdr ElfHeader;
|
||||
typedef Elf64_Shdr ElfSection;
|
||||
typedef Elf64_Nhdr ElfNote;
|
||||
typedef Elf64_Sym ElfSymbol;
|
||||
typedef Elf64_Rel ElfRelocation;
|
||||
#define ELF_R_SYM ELF64_R_SYM
|
||||
#else
|
||||
const unsigned char ELFCLASS_SUPPORTED = ELFCLASS32;
|
||||
typedef Elf32_Ehdr ElfHeader;
|
||||
typedef Elf32_Shdr ElfSection;
|
||||
typedef Elf32_Nhdr ElfNote;
|
||||
typedef Elf32_Sym ElfSymbol;
|
||||
typedef Elf32_Rel ElfRelocation;
|
||||
#define ELF_R_SYM ELF32_R_SYM
|
||||
#endif // __LP64__
|
||||
|
||||
|
||||
@@ -133,6 +140,7 @@ class ElfParser {
|
||||
bool loadSymbolsUsingBuildId();
|
||||
bool loadSymbolsUsingDebugLink();
|
||||
void loadSymbolTable(ElfSection* symtab);
|
||||
void addRelocationSymbols(ElfSection* reltab, const char* plt);
|
||||
|
||||
public:
|
||||
static bool parseFile(NativeCodeCache* cc, const char* base, const char* file_name, bool use_debug);
|
||||
@@ -165,7 +173,14 @@ bool ElfParser::parseFile(NativeCodeCache* cc, const char* base, const char* fil
|
||||
void* addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
|
||||
if (addr != NULL) {
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
ElfParser elf(cc, base, addr, file_name);
|
||||
elf.loadSymbols(use_debug);
|
||||
munmap(addr, length);
|
||||
@@ -187,13 +202,13 @@ void ElfParser::loadSymbols(bool use_debug) {
|
||||
ElfSection* section = findSection(SHT_SYMTAB, ".symtab");
|
||||
if (section != NULL) {
|
||||
loadSymbolTable(section);
|
||||
return;
|
||||
goto loaded;
|
||||
}
|
||||
|
||||
// Try to load symbols from an external debuginfo library
|
||||
if (use_debug) {
|
||||
if (loadSymbolsUsingBuildId() || loadSymbolsUsingDebugLink()) {
|
||||
return;
|
||||
goto loaded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +217,19 @@ void ElfParser::loadSymbols(bool use_debug) {
|
||||
if (section != NULL) {
|
||||
loadSymbolTable(section);
|
||||
}
|
||||
|
||||
loaded:
|
||||
// Synthesize names for PLT stubs
|
||||
if (use_debug) {
|
||||
ElfSection* plt = findSection(SHT_PROGBITS, ".plt");
|
||||
ElfSection* reltab = findSection(SHT_RELA, ".rela.plt");
|
||||
if (reltab == NULL) {
|
||||
reltab = findSection(SHT_REL, ".rel.plt");
|
||||
}
|
||||
if (plt != NULL && reltab != NULL) {
|
||||
addRelocationSymbols(reltab, _base + plt->sh_offset + PLT_HEADER_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load symbols from /usr/lib/debug/.build-id/ab/cdef1234.debug, where abcdef1234 is Build ID
|
||||
@@ -284,6 +312,37 @@ void ElfParser::loadSymbolTable(ElfSection* symtab) {
|
||||
}
|
||||
}
|
||||
|
||||
void ElfParser::addRelocationSymbols(ElfSection* reltab, const char* plt) {
|
||||
ElfSection* symtab = section(reltab->sh_link);
|
||||
const char* symbols = at(symtab);
|
||||
|
||||
ElfSection* strtab = section(symtab->sh_link);
|
||||
const char* strings = at(strtab);
|
||||
|
||||
const char* relocations = at(reltab);
|
||||
const char* relocations_end = relocations + reltab->sh_size;
|
||||
for (; relocations < relocations_end; relocations += reltab->sh_entsize) {
|
||||
ElfRelocation* r = (ElfRelocation*)relocations;
|
||||
ElfSymbol* sym = (ElfSymbol*)(symbols + ELF_R_SYM(r->r_info) * symtab->sh_entsize);
|
||||
|
||||
char name[256];
|
||||
if (sym->st_name == 0) {
|
||||
strcpy(name, "@plt");
|
||||
} else {
|
||||
const char* sym_name = strings + sym->st_name;
|
||||
snprintf(name, sizeof(name), "%s%cplt", sym_name, sym_name[0] == '_' && sym_name[1] == 'Z' ? '.' : '@');
|
||||
name[sizeof(name) - 1] = 0;
|
||||
}
|
||||
|
||||
_cc->add(plt, PLT_ENTRY_SIZE, name);
|
||||
plt += PLT_ENTRY_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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");
|
||||
@@ -297,18 +356,26 @@ void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
|
||||
const char* addr = symbol.addr();
|
||||
if (addr != NULL) {
|
||||
cc->add(addr, 0, symbol.name());
|
||||
_have_kernel_symbols = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Symbols::parseMaps(NativeCodeCache** array, int size) {
|
||||
int count = 0;
|
||||
if (count < size) {
|
||||
void Symbols::parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols) {
|
||||
MutexLocker ml(_parse_lock);
|
||||
|
||||
if (kernel_symbols && !haveKernelSymbols()) {
|
||||
NativeCodeCache* cc = new NativeCodeCache("[kernel]");
|
||||
parseKernelSymbols(cc);
|
||||
cc->sort();
|
||||
array[count++] = cc;
|
||||
|
||||
if (haveKernelSymbols()) {
|
||||
cc->sort();
|
||||
array[count] = cc;
|
||||
atomicInc(count);
|
||||
} else {
|
||||
delete cc;
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
@@ -317,20 +384,24 @@ int Symbols::parseMaps(NativeCodeCache** array, int size) {
|
||||
while (count < size && std::getline(maps, str)) {
|
||||
MemoryMapDesc map(str.c_str());
|
||||
if (map.isExecutable() && map.file() != NULL && map.file()[0] != 0) {
|
||||
NativeCodeCache* cc = new NativeCodeCache(map.file(), map.addr(), map.end());
|
||||
const char* image_base = map.addr();
|
||||
if (!_parsed_libraries.insert(image_base).second) {
|
||||
continue; // the library was already parsed
|
||||
}
|
||||
|
||||
NativeCodeCache* cc = new NativeCodeCache(map.file(), image_base, map.end());
|
||||
|
||||
if (map.inode() != 0) {
|
||||
ElfParser::parseFile(cc, map.addr() - map.offs(), map.file(), true);
|
||||
ElfParser::parseFile(cc, image_base - map.offs(), map.file(), true);
|
||||
} else if (strcmp(map.file(), "[vdso]") == 0) {
|
||||
ElfParser::parseMem(cc, map.addr());
|
||||
ElfParser::parseMem(cc, image_base);
|
||||
}
|
||||
|
||||
cc->sort();
|
||||
array[count++] = cc;
|
||||
array[count] = cc;
|
||||
atomicInc(count);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
@@ -25,6 +27,7 @@
|
||||
#include <mach-o/fat.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include "symbols.h"
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
class MachOParser {
|
||||
@@ -113,7 +116,9 @@ class MachOParser {
|
||||
void* addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
|
||||
if (addr != NULL) {
|
||||
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);
|
||||
@@ -122,24 +127,32 @@ class MachOParser {
|
||||
};
|
||||
|
||||
|
||||
Mutex Symbols::_parse_lock;
|
||||
std::set<const void*> Symbols::_parsed_libraries;
|
||||
bool Symbols::_have_kernel_symbols = false;
|
||||
|
||||
void Symbols::parseKernelSymbols(NativeCodeCache* cc) {
|
||||
}
|
||||
|
||||
int Symbols::parseMaps(NativeCodeCache** array, int size) {
|
||||
int count = 0;
|
||||
void Symbols::parseLibraries(NativeCodeCache** array, volatile int& count, int size, bool kernel_symbols) {
|
||||
MutexLocker ml(_parse_lock);
|
||||
uint32_t images = _dyld_image_count();
|
||||
|
||||
for (uint32_t i = 0; i < images && count < size; i++) {
|
||||
const mach_header* image_base = _dyld_get_image_header(i);
|
||||
if (!_parsed_libraries.insert(image_base).second) {
|
||||
continue; // the library was already parsed
|
||||
}
|
||||
|
||||
const char* path = _dyld_get_image_name(i);
|
||||
const mach_header* base = _dyld_get_image_header(i);
|
||||
|
||||
NativeCodeCache* cc = new NativeCodeCache(path);
|
||||
MachOParser::parseFile(cc, base, path);
|
||||
cc->sort();
|
||||
array[count++] = cc;
|
||||
}
|
||||
MachOParser::parseFile(cc, image_base, path);
|
||||
|
||||
return count;
|
||||
cc->sort();
|
||||
array[count] = cc;
|
||||
atomicInc(count);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
127
src/threadFilter.cpp
Normal file
127
src/threadFilter.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "threadFilter.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
ThreadFilter::ThreadFilter() {
|
||||
memset(_bitmap, 0, sizeof(_bitmap));
|
||||
_bitmap[0] = (u32*)OS::safeAlloc(BITMAP_SIZE);
|
||||
|
||||
_enabled = false;
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
ThreadFilter::~ThreadFilter() {
|
||||
for (int i = 0; i < MAX_BITMAPS; i++) {
|
||||
if (_bitmap[i] != NULL) {
|
||||
OS::safeFree(_bitmap[i], BITMAP_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadFilter::init(const char* filter) {
|
||||
if (filter == NULL) {
|
||||
_enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
char* end;
|
||||
do {
|
||||
int id = strtol(filter, &end, 0);
|
||||
if (id <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (*end == '-') {
|
||||
int to = strtol(end + 1, &end, 0);
|
||||
while (id <= to) {
|
||||
add(id++);
|
||||
}
|
||||
} else {
|
||||
add(id);
|
||||
}
|
||||
|
||||
filter = end + 1;
|
||||
} while (*end);
|
||||
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
void ThreadFilter::clear() {
|
||||
for (int i = 0; i < MAX_BITMAPS; i++) {
|
||||
if (_bitmap[i] != NULL) {
|
||||
memset(_bitmap[i], 0, BITMAP_SIZE);
|
||||
}
|
||||
}
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
bool ThreadFilter::accept(int thread_id) {
|
||||
u32* b = bitmap(thread_id);
|
||||
return b != NULL && (word(b, thread_id) & (1 << (thread_id & 0x1f)));
|
||||
}
|
||||
|
||||
void ThreadFilter::add(int thread_id) {
|
||||
u32* b = bitmap(thread_id);
|
||||
if (b == NULL) {
|
||||
b = (u32*)OS::safeAlloc(BITMAP_SIZE);
|
||||
u32* oldb = __sync_val_compare_and_swap(&_bitmap[(u32)thread_id / BITMAP_CAPACITY], NULL, b);
|
||||
if (oldb != NULL) {
|
||||
OS::safeFree(b, BITMAP_SIZE);
|
||||
b = oldb;
|
||||
}
|
||||
}
|
||||
|
||||
u32 bit = 1 << (thread_id & 0x1f);
|
||||
if (!(__sync_fetch_and_or(&word(b, thread_id), bit) & bit)) {
|
||||
atomicInc(_size);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadFilter::remove(int thread_id) {
|
||||
u32* b = bitmap(thread_id);
|
||||
if (b == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 bit = 1 << (thread_id & 0x1f);
|
||||
if (__sync_fetch_and_and(&word(b, thread_id), ~bit) & bit) {
|
||||
atomicInc(_size, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadFilter::collect(std::vector<int>& v) {
|
||||
for (int i = 0; i < MAX_BITMAPS; i++) {
|
||||
u32* b = _bitmap[i];
|
||||
if (b != NULL) {
|
||||
int start_id = i * BITMAP_CAPACITY;
|
||||
for (int j = 0; j < BITMAP_SIZE / sizeof(u32); j++) {
|
||||
u32 word = b[j];
|
||||
if (word) {
|
||||
for (int bit = 0; bit < 32; bit++) {
|
||||
if (word & (1 << bit)) {
|
||||
v.push_back(start_id + j * 32 + bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/threadFilter.h
Normal file
70
src/threadFilter.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _THREADFILTER_H
|
||||
#define _THREADFILTER_H
|
||||
|
||||
#include <vector>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
// The size of thread ID bitmap in bytes. Must be at least 64K to allow mmap()
|
||||
const u32 BITMAP_SIZE = 65536;
|
||||
// How many thread IDs one bitmap can hold
|
||||
const u32 BITMAP_CAPACITY = BITMAP_SIZE * 8;
|
||||
// Total number of bitmaps required to hold the entire range of thread IDs
|
||||
const u32 MAX_BITMAPS = (1 << 31) / BITMAP_CAPACITY;
|
||||
|
||||
|
||||
// ThreadFilter query operations must be lock-free and signal-safe;
|
||||
// update operations are mostly lock-free, except rare bitmap allocations
|
||||
class ThreadFilter {
|
||||
private:
|
||||
u32* _bitmap[MAX_BITMAPS];
|
||||
bool _enabled;
|
||||
volatile int _size;
|
||||
|
||||
u32* bitmap(int thread_id) {
|
||||
return _bitmap[(u32)thread_id / BITMAP_CAPACITY];
|
||||
}
|
||||
|
||||
u32& word(u32* bitmap, int thread_id) {
|
||||
return bitmap[((u32)thread_id % BITMAP_CAPACITY) >> 5];
|
||||
}
|
||||
|
||||
public:
|
||||
ThreadFilter();
|
||||
~ThreadFilter();
|
||||
|
||||
bool enabled() {
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
int size() {
|
||||
return _size;
|
||||
}
|
||||
|
||||
void init(const char* filter);
|
||||
void clear();
|
||||
|
||||
bool accept(int thread_id);
|
||||
void add(int thread_id);
|
||||
void remove(int thread_id);
|
||||
|
||||
void collect(std::vector<int>& v);
|
||||
};
|
||||
|
||||
#endif // _THREADFILTER_H
|
||||
127
src/tracer.cpp
127
src/tracer.cpp
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <set>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include "tracer.h"
|
||||
#include "frameName.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
struct TracerEvent {
|
||||
u64 timestamp;
|
||||
int tid;
|
||||
int call_trace_id;
|
||||
u64 counter;
|
||||
};
|
||||
|
||||
|
||||
Error Tracer::start(Arguments& args) {
|
||||
if (args._file == NULL || args._file[0] == 0) {
|
||||
return Error("Tracer output file is not specified");
|
||||
}
|
||||
|
||||
_out = fopen(args._file, "w");
|
||||
if (_out == NULL) {
|
||||
return Error("Cannot open Tracer output file");
|
||||
}
|
||||
|
||||
if (pipe(_pipefd) != 0) {
|
||||
fclose(_out);
|
||||
return Error("Unable to create poll pipe");
|
||||
}
|
||||
|
||||
_simple = args._simple;
|
||||
_annotate = args._annotate;
|
||||
|
||||
if (pthread_create(&_thread, NULL, threadEntry, this) != 0) {
|
||||
close(_pipefd[1]);
|
||||
close(_pipefd[0]);
|
||||
fclose(_out);
|
||||
return Error("Unable to create tracer thread");
|
||||
}
|
||||
|
||||
_running = true;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
void Tracer::stop() {
|
||||
if (_running) {
|
||||
close(_pipefd[1]);
|
||||
pthread_join(_thread, NULL);
|
||||
close(_pipefd[0]);
|
||||
fclose(_out);
|
||||
_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Tracer::recordExecutionSample(int tid, int call_trace_id, u64 counter) {
|
||||
if (_running && tid != _tracer_tid && call_trace_id != 0) {
|
||||
TracerEvent event;
|
||||
event.timestamp = OS::millis();
|
||||
event.tid = tid;
|
||||
event.call_trace_id = call_trace_id;
|
||||
event.counter = counter;
|
||||
|
||||
ssize_t result = write(_pipefd[1], &event, sizeof(event));
|
||||
(void)result;
|
||||
}
|
||||
}
|
||||
|
||||
void Tracer::tracerLoop() {
|
||||
_tracer_tid = OS::threadId();
|
||||
TracerEvent event_buf[256];
|
||||
std::set<int> written_traces;
|
||||
|
||||
void* env;
|
||||
JavaVMAttachArgs attach_args = {JNI_VERSION_1_6, (char*)"async-profiler tracer", NULL};
|
||||
VM::vm()->AttachCurrentThreadAsDaemon(&env, &attach_args);
|
||||
|
||||
FrameName fn(_simple, _annotate, false,
|
||||
Profiler::_instance._thread_names_lock, Profiler::_instance._thread_names);
|
||||
|
||||
while (true) {
|
||||
ssize_t bytes = read(_pipefd[0], event_buf, sizeof(event_buf));
|
||||
if (bytes == 0 || (bytes < 0 && errno != EINTR)) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (TracerEvent* event = event_buf; (bytes -= sizeof(TracerEvent)) >= 0; event++) {
|
||||
int call_trace_id = event->call_trace_id;
|
||||
if (written_traces.insert(call_trace_id).second) {
|
||||
// The stacktrace with this id was not yet written
|
||||
CallTraceSample* trace = &Profiler::_instance._traces[call_trace_id];
|
||||
ASGCT_CallFrame* frame_buffer = &Profiler::_instance._frame_buffer[trace->_start_frame];
|
||||
|
||||
fprintf(_out, "[stacktrace] id=%d ", call_trace_id);
|
||||
for (int i = trace->_num_frames - 1; i >= 0; i--) {
|
||||
const char* frame_name = fn.name(frame_buffer[i]);
|
||||
fprintf(_out, (i == 0 ? "%s" : "%s;"), frame_name);
|
||||
}
|
||||
fprintf(_out, "\n");
|
||||
}
|
||||
|
||||
fprintf(_out, "[event] time=%llu thread=%d stacktrace=%d counter=%llu\n",
|
||||
event->timestamp, event->tid, event->call_trace_id, event->counter);
|
||||
fflush(_out);
|
||||
}
|
||||
}
|
||||
|
||||
VM::vm()->DetachCurrentThread();
|
||||
}
|
||||
63
src/trap.cpp
Normal file
63
src/trap.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include "trap.h"
|
||||
|
||||
|
||||
bool Trap::assign(const void* address) {
|
||||
uintptr_t entry = (uintptr_t)address;
|
||||
if (entry == 0) {
|
||||
_entry = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(__arm__) || defined(__thumb__)
|
||||
if (entry & 1) {
|
||||
entry ^= 1;
|
||||
_breakpoint_insn = BREAKPOINT_THUMB;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (entry != _entry) {
|
||||
// Make the entry point writable, so we can rewrite instructions
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
if (mprotect((void*)(entry & -page_size), page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
return false;
|
||||
}
|
||||
_entry = entry;
|
||||
_saved_insn = *(instruction_t*)entry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Insert breakpoint at the very first instruction
|
||||
void Trap::install() {
|
||||
if (_entry) {
|
||||
*(instruction_t*)_entry = _breakpoint_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear breakpoint - restore the original instruction
|
||||
void Trap::uninstall() {
|
||||
if (_entry) {
|
||||
*(instruction_t*)_entry = _saved_insn;
|
||||
flushCache(_entry);
|
||||
}
|
||||
}
|
||||
48
src/trap.h
Normal file
48
src/trap.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _TRAP_H
|
||||
#define _TRAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "arch.h"
|
||||
|
||||
|
||||
class Trap {
|
||||
private:
|
||||
uintptr_t _entry;
|
||||
instruction_t _breakpoint_insn;
|
||||
instruction_t _saved_insn;
|
||||
|
||||
public:
|
||||
Trap() : _entry(0), _breakpoint_insn(BREAKPOINT) {
|
||||
}
|
||||
|
||||
uintptr_t entry() {
|
||||
return _entry;
|
||||
}
|
||||
|
||||
bool covers(uintptr_t pc) {
|
||||
// PC points either to BREAKPOINT instruction or to the next one
|
||||
return pc - _entry <= sizeof(instruction_t);
|
||||
}
|
||||
|
||||
bool assign(const void* address);
|
||||
void install();
|
||||
void uninstall();
|
||||
};
|
||||
|
||||
#endif // _TRAP_H
|
||||
151
src/vmEntry.cpp
151
src/vmEntry.cpp
@@ -16,17 +16,30 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "vmEntry.h"
|
||||
#include "arguments.h"
|
||||
#include "javaApi.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
#include "instrument.h"
|
||||
#include "lockTracer.h"
|
||||
#include "vmStructs.h"
|
||||
|
||||
|
||||
static Arguments _agent_args;
|
||||
|
||||
JavaVM* VM::_vm;
|
||||
jvmtiEnv* VM::_jvmti = NULL;
|
||||
int VM::_hotspot_version = 0;
|
||||
void* VM::_libjvm;
|
||||
void* VM::_libjava;
|
||||
AsyncGetCallTrace VM::_asyncGetCallTrace;
|
||||
JVM_GetManagement VM::_getManagement;
|
||||
jvmtiError (JNICALL *VM::_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
|
||||
jvmtiError (JNICALL *VM::_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
|
||||
volatile int VM::_in_redefine_classes = 0;
|
||||
|
||||
|
||||
void VM::init(JavaVM* vm, bool attach) {
|
||||
@@ -35,8 +48,47 @@ void VM::init(JavaVM* vm, bool attach) {
|
||||
_vm = vm;
|
||||
_vm->GetEnv((void**)&_jvmti, JVMTI_VERSION_1_0);
|
||||
|
||||
char* prop;
|
||||
if (_jvmti->GetSystemProperty("java.vm.name", &prop) == 0) {
|
||||
bool is_hotspot = strstr(prop, "OpenJDK") != NULL ||
|
||||
strstr(prop, "HotSpot") != NULL ||
|
||||
strstr(prop, "GraalVM") != NULL;
|
||||
_jvmti->Deallocate((unsigned char*)prop);
|
||||
|
||||
if (is_hotspot && _jvmti->GetSystemProperty("java.vm.version", &prop) == 0) {
|
||||
if (strncmp(prop, "25.", 3) == 0) {
|
||||
_hotspot_version = 8;
|
||||
} else if (strncmp(prop, "24.", 3) == 0) {
|
||||
_hotspot_version = 7;
|
||||
} else if (strncmp(prop, "20.", 3) == 0) {
|
||||
_hotspot_version = 6;
|
||||
} else if ((_hotspot_version = atoi(prop)) < 9) {
|
||||
_hotspot_version = 9;
|
||||
}
|
||||
_jvmti->Deallocate((unsigned char*)prop);
|
||||
}
|
||||
|
||||
if (is_hotspot) {
|
||||
JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
|
||||
_orig_RedefineClasses = functions->RedefineClasses;
|
||||
_orig_RetransformClasses = functions->RetransformClasses;
|
||||
functions->RedefineClasses = RedefineClassesHook;
|
||||
functions->RetransformClasses = RetransformClassesHook;
|
||||
}
|
||||
}
|
||||
|
||||
_libjvm = getLibraryHandle("libjvm.so");
|
||||
_asyncGetCallTrace = (AsyncGetCallTrace)dlsym(_libjvm, "AsyncGetCallTrace");
|
||||
_getManagement = (JVM_GetManagement)dlsym(_libjvm, "JVM_GetManagement");
|
||||
|
||||
if (attach) {
|
||||
ready();
|
||||
}
|
||||
|
||||
jvmtiCapabilities capabilities = {0};
|
||||
capabilities.can_generate_all_class_hook_events = 1;
|
||||
capabilities.can_retransform_classes = 1;
|
||||
capabilities.can_retransform_any_class = 1;
|
||||
capabilities.can_get_bytecodes = 1;
|
||||
capabilities.can_get_constant_pool = 1;
|
||||
capabilities.can_get_source_file_name = 1;
|
||||
@@ -51,6 +103,7 @@ void VM::init(JavaVM* vm, bool attach) {
|
||||
callbacks.VMDeath = VMDeath;
|
||||
callbacks.ClassLoad = ClassLoad;
|
||||
callbacks.ClassPrepare = ClassPrepare;
|
||||
callbacks.ClassFileLoadHook = Instrument::ClassFileLoadHook;
|
||||
callbacks.CompiledMethodLoad = Profiler::CompiledMethodLoad;
|
||||
callbacks.CompiledMethodUnload = Profiler::CompiledMethodUnload;
|
||||
callbacks.DynamicCodeGenerated = Profiler::DynamicCodeGenerated;
|
||||
@@ -68,26 +121,51 @@ void VM::init(JavaVM* vm, bool attach) {
|
||||
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, NULL);
|
||||
_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);
|
||||
|
||||
_asyncGetCallTrace = (AsyncGetCallTrace)dlsym(RTLD_DEFAULT, "AsyncGetCallTrace");
|
||||
if (_asyncGetCallTrace == NULL) {
|
||||
// Unable to locate AsyncGetCallTrace, it is likely that JVM has been started
|
||||
// by JNI_CreateJavaVM() via dynamically loaded libjvm.so from a C/C++ program
|
||||
void* libjvm_handle = dlopen("libjvm.so", RTLD_NOW);
|
||||
if (!libjvm_handle) {
|
||||
std::cerr << "Failed to load libjvm.so: " << dlerror() << std::endl;
|
||||
}
|
||||
// Try loading AGCT after opening libjvm.so
|
||||
_asyncGetCallTrace = (AsyncGetCallTrace)dlsym(libjvm_handle, "AsyncGetCallTrace");
|
||||
}
|
||||
|
||||
if (attach) {
|
||||
loadAllMethodIDs(_jvmti);
|
||||
loadAllMethodIDs(jvmti(), jni());
|
||||
_jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
|
||||
_jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD);
|
||||
}
|
||||
}
|
||||
|
||||
void VM::loadMethodIDs(jvmtiEnv* jvmti, jclass klass) {
|
||||
// Run late initialization when JVM is ready
|
||||
void VM::ready() {
|
||||
Profiler::_instance.updateSymbols(false);
|
||||
NativeCodeCache* libjvm = Profiler::_instance.findNativeLibrary((const void*)_asyncGetCallTrace);
|
||||
if (libjvm != NULL) {
|
||||
VMStructs::init(libjvm);
|
||||
}
|
||||
|
||||
_libjava = getLibraryHandle("libjava.so");
|
||||
}
|
||||
|
||||
void* VM::getLibraryHandle(const char* name) {
|
||||
if (!OS::isJavaLibraryVisible()) {
|
||||
void* handle = dlopen(name, RTLD_LAZY);
|
||||
if (handle != NULL) {
|
||||
return handle;
|
||||
}
|
||||
std::cerr << "Failed to load " << name << ": " << dlerror() << std::endl;
|
||||
}
|
||||
return RTLD_DEFAULT;
|
||||
}
|
||||
|
||||
void VM::loadMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) {
|
||||
if (VMStructs::hasClassLoaderData()) {
|
||||
VMKlass* vmklass = VMKlass::fromJavaClass(jni, klass);
|
||||
int method_count = vmklass->methodCount();
|
||||
if (method_count > 0) {
|
||||
ClassLoaderData* cld = vmklass->classLoaderData();
|
||||
cld->lock();
|
||||
// Workaround for JVM bug: preallocate space for jmethodIDs
|
||||
// at the beginning of the list (rather than at the end)
|
||||
for (int i = 0; i < method_count; i += MethodList::SIZE) {
|
||||
*cld->methodList() = new MethodList(*cld->methodList());
|
||||
}
|
||||
cld->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
jint method_count;
|
||||
jmethodID* methods;
|
||||
if (jvmti->GetClassMethods(klass, &method_count, &methods) == 0) {
|
||||
@@ -95,19 +173,21 @@ void VM::loadMethodIDs(jvmtiEnv* jvmti, jclass klass) {
|
||||
}
|
||||
}
|
||||
|
||||
void VM::loadAllMethodIDs(jvmtiEnv* jvmti) {
|
||||
void VM::loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni) {
|
||||
jint class_count;
|
||||
jclass* classes;
|
||||
if (jvmti->GetLoadedClasses(&class_count, &classes) == 0) {
|
||||
for (int i = 0; i < class_count; i++) {
|
||||
loadMethodIDs(jvmti, classes[i]);
|
||||
loadMethodIDs(jvmti, jni, classes[i]);
|
||||
}
|
||||
jvmti->Deallocate((unsigned char*)classes);
|
||||
}
|
||||
}
|
||||
|
||||
void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
|
||||
loadAllMethodIDs(jvmti);
|
||||
ready();
|
||||
loadAllMethodIDs(jvmti, jni);
|
||||
|
||||
// Delayed start of profiler if agent has been loaded at VM bootstrap
|
||||
Profiler::_instance.run(_agent_args);
|
||||
}
|
||||
@@ -116,6 +196,38 @@ void JNICALL VM::VMDeath(jvmtiEnv* jvmti, JNIEnv* jni) {
|
||||
Profiler::_instance.shutdown(_agent_args);
|
||||
}
|
||||
|
||||
jvmtiError VM::RedefineClassesHook(jvmtiEnv* jvmti, jint class_count, const jvmtiClassDefinition* class_definitions) {
|
||||
atomicInc(_in_redefine_classes);
|
||||
jvmtiError result = _orig_RedefineClasses(jvmti, class_count, class_definitions);
|
||||
|
||||
// jmethodIDs are invalidated after RedefineClasses
|
||||
JNIEnv* env = jni();
|
||||
for (int i = 0; i < class_count; i++) {
|
||||
if (class_definitions[i].klass != NULL) {
|
||||
loadMethodIDs(jvmti, env, class_definitions[i].klass);
|
||||
}
|
||||
}
|
||||
|
||||
atomicInc(_in_redefine_classes, -1);
|
||||
return result;
|
||||
}
|
||||
|
||||
jvmtiError VM::RetransformClassesHook(jvmtiEnv* jvmti, jint class_count, const jclass* classes) {
|
||||
atomicInc(_in_redefine_classes);
|
||||
jvmtiError result = _orig_RetransformClasses(jvmti, class_count, classes);
|
||||
|
||||
// jmethodIDs are invalidated after RetransformClasses
|
||||
JNIEnv* env = jni();
|
||||
for (int i = 0; i < class_count; i++) {
|
||||
if (classes[i] != NULL) {
|
||||
loadMethodIDs(jvmti, env, classes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
atomicInc(_in_redefine_classes, -1);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
|
||||
@@ -142,7 +254,9 @@ Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
|
||||
}
|
||||
|
||||
// Save the arguments in case of shutdown
|
||||
_agent_args = args;
|
||||
if (args._action == ACTION_START || args._action == ACTION_RESUME) {
|
||||
_agent_args.save(args);
|
||||
}
|
||||
Profiler::_instance.run(args);
|
||||
|
||||
return 0;
|
||||
@@ -151,5 +265,6 @@ Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
VM::init(vm, true);
|
||||
JavaAPI::registerNatives(VM::jvmti(), VM::jni());
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,39 @@
|
||||
#include <jvmti.h>
|
||||
|
||||
|
||||
#if __GNUC__ == 4
|
||||
# undef JNIEXPORT
|
||||
# define JNIEXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
|
||||
// Denotes ASGCT_CallFrame where method_id has special meaning (not jmethodID)
|
||||
enum ASGCT_CallFrameType {
|
||||
BCI_NATIVE_FRAME = -10, // method_id is native function name (char*)
|
||||
BCI_SYMBOL = -11, // method_id is VMSymbol*
|
||||
BCI_SYMBOL_OUTSIDE_TLAB = -12, // VMSymbol* specifically for allocations outside TLAB
|
||||
BCI_THREAD_ID = -13, // method_id designates a thread
|
||||
BCI_NATIVE_FRAME = -10, // native function name (char*)
|
||||
BCI_ALLOC = -11, // name of the allocated class
|
||||
BCI_ALLOC_OUTSIDE_TLAB = -12, // name of the class allocated outside TLAB
|
||||
BCI_LOCK = -13, // class name of the locked object
|
||||
BCI_PARK = -14, // class name of the park() blocker
|
||||
BCI_THREAD_ID = -15, // method_id designates a thread
|
||||
BCI_ERROR = -16, // method_id is an error string
|
||||
BCI_INSTRUMENT = -17, // synthetic method_id that should not appear in the call stack
|
||||
};
|
||||
|
||||
// See hotspot/src/share/vm/prims/forte.cpp
|
||||
enum ASGCT_Failure {
|
||||
ticks_no_Java_frame = 0,
|
||||
ticks_no_class_load = -1,
|
||||
ticks_GC_active = -2,
|
||||
ticks_unknown_not_Java = -3,
|
||||
ticks_not_walkable_not_Java = -4,
|
||||
ticks_unknown_Java = -5,
|
||||
ticks_not_walkable_Java = -6,
|
||||
ticks_unknown_state = -7,
|
||||
ticks_thread_exit = -8,
|
||||
ticks_deopt = -9,
|
||||
ticks_safepoint = -10,
|
||||
ticks_skipped = -11,
|
||||
ASGCT_FAILURE_TYPES = 12
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -41,24 +68,43 @@ typedef struct {
|
||||
|
||||
typedef void (*AsyncGetCallTrace)(ASGCT_CallTrace*, jint, void*);
|
||||
|
||||
typedef struct {
|
||||
void* unused[38];
|
||||
jstring (JNICALL *ExecuteDiagnosticCommand)(JNIEnv*, jstring);
|
||||
} VMManagement;
|
||||
|
||||
typedef VMManagement* (*JVM_GetManagement)(jint);
|
||||
|
||||
typedef struct {
|
||||
void* unused1[86];
|
||||
jvmtiError (JNICALL *RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
|
||||
void* unused2[64];
|
||||
jvmtiError (JNICALL *RetransformClasses)(jvmtiEnv*, jint, const jclass*);
|
||||
} JVMTIFunctions;
|
||||
|
||||
|
||||
class VM {
|
||||
private:
|
||||
static JavaVM* _vm;
|
||||
static jvmtiEnv* _jvmti;
|
||||
static JVM_GetManagement _getManagement;
|
||||
static jvmtiError (JNICALL *_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
|
||||
static jvmtiError (JNICALL *_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
|
||||
static volatile int _in_redefine_classes;
|
||||
static int _hotspot_version;
|
||||
|
||||
static void loadMethodIDs(jvmtiEnv* jvmti, jclass klass);
|
||||
static void loadAllMethodIDs(jvmtiEnv* jvmti);
|
||||
static void ready();
|
||||
static void* getLibraryHandle(const char* name);
|
||||
static void loadMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass);
|
||||
static void loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni);
|
||||
|
||||
public:
|
||||
static void* _libjvm;
|
||||
static void* _libjava;
|
||||
static AsyncGetCallTrace _asyncGetCallTrace;
|
||||
|
||||
static void init(JavaVM* vm, bool attach);
|
||||
|
||||
static JavaVM* vm() {
|
||||
return _vm;
|
||||
}
|
||||
|
||||
static jvmtiEnv* jvmti() {
|
||||
return _jvmti;
|
||||
}
|
||||
@@ -68,6 +114,18 @@ class VM {
|
||||
return _vm->GetEnv((void**)&jni, JNI_VERSION_1_6) == 0 ? jni : NULL;
|
||||
}
|
||||
|
||||
static VMManagement* management() {
|
||||
return _getManagement != NULL ? _getManagement(0x20030000) : NULL;
|
||||
}
|
||||
|
||||
static int hotspot_version() {
|
||||
return _hotspot_version;
|
||||
}
|
||||
|
||||
static bool inRedefineClasses() {
|
||||
return _in_redefine_classes > 0;
|
||||
}
|
||||
|
||||
static void JNICALL VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread);
|
||||
static void JNICALL VMDeath(jvmtiEnv* jvmti, JNIEnv* jni);
|
||||
|
||||
@@ -76,8 +134,11 @@ class VM {
|
||||
}
|
||||
|
||||
static void JNICALL ClassPrepare(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass klass) {
|
||||
loadMethodIDs(jvmti, klass);
|
||||
loadMethodIDs(jvmti, jni, klass);
|
||||
}
|
||||
|
||||
static jvmtiError JNICALL RedefineClassesHook(jvmtiEnv* jvmti, jint class_count, const jvmtiClassDefinition* class_definitions);
|
||||
static jvmtiError JNICALL RetransformClassesHook(jvmtiEnv* jvmti, jint class_count, const jclass* classes);
|
||||
};
|
||||
|
||||
#endif // _VMENTRY_H
|
||||
|
||||
@@ -14,23 +14,51 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "vmStructs.h"
|
||||
#include "vmEntry.h"
|
||||
|
||||
|
||||
NativeCodeCache* VMStructs::_libjvm = NULL;
|
||||
|
||||
bool VMStructs::_has_class_names = false;
|
||||
bool VMStructs::_has_class_loader_data = false;
|
||||
bool VMStructs::_has_thread_bridge = false;
|
||||
bool VMStructs::_has_perm_gen = false;
|
||||
|
||||
int VMStructs::_klass_name_offset = -1;
|
||||
int VMStructs::_symbol_length_offset = -1;
|
||||
int VMStructs::_symbol_length_and_refcount_offset = -1;
|
||||
int VMStructs::_symbol_body_offset = -1;
|
||||
int VMStructs::_class_klass_offset = -1;
|
||||
int VMStructs::_class_loader_data_offset = -1;
|
||||
int VMStructs::_methods_offset = -1;
|
||||
int VMStructs::_thread_osthread_offset = -1;
|
||||
int VMStructs::_thread_anchor_offset = -1;
|
||||
int VMStructs::_thread_state_offset = -1;
|
||||
int VMStructs::_osthread_id_offset = -1;
|
||||
bool VMStructs::_has_perm_gen = false;
|
||||
jfieldID VMStructs::_eetop = NULL;
|
||||
int VMStructs::_anchor_sp_offset = -1;
|
||||
int VMStructs::_anchor_pc_offset = -1;
|
||||
int VMStructs::_frame_size_offset = -1;
|
||||
int VMStructs::_is_gc_active_offset = -1;
|
||||
char* VMStructs::_collected_heap_addr = NULL;
|
||||
|
||||
static uintptr_t readSymbol(NativeCodeCache* lib, const char* symbol_name) {
|
||||
const void* symbol = lib->findSymbol(symbol_name);
|
||||
jfieldID VMStructs::_eetop;
|
||||
jfieldID VMStructs::_tid;
|
||||
jfieldID VMStructs::_klass = NULL;
|
||||
int VMStructs::_tls_index = -1;
|
||||
intptr_t VMStructs::_env_offset;
|
||||
|
||||
VMStructs::GetStackTraceFunc VMStructs::_get_stack_trace = NULL;
|
||||
VMStructs::UnsafeParkFunc VMStructs::_unsafe_park = NULL;
|
||||
VMStructs::FindBlobFunc VMStructs::_find_blob = NULL;
|
||||
VMStructs::LockFunc VMStructs::_lock_func;
|
||||
VMStructs::LockFunc VMStructs::_unlock_func;
|
||||
|
||||
|
||||
uintptr_t VMStructs::readSymbol(const char* symbol_name) {
|
||||
const void* symbol = _libjvm->findSymbol(symbol_name);
|
||||
if (symbol == NULL) {
|
||||
// Avoid JVM crash in case of missing symbols
|
||||
return 0;
|
||||
@@ -39,16 +67,24 @@ static uintptr_t readSymbol(NativeCodeCache* lib, const char* symbol_name) {
|
||||
}
|
||||
|
||||
void VMStructs::init(NativeCodeCache* libjvm) {
|
||||
if (available()) {
|
||||
return;
|
||||
}
|
||||
_libjvm = libjvm;
|
||||
|
||||
uintptr_t entry = readSymbol(libjvm, "gHotSpotVMStructs");
|
||||
uintptr_t stride = readSymbol(libjvm, "gHotSpotVMStructEntryArrayStride");
|
||||
uintptr_t type_offset = readSymbol(libjvm, "gHotSpotVMStructEntryTypeNameOffset");
|
||||
uintptr_t field_offset = readSymbol(libjvm, "gHotSpotVMStructEntryFieldNameOffset");
|
||||
uintptr_t offset_offset = readSymbol(libjvm, "gHotSpotVMStructEntryOffsetOffset");
|
||||
uintptr_t address_offset = readSymbol(libjvm, "gHotSpotVMStructEntryAddressOffset");
|
||||
initOffsets();
|
||||
initJvmFunctions();
|
||||
|
||||
JNIEnv* env = VM::jni();
|
||||
initThreadBridge(env);
|
||||
initLogging(env);
|
||||
env->ExceptionClear();
|
||||
}
|
||||
|
||||
void VMStructs::initOffsets() {
|
||||
uintptr_t entry = readSymbol("gHotSpotVMStructs");
|
||||
uintptr_t stride = readSymbol("gHotSpotVMStructEntryArrayStride");
|
||||
uintptr_t type_offset = readSymbol("gHotSpotVMStructEntryTypeNameOffset");
|
||||
uintptr_t field_offset = readSymbol("gHotSpotVMStructEntryFieldNameOffset");
|
||||
uintptr_t offset_offset = readSymbol("gHotSpotVMStructEntryOffsetOffset");
|
||||
uintptr_t address_offset = readSymbol("gHotSpotVMStructEntryAddressOffset");
|
||||
|
||||
if (entry == 0 || stride == 0) {
|
||||
return;
|
||||
@@ -68,21 +104,52 @@ void VMStructs::init(NativeCodeCache* libjvm) {
|
||||
} else if (strcmp(type, "Symbol") == 0) {
|
||||
if (strcmp(field, "_length") == 0) {
|
||||
_symbol_length_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_length_and_refcount") == 0) {
|
||||
_symbol_length_and_refcount_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_body") == 0) {
|
||||
_symbol_body_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "InstanceKlass") == 0) {
|
||||
if (strcmp(field, "_class_loader_data") == 0) {
|
||||
_class_loader_data_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_methods") == 0) {
|
||||
_methods_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "java_lang_Class") == 0) {
|
||||
if (strcmp(field, "_klass_offset") == 0) {
|
||||
_class_klass_offset = **(int**)(entry + address_offset);
|
||||
int klass_offset = **(int**)(entry + address_offset);
|
||||
_klass = (jfieldID)(uintptr_t)(klass_offset << 2 | 2);
|
||||
}
|
||||
} else if (strcmp(type, "JavaThread") == 0) {
|
||||
if (strcmp(field, "_osthread") == 0) {
|
||||
_thread_osthread_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_anchor") == 0) {
|
||||
_thread_anchor_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_thread_state") == 0) {
|
||||
_thread_state_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "OSThread") == 0) {
|
||||
if (strcmp(field, "_thread_id") == 0) {
|
||||
_osthread_id_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "JavaFrameAnchor") == 0) {
|
||||
if (strcmp(field, "_last_Java_sp") == 0) {
|
||||
_anchor_sp_offset = *(int*)(entry + offset_offset);
|
||||
} else if (strcmp(field, "_last_Java_pc") == 0) {
|
||||
_anchor_pc_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "CodeBlob") == 0) {
|
||||
if (strcmp(field, "_frame_size") == 0) {
|
||||
_frame_size_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "Universe") == 0) {
|
||||
if (strcmp(field, "_collectedHeap") == 0) {
|
||||
_collected_heap_addr = **(char***)(entry + address_offset);
|
||||
}
|
||||
} else if (strcmp(type, "CollectedHeap") == 0) {
|
||||
if (strcmp(field, "_is_gc_active") == 0) {
|
||||
_is_gc_active_offset = *(int*)(entry + offset_offset);
|
||||
}
|
||||
} else if (strcmp(type, "PermGen") == 0) {
|
||||
_has_perm_gen = true;
|
||||
}
|
||||
@@ -90,12 +157,84 @@ void VMStructs::init(NativeCodeCache* libjvm) {
|
||||
entry += stride;
|
||||
}
|
||||
|
||||
_has_class_names = _klass_name_offset >= 0
|
||||
&& (_symbol_length_offset >= 0 || _symbol_length_and_refcount_offset >= 0)
|
||||
&& _symbol_body_offset >= 0
|
||||
&& _klass != NULL;
|
||||
}
|
||||
|
||||
void VMStructs::initJvmFunctions() {
|
||||
_get_stack_trace = (GetStackTraceFunc)_libjvm->findSymbol("_ZN8JvmtiEnv13GetStackTraceEP10JavaThreadiiP15_jvmtiFrameInfoPi");
|
||||
if (_get_stack_trace == NULL) {
|
||||
_get_stack_trace = (GetStackTraceFunc)_libjvm->findSymbol("_ZN8JvmtiEnv13GetStackTraceEP10JavaThreadiiP14jvmtiFrameInfoPi");
|
||||
}
|
||||
|
||||
_unsafe_park = (UnsafeParkFunc)_libjvm->findSymbol("Unsafe_Park");
|
||||
if (_unsafe_park == NULL) {
|
||||
// In some macOS builds of JDK 11 Unsafe_Park appears to have a C++ decorated name
|
||||
_unsafe_park = (UnsafeParkFunc)_libjvm->findSymbol("_ZL11Unsafe_ParkP7JNIEnv_P8_jobjecthl");
|
||||
}
|
||||
|
||||
if (_frame_size_offset >= 0) {
|
||||
_find_blob = (FindBlobFunc)_libjvm->findSymbol("_ZN9CodeCache16find_blob_unsafeEPv");
|
||||
if (_find_blob == NULL) {
|
||||
_find_blob = (FindBlobFunc)_libjvm->findSymbol("_ZN9CodeCache9find_blobEPv");
|
||||
}
|
||||
}
|
||||
|
||||
if (VM::hotspot_version() == 8 && _class_loader_data_offset >= 0 && _methods_offset >= 0 && _klass != NULL) {
|
||||
_lock_func = (LockFunc)_libjvm->findSymbol("_ZN7Monitor28lock_without_safepoint_checkEv");
|
||||
_unlock_func = (LockFunc)_libjvm->findSymbol("_ZN7Monitor6unlockEv");
|
||||
_has_class_loader_data = _lock_func != NULL && _unlock_func != NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void VMStructs::initThreadBridge(JNIEnv* env) {
|
||||
// Get eetop field - a bridge from Java Thread to VMThread
|
||||
if (_thread_osthread_offset >= 0 && _osthread_id_offset >= 0) {
|
||||
JNIEnv* env = VM::jni();
|
||||
jclass threadClass = env->FindClass("java/lang/Thread");
|
||||
if (threadClass != NULL) {
|
||||
_eetop = env->GetFieldID(threadClass, "eetop", "J");
|
||||
jthread thread;
|
||||
if (VM::jvmti()->GetCurrentThread(&thread) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
jclass thread_class = env->GetObjectClass(thread);
|
||||
_eetop = env->GetFieldID(thread_class, "eetop", "J");
|
||||
_tid = env->GetFieldID(thread_class, "tid", "J");
|
||||
if (_eetop == NULL || _tid == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
VMThread* vm_thread = VMThread::fromJavaThread(env, thread);
|
||||
if (vm_thread == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for JDK-8132510: it's not safe to call GetEnv() inside a signal handler
|
||||
// since JDK 9, so we do it only for threads already registered in ThreadLocalStorage
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
if (pthread_getspecific((pthread_key_t)i) == vm_thread) {
|
||||
_tls_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tls_index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_env_offset = (intptr_t)env - (intptr_t)vm_thread;
|
||||
_has_thread_bridge = true;
|
||||
}
|
||||
|
||||
void VMStructs::initLogging(JNIEnv* env) {
|
||||
// Workaround for JDK-8238460
|
||||
if (VM::hotspot_version() >= 15) {
|
||||
VMManagement* management = VM::management();
|
||||
if (management != NULL) {
|
||||
management->ExecuteDiagnosticCommand(env, env->NewStringUTF("VM.log what=jni+resolve=error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VMThread* VMThread::current() {
|
||||
return (VMThread*)pthread_getspecific((pthread_key_t)_tls_index);
|
||||
}
|
||||
|
||||
179
src/vmStructs.h
179
src/vmStructs.h
@@ -24,14 +24,47 @@
|
||||
|
||||
class VMStructs {
|
||||
protected:
|
||||
static NativeCodeCache* _libjvm;
|
||||
|
||||
static bool _has_class_names;
|
||||
static bool _has_class_loader_data;
|
||||
static bool _has_thread_bridge;
|
||||
static bool _has_perm_gen;
|
||||
|
||||
static int _klass_name_offset;
|
||||
static int _symbol_length_offset;
|
||||
static int _symbol_length_and_refcount_offset;
|
||||
static int _symbol_body_offset;
|
||||
static int _class_klass_offset;
|
||||
static int _class_loader_data_offset;
|
||||
static int _methods_offset;
|
||||
static int _thread_osthread_offset;
|
||||
static int _thread_anchor_offset;
|
||||
static int _thread_state_offset;
|
||||
static int _osthread_id_offset;
|
||||
static bool _has_perm_gen;
|
||||
static int _anchor_sp_offset;
|
||||
static int _anchor_pc_offset;
|
||||
static int _frame_size_offset;
|
||||
static int _is_gc_active_offset;
|
||||
static char* _collected_heap_addr;
|
||||
|
||||
static jfieldID _eetop;
|
||||
static jfieldID _tid;
|
||||
static jfieldID _klass;
|
||||
static int _tls_index;
|
||||
static intptr_t _env_offset;
|
||||
|
||||
typedef void* (*FindBlobFunc)(const void*);
|
||||
static FindBlobFunc _find_blob;
|
||||
|
||||
typedef void (*LockFunc)(void*);
|
||||
static LockFunc _lock_func;
|
||||
static LockFunc _unlock_func;
|
||||
|
||||
static uintptr_t readSymbol(const char* symbol_name);
|
||||
static void initOffsets();
|
||||
static void initJvmFunctions();
|
||||
static void initThreadBridge(JNIEnv* env);
|
||||
static void initLogging(JNIEnv* env);
|
||||
|
||||
const char* at(int offset) {
|
||||
return (const char*)this + offset;
|
||||
@@ -40,15 +73,47 @@ class VMStructs {
|
||||
public:
|
||||
static void init(NativeCodeCache* libjvm);
|
||||
|
||||
static bool available() {
|
||||
return _klass_name_offset >= 0
|
||||
&& _symbol_length_offset >= 0
|
||||
&& _symbol_body_offset >= 0
|
||||
&& _class_klass_offset >= 0;
|
||||
static NativeCodeCache* libjvm() {
|
||||
return _libjvm;
|
||||
}
|
||||
|
||||
static bool hasPermGen() {
|
||||
return _has_perm_gen;
|
||||
static bool hasClassNames() {
|
||||
return _has_class_names;
|
||||
}
|
||||
|
||||
static bool hasClassLoaderData() {
|
||||
return _has_class_loader_data;
|
||||
}
|
||||
|
||||
static bool hasThreadBridge() {
|
||||
return _has_thread_bridge;
|
||||
}
|
||||
|
||||
typedef jvmtiError (*GetStackTraceFunc)(void* self, void* thread,
|
||||
jint start_depth, jint max_frame_count,
|
||||
jvmtiFrameInfo* frame_buffer, jint* count_ptr);
|
||||
static GetStackTraceFunc _get_stack_trace;
|
||||
|
||||
typedef void (JNICALL *UnsafeParkFunc)(JNIEnv*, jobject, jboolean, jlong);
|
||||
static UnsafeParkFunc _unsafe_park;
|
||||
};
|
||||
|
||||
|
||||
class MethodList {
|
||||
public:
|
||||
enum { SIZE = 8 };
|
||||
|
||||
private:
|
||||
intptr_t _method[SIZE];
|
||||
int _ptr;
|
||||
MethodList* _next;
|
||||
int _padding;
|
||||
|
||||
public:
|
||||
MethodList(MethodList* next) : _ptr(0), _next(next), _padding(0) {
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
_method[i] = 0x37;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,7 +121,11 @@ class VMStructs {
|
||||
class VMSymbol : VMStructs {
|
||||
public:
|
||||
unsigned short length() {
|
||||
return *(unsigned short*) at(_symbol_length_offset);
|
||||
if (_symbol_length_offset >= 0) {
|
||||
return *(unsigned short*) at(_symbol_length_offset);
|
||||
} else {
|
||||
return *(unsigned int*) at(_symbol_length_and_refcount_offset) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
const char* body() {
|
||||
@@ -64,8 +133,39 @@ class VMSymbol : VMStructs {
|
||||
}
|
||||
};
|
||||
|
||||
class ClassLoaderData : VMStructs {
|
||||
private:
|
||||
void* mutex() {
|
||||
return *(void**) at(sizeof(uintptr_t) * 3);
|
||||
}
|
||||
|
||||
public:
|
||||
void lock() {
|
||||
_lock_func(mutex());
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
_unlock_func(mutex());
|
||||
}
|
||||
|
||||
MethodList** methodList() {
|
||||
return (MethodList**) at(sizeof(uintptr_t) * 6 + 8);
|
||||
}
|
||||
};
|
||||
|
||||
class VMKlass : VMStructs {
|
||||
public:
|
||||
static VMKlass* fromJavaClass(JNIEnv* env, jclass cls) {
|
||||
if (_has_perm_gen) {
|
||||
jobject klassOop = env->GetObjectField(cls, _klass);
|
||||
return (VMKlass*)(*(uintptr_t**)klassOop + 2);
|
||||
} else if (sizeof(VMKlass*) == 8) {
|
||||
return (VMKlass*)(uintptr_t)env->GetLongField(cls, _klass);
|
||||
} else {
|
||||
return (VMKlass*)(uintptr_t)env->GetIntField(cls, _klass);
|
||||
}
|
||||
}
|
||||
|
||||
static VMKlass* fromHandle(uintptr_t handle) {
|
||||
if (_has_perm_gen) {
|
||||
// On JDK 7 KlassHandle is a pointer to klassOop, hence one more indirection
|
||||
@@ -78,29 +178,72 @@ class VMKlass : VMStructs {
|
||||
VMSymbol* name() {
|
||||
return *(VMSymbol**) at(_klass_name_offset);
|
||||
}
|
||||
};
|
||||
|
||||
class java_lang_Class : VMStructs {
|
||||
public:
|
||||
VMKlass* klass() {
|
||||
return *(VMKlass**) at(_class_klass_offset);
|
||||
ClassLoaderData* classLoaderData() {
|
||||
return *(ClassLoaderData**) at(_class_loader_data_offset);
|
||||
}
|
||||
|
||||
int methodCount() {
|
||||
int* methods = *(int**) at(_methods_offset);
|
||||
return methods == NULL ? 0 : *methods & 0xffff;
|
||||
}
|
||||
};
|
||||
|
||||
class VMThread : VMStructs {
|
||||
public:
|
||||
static bool available() {
|
||||
return _eetop != NULL;
|
||||
}
|
||||
static VMThread* current();
|
||||
|
||||
static VMThread* fromJavaThread(JNIEnv* env, jthread thread) {
|
||||
return (VMThread*)(uintptr_t)env->GetLongField(thread, _eetop);
|
||||
}
|
||||
|
||||
static VMThread* fromEnv(JNIEnv* env) {
|
||||
return (VMThread*)((intptr_t)env - _env_offset);
|
||||
}
|
||||
|
||||
static jlong javaThreadId(JNIEnv* env, jthread thread) {
|
||||
return env->GetLongField(thread, _tid);
|
||||
}
|
||||
|
||||
static bool hasNativeId() {
|
||||
return _thread_osthread_offset >= 0 && _osthread_id_offset >= 0;
|
||||
}
|
||||
|
||||
int osThreadId() {
|
||||
const char* osthread = *(const char**) at(_thread_osthread_offset);
|
||||
return *(int*)(osthread + _osthread_id_offset);
|
||||
}
|
||||
|
||||
int state() {
|
||||
return _thread_state_offset >= 0 ? *(int*) at(_thread_state_offset) : 0;
|
||||
}
|
||||
|
||||
uintptr_t& lastJavaSP() {
|
||||
return *(uintptr_t*) (at(_thread_anchor_offset) + _anchor_sp_offset);
|
||||
}
|
||||
|
||||
uintptr_t& lastJavaPC() {
|
||||
return *(uintptr_t*) (at(_thread_anchor_offset) + _anchor_pc_offset);
|
||||
}
|
||||
};
|
||||
|
||||
class RuntimeStub : VMStructs {
|
||||
public:
|
||||
static RuntimeStub* findBlob(const void* pc) {
|
||||
return _find_blob != NULL ? (RuntimeStub*)_find_blob(pc) : NULL;
|
||||
}
|
||||
|
||||
int frameSize() {
|
||||
return *(int*) at(_frame_size_offset);
|
||||
}
|
||||
};
|
||||
|
||||
class CollectedHeap : VMStructs {
|
||||
public:
|
||||
static bool isGCActive() {
|
||||
return _collected_heap_addr != NULL && _is_gc_active_offset >= 0 &&
|
||||
_collected_heap_addr[_is_gc_active_offset] != 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _VMSTRUCTS_H
|
||||
|
||||
@@ -14,41 +14,73 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include "wallClock.h"
|
||||
#include "os.h"
|
||||
#include "profiler.h"
|
||||
|
||||
|
||||
// Maximum number of threads sampled in one iteration. This limit serves as a throttle
|
||||
// when generating profiling signals. Otherwise applications with too many threads may
|
||||
// suffer from a big profiling overhead. Also, keeping this limit low enough helps
|
||||
// to avoid contention on a spin lock inside Profiler::recordSample().
|
||||
const int THREADS_PER_TICK = 8;
|
||||
|
||||
// Set the hard limit for thread walking interval to 100 microseconds.
|
||||
// Smaller intervals are practically unusable due to large overhead.
|
||||
const long MIN_INTERVAL = 100000;
|
||||
|
||||
// Stop profiling thread with this signal. The same signal is used inside JDK to interrupt I/O operations.
|
||||
const int WAKEUP_SIGNAL = SIGIO;
|
||||
|
||||
|
||||
long WallClock::_interval;
|
||||
bool WallClock::_sample_idle_threads;
|
||||
|
||||
void WallClock::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, NULL);
|
||||
ExecutionEvent event;
|
||||
event._thread_state = _sample_idle_threads ? Profiler::_instance.getThreadState(ucontext) : THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
void WallClock::wakeupHandler(int signo) {
|
||||
// Dummy handler for interrupting syscalls
|
||||
}
|
||||
|
||||
long WallClock::adjustInterval(long interval, int thread_count) {
|
||||
if (thread_count > THREADS_PER_TICK) {
|
||||
interval /= (thread_count + THREADS_PER_TICK - 1) / THREADS_PER_TICK;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
void WallClock::sleep(long interval) {
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = interval / 1000000000;
|
||||
timeout.tv_nsec = interval % 1000000000;
|
||||
|
||||
nanosleep(&timeout, NULL);
|
||||
}
|
||||
|
||||
Error WallClock::start(Arguments& args) {
|
||||
if (args._interval < 0) {
|
||||
return Error("interval must be positive");
|
||||
}
|
||||
_interval = args._interval ? args._interval : DEFAULT_INTERVAL;
|
||||
_sample_idle_threads = strcmp(args._event, EVENT_WALL) == 0;
|
||||
|
||||
OS::installSignalHandler(SIGPROF, signalHandler);
|
||||
_sample_idle_threads = strcmp(args._event_desc, EVENT_WALL) == 0;
|
||||
|
||||
if (pipe(_pipefd) != 0) {
|
||||
return Error("Unable to create poll pipe");
|
||||
}
|
||||
// Increase default interval for wall clock mode due to larger number of sampled threads
|
||||
_interval = args._interval ? args._interval : (_sample_idle_threads ? DEFAULT_INTERVAL * 5 : DEFAULT_INTERVAL);
|
||||
|
||||
OS::installSignalHandler(SIGVTALRM, signalHandler);
|
||||
OS::installSignalHandler(WAKEUP_SIGNAL, NULL, wakeupHandler);
|
||||
|
||||
_running = true;
|
||||
|
||||
if (pthread_create(&_thread, NULL, threadEntry, this) != 0) {
|
||||
close(_pipefd[1]);
|
||||
close(_pipefd[0]);
|
||||
return Error("Unable to create timer thread");
|
||||
}
|
||||
|
||||
@@ -56,39 +88,60 @@ Error WallClock::start(Arguments& args) {
|
||||
}
|
||||
|
||||
void WallClock::stop() {
|
||||
char val = 1;
|
||||
ssize_t r = write(_pipefd[1], &val, sizeof(val));
|
||||
(void)r;
|
||||
|
||||
close(_pipefd[1]);
|
||||
_running = false;
|
||||
pthread_kill(_thread, WAKEUP_SIGNAL);
|
||||
pthread_join(_thread, NULL);
|
||||
close(_pipefd[0]);
|
||||
}
|
||||
|
||||
void WallClock::timerLoop() {
|
||||
ThreadList* thread_list = NULL;
|
||||
|
||||
int self = OS::threadId();
|
||||
ThreadFilter* thread_filter = Profiler::_instance.threadFilter();
|
||||
bool thread_filter_enabled = thread_filter->enabled();
|
||||
bool sample_idle_threads = _sample_idle_threads;
|
||||
struct pollfd fds = {_pipefd[0], POLLIN, 0};
|
||||
int timeout = _interval > 1000000 ? (int)(_interval / 1000000) : 1;
|
||||
|
||||
while (poll(&fds, 1, timeout) == 0) {
|
||||
if (thread_list == NULL) {
|
||||
thread_list = OS::listThreads();
|
||||
ThreadList* thread_list = OS::listThreads();
|
||||
long long next_cycle_time = OS::nanotime();
|
||||
|
||||
while (_running) {
|
||||
if (!_enabled) {
|
||||
sleep(_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sample_idle_threads) {
|
||||
// Try to keep the wall clock interval stable, regardless of the number of profiled threads
|
||||
int estimated_thread_count = thread_filter_enabled ? thread_filter->size() : thread_list->size();
|
||||
next_cycle_time += adjustInterval(_interval, estimated_thread_count);
|
||||
}
|
||||
|
||||
for (int count = 0; count < THREADS_PER_TICK; ) {
|
||||
int thread_id = thread_list->next();
|
||||
if (thread_id == -1) {
|
||||
delete thread_list;
|
||||
thread_list = NULL;
|
||||
thread_list->rewind();
|
||||
break;
|
||||
}
|
||||
if (thread_id != self && (sample_idle_threads || OS::isThreadRunning(thread_id))) {
|
||||
OS::sendSignalToThread(thread_id, SIGPROF);
|
||||
count++;
|
||||
|
||||
if (thread_id == self || (thread_filter_enabled && !thread_filter->accept(thread_id))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sample_idle_threads || OS::threadState(thread_id) == THREAD_RUNNING) {
|
||||
if (OS::sendSignalToThread(thread_id, SIGVTALRM)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_idle_threads) {
|
||||
long long current_time = OS::nanotime();
|
||||
if (next_cycle_time - current_time > MIN_INTERVAL) {
|
||||
sleep(next_cycle_time - current_time);
|
||||
} else {
|
||||
next_cycle_time = current_time + MIN_INTERVAL;
|
||||
sleep(MIN_INTERVAL);
|
||||
}
|
||||
} else {
|
||||
sleep(_interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class WallClock : public Engine {
|
||||
static long _interval;
|
||||
static bool _sample_idle_threads;
|
||||
|
||||
int _pipefd[2];
|
||||
volatile bool _running;
|
||||
pthread_t _thread;
|
||||
|
||||
void timerLoop();
|
||||
@@ -39,10 +39,14 @@ class WallClock : public Engine {
|
||||
}
|
||||
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void wakeupHandler(int signo);
|
||||
|
||||
static long adjustInterval(long interval, int thread_count);
|
||||
static void sleep(long interval);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return "wall";
|
||||
return _sample_idle_threads ? EVENT_WALL : EVENT_CPU;
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
|
||||
21
test/LoadLibraryTest.java
Normal file
21
test/LoadLibraryTest.java
Normal file
@@ -0,0 +1,21 @@
|
||||
import java.lang.management.ClassLoadingMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
||||
class LoadLibraryTest {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
for (int i = 0; i < 200; i++) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
|
||||
// Late load of libmanagement.so
|
||||
ClassLoadingMXBean bean = ManagementFactory.getClassLoadingMXBean();
|
||||
|
||||
long n = 0;
|
||||
while (n >= 0) {
|
||||
n += bean.getLoadedClassCount();
|
||||
n += bean.getTotalLoadedClassCount();
|
||||
n += bean.getUnloadedClassCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import java.util.Scanner;
|
||||
import java.io.File;
|
||||
|
||||
class Target {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class ThreadsTarget {
|
||||
public static void main(String[] args) {
|
||||
new Thread(new Runnable() {
|
||||
@@ -17,17 +19,17 @@ public class ThreadsTarget {
|
||||
|
||||
static void methodForThreadEarlyEnd() {
|
||||
long now = System.currentTimeMillis();
|
||||
long counter = 0;
|
||||
BigInteger counter = BigInteger.ZERO;
|
||||
while (System.currentTimeMillis() - now < 300) {
|
||||
counter++;
|
||||
counter = counter.nextProbablePrime();
|
||||
}
|
||||
}
|
||||
|
||||
static void methodForRenamedThread() {
|
||||
long now = System.currentTimeMillis();
|
||||
long counter = 0;
|
||||
BigInteger counter = BigInteger.ZERO;
|
||||
while (System.currentTimeMillis() - now < 1000) {
|
||||
counter++;
|
||||
counter = counter.nextProbablePrime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
test/load-library-test.sh
Executable file
35
test/load-library-test.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e # exit on any failure
|
||||
set -x # print all executed lines
|
||||
|
||||
if [ -z "${JAVA_HOME}" ]; then
|
||||
echo "JAVA_HOME is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
cd $(dirname $0)
|
||||
|
||||
if [ "LoadLibraryTest.class" -ot "LoadLibraryTest.java" ]; then
|
||||
${JAVA_HOME}/bin/javac LoadLibraryTest.java
|
||||
fi
|
||||
|
||||
${JAVA_HOME}/bin/java -agentpath:../build/libasyncProfiler.so LoadLibraryTest &
|
||||
|
||||
FILENAME=/tmp/java.trace
|
||||
JAVAPID=$!
|
||||
|
||||
sleep 1 # allow the Java runtime to initialize
|
||||
../profiler.sh -f $FILENAME -o collapsed -d 5 -i 1ms $JAVAPID
|
||||
|
||||
kill $JAVAPID
|
||||
|
||||
function assert_string() {
|
||||
if ! grep -q "$1" $FILENAME; then
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_string "Java_sun_management"
|
||||
)
|
||||
@@ -17,7 +17,7 @@ fi
|
||||
|
||||
FILENAME=/tmp/java.trace
|
||||
|
||||
${JAVA_HOME}/bin/java -agentpath:../build/libasyncProfiler.so=start,collapsed,threads,file=$FILENAME ThreadsTarget
|
||||
${JAVA_HOME}/bin/java -agentpath:../build/libasyncProfiler.so=start,event=cpu,collapsed,threads,file=$FILENAME ThreadsTarget
|
||||
|
||||
# wait for normal termination
|
||||
|
||||
|
||||
Reference in New Issue
Block a user