mirror of
https://github.com/async-profiler/async-profiler.git
synced 2026-04-28 19:03:33 +00:00
Compare commits
24 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 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -16,6 +16,16 @@
|
||||
### 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
|
||||
|
||||
25
Makefile
25
Makefile
@@ -1,18 +1,24 @@
|
||||
PROFILER_VERSION=2.0-b1
|
||||
JATTACH_VERSION=1.5
|
||||
JAVAC_RELEASE_VERSION=6
|
||||
|
||||
PACKAGE_NAME=async-profiler-$(PROFILER_VERSION)-$(OS_TAG)-$(ARCH_TAG)
|
||||
PACKAGE_DIR=/tmp/$(PACKAGE_NAME)
|
||||
LIB_PROFILER=libasyncProfiler.so
|
||||
|
||||
LIB_PROFILER=libasyncProfiler.$(SOEXT)
|
||||
LIB_PROFILER_SO=libasyncProfiler.so
|
||||
JATTACH=jattach
|
||||
API_JAR=async-profiler.jar
|
||||
CONVERTER_JAR=converter.jar
|
||||
CFLAGS=-O3 -fno-omit-frame-pointer
|
||||
CXXFLAGS=-O3 -fno-omit-frame-pointer
|
||||
|
||||
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)
|
||||
@@ -26,10 +32,12 @@ OS:=$(shell uname -s)
|
||||
ifeq ($(OS), Darwin)
|
||||
CXXFLAGS += -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE
|
||||
INCLUDES += -I$(JAVA_HOME)/include/darwin
|
||||
SOEXT=dylib
|
||||
OS_TAG=macos
|
||||
else
|
||||
LIBS += -lrt
|
||||
INCLUDES += -I$(JAVA_HOME)/include/linux
|
||||
SOEXT=so
|
||||
ifeq ($(findstring musl,$(shell ldd /bin/ls)),musl)
|
||||
OS_TAG=linux-musl
|
||||
else
|
||||
@@ -61,18 +69,21 @@ release: build $(PACKAGE_NAME).tar.gz
|
||||
|
||||
$(PACKAGE_NAME).tar.gz: build/$(LIB_PROFILER) build/$(JATTACH) \
|
||||
build/$(API_JAR) build/$(CONVERTER_JAR) \
|
||||
profiler.sh LICENSE NOTICE *.md
|
||||
profiler.sh LICENSE *.md
|
||||
mkdir -p $(PACKAGE_DIR)
|
||||
cp -r build profiler.sh LICENSE NOTICE *.md $(PACKAGE_DIR)
|
||||
cp -RP build profiler.sh LICENSE *.md $(PACKAGE_DIR)
|
||||
chmod -R 755 $(PACKAGE_DIR)
|
||||
chmod 644 $(PACKAGE_DIR)/LICENSE $(PACKAGE_DIR)/NOTICE $(PACKAGE_DIR)/*.md $(PACKAGE_DIR)/build/*.jar
|
||||
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): $(SOURCES) $(HEADERS)
|
||||
build/$(LIB_PROFILER_SO): $(SOURCES) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) -DPROFILER_VERSION=\"$(PROFILER_VERSION)\" $(INCLUDES) -fPIC -shared -o $@ $(SOURCES) $(LIBS)
|
||||
|
||||
build/$(JATTACH): src/jattach/jattach.c
|
||||
|
||||
509
README.md
509
README.md
@@ -1,5 +1,7 @@
|
||||
# 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
|
||||
@@ -11,19 +13,23 @@ async-profiler can trace the following kinds of events:
|
||||
- Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc.
|
||||
- Allocations in Java Heap
|
||||
- Contented lock attempts, including both Java object monitors and ReentrantLocks
|
||||
|
||||
## Usage
|
||||
|
||||
See our [Wiki](https://github.com/jvm-profiling-tools/async-profiler/wiki) or [3 hours playlist](https://www.youtube.com/playlist?list=PLNCLTEx3B8h4Yo_WvKWdLvI9mj1XpTKBr) to learn about all set of features.
|
||||
|
||||
## Download
|
||||
|
||||
Stable release (1.8.2):
|
||||
Latest release (1.8.3):
|
||||
|
||||
- Linux x64 (glibc): [async-profiler-1.8.2-linux-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.2/async-profiler-1.8.2-linux-x64.tar.gz)
|
||||
- Linux x86 (glibc): [async-profiler-1.8.2-linux-x86.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.2/async-profiler-1.8.2-linux-x86.tar.gz)
|
||||
- Linux x64 (musl): [async-profiler-1.8.2-linux-musl-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.2/async-profiler-1.8.2-linux-musl-x64.tar.gz)
|
||||
- Linux ARM: [async-profiler-1.8.2-linux-arm.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.2/async-profiler-1.8.2-linux-arm.tar.gz)
|
||||
- Linux AArch64: [async-profiler-1.8.2-linux-aarch64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.2/async-profiler-1.8.2-linux-aarch64.tar.gz)
|
||||
- macOS x64: [async-profiler-1.8.2-macos-x64.tar.gz](https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.8.2/async-profiler-1.8.2-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 (2.0-b1):
|
||||
[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)
|
||||
@@ -37,498 +43,11 @@ Early access (2.0-b1):
|
||||
|
||||
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.
|
||||
|
||||
If you wish to resolve frames within `libjvm`, the [debug symbols](#installing-debug-symbols) are required.
|
||||
|
||||
## ALLOCATION profiling
|
||||
|
||||
Instead of detecting CPU-consuming code, the profiler can be configured
|
||||
to collect call sites where the largest amount of heap memory is allocated.
|
||||
|
||||
async-profiler does not use intrusive techniques like bytecode instrumentation
|
||||
or expensive DTrace probes which have significant performance impact.
|
||||
It also does not affect Escape Analysis or prevent from JIT optimizations
|
||||
like allocation elimination. Only actual heap allocations are measured.
|
||||
|
||||
The profiler features TLAB-driven sampling. It relies on HotSpot-specific
|
||||
callbacks to receive two kinds of notifications:
|
||||
- when an object is allocated in a newly created TLAB (aqua frames in a Flame Graph);
|
||||
- when an object is allocated on a slow path outside TLAB (brown frames).
|
||||
|
||||
This means not each allocation is counted, but only allocations every _N_ kB,
|
||||
where _N_ is the average size of TLAB. This makes heap sampling very cheap
|
||||
and suitable for production. On the other hand, the collected data
|
||||
may be incomplete, though in practice it will often reflect the top allocation
|
||||
sources.
|
||||
|
||||
Sampling interval can be adjusted with `-i` option.
|
||||
For example, `-i 500k` will take one sample after 500 KB of allocated
|
||||
space on average. However, intervals less than TLAB size will not take effect.
|
||||
|
||||
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.
|
||||
|
||||
### Installing Debug Symbols
|
||||
|
||||
The allocation profiler requires HotSpot debug symbols. Oracle JDK already has them
|
||||
embedded in `libjvm.so`, but in OpenJDK builds they are typically shipped
|
||||
in a separate package. For example, to install OpenJDK debug symbols on
|
||||
Debian / Ubuntu, run:
|
||||
```
|
||||
# apt install openjdk-8-dbg
|
||||
```
|
||||
or for OpenJDK 11:
|
||||
```
|
||||
# apt install openjdk-11-dbg
|
||||
```
|
||||
|
||||
On CentOS, RHEL and some other RPM-based distributions, this could be done with
|
||||
[debuginfo-install](http://man7.org/linux/man-pages/man1/debuginfo-install.1.html) utility:
|
||||
```
|
||||
# debuginfo-install java-1.8.0-openjdk
|
||||
```
|
||||
|
||||
On Gentoo the `icedtea` OpenJDK package can be built with the per-package setting
|
||||
`FEATURES="nostrip"` to retain symbols.
|
||||
|
||||
The `gdb` tool can be used to verify if the debug symbols are properly installed for the `libjvm` library.
|
||||
For example on Linux:
|
||||
```
|
||||
$ gdb $JAVA_HOME/lib/server/libjvm.so -ex 'info address UseG1GC'
|
||||
```
|
||||
This command's output will either contain `Symbol "UseG1GC" is at 0xxxxx` or `No symbol "UseG1GC" in current context`.
|
||||
|
||||
## Wall-clock profiling
|
||||
|
||||
`-e wall` option tells async-profiler to sample all threads equally every given
|
||||
period of time regardless of thread status: Running, Sleeping or Blocked.
|
||||
For instance, this can be helpful when profiling application start-up time.
|
||||
|
||||
Wall-clock profiler is most useful in per-thread mode: `-t`.
|
||||
|
||||
Example: `./profiler.sh -e wall -t -i 5ms -f result.html 8983`
|
||||
|
||||
## Java method profiling
|
||||
|
||||
`-e ClassName.methodName` option instruments the given Java method
|
||||
in order to record all invocations of this method with the stack traces.
|
||||
|
||||
Example: `-e java.util.Properties.getProperty` will profile all places
|
||||
where `getProperty` method is called from.
|
||||
|
||||
Only non-native Java methods are supported. To profile a native method,
|
||||
use hardware breakpoint event instead, e.g. `-e Java_java_lang_Throwable_fillInStackTrace`
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
The following may be used in lieu of the `pid` (8983):
|
||||
|
||||
- The keyword `jps`, which will use the most recently launched Java process.
|
||||
- The application name as it appears in the `jps` output: e.g. `Computey`
|
||||
|
||||
Alternatively, you may specify `-d` (duration) argument to profile
|
||||
the application for a fixed period of time with a single command.
|
||||
|
||||
```
|
||||
$ ./profiler.sh -d 30 8983
|
||||
```
|
||||
|
||||
By default, the profiling frequency is 100Hz (every 10ms of CPU time).
|
||||
Here is a sample of the output printed to the Java application's terminal:
|
||||
|
||||
```
|
||||
--- Execution profile ---
|
||||
Total samples: 687
|
||||
Unknown (native): 1 (0.15%)
|
||||
|
||||
--- 6790000000 (98.84%) ns, 679 samples
|
||||
[ 0] Primes.isPrime
|
||||
[ 1] Primes.primesThread
|
||||
[ 2] Primes.access$000
|
||||
[ 3] Primes$1.run
|
||||
[ 4] java.lang.Thread.run
|
||||
|
||||
... a lot of output omitted for brevity ...
|
||||
|
||||
ns percent samples top
|
||||
---------- ------- ------- ---
|
||||
6790000000 98.84% 679 Primes.isPrime
|
||||
40000000 0.58% 4 __do_softirq
|
||||
|
||||
... more output omitted ...
|
||||
```
|
||||
|
||||
This indicates that the hottest method was `Primes.isPrime`, and the hottest
|
||||
call stack leading to it comes from `Primes.primesThread`.
|
||||
|
||||
## Launching as an Agent
|
||||
|
||||
If you need to profile some code as soon as the JVM starts up, instead of using the `profiler.sh` script,
|
||||
it is possible to attach async-profiler as an agent on the command line. For example:
|
||||
|
||||
```
|
||||
$ java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ...
|
||||
```
|
||||
|
||||
Agent library is configured through the JVMTI argument interface.
|
||||
The format of the arguments string is described
|
||||
[in the source code](https://github.com/jvm-profiling-tools/async-profiler/blob/v1.8.2/src/arguments.cpp#L49).
|
||||
The `profiler.sh` script actually converts command line arguments to that format.
|
||||
|
||||
For instance, `-e alloc` is converted to `event=alloc`, `-f profile.html`
|
||||
is converted to `file=profile.html` and so on. But some arguments are processed
|
||||
directly by `profiler.sh` script. E.g. `-d 5` results in 3 actions:
|
||||
attaching profiler agent with start command, sleeping for 5 seconds,
|
||||
and then attaching the agent again with stop command.
|
||||
|
||||
## Flame Graph visualization
|
||||
|
||||
async-profiler provides out-of-the-box [Flame Graph](https://github.com/BrendanGregg/FlameGraph) support.
|
||||
Specify `-o html` argument to dump profiling results as an interactive HTML Flame Graph.
|
||||
Also, HTML output format will be chosen automatically if the target filename ends with `.html`.
|
||||
|
||||
```
|
||||
$ jps
|
||||
9234 Jps
|
||||
8983 Computey
|
||||
$ ./profiler.sh -d 30 -f /tmp/flamegraph.html 8983
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Profiler Options
|
||||
|
||||
The following is a complete list of the command-line options accepted by
|
||||
`profiler.sh` script.
|
||||
|
||||
* `start` - starts profiling in semi-automatic mode, i.e. profiler will run
|
||||
until `stop` command is explicitly called.
|
||||
|
||||
* `resume` - starts or resumes earlier profiling session that has been stopped.
|
||||
All the collected data remains valid. The profiling options are not preserved
|
||||
between sessions, and should be specified again.
|
||||
|
||||
* `stop` - stops profiling and prints the report.
|
||||
|
||||
* `check` - check if the specified profiling event is available.
|
||||
|
||||
* `status` - prints profiling status: whether profiler is active and
|
||||
for how long.
|
||||
|
||||
* `list` - show the list of available profiling events. This option still
|
||||
requires PID, since supported events may differ depending on JVM version.
|
||||
|
||||
* `-d N` - the profiling duration, in seconds. If no `start`, `resume`, `stop`
|
||||
or `status` option is given, the profiler will run for the specified period
|
||||
of time and then automatically stop.
|
||||
Example: `./profiler.sh -d 30 8983`
|
||||
|
||||
* `-e event` - the profiling event: `cpu`, `alloc`, `lock`, `cache-misses` etc.
|
||||
Use `list` to see the complete list of available events.
|
||||
|
||||
In allocation profiling mode the top frame of every call trace is the class
|
||||
of the allocated object, and the counter is the heap pressure (the total size
|
||||
of allocated TLABs or objects outside TLAB).
|
||||
|
||||
In lock profiling mode the top frame is the class of lock/monitor, and
|
||||
the counter is number of nanoseconds it took to enter this lock/monitor.
|
||||
|
||||
Two special event types are supported on Linux: hardware breakpoints
|
||||
and kernel tracepoints:
|
||||
- `-e mem:<func>[:rwx]` sets read/write/exec breakpoint at function
|
||||
`<func>`. The format of `mem` event is the same as in `perf-record`.
|
||||
Execution breakpoints can be also specified by the function name,
|
||||
e.g. `-e malloc` will trace all calls of native `malloc` function.
|
||||
- `-e trace:<id>` sets a kernel tracepoint. It is possible to specify
|
||||
tracepoint symbolic name, e.g. `-e syscalls:sys_enter_open` will trace
|
||||
all `open` syscalls.
|
||||
|
||||
* `-i N` - sets the profiling interval in nanoseconds or in other units,
|
||||
if N is followed by `ms` (for milliseconds), `us` (for microseconds)
|
||||
or `s` (for seconds). Only CPU active time is counted. No samples
|
||||
are collected while CPU is idle. The default is 10000000 (10ms).
|
||||
Example: `./profiler.sh -i 500us 8983`
|
||||
|
||||
* `-j N` - sets the Java stack profiling depth. This option will be ignored if N is greater
|
||||
than default 2048.
|
||||
Example: `./profiler.sh -j 30 8983`
|
||||
|
||||
* `-t` - profile threads separately. Each stack trace will end with a frame
|
||||
that denotes a single thread.
|
||||
Example: `./profiler.sh -t 8983`
|
||||
|
||||
* `-s` - print simple class names instead of FQN.
|
||||
|
||||
* `-g` - print method signatures.
|
||||
|
||||
* `-a` - annotate Java method names by adding `_[j]` suffix.
|
||||
|
||||
* `-o fmt` - specifies what information to dump when profiling ends.
|
||||
`fmt` can be one 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.
|
||||
- `html[=C]` - produce Flame Graph in HTML 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.
|
||||
|
||||
`summary`, `traces` and `flat` can be combined together.
|
||||
The default format is `summary,traces=200,flat=200`.
|
||||
|
||||
* `-I include`, `-X exclude` - filter stack traces by the given pattern(s).
|
||||
`-I` defines the name pattern that *must* be present in the stack traces,
|
||||
while `-X` is the pattern that *must not* occur in any of stack traces in the output.
|
||||
`-I` and `-X` options can be specified multiple times. A pattern may begin or end with
|
||||
a star `*` that denotes any (possibly empty) sequence of characters.
|
||||
Example: `./profiler.sh -I 'Primes.*' -I 'java/*' -X '*Unsafe.park*' 8983`
|
||||
|
||||
* `--title TITLE`, `--minwidth PX`, `--reverse` - FlameGraph parameters.
|
||||
Example: `./profiler.sh -f profile.html --title "Sample CPU profile" --minwidth 0.5 8983`
|
||||
|
||||
* `-f FILENAME` - the file name to dump the profile information to.
|
||||
`%p` in the file name is expanded to the PID of the target JVM;
|
||||
`%t` - to the timestamp at the time of command invocation.
|
||||
Example: `./profiler.sh -o collapsed -f /tmp/traces-%t.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.
|
||||
|
||||
* `--cstack MODE` - how to traverse native frames (C stack). Possible modes are
|
||||
`fp` (Frame Pointer), `lbr` (Last Branch Record, available on Haswell since Linux 4.1),
|
||||
and `no` (do not collect C stack).
|
||||
|
||||
By default, C stack is shown in cpu, itimer, wall-clock and perf-events profiles.
|
||||
Java-level events like `alloc` and `lock` collect only Java stack.
|
||||
|
||||
* `-v`, `--version` - prints the version of profiler library. If PID is specified,
|
||||
gets the version of the library loaded into the given process.
|
||||
|
||||
## Profiling Java in a container
|
||||
|
||||
It is possible to profile Java processes running in a Docker or LXC container
|
||||
both from within a container and from the host system.
|
||||
|
||||
When profiling from the host, `pid` should be the Java process ID in the host
|
||||
namespace. Use `ps aux | grep java` or `docker top <container>` to find
|
||||
the process ID.
|
||||
|
||||
async-profiler should be run from the host by a privileged user - it will
|
||||
automatically switch to the proper pid/mount namespace and change
|
||||
user credentials to match the target process. Also make sure that
|
||||
the target container can access `libasyncProfiler.so` by the same
|
||||
absolute path as on the host.
|
||||
|
||||
By default, Docker container restricts the access to `perf_event_open`
|
||||
syscall. So, in order to allow profiling inside a container, you'll need
|
||||
to modify [seccomp profile](https://docs.docker.com/engine/security/seccomp/)
|
||||
or disable it altogether with `--security-opt seccomp=unconfined` option. In
|
||||
addition, `--cap-add SYS_ADMIN` may be required.
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
No AllocTracer symbols found. Are JDK debug symbols installed?
|
||||
```
|
||||
The OpenJDK debug symbols are required for allocation profiling.
|
||||
See [Installing Debug Symbols](#installing-debug-symbols) for more details.
|
||||
If the error message persists after a successful installation of the debug symbols, it is possible that the JDK was upgraded when installing the debug symbols.
|
||||
In this case, profiling any Java process which had started prior to the installation will continue to display this message, since the process had loaded the older version of the JDK which lacked debug symbols.
|
||||
Restarting the affected Java processes should resolve the issue.
|
||||
|
||||
```
|
||||
VMStructs unavailable. Unsupported JVM?
|
||||
```
|
||||
JVM shared library does not export `gHotSpotVMStructs*` symbols -
|
||||
apparently this is not a HotSpot JVM. Sometimes the same message
|
||||
can be also caused by an incorrectly built JDK
|
||||
(see [#218](https://github.com/jvm-profiling-tools/async-profiler/issues/218)).
|
||||
In these cases installing JDK debug symbols may solve the problem.
|
||||
|
||||
```
|
||||
Could not parse symbols due to the OS bug
|
||||
```
|
||||
Async-profiler was unable to parse non-Java function names because of
|
||||
the corrupted contents in `/proc/[pid]/maps`. The problem is known to
|
||||
occur in a container when running Ubuntu with Linux kernel 5.x.
|
||||
This is the OS bug, see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1843018.
|
||||
|
||||
```
|
||||
Output file is not created
|
||||
```
|
||||
Output file is written by the target JVM process, not by the profiler script.
|
||||
If the file cannot be opened (e.g. due to lack of permissions), the error message
|
||||
is printed to `stderr` of the target process (JVM console).
|
||||
|
||||
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>
|
||||
@@ -7,6 +7,7 @@ usage() {
|
||||
echo " start start profiling and return immediately"
|
||||
echo " resume resume profiling without resetting collected data"
|
||||
echo " stop stop profiling"
|
||||
echo " 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"
|
||||
@@ -109,6 +110,11 @@ while [ $# -gt 0 ]; do
|
||||
start|resume|stop|check|status|list|collect)
|
||||
ACTION="$1"
|
||||
;;
|
||||
jstack)
|
||||
ACTION="start"
|
||||
EVENT="jstack"
|
||||
PARAMS="$PARAMS,threads"
|
||||
;;
|
||||
-v|--version)
|
||||
ACTION="version"
|
||||
;;
|
||||
|
||||
@@ -350,11 +350,11 @@ long Arguments::parseUnits(const char* str) {
|
||||
}
|
||||
|
||||
Arguments::~Arguments() {
|
||||
free(_buf);
|
||||
if (!_shared) free(_buf);
|
||||
}
|
||||
|
||||
void Arguments::save(Arguments& other) {
|
||||
free(_buf);
|
||||
if (!_shared) free(_buf);
|
||||
*this = other;
|
||||
other._buf = NULL;
|
||||
other._shared = true;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ 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,
|
||||
@@ -106,6 +107,7 @@ class Error {
|
||||
class Arguments {
|
||||
private:
|
||||
char* _buf;
|
||||
bool _shared;
|
||||
|
||||
void appendToEmbeddedList(int& list, char* value);
|
||||
|
||||
@@ -141,6 +143,7 @@ class Arguments {
|
||||
|
||||
Arguments() :
|
||||
_buf(NULL),
|
||||
_shared(false),
|
||||
_action(ACTION_NONE),
|
||||
_counter(COUNTER_SAMPLES),
|
||||
_ring(RING_ANY),
|
||||
@@ -173,6 +176,7 @@ class Arguments {
|
||||
bool addEvent(const char* event);
|
||||
|
||||
friend class FrameName;
|
||||
friend class Recording;
|
||||
};
|
||||
|
||||
#endif // _ARGUMENTS_H
|
||||
|
||||
@@ -119,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;
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
* 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;
|
||||
@@ -69,32 +71,36 @@ public class FlameGraph {
|
||||
|
||||
String[] trace = line.substring(0, space).split(";");
|
||||
long ticks = Long.parseLong(line.substring(space + 1));
|
||||
|
||||
depth = Math.max(depth, trace.length);
|
||||
|
||||
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;
|
||||
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 (PrintStream out = new PrintStream(output, "UTF-8")) {
|
||||
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output), 32768);
|
||||
PrintStream out = new PrintStream(bos, false, "UTF-8")) {
|
||||
dump(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class Main {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
|
||||
import one.jfr.ClassRef;
|
||||
import one.jfr.Frame;
|
||||
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;
|
||||
@@ -26,7 +27,6 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Converts .jfr output produced by async-profiler to nflxprofile format
|
||||
@@ -35,7 +35,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class jfr2nflx {
|
||||
|
||||
private static final String[] FRAME_TYPE = {"user", "jit", "jit", "inlined", "user", "user", "kernel"};
|
||||
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;
|
||||
@@ -47,9 +47,12 @@ public class jfr2nflx {
|
||||
public void dump(OutputStream out) throws IOException {
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
Proto profile = new Proto(200000)
|
||||
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, (jfr.stopNanos - jfr.startNanos) / 1e9)
|
||||
.field(2, Math.max(jfr.durationNanos / 1e9, durationTicks / (double) jfr.ticksPerSec))
|
||||
.field(3, packSamples())
|
||||
.field(4, packDeltas())
|
||||
.field(6, "async-profiler")
|
||||
@@ -57,16 +60,20 @@ public class jfr2nflx {
|
||||
.field(8, new Proto(32).field(1, "has_samples_tid").field(2, "true"))
|
||||
.field(11, packTids());
|
||||
|
||||
Proto nodes = new Proto(10000);
|
||||
Proto node = new Proto(10000);
|
||||
final Proto nodes = new Proto(10000);
|
||||
final Proto node = new Proto(10000);
|
||||
|
||||
for (Map.Entry<Integer, Frame[]> entry : jfr.stackTraces.entrySet()) {
|
||||
profile.field(5, nodes
|
||||
.field(1, entry.getKey())
|
||||
.field(2, packNode(node, entry.getValue())));
|
||||
nodes.reset();
|
||||
node.reset();
|
||||
}
|
||||
// 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());
|
||||
|
||||
@@ -74,16 +81,19 @@ public class jfr2nflx {
|
||||
System.out.println("Wrote " + profile.size() + " bytes in " + (endTime - startTime) / 1e9 + " s");
|
||||
}
|
||||
|
||||
private Proto packNode(Proto node, Frame[] frames) {
|
||||
int top = frames.length - 1;
|
||||
node.field(1, top >= 0 ? getMethodName(frames[top].method) : NO_STACK);
|
||||
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[frames[top].type] : "user");
|
||||
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(frames[top].method))
|
||||
.field(2, FRAME_TYPE[frames[top].type]));
|
||||
.field(1, getMethodName(methods[top]))
|
||||
.field(2, FRAME_TYPE[types[top]]));
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -99,9 +109,10 @@ public class jfr2nflx {
|
||||
|
||||
private Proto packDeltas() {
|
||||
Proto proto = new Proto(10000);
|
||||
long prevTime = jfr.startNanos;
|
||||
double ticksPerSec = jfr.ticksPerSec;
|
||||
long prevTime = jfr.startTicks;
|
||||
for (Sample sample : jfr.samples) {
|
||||
proto.writeDouble((sample.time - prevTime) / 1e9);
|
||||
proto.writeDouble((sample.time - prevTime) / ticksPerSec);
|
||||
prevTime = sample.time;
|
||||
}
|
||||
return proto;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,8 @@
|
||||
|
||||
package one.jfr;
|
||||
|
||||
public class Frame {
|
||||
public final long method;
|
||||
public final byte type;
|
||||
class Element {
|
||||
|
||||
public Frame(long method, byte type) {
|
||||
this.method = method;
|
||||
this.type = type;
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ 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;
|
||||
@@ -30,44 +31,52 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses JFR output produced by async-profiler.
|
||||
* Note: this class is not supposed to read JFR files produced by other tools.
|
||||
*/
|
||||
public class JfrReader implements Closeable {
|
||||
|
||||
private static final int
|
||||
CONTENT_THREAD = 7,
|
||||
CONTENT_STACKTRACE = 9,
|
||||
CONTENT_CLASS = 10,
|
||||
CONTENT_METHOD = 32,
|
||||
CONTENT_SYMBOL = 33,
|
||||
CONTENT_STATE = 34,
|
||||
CONTENT_FRAME_TYPE = 47;
|
||||
|
||||
private static final int
|
||||
EVENT_EXECUTION_SAMPLE = 20;
|
||||
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 stopNanos;
|
||||
public final Map<Integer, Frame[]> stackTraces = new HashMap<>();
|
||||
public final Map<Long, MethodRef> methods = new HashMap<>();
|
||||
public final Map<Long, ClassRef> classes = new HashMap<>();
|
||||
public final Map<Long, byte[]> symbols = new HashMap<>();
|
||||
public final Map<Integer, byte[]> threads = new HashMap<>();
|
||||
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());
|
||||
|
||||
int checkpointOffset = buf.getInt(buf.capacity() - 4);
|
||||
this.startNanos = buf.getLong(buf.capacity() - 24);
|
||||
this.stopNanos = buf.getLong(checkpointOffset + 8);
|
||||
if (buf.getInt(0) != 0x464c5200) {
|
||||
throw new IOException("Not a valid JFR file");
|
||||
}
|
||||
|
||||
readCheckpoint(checkpointOffset);
|
||||
readEvents(checkpointOffset);
|
||||
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
|
||||
@@ -75,122 +84,301 @@ public class JfrReader implements Closeable {
|
||||
ch.close();
|
||||
}
|
||||
|
||||
private void readEvents(int checkpointOffset) {
|
||||
buf.position(16);
|
||||
private void readMeta() {
|
||||
buf.position(buf.getInt(META_OFFSET + 4));
|
||||
getVarint();
|
||||
getVarint();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
getVarlong();
|
||||
|
||||
while (buf.position() < checkpointOffset) {
|
||||
int size = buf.getInt();
|
||||
int type = buf.getInt();
|
||||
if (type == EVENT_EXECUTION_SAMPLE) {
|
||||
long time = buf.getLong();
|
||||
int tid = buf.getInt();
|
||||
int stackTraceId = (int) buf.getLong();
|
||||
short threadState = buf.getShort();
|
||||
samples.add(new Sample(time, tid, stackTraceId, threadState));
|
||||
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 {
|
||||
buf.position(buf.position() + size - 8);
|
||||
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 readCheckpoint(int checkpointOffset) {
|
||||
buf.position(checkpointOffset + 24);
|
||||
private void readExecutionSample() {
|
||||
long time = getVarlong();
|
||||
int tid = getVarint();
|
||||
int stackTraceId = getVarint();
|
||||
int threadState = getVarint();
|
||||
samples.add(new Sample(time, tid, stackTraceId, threadState));
|
||||
|
||||
readFrameTypes();
|
||||
readThreadStates();
|
||||
readStackTraces();
|
||||
readMethods();
|
||||
readClasses();
|
||||
readSymbols();
|
||||
readThreads();
|
||||
}
|
||||
|
||||
private void readFrameTypes() {
|
||||
int count = getTableSize(CONTENT_FRAME_TYPE);
|
||||
for (int i = 0; i < count; i++) {
|
||||
buf.get();
|
||||
getSymbol();
|
||||
StackTrace stackTrace = stackTraces.get(stackTraceId);
|
||||
if (stackTrace != null) {
|
||||
stackTrace.samples++;
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreadStates() {
|
||||
int count = getTableSize(CONTENT_STATE);
|
||||
for (int i = 0; i < count; i++) {
|
||||
buf.getShort();
|
||||
getSymbol();
|
||||
}
|
||||
private int getTypeId(String typeName) {
|
||||
JfrClass type = typesByName.get(typeName);
|
||||
return type != null ? type.id : -1;
|
||||
}
|
||||
|
||||
private void readStackTraces() {
|
||||
int count = getTableSize(CONTENT_STACKTRACE);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int id = (int) buf.getLong();
|
||||
byte truncated = buf.get();
|
||||
Frame[] frames = new Frame[buf.getInt()];
|
||||
for (int j = 0; j < frames.length; j++) {
|
||||
long method = buf.getLong();
|
||||
int bci = buf.getInt();
|
||||
byte type = buf.get();
|
||||
frames[j] = new Frame(method, type);
|
||||
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;
|
||||
}
|
||||
stackTraces.put(id, frames);
|
||||
}
|
||||
}
|
||||
|
||||
private void readMethods() {
|
||||
int count = getTableSize(CONTENT_METHOD);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = buf.getLong();
|
||||
long cls = buf.getLong();
|
||||
long name = buf.getLong();
|
||||
long sig = buf.getLong();
|
||||
short modifiers = buf.getShort();
|
||||
byte hidden = buf.get();
|
||||
methods.put(id, new MethodRef(cls, name, sig));
|
||||
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 void readClasses() {
|
||||
int count = getTableSize(CONTENT_CLASS);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = buf.getLong();
|
||||
long loader = buf.getLong();
|
||||
long name = buf.getLong();
|
||||
short modifiers = buf.getShort();
|
||||
classes.put(id, new ClassRef(name));
|
||||
}
|
||||
}
|
||||
|
||||
private void readSymbols() {
|
||||
int count = getTableSize(CONTENT_SYMBOL);
|
||||
for (int i = 0; i < count; i++) {
|
||||
long id = buf.getLong();
|
||||
byte[] symbol = getSymbol();
|
||||
symbols.put(id, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
private void readThreads() {
|
||||
int count = getTableSize(CONTENT_THREAD);
|
||||
for (int i = 0; i < count; i++) {
|
||||
int id = buf.getInt();
|
||||
byte[] name = getSymbol();
|
||||
threads.put(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
private int getTableSize(int contentType) {
|
||||
if (buf.getInt() != contentType) {
|
||||
throw new IllegalArgumentException("Expected content type " + contentType);
|
||||
}
|
||||
return buf.getInt();
|
||||
}
|
||||
|
||||
private byte[] getSymbol() {
|
||||
byte[] symbol = new byte[buf.getShort() & 0xffff];
|
||||
buf.get(symbol);
|
||||
return symbol;
|
||||
private byte[] getBytes() {
|
||||
byte[] bytes = new byte[getVarint()];
|
||||
buf.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ public class Sample implements Comparable<Sample> {
|
||||
public final long time;
|
||||
public final int tid;
|
||||
public final int stackTraceId;
|
||||
public final short threadState;
|
||||
public final int threadState;
|
||||
|
||||
public Sample(long time, int tid, int stackTraceId, short threadState) {
|
||||
public Sample(long time, int tid, int stackTraceId, int threadState) {
|
||||
this.time = time;
|
||||
this.tid = tid;
|
||||
this.stackTraceId = stackTraceId;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -30,16 +30,20 @@ CStack Engine::cstack() {
|
||||
|
||||
int Engine::getNativeTrace(void* ucontext, int tid, const void** callchain, int max_depth,
|
||||
CodeCache* java_methods, CodeCache* runtime_stubs) {
|
||||
if (ucontext == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
StackFrame frame(ucontext);
|
||||
const void* pc = (const void*)frame.pc();
|
||||
uintptr_t fp = frame.fp();
|
||||
const void* pc;
|
||||
uintptr_t fp;
|
||||
uintptr_t prev_fp = (uintptr_t)&fp;
|
||||
uintptr_t bottom = prev_fp + 0x100000;
|
||||
|
||||
if (ucontext == NULL) {
|
||||
pc = __builtin_return_address(0);
|
||||
fp = (uintptr_t)__builtin_frame_address(1);
|
||||
} else {
|
||||
StackFrame frame(ucontext);
|
||||
pc = (const void*)frame.pc();
|
||||
fp = frame.fp();
|
||||
}
|
||||
|
||||
int depth = 0;
|
||||
const void* const valid_pc = (const void* const)0x1000;
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ class NoopEngine : public Engine {
|
||||
|
||||
void stop() {
|
||||
}
|
||||
|
||||
CStack cstack() {
|
||||
return CSTACK_NO;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _ENGINE_H
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
#include "flightRecorder.h"
|
||||
#include "jfrMetadata.h"
|
||||
@@ -37,15 +38,21 @@ const int BUFFER_SIZE = 1024;
|
||||
const int BUFFER_LIMIT = BUFFER_SIZE - 128;
|
||||
const int RECORDING_BUFFER_SIZE = 65536;
|
||||
const int RECORDING_BUFFER_LIMIT = RECORDING_BUFFER_SIZE - 4096;
|
||||
const int MAX_STRING_LENGTH = 16383;
|
||||
|
||||
|
||||
static const char* const SETTING_COUNTER[] = {"samples", "total"};
|
||||
static const char* const SETTING_RING[] = {NULL, "kernel", "user"};
|
||||
static const char* const SETTING_CSTACK[] = {NULL, "no", "fp", "lbr"};
|
||||
|
||||
|
||||
enum FrameTypeId {
|
||||
FRAME_INTERPRETED = 1,
|
||||
FRAME_JIT_COMPILED = 2,
|
||||
FRAME_INLINED = 3,
|
||||
FRAME_NATIVE = 4,
|
||||
FRAME_CPP = 5,
|
||||
FRAME_KERNEL = 6,
|
||||
FRAME_INTERPRETED = 0,
|
||||
FRAME_JIT_COMPILED = 1,
|
||||
FRAME_INLINED = 2,
|
||||
FRAME_NATIVE = 3,
|
||||
FRAME_CPP = 4,
|
||||
FRAME_KERNEL = 5,
|
||||
};
|
||||
|
||||
|
||||
@@ -160,7 +167,11 @@ class Buffer {
|
||||
}
|
||||
|
||||
void putUtf8(const char* v) {
|
||||
putUtf8(v, strlen(v));
|
||||
if (v == NULL) {
|
||||
put8(0);
|
||||
} else {
|
||||
putUtf8(v, strlen(v) & MAX_STRING_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
void putUtf8(const char* v, int len) {
|
||||
@@ -196,8 +207,12 @@ class Recording {
|
||||
private:
|
||||
static SpinLock _cpu_monitor_lock;
|
||||
|
||||
static char* _agent_properties;
|
||||
static char* _jvm_args;
|
||||
static char* _jvm_flags;
|
||||
static char* _java_command;
|
||||
|
||||
RecordingBuffer _buf[CONCURRENCY_LEVEL];
|
||||
int _available_processors;
|
||||
int _fd;
|
||||
off_t _file_offset;
|
||||
ThreadFilter _thread_set;
|
||||
@@ -208,12 +223,13 @@ class Recording {
|
||||
u64 _start_nanos;
|
||||
u64 _stop_time;
|
||||
u64 _stop_nanos;
|
||||
int _tid;
|
||||
int _available_processors;
|
||||
Buffer _cpu_monitor_buf;
|
||||
Timer* _cpu_monitor;
|
||||
CpuTimes _last_times;
|
||||
|
||||
void startCpuMonitor() {
|
||||
VM::jvmti()->GetAvailableProcessors(&_available_processors);
|
||||
_last_times.proc.real = OS::getProcessCpuTime(&_last_times.proc.user, &_last_times.proc.system);
|
||||
_last_times.total.real = OS::getTotalCpuTime(&_last_times.total.user, &_last_times.total.system);
|
||||
|
||||
@@ -269,14 +285,21 @@ class Recording {
|
||||
}
|
||||
|
||||
public:
|
||||
Recording(int fd) : _fd(fd), _thread_set(), _packages(), _symbols(), _method_map() {
|
||||
Recording(int fd, Arguments& args) : _fd(fd), _thread_set(), _packages(), _symbols(), _method_map() {
|
||||
_file_offset = lseek(_fd, 0, SEEK_END);
|
||||
_start_time = OS::millis();
|
||||
_start_nanos = OS::nanotime();
|
||||
_tid = OS::threadId();
|
||||
addThread(_tid);
|
||||
VM::jvmti()->GetAvailableProcessors(&_available_processors);
|
||||
|
||||
writeHeader(_buf);
|
||||
writeMetadata(_buf);
|
||||
writeRecordingInfo(_buf);
|
||||
writeSettings(_buf, args);
|
||||
writeOsCpuInfo(_buf);
|
||||
writeJvmInfo(_buf);
|
||||
writeSystemProperties(_buf);
|
||||
flush(_buf);
|
||||
|
||||
startCpuMonitor();
|
||||
@@ -417,6 +440,52 @@ class Recording {
|
||||
return _packages.lookup(class_name, package - class_name);
|
||||
}
|
||||
|
||||
bool parseAgentProperties() {
|
||||
JNIEnv* env = VM::jni();
|
||||
jclass vm_support = env->FindClass("jdk/internal/vm/VMSupport");
|
||||
if (vm_support == NULL) vm_support = env->FindClass("sun/misc/VMSupport");
|
||||
if (vm_support != NULL) {
|
||||
jmethodID get_agent_props = env->GetStaticMethodID(vm_support, "getAgentProperties", "()Ljava/util/Properties;");
|
||||
jmethodID to_string = env->GetMethodID(env->FindClass("java/lang/Object"), "toString", "()Ljava/lang/String;");
|
||||
if (get_agent_props != NULL && to_string != NULL) {
|
||||
jobject props = env->CallStaticObjectMethod(vm_support, get_agent_props);
|
||||
if (props != NULL) {
|
||||
jstring str = (jstring)env->CallObjectMethod(props, to_string);
|
||||
if (str != NULL) {
|
||||
_agent_properties = (char*)env->GetStringUTFChars(str, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
env->ExceptionClear();
|
||||
|
||||
if (_agent_properties == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* p = _agent_properties + 1;
|
||||
p[strlen(p) - 1] = 0;
|
||||
|
||||
while (*p) {
|
||||
if (strncmp(p, "sun.jvm.args=", 13) == 0) {
|
||||
_jvm_args = p + 13;
|
||||
} else if (strncmp(p, "sun.jvm.flags=", 14) == 0) {
|
||||
_jvm_flags = p + 14;
|
||||
} else if (strncmp(p, "sun.java.command=", 17) == 0) {
|
||||
_java_command = p + 17;
|
||||
}
|
||||
|
||||
if ((p = strstr(p, ", ")) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
*p = 0;
|
||||
p += 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void flush(Buffer* buf) {
|
||||
ssize_t result = write(_fd, buf->data(), buf->offset());
|
||||
(void)result;
|
||||
@@ -477,16 +546,13 @@ class Recording {
|
||||
}
|
||||
|
||||
void writeRecordingInfo(Buffer* buf) {
|
||||
int tid = OS::threadId();
|
||||
addThread(tid);
|
||||
|
||||
int start = buf->skip(1);
|
||||
buf->put8(T_ACTIVE_RECORDING);
|
||||
buf->putVarint(_start_nanos);
|
||||
buf->putVarint(0);
|
||||
buf->putVarint(tid);
|
||||
buf->putVarint(_tid);
|
||||
buf->putVarint(1);
|
||||
buf->putUtf8("async-profiler");
|
||||
buf->putUtf8("async-profiler " PROFILER_VERSION);
|
||||
buf->putUtf8("async-profiler.jfr");
|
||||
buf->putVarint(0x7fffffffffffffffULL);
|
||||
buf->putVarint(0);
|
||||
@@ -495,6 +561,140 @@ class Recording {
|
||||
buf->put8(start, buf->offset() - start);
|
||||
}
|
||||
|
||||
void writeSettings(Buffer* buf, Arguments& args) {
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "version", PROFILER_VERSION);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "counter", SETTING_COUNTER[args._counter]);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "ring", SETTING_RING[args._ring]);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "cstack", SETTING_CSTACK[args._cstack]);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "event", args._event_desc);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "filter", args._filter);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "begin", args._begin);
|
||||
writeStringSetting(buf, T_ACTIVE_RECORDING, "end", args._end);
|
||||
writeListSetting(buf, T_ACTIVE_RECORDING, "include", args._buf, args._include);
|
||||
writeListSetting(buf, T_ACTIVE_RECORDING, "exclude", args._buf, args._exclude);
|
||||
writeIntSetting(buf, T_ACTIVE_RECORDING, "jstackdepth", args._jstackdepth);
|
||||
writeIntSetting(buf, T_ACTIVE_RECORDING, "safemode", args._safe_mode);
|
||||
writeBoolSetting(buf, T_EXECUTION_SAMPLE, "enabled", args._events & EK_CPU);
|
||||
writeIntSetting(buf, T_EXECUTION_SAMPLE, "interval", args._interval);
|
||||
writeBoolSetting(buf, T_ALLOC_IN_NEW_TLAB, "enabled", args._events & EK_ALLOC);
|
||||
writeBoolSetting(buf, T_ALLOC_OUTSIDE_TLAB, "enabled", args._events & EK_ALLOC);
|
||||
writeBoolSetting(buf, T_MONITOR_ENTER, "enabled", args._events & EK_LOCK);
|
||||
writeBoolSetting(buf, T_THREAD_PARK, "enabled", args._events & EK_LOCK);
|
||||
}
|
||||
|
||||
void writeStringSetting(Buffer* buf, int category, const char* key, const char* value) {
|
||||
int start = buf->skip(5);
|
||||
buf->put8(T_ACTIVE_SETTING);
|
||||
buf->putVarint(_start_nanos);
|
||||
buf->putVarint(0);
|
||||
buf->putVarint(_tid);
|
||||
buf->putVarint(category);
|
||||
buf->putUtf8(key);
|
||||
buf->putUtf8(value);
|
||||
buf->putVar32(start, buf->offset() - start);
|
||||
flushIfNeeded(buf);
|
||||
}
|
||||
|
||||
void writeBoolSetting(Buffer* buf, int category, const char* key, bool value) {
|
||||
writeStringSetting(buf, category, key, value ? "true" : "false");
|
||||
}
|
||||
|
||||
void writeIntSetting(Buffer* buf, int category, const char* key, long value) {
|
||||
char str[32];
|
||||
sprintf(str, "%ld", value);
|
||||
writeStringSetting(buf, category, key, str);
|
||||
}
|
||||
|
||||
void writeListSetting(Buffer* buf, int category, const char* key, const char* base, int offset) {
|
||||
while (offset != 0) {
|
||||
writeStringSetting(buf, category, key, base + offset);
|
||||
offset = ((int*)(base + offset))[-1];
|
||||
}
|
||||
}
|
||||
|
||||
void writeOsCpuInfo(Buffer* buf) {
|
||||
struct utsname u;
|
||||
if (uname(&u) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
char str[512];
|
||||
snprintf(str, sizeof(str) - 1, "uname: %s %s %s %s", u.sysname, u.release, u.version, u.machine);
|
||||
str[sizeof(str) - 1] = 0;
|
||||
|
||||
int start = buf->skip(5);
|
||||
buf->put8(T_OS_INFORMATION);
|
||||
buf->putVarint(_start_nanos);
|
||||
buf->putUtf8(str);
|
||||
buf->putVar32(start, buf->offset() - start);
|
||||
|
||||
start = buf->skip(5);
|
||||
buf->put8(T_CPU_INFORMATION);
|
||||
buf->putVarint(_start_nanos);
|
||||
buf->putUtf8(u.machine);
|
||||
buf->putUtf8(OS::getCpuDescription(str, sizeof(str) - 1) ? str : "");
|
||||
buf->putVarint(1);
|
||||
buf->putVarint(_available_processors);
|
||||
buf->putVarint(_available_processors);
|
||||
buf->putVar32(start, buf->offset() - start);
|
||||
}
|
||||
|
||||
void writeJvmInfo(Buffer* buf) {
|
||||
if (_agent_properties == NULL && !parseAgentProperties()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* jvm_name = NULL;
|
||||
char* jvm_version = NULL;
|
||||
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jvmti->GetSystemProperty("java.vm.name", &jvm_name);
|
||||
jvmti->GetSystemProperty("java.vm.version", &jvm_version);
|
||||
|
||||
int start = buf->skip(5);
|
||||
buf->put8(T_JVM_INFORMATION);
|
||||
buf->putVarint(_start_nanos);
|
||||
buf->putUtf8(jvm_name);
|
||||
buf->putUtf8(jvm_version);
|
||||
buf->putUtf8(_jvm_args);
|
||||
buf->putUtf8(_jvm_flags);
|
||||
buf->putUtf8(_java_command);
|
||||
buf->putVarint(OS::processStartTime());
|
||||
buf->putVarint(OS::processId());
|
||||
buf->putVar32(start, buf->offset() - start);
|
||||
flushIfNeeded(buf);
|
||||
|
||||
jvmti->Deallocate((unsigned char*)jvm_version);
|
||||
jvmti->Deallocate((unsigned char*)jvm_name);
|
||||
}
|
||||
|
||||
void writeSystemProperties(Buffer* buf) {
|
||||
jvmtiEnv* jvmti = VM::jvmti();
|
||||
jint count;
|
||||
char** keys;
|
||||
if (jvmti->GetSystemProperties(&count, &keys) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
char* key = keys[i];
|
||||
char* value = NULL;
|
||||
if (jvmti->GetSystemProperty(key, &value) == 0) {
|
||||
int start = buf->skip(5);
|
||||
buf->put8(T_INITIAL_SYSTEM_PROPERTY);
|
||||
buf->putVarint(_start_nanos);
|
||||
buf->putUtf8(key);
|
||||
buf->putUtf8(value);
|
||||
buf->putVar32(start, buf->offset() - start);
|
||||
flushIfNeeded(buf);
|
||||
jvmti->Deallocate((unsigned char*)value);
|
||||
}
|
||||
}
|
||||
|
||||
jvmti->Deallocate((unsigned char*)keys);
|
||||
|
||||
}
|
||||
|
||||
void writeCpool(Buffer* buf) {
|
||||
buf->skip(5); // size will be patched later
|
||||
buf->putVarint(T_CPOOL);
|
||||
@@ -740,18 +940,23 @@ class Recording {
|
||||
|
||||
SpinLock Recording::_cpu_monitor_lock(1);
|
||||
|
||||
char* Recording::_agent_properties = NULL;
|
||||
char* Recording::_jvm_args = NULL;
|
||||
char* Recording::_jvm_flags = NULL;
|
||||
char* Recording::_java_command = NULL;
|
||||
|
||||
Error FlightRecorder::start(const char* file, bool reset) {
|
||||
if (file == NULL || file[0] == 0) {
|
||||
|
||||
Error FlightRecorder::start(Arguments& args, bool reset) {
|
||||
if (args._file == NULL || args._file[0] == 0) {
|
||||
return Error("Flight Recorder output file is not specified");
|
||||
}
|
||||
|
||||
int fd = open(file, O_CREAT | O_WRONLY | (reset ? O_TRUNC : 0), 0644);
|
||||
int fd = open(args._file, O_CREAT | O_WRONLY | (reset ? O_TRUNC : 0), 0644);
|
||||
if (fd == -1) {
|
||||
return Error("Cannot open Flight Recorder output file");
|
||||
}
|
||||
|
||||
_rec = new Recording(fd);
|
||||
_rec = new Recording(fd, args);
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class FlightRecorder {
|
||||
FlightRecorder() : _rec(NULL) {
|
||||
}
|
||||
|
||||
Error start(const char* file, bool reset);
|
||||
Error start(Arguments& args, bool reset);
|
||||
void stop();
|
||||
|
||||
bool active() {
|
||||
|
||||
@@ -145,6 +145,46 @@ JfrMetadata::JfrMetadata() : Element("root") {
|
||||
<< field("recordingStart", T_LONG, "Start Time", F_TIME_MILLIS)
|
||||
<< field("recordingDuration", T_LONG, "Recording Duration", F_DURATION_MILLIS))
|
||||
|
||||
<< (type("jdk.ActiveSetting", T_ACTIVE_SETTING, "Recording Setting")
|
||||
<< 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))
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ enum JfrType {
|
||||
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,
|
||||
@@ -126,14 +131,15 @@ class JfrMetadata : Element {
|
||||
enum FieldFlags {
|
||||
F_CPOOL = 0x1,
|
||||
F_ARRAY = 0x2,
|
||||
F_BYTES = 0x4,
|
||||
F_TIME_TICKS = 0x8,
|
||||
F_TIME_MILLIS = 0x10,
|
||||
F_DURATION_TICKS = 0x20,
|
||||
F_DURATION_NANOS = 0x40,
|
||||
F_DURATION_MILLIS = 0x80,
|
||||
F_ADDRESS = 0x100,
|
||||
F_PERCENTAGE = 0x200,
|
||||
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) {
|
||||
@@ -170,7 +176,9 @@ class JfrMetadata : Element {
|
||||
if (label != NULL) {
|
||||
e << annotation(T_LABEL, label);
|
||||
}
|
||||
if (flags & F_BYTES) {
|
||||
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");
|
||||
|
||||
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
|
||||
}
|
||||
41
src/jstack.h
Normal file
41
src/jstack.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2021 Andrei Pangin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _JSTACK_H
|
||||
#define _JSTACK_H
|
||||
|
||||
#include <signal.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
class JStack : public Engine {
|
||||
private:
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
|
||||
public:
|
||||
const char* name() {
|
||||
return EVENT_JSTACK;
|
||||
}
|
||||
|
||||
const char* units() {
|
||||
return "samples";
|
||||
}
|
||||
|
||||
Error start(Arguments& args);
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif // _JSTACK_H
|
||||
3
src/os.h
3
src/os.h
@@ -51,11 +51,13 @@ class OS {
|
||||
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 threadName(int thread_id, char* name_buf, size_t name_len);
|
||||
static ThreadState threadState(int thread_id);
|
||||
@@ -72,6 +74,7 @@ class OS {
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -119,6 +119,22 @@ 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);
|
||||
}
|
||||
@@ -138,6 +154,12 @@ int OS::getMaxThreadId() {
|
||||
return atoi(buf);
|
||||
}
|
||||
|
||||
int OS::processId() {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
return self_pid;
|
||||
}
|
||||
|
||||
int OS::threadId() {
|
||||
return syscall(__NR_gettid);
|
||||
}
|
||||
@@ -202,9 +224,7 @@ void OS::installSignalHandler(int signo, SigAction action, SigHandler handler) {
|
||||
}
|
||||
|
||||
bool OS::sendSignalToThread(int thread_id, int signo) {
|
||||
static const int self_pid = getpid();
|
||||
|
||||
return syscall(__NR_tgkill, self_pid, thread_id, signo) == 0;
|
||||
return syscall(__NR_tgkill, processId(), thread_id, signo) == 0;
|
||||
}
|
||||
|
||||
void* OS::safeAlloc(size_t size) {
|
||||
@@ -245,6 +265,28 @@ 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);
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <libkern/OSByteOrder.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"
|
||||
@@ -94,6 +96,19 @@ 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);
|
||||
}
|
||||
@@ -106,6 +121,12 @@ 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
|
||||
@@ -175,7 +196,7 @@ void OS::safeFree(void* addr, size_t size) {
|
||||
}
|
||||
|
||||
Timer* OS::startTimer(u64 interval, TimerCallback callback, void* arg) {
|
||||
dispatch_queue_global_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
|
||||
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);
|
||||
@@ -194,6 +215,10 @@ void OS::stopTimer(Timer* timer) {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
151
src/profiler.cpp
151
src/profiler.cpp
@@ -18,6 +18,7 @@
|
||||
#include <fstream>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <sched.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -30,6 +31,7 @@
|
||||
#include "wallClock.h"
|
||||
#include "instrument.h"
|
||||
#include "itimer.h"
|
||||
#include "jstack.h"
|
||||
#include "flameGraph.h"
|
||||
#include "flightRecorder.h"
|
||||
#include "frameName.h"
|
||||
@@ -47,20 +49,22 @@ static AllocTracer alloc_tracer;
|
||||
static LockTracer lock_tracer;
|
||||
static WallClock wall_clock;
|
||||
static ITimer itimer;
|
||||
static JStack jstack;
|
||||
static Instrument instrument;
|
||||
|
||||
|
||||
// Stack recovery techniques used to workaround AsyncGetCallTrace flaws.
|
||||
// Can be disabled with 'safemode' option.
|
||||
enum StackRecovery {
|
||||
MOVE_SP = 1,
|
||||
POP_FRAME = 2,
|
||||
SCAN_STACK = 4,
|
||||
LAST_JAVA_PC = 8,
|
||||
GC_TRACES = 16,
|
||||
MOVE_SP = 0x1,
|
||||
MOVE_SP2 = 0x2,
|
||||
POP_FRAME = 0x4,
|
||||
SCAN_STACK = 0x8,
|
||||
LAST_JAVA_PC = 0x10,
|
||||
GC_TRACES = 0x20,
|
||||
|
||||
HOTSPOT_ONLY = LAST_JAVA_PC | GC_TRACES,
|
||||
MAX_RECOVERY = MOVE_SP | POP_FRAME | SCAN_STACK | LAST_JAVA_PC | GC_TRACES
|
||||
MAX_RECOVERY = MOVE_SP | MOVE_SP2 | POP_FRAME | SCAN_STACK | LAST_JAVA_PC | GC_TRACES
|
||||
};
|
||||
|
||||
|
||||
@@ -245,6 +249,11 @@ int Profiler::getNativeTrace(void* ucontext, ASGCT_CallFrame* frames, int tid) {
|
||||
}
|
||||
|
||||
int Profiler::getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max_depth) {
|
||||
VMThread* vm_thread = VMThread::current();
|
||||
if (vm_thread == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEnv* jni = VM::jni();
|
||||
if (jni == NULL) {
|
||||
// Not a Java thread
|
||||
@@ -305,6 +314,23 @@ int Profiler::getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max
|
||||
}
|
||||
}
|
||||
|
||||
// Retry moving stack pointer, but now in wider range: 3 to 6 slots.
|
||||
// Helps to recover from String.indexOf() intrinsic
|
||||
if (!(_safe_mode & MOVE_SP2)) {
|
||||
ASGCT_CallFrame* prev_frames = trace.frames;
|
||||
trace.frames = frames;
|
||||
for (int extra_stack_slots = 3; extra_stack_slots <= 6; extra_stack_slots = (extra_stack_slots - 1) << 1) {
|
||||
top_frame.sp() = sp + extra_stack_slots * sizeof(uintptr_t);
|
||||
VM::_asyncGetCallTrace(&trace, max_depth, ucontext);
|
||||
top_frame.sp() = sp;
|
||||
|
||||
if (trace.num_frames > 0) {
|
||||
return trace.num_frames;
|
||||
}
|
||||
}
|
||||
trace.frames = prev_frames;
|
||||
}
|
||||
|
||||
// Try to find the previous frame by looking a few top stack slots
|
||||
// for something that resembles a return address
|
||||
if (!(_safe_mode & SCAN_STACK)) {
|
||||
@@ -351,9 +377,11 @@ int Profiler::getJavaTraceAsync(void* ucontext, ASGCT_CallFrame* frames, int max
|
||||
pc = 0;
|
||||
}
|
||||
}
|
||||
} else if (trace.num_frames == ticks_GC_active && VMStructs::_get_stack_trace != NULL && !(_safe_mode & GC_TRACES)) {
|
||||
// While GC is running Java threads are known to be at safepoint
|
||||
return getJavaTraceJvmti((jvmtiFrameInfo*)frames, frames, max_depth);
|
||||
} else if (trace.num_frames == ticks_GC_active && !(_safe_mode & GC_TRACES)) {
|
||||
if (VMStructs::_get_stack_trace != NULL && CollectedHeap::isGCActive() && !VM::inRedefineClasses()) {
|
||||
// While GC is running Java threads are known to be at safepoint
|
||||
return getJavaTraceJvmti((jvmtiFrameInfo*)frames, frames, max_depth);
|
||||
}
|
||||
}
|
||||
|
||||
if (trace.num_frames > 0) {
|
||||
@@ -466,15 +494,42 @@ AddressType Profiler::getAddressType(instruction_t* pc) {
|
||||
return ADDR_UNKNOWN;
|
||||
}
|
||||
|
||||
ThreadState Profiler::getThreadState(void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
uintptr_t pc = frame.pc();
|
||||
|
||||
// Consider a thread sleeping, if it has been interrupted in the middle of syscall execution,
|
||||
// either when PC points to the syscall instruction, or if syscall has just returned with EINTR
|
||||
if (StackFrame::isSyscall((instruction_t*)pc)) {
|
||||
return THREAD_SLEEPING;
|
||||
}
|
||||
|
||||
// Make sure the previous instruction address is readable
|
||||
uintptr_t prev_pc = pc - SYSCALL_SIZE;
|
||||
if ((pc & 0xfff) >= SYSCALL_SIZE || findNativeLibrary((instruction_t*)prev_pc) != NULL) {
|
||||
if (StackFrame::isSyscall((instruction_t*)prev_pc) && frame.checkInterruptedSyscall()) {
|
||||
return THREAD_SLEEPING;
|
||||
}
|
||||
}
|
||||
|
||||
return THREAD_RUNNING;
|
||||
}
|
||||
|
||||
void Profiler::recordSample(void* ucontext, u64 counter, jint event_type, Event* event) {
|
||||
atomicInc(_total_samples);
|
||||
|
||||
int tid = OS::threadId();
|
||||
u32 lock_index = getLockIndex(tid);
|
||||
if (!_locks[lock_index].tryLock() &&
|
||||
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
|
||||
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())
|
||||
while (!_locks[lock_index].tryLock() &&
|
||||
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
|
||||
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())
|
||||
{
|
||||
if (_engine == &jstack) {
|
||||
// JStack strictly needs all stack traces, but we cannot lock inside a signal handler
|
||||
sched_yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Too many concurrent signals already
|
||||
atomicInc(_failures[-ticks_skipped]);
|
||||
|
||||
@@ -491,7 +546,7 @@ void Profiler::recordSample(void* ucontext, u64 counter, jint event_type, Event*
|
||||
if (!_jfr.active() && event_type <= BCI_ALLOC && event_type >= BCI_PARK && event->id()) {
|
||||
num_frames = makeEventFrame(frames, event_type, event->id());
|
||||
}
|
||||
if (_cstack != CSTACK_NO && event_type == 0) {
|
||||
if (_cstack != CSTACK_NO) {
|
||||
num_frames += getNativeTrace(ucontext, frames + num_frames, tid);
|
||||
}
|
||||
|
||||
@@ -500,7 +555,7 @@ void Profiler::recordSample(void* ucontext, u64 counter, jint event_type, Event*
|
||||
// Events like object allocation happen at known places where it is safe to call JVM TI
|
||||
jvmtiFrameInfo* jvmti_frames = _calltrace_buffer[lock_index]->_jvmti_frames;
|
||||
num_frames += getJavaTraceJvmti(jvmti_frames + num_frames, frames + num_frames, _max_stack_depth);
|
||||
} else if (VMStructs::hasJNIEnv()) {
|
||||
} else {
|
||||
num_frames += getJavaTraceAsync(ucontext, frames + num_frames, _max_stack_depth);
|
||||
}
|
||||
|
||||
@@ -773,6 +828,8 @@ Engine* Profiler::selectEngine(const char* event_name) {
|
||||
return &wall_clock;
|
||||
} else if (strcmp(event_name, EVENT_ITIMER) == 0) {
|
||||
return &itimer;
|
||||
} else if (strcmp(event_name, EVENT_JSTACK) == 0) {
|
||||
return &jstack;
|
||||
} else if (strchr(event_name, '.') != NULL) {
|
||||
return &instrument;
|
||||
} else {
|
||||
@@ -864,7 +921,7 @@ Error Profiler::start(Arguments& args, bool reset) {
|
||||
}
|
||||
|
||||
if (args._output == OUTPUT_JFR) {
|
||||
error = _jfr.start(args._file, reset);
|
||||
error = _jfr.start(args, reset);
|
||||
if (error) {
|
||||
uninstallTraps();
|
||||
return error;
|
||||
@@ -883,9 +940,11 @@ Error Profiler::start(Arguments& args, bool reset) {
|
||||
if (_events & EK_ALLOC) alloc_tracer.start(args);
|
||||
if (_events & EK_LOCK) lock_tracer.start(args);
|
||||
|
||||
// Thread events might be already enabled by PerfEvents::start
|
||||
switchThreadEvents(JVMTI_ENABLE);
|
||||
switchNativeMethodTraps(true);
|
||||
if (_engine != &jstack) {
|
||||
// Thread events might be already enabled by PerfEvents::start
|
||||
switchThreadEvents(JVMTI_ENABLE);
|
||||
switchNativeMethodTraps(true);
|
||||
}
|
||||
|
||||
_state = RUNNING;
|
||||
_start_time = time(NULL);
|
||||
@@ -905,8 +964,11 @@ Error Profiler::stop() {
|
||||
|
||||
_engine->stop();
|
||||
|
||||
switchNativeMethodTraps(false);
|
||||
switchThreadEvents(JVMTI_DISABLE);
|
||||
if (_engine != &jstack) {
|
||||
switchNativeMethodTraps(false);
|
||||
switchThreadEvents(JVMTI_DISABLE);
|
||||
}
|
||||
|
||||
updateJavaThreadNames();
|
||||
updateNativeThreadNames();
|
||||
|
||||
@@ -943,6 +1005,25 @@ void Profiler::switchThreadEvents(jvmtiEventMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
void Profiler::dump(std::ostream& out, Arguments& args) {
|
||||
switch (args._output) {
|
||||
case OUTPUT_COLLAPSED:
|
||||
dumpCollapsed(out, args);
|
||||
break;
|
||||
case OUTPUT_FLAMEGRAPH:
|
||||
dumpFlameGraph(out, args, false);
|
||||
break;
|
||||
case OUTPUT_TREE:
|
||||
dumpFlameGraph(out, args, true);
|
||||
break;
|
||||
case OUTPUT_FLAT:
|
||||
dumpFlat(out, args);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump stacks in FlameGraph input format:
|
||||
*
|
||||
@@ -1072,6 +1153,9 @@ void Profiler::runInternal(Arguments& args, std::ostream& out) {
|
||||
Error error = start(args, args._action == ACTION_START);
|
||||
if (error) {
|
||||
out << error.message() << std::endl;
|
||||
} else if (_engine == &jstack) {
|
||||
stop();
|
||||
dump(out, args);
|
||||
} else {
|
||||
out << "Profiling started" << std::endl;
|
||||
}
|
||||
@@ -1135,22 +1219,7 @@ void Profiler::runInternal(Arguments& args, std::ostream& out) {
|
||||
break;
|
||||
case ACTION_DUMP:
|
||||
stop();
|
||||
switch (args._output) {
|
||||
case OUTPUT_COLLAPSED:
|
||||
dumpCollapsed(out, args);
|
||||
break;
|
||||
case OUTPUT_FLAMEGRAPH:
|
||||
dumpFlameGraph(out, args, false);
|
||||
break;
|
||||
case OUTPUT_TREE:
|
||||
dumpFlameGraph(out, args, true);
|
||||
break;
|
||||
case OUTPUT_FLAT:
|
||||
dumpFlat(out, args);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dump(out, args);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -1175,9 +1244,13 @@ void Profiler::shutdown(Arguments& args) {
|
||||
MutexLocker ml(_state_lock);
|
||||
|
||||
// The last chance to dump profile before VM terminates
|
||||
if (_state == RUNNING && args._output != OUTPUT_NONE) {
|
||||
args._action = ACTION_DUMP;
|
||||
run(args);
|
||||
if (_state == RUNNING) {
|
||||
if (args._output == OUTPUT_NONE) {
|
||||
stop();
|
||||
} else {
|
||||
args._action = ACTION_DUMP;
|
||||
run(args);
|
||||
}
|
||||
}
|
||||
|
||||
_state = TERMINATED;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
const char FULL_VERSION_STRING[] =
|
||||
"Async-profiler " PROFILER_VERSION " built on " __DATE__ "\n"
|
||||
"Copyright 2016-2020 Andrei Pangin\n";
|
||||
"Copyright 2016-2021 Andrei Pangin\n";
|
||||
|
||||
const int MAX_NATIVE_FRAMES = 128;
|
||||
const int RESERVED_FRAMES = 4;
|
||||
@@ -188,9 +188,11 @@ class Profiler {
|
||||
Error start(Arguments& args, bool reset);
|
||||
Error stop();
|
||||
void switchThreadEvents(jvmtiEventMode mode);
|
||||
void dump(std::ostream& out, Arguments& args);
|
||||
void dumpCollapsed(std::ostream& out, Arguments& args);
|
||||
void dumpFlameGraph(std::ostream& out, Arguments& args, bool tree);
|
||||
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);
|
||||
|
||||
@@ -37,6 +37,9 @@ 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) {
|
||||
@@ -64,6 +67,14 @@ void VM::init(JavaVM* vm, bool attach) {
|
||||
}
|
||||
_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");
|
||||
@@ -185,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) {
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
#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, // native function name (char*)
|
||||
@@ -69,12 +75,22 @@ typedef struct {
|
||||
|
||||
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 ready();
|
||||
@@ -106,6 +122,10 @@ class VM {
|
||||
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);
|
||||
|
||||
@@ -116,6 +136,9 @@ class VM {
|
||||
static void JNICALL ClassPrepare(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass 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
|
||||
|
||||
@@ -36,10 +36,13 @@ 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;
|
||||
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;
|
||||
|
||||
jfieldID VMStructs::_eetop;
|
||||
jfieldID VMStructs::_tid;
|
||||
@@ -122,6 +125,8 @@ void VMStructs::initOffsets() {
|
||||
_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) {
|
||||
@@ -137,6 +142,14 @@ void VMStructs::initOffsets() {
|
||||
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;
|
||||
}
|
||||
@@ -197,15 +210,17 @@ void VMStructs::initThreadBridge(JNIEnv* env) {
|
||||
|
||||
// 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
|
||||
if (VM::hotspot_version() >= 9) {
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
if (pthread_getspecific((pthread_key_t)i) == vm_thread) {
|
||||
_tls_index = i;
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -220,6 +235,6 @@ void VMStructs::initLogging(JNIEnv* env) {
|
||||
}
|
||||
}
|
||||
|
||||
bool VMStructs::hasJNIEnv() {
|
||||
return _tls_index < 0 || pthread_getspecific((pthread_key_t)_tls_index) != NULL;
|
||||
VMThread* VMThread::current() {
|
||||
return (VMThread*)pthread_getspecific((pthread_key_t)_tls_index);
|
||||
}
|
||||
|
||||
@@ -39,10 +39,13 @@ class VMStructs {
|
||||
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 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;
|
||||
@@ -70,8 +73,6 @@ class VMStructs {
|
||||
public:
|
||||
static void init(NativeCodeCache* libjvm);
|
||||
|
||||
static bool hasJNIEnv();
|
||||
|
||||
static NativeCodeCache* libjvm() {
|
||||
return _libjvm;
|
||||
}
|
||||
@@ -190,6 +191,8 @@ class VMKlass : VMStructs {
|
||||
|
||||
class VMThread : VMStructs {
|
||||
public:
|
||||
static VMThread* current();
|
||||
|
||||
static VMThread* fromJavaThread(JNIEnv* env, jthread thread) {
|
||||
return (VMThread*)(uintptr_t)env->GetLongField(thread, _eetop);
|
||||
}
|
||||
@@ -211,6 +214,10 @@ class VMThread : VMStructs {
|
||||
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);
|
||||
}
|
||||
@@ -231,4 +238,12 @@ class RuntimeStub : VMStructs {
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <sys/types.h>
|
||||
#include "wallClock.h"
|
||||
#include "profiler.h"
|
||||
#include "stackFrame.h"
|
||||
|
||||
|
||||
// Maximum number of threads sampled in one iteration. This limit serves as a throttle
|
||||
@@ -41,30 +40,9 @@ const int WAKEUP_SIGNAL = SIGIO;
|
||||
long WallClock::_interval;
|
||||
bool WallClock::_sample_idle_threads;
|
||||
|
||||
ThreadState WallClock::getThreadState(void* ucontext) {
|
||||
StackFrame frame(ucontext);
|
||||
uintptr_t pc = frame.pc();
|
||||
|
||||
// Consider a thread sleeping, if it has been interrupted in the middle of syscall execution,
|
||||
// either when PC points to the syscall instruction, or if syscall has just returned with EINTR
|
||||
if (StackFrame::isSyscall((instruction_t*)pc)) {
|
||||
return THREAD_SLEEPING;
|
||||
}
|
||||
|
||||
// Make sure the previous instruction address is readable
|
||||
uintptr_t prev_pc = pc - SYSCALL_SIZE;
|
||||
if ((pc & 0xfff) >= SYSCALL_SIZE || Profiler::_instance.findNativeLibrary((instruction_t*)prev_pc) != NULL) {
|
||||
if (StackFrame::isSyscall((instruction_t*)prev_pc) && frame.checkInterruptedSyscall()) {
|
||||
return THREAD_SLEEPING;
|
||||
}
|
||||
}
|
||||
|
||||
return THREAD_RUNNING;
|
||||
}
|
||||
|
||||
void WallClock::signalHandler(int signo, siginfo_t* siginfo, void* ucontext) {
|
||||
ExecutionEvent event;
|
||||
event._thread_state = _sample_idle_threads ? getThreadState(ucontext) : THREAD_RUNNING;
|
||||
event._thread_state = _sample_idle_threads ? Profiler::_instance.getThreadState(ucontext) : THREAD_RUNNING;
|
||||
Profiler::_instance.recordSample(ucontext, _interval, 0, &event);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include "engine.h"
|
||||
#include "os.h"
|
||||
|
||||
|
||||
class WallClock : public Engine {
|
||||
@@ -39,7 +38,6 @@ class WallClock : public Engine {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ThreadState getThreadState(void* ucontext);
|
||||
static void signalHandler(int signo, siginfo_t* siginfo, void* ucontext);
|
||||
static void wakeupHandler(int signo);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user